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.lua227
1 files changed, 227 insertions, 0 deletions
diff --git a/plugins/mod_storage_memory.lua b/plugins/mod_storage_memory.lua
new file mode 100644
index 00000000..ed04a5fb
--- /dev/null
+++ b/plugins/mod_storage_memory.lua
@@ -0,0 +1,227 @@
+local serialize = require "util.serialization".serialize;
+local array = require "util.array";
+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 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 = tostring(a):match"%x+$"..tostring(v):match"%x+$";
+ v.key = key;
+ end
+ if a[key] then
+ table.remove(a, a[key]);
+ end
+ local i = #a+1;
+ 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 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);