mirror of
https://github.com/bjc/prosody.git
synced 2025-04-05 14:17:37 +03:00
All data is compiled into functions that return new copies in order to prevent mutation of stored data. This has downsides. Also an ugly hack.
178 lines
4.6 KiB
Lua
178 lines
4.6 KiB
Lua
local serialize = require "util.serialization".serialize;
|
|
local envload = require "util.envload".envload;
|
|
local st = require "util.stanza";
|
|
local is_stanza = st.is_stanza or function (s) return getmetatable(s) == st.stanza_mt end
|
|
|
|
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 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 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;
|
|
|
|
local archive_store = {};
|
|
archive_store.__index = archive_store;
|
|
|
|
function archive_store:append(username, key, value, when, with)
|
|
if type(when) ~= "number" then
|
|
when, with, value = value, when, with;
|
|
end
|
|
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 i = #a+1;
|
|
local v = { key = key, when = when, with = with, value = value };
|
|
if not key then
|
|
key = tostring(a):match"%x+$"..tostring(v):match"%x+$";
|
|
v.key = key;
|
|
end
|
|
if a[key] then
|
|
table.remove(a, a[key]);
|
|
end
|
|
a[i] = v;
|
|
a[key] = i;
|
|
return key;
|
|
end
|
|
|
|
local function archive_iter (a, start, stop, step, limit, when_start, when_end, match_with)
|
|
local item, when, with;
|
|
local count = 0;
|
|
coroutine.yield(true); -- Ready
|
|
for i = start, stop, step do
|
|
item = a[i];
|
|
when, with = item.when, item.with;
|
|
if when >= when_start and when_end >= when and (not match_with or match_with == with) then
|
|
coroutine.yield(item.key, item.value(), when, with);
|
|
count = count + 1;
|
|
if limit and count >= limit then return end
|
|
end
|
|
end
|
|
end
|
|
|
|
function archive_store:find(username, query)
|
|
local a = self.store[username or NULL] or {};
|
|
local start, stop, step = 1, #a, 1;
|
|
local qstart, qend, qwith = -math.huge, math.huge;
|
|
local limit;
|
|
if query then
|
|
module:log("debug", "query included")
|
|
if query.reverse then
|
|
start, stop, step = stop, start, -1;
|
|
if query.before then
|
|
start = a[query.before];
|
|
end
|
|
elseif query.after then
|
|
start = a[query.after];
|
|
end
|
|
limit = query.limit;
|
|
qstart = query.start or qstart;
|
|
qend = query["end"] or qend;
|
|
qwith = query.with;
|
|
end
|
|
if not start then return nil, "invalid-key"; end
|
|
local iter = coroutine.wrap(archive_iter);
|
|
iter(a, start, stop, step, limit, qstart, qend, qwith);
|
|
return iter;
|
|
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 old = self.store[username or NULL];
|
|
if not old then return true; end
|
|
local qstart = query.start or -math.huge;
|
|
local qend = query["end"] or math.huge;
|
|
local qwith = query.with;
|
|
local new = {};
|
|
self.store[username or NULL] = new;
|
|
local t;
|
|
for i = 1, #old do
|
|
i = old[i];
|
|
t = i.when;
|
|
if not(qstart >= t and qend <= t and (not qwith or i.with == qwith)) then
|
|
self:append(username, i.key, i.value(), t, i.with);
|
|
end
|
|
end
|
|
if #new == 0 then
|
|
self.store[username or NULL] = nil;
|
|
end
|
|
return true;
|
|
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
|
|
|
|
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);
|