aboutsummaryrefslogtreecommitdiffstats
path: root/plugins/mod_storage_memory.lua
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/mod_storage_memory.lua')
-rw-r--r--plugins/mod_storage_memory.lua178
1 files changed, 178 insertions, 0 deletions
diff --git a/plugins/mod_storage_memory.lua b/plugins/mod_storage_memory.lua
new file mode 100644
index 00000000..8a95a587
--- /dev/null
+++ b/plugins/mod_storage_memory.lua
@@ -0,0 +1,178 @@
+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);