mirror of
https://github.com/bjc/prosody.git
synced 2025-04-01 20:27:39 +03:00
Many of these fall into a few categories: - util.cache size, must be >= 1 - byte or item counts that logically can't be negative - port numbers that should be in 1..0xffff
350 lines
8.1 KiB
Lua
350 lines
8.1 KiB
Lua
local serialize = require "prosody.util.serialization".serialize;
|
|
local array = require "prosody.util.array";
|
|
local envload = require "prosody.util.envload".envload;
|
|
local st = require "prosody.util.stanza";
|
|
local is_stanza = st.is_stanza or function (s) return getmetatable(s) == st.stanza_mt end
|
|
local new_id = require "prosody.util.id".medium;
|
|
local set = require "prosody.util.set";
|
|
|
|
local auto_purge_enabled = module:get_option_boolean("storage_memory_temporary", false);
|
|
local auto_purge_stores = module:get_option_set("storage_memory_temporary_stores", {});
|
|
|
|
local archive_item_limit = module:get_option_integer("storage_archive_item_limit", 1000, 0);
|
|
|
|
local memory = setmetatable({}, {
|
|
__index = function(t, k)
|
|
local store = module:shared(k)
|
|
t[k] = store;
|
|
return store;
|
|
end
|
|
});
|
|
|
|
local function NULL() return nil end
|
|
|
|
local function _purge_store(self, username)
|
|
self.store[username or NULL] = nil;
|
|
return true;
|
|
end
|
|
|
|
local function _users(self)
|
|
return next, self.store, nil;
|
|
end
|
|
|
|
local keyval_store = {};
|
|
keyval_store.__index = keyval_store;
|
|
|
|
function keyval_store:get(username)
|
|
return (self.store[username or NULL] or NULL)();
|
|
end
|
|
|
|
function keyval_store:set(username, data)
|
|
if data ~= nil then
|
|
data = envload("return "..serialize(data), "=(data)", {});
|
|
end
|
|
self.store[username or NULL] = data;
|
|
return true;
|
|
end
|
|
|
|
keyval_store.purge = _purge_store;
|
|
|
|
keyval_store.users = _users;
|
|
|
|
local archive_store = {};
|
|
archive_store.__index = archive_store;
|
|
|
|
archive_store.users = _users;
|
|
|
|
archive_store.caps = {
|
|
total = true;
|
|
quota = archive_item_limit;
|
|
truncate = true;
|
|
full_id_range = true;
|
|
ids = true;
|
|
};
|
|
|
|
function archive_store:append(username, key, value, when, with)
|
|
if is_stanza(value) then
|
|
value = st.preserialize(value);
|
|
value = envload("return xml"..serialize(value), "=(stanza)", { xml = st.deserialize })
|
|
else
|
|
value = envload("return "..serialize(value), "=(data)", {});
|
|
end
|
|
local a = self.store[username or NULL];
|
|
if not a then
|
|
a = {};
|
|
self.store[username or NULL] = a;
|
|
end
|
|
local v = { key = key, when = when, with = with, value = value };
|
|
if not key then
|
|
key = new_id();
|
|
v.key = key;
|
|
end
|
|
if a[key] then
|
|
table.remove(a, a[key]);
|
|
elseif #a >= archive_item_limit then
|
|
return nil, "quota-limit";
|
|
end
|
|
local i = #a+1;
|
|
a[i] = v;
|
|
a[key] = i;
|
|
return key;
|
|
end
|
|
|
|
function archive_store:find(username, query)
|
|
local items = self.store[username or NULL];
|
|
if not items then
|
|
if query then
|
|
if query.before or query.after then
|
|
return nil, "item-not-found";
|
|
end
|
|
if query.total then
|
|
return function () end, 0;
|
|
end
|
|
end
|
|
return function () end;
|
|
end
|
|
local count = nil;
|
|
local i, last_key = 0;
|
|
if query then
|
|
items = array():append(items);
|
|
if query.key then
|
|
items:filter(function (item)
|
|
return item.key == query.key;
|
|
end);
|
|
end
|
|
if query.ids then
|
|
local ids = set.new(query.ids);
|
|
items:filter(function (item)
|
|
return ids:contains(item.key);
|
|
end);
|
|
end
|
|
if query.with then
|
|
items:filter(function (item)
|
|
return item.with == query.with;
|
|
end);
|
|
end
|
|
if query.start then
|
|
items:filter(function (item)
|
|
return item.when >= query.start;
|
|
end);
|
|
end
|
|
if query["end"] then
|
|
items:filter(function (item)
|
|
return item.when <= query["end"];
|
|
end);
|
|
end
|
|
if query.total then
|
|
count = #items;
|
|
end
|
|
if query.reverse then
|
|
items:reverse();
|
|
if query.before then
|
|
local found = false;
|
|
for j = 1, #items do
|
|
if (items[j].key or tostring(j)) == query.before then
|
|
found = true;
|
|
i = j;
|
|
break;
|
|
end
|
|
end
|
|
if not found then
|
|
return nil, "item-not-found";
|
|
end
|
|
end
|
|
last_key = query.after;
|
|
elseif query.after then
|
|
local found = false;
|
|
for j = 1, #items do
|
|
if (items[j].key or tostring(j)) == query.after then
|
|
found = true;
|
|
i = j;
|
|
break;
|
|
end
|
|
end
|
|
if not found then
|
|
return nil, "item-not-found";
|
|
end
|
|
last_key = query.before;
|
|
elseif query.before then
|
|
last_key = query.before;
|
|
end
|
|
if query.limit and #items - i > query.limit then
|
|
items[i+query.limit+1] = nil;
|
|
end
|
|
end
|
|
return function ()
|
|
i = i + 1;
|
|
local item = items[i];
|
|
if not item or (last_key and item.key == last_key) then return; end
|
|
return item.key, item.value(), item.when, item.with;
|
|
end, count;
|
|
end
|
|
|
|
function archive_store:get(username, wanted_key)
|
|
local items = self.store[username or NULL];
|
|
if not items then return nil, "item-not-found"; end
|
|
local i = items[wanted_key];
|
|
if not i then return nil, "item-not-found"; end
|
|
local item = items[i];
|
|
return item.value(), item.when, item.with;
|
|
end
|
|
|
|
function archive_store:set(username, wanted_key, new_value, new_when, new_with)
|
|
local items = self.store[username or NULL];
|
|
if not items then return nil, "item-not-found"; end
|
|
local i = items[wanted_key];
|
|
if not i then return nil, "item-not-found"; end
|
|
local item = items[i];
|
|
|
|
if is_stanza(new_value) then
|
|
new_value = st.preserialize(new_value);
|
|
item.value = envload("return xml"..serialize(new_value), "=(stanza)", { xml = st.deserialize })
|
|
else
|
|
item.value = envload("return "..serialize(new_value), "=(data)", {});
|
|
end
|
|
if new_when then
|
|
item.when = new_when;
|
|
end
|
|
if new_with then
|
|
item.with = new_when;
|
|
end
|
|
return true;
|
|
end
|
|
|
|
function archive_store:summary(username, query)
|
|
local iter, err = self:find(username, query)
|
|
if not iter then return iter, err; end
|
|
local counts = {};
|
|
local earliest = {};
|
|
local latest = {};
|
|
for _, _, when, with in iter do
|
|
counts[with] = (counts[with] or 0) + 1;
|
|
if earliest[with] == nil then
|
|
earliest[with] = when;
|
|
end
|
|
latest[with] = when;
|
|
end
|
|
return {
|
|
counts = counts;
|
|
earliest = earliest;
|
|
latest = latest;
|
|
};
|
|
end
|
|
|
|
|
|
function archive_store:delete(username, query)
|
|
if not query or next(query) == nil then
|
|
self.store[username or NULL] = nil;
|
|
return true;
|
|
end
|
|
local items = self.store[username or NULL];
|
|
if not items then
|
|
-- Store is empty
|
|
return 0;
|
|
end
|
|
items = array(items);
|
|
local count_before = #items;
|
|
if query then
|
|
if query.key then
|
|
items:filter(function (item)
|
|
return item.key ~= query.key;
|
|
end);
|
|
end
|
|
if query.with then
|
|
items:filter(function (item)
|
|
return item.with ~= query.with;
|
|
end);
|
|
end
|
|
if query.start then
|
|
items:filter(function (item)
|
|
return item.when < query.start;
|
|
end);
|
|
end
|
|
if query["end"] then
|
|
items:filter(function (item)
|
|
return item.when > query["end"];
|
|
end);
|
|
end
|
|
if query.truncate and #items > query.truncate then
|
|
if query.reverse then
|
|
-- Before: { 1, 2, 3, 4, 5, }
|
|
-- After: { 1, 2, 3 }
|
|
for i = #items, query.truncate + 1, -1 do
|
|
items[i] = nil;
|
|
end
|
|
else
|
|
-- Before: { 1, 2, 3, 4, 5, }
|
|
-- After: { 3, 4, 5 }
|
|
local offset = #items - query.truncate;
|
|
for i = 1, #items do
|
|
items[i] = items[i+offset];
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local count = count_before - #items;
|
|
if count == 0 then
|
|
return 0; -- No changes, skip write
|
|
end
|
|
setmetatable(items, nil);
|
|
|
|
do -- re-index by key
|
|
for k in pairs(items) do
|
|
if type(k) == "string" then
|
|
items[k] = nil;
|
|
end
|
|
end
|
|
|
|
for i = 1, #items do
|
|
items[ items[i].key ] = i;
|
|
end
|
|
end
|
|
|
|
return count;
|
|
end
|
|
|
|
archive_store.purge = _purge_store;
|
|
|
|
local stores = {
|
|
keyval = keyval_store;
|
|
archive = archive_store;
|
|
}
|
|
|
|
local driver = {};
|
|
|
|
function driver:open(store, typ) -- luacheck: ignore 212/self
|
|
local store_mt = stores[typ or "keyval"];
|
|
if store_mt then
|
|
return setmetatable({ store = memory[store] }, store_mt);
|
|
end
|
|
return nil, "unsupported-store";
|
|
end
|
|
|
|
function driver:purge(user) -- luacheck: ignore 212/self
|
|
for _, store in pairs(memory) do
|
|
store[user] = nil;
|
|
end
|
|
end
|
|
|
|
if auto_purge_enabled then
|
|
module:hook("resource-unbind", function (event)
|
|
local user_bare_jid = event.session.username.."@"..event.session.host;
|
|
if not prosody.bare_sessions[user_bare_jid] then -- User went offline
|
|
module:log("debug", "Clearing store for offline user %s", user_bare_jid);
|
|
local f, s, v;
|
|
if auto_purge_stores:empty() then
|
|
f, s, v = pairs(memory);
|
|
else
|
|
f, s, v = auto_purge_stores:items();
|
|
end
|
|
|
|
for store_name in f, s, v do
|
|
if memory[store_name] then
|
|
memory[store_name][event.session.username] = nil;
|
|
end
|
|
end
|
|
end
|
|
end);
|
|
end
|
|
|
|
module:provides("storage", driver);
|