1 local serialize
= require
"util.serialization".serialize
;
2 local array
= require
"util.array";
3 local envload
= require
"util.envload".envload
;
4 local st
= require
"util.stanza";
5 local is_stanza
= st
.is_stanza
or function (s
) return getmetatable(s
) == st
.stanza_mt
end
6 local new_id
= require
"util.id".medium
;
8 local auto_purge_enabled
= module
:get_option_boolean("storage_memory_temporary", false);
9 local auto_purge_stores
= module
:get_option_set("storage_memory_temporary_stores", {});
11 local archive_item_limit
= module
:get_option_number("storage_archive_item_limit", 1000);
13 local memory
= setmetatable({}, {
14 __index
= function(t
, k
)
15 local store
= module
:shared(k
)
21 local function NULL() return nil end
23 local function _purge_store(self
, username
)
24 self
.store
[username
or NULL
] = nil;
28 local function _users(self
)
29 return next, self
.store
, nil;
32 local keyval_store
= {};
33 keyval_store
.__index
= keyval_store
;
35 function keyval_store
:get(username
)
36 return (self
.store
[username
or NULL
] or NULL
)();
39 function keyval_store
:set(username
, data
)
41 data
= envload("return "..serialize(data
), "=(data)", {});
43 self
.store
[username
or NULL
] = data
;
47 keyval_store
.purge
= _purge_store
;
49 keyval_store
.users
= _users
;
51 local archive_store
= {};
52 archive_store
.__index
= archive_store
;
54 archive_store
.users
= _users
;
56 archive_store
.caps
= {
58 quota
= archive_item_limit
;
62 function archive_store
:append(username
, key
, value
, when
, with
)
63 if is_stanza(value
) then
64 value
= st
.preserialize(value
);
65 value
= envload("return xml"..serialize(value
), "=(stanza)", { xml
= st
.deserialize
})
67 value
= envload("return "..serialize(value
), "=(data)", {});
69 local a
= self
.store
[username
or NULL
];
72 self
.store
[username
or NULL
] = a
;
74 local v
= { key
= key
, when
= when
, with
= with
, value
= value
};
80 table.remove(a
, a
[key
]);
81 elseif #a
>= archive_item_limit
then
82 return nil, "quota-limit";
90 function archive_store
:find(username
, query
)
91 local items
= self
.store
[username
or NULL
];
94 if query
.before
or query
.after
then
95 return nil, "item-not-found";
98 return function () end, 0;
101 return function () end;
106 items
= array():append(items
);
108 items
:filter(function (item
)
109 return item
.key
== query
.key
;
113 items
:filter(function (item
)
114 return item
.with
== query
.with
;
118 items
:filter(function (item
)
119 return item
.when
>= query
.start
;
123 items
:filter(function (item
)
124 return item
.when
<= query
["end"];
130 if query
.reverse
then
135 if (items
[j
].key
or tostring(j
)) == query
.before
then
142 return nil, "item-not-found";
145 elseif query
.after
then
148 if (items
[j
].key
or tostring(j
)) == query
.after
then
155 return nil, "item-not-found";
158 if query
.limit
and #items
- i
> query
.limit
then
159 items
[i
+query
.limit
+1] = nil;
164 local item
= items
[i
];
165 if not item
then return; end
166 return item
.key
, item
.value(), item
.when
, item
.with
;
170 function archive_store
:summary(username
, query
)
171 local iter
, err
= self
:find(username
, query
)
172 if not iter
then return iter
, err
; end
176 for _
, _
, when
, with
in iter
do
177 counts
[with
] = (counts
[with
] or 0) + 1;
178 if earliest
[with
] == nil then
179 earliest
[with
] = when
;
191 function archive_store
:delete(username
, query
)
192 if not query
or next(query
) == nil then
193 self
.store
[username
or NULL
] = nil;
196 local items
= self
.store
[username
or NULL
];
201 items
= array(items
);
202 local count_before
= #items
;
205 items
:filter(function (item
)
206 return item
.key
~= query
.key
;
210 items
:filter(function (item
)
211 return item
.with
~= query
.with
;
215 items
:filter(function (item
)
216 return item
.when
< query
.start
;
220 items
:filter(function (item
)
221 return item
.when
> query
["end"];
224 if query
.truncate
and #items
> query
.truncate
then
225 if query
.reverse
then
226 -- Before: { 1, 2, 3, 4, 5, }
227 -- After: { 1, 2, 3 }
228 for i
= #items
, query
.truncate
+ 1, -1 do
232 -- Before: { 1, 2, 3, 4, 5, }
233 -- After: { 3, 4, 5 }
234 local offset
= #items
- query
.truncate
;
236 items
[i
] = items
[i
+offset
];
241 local count
= count_before
- #items
;
243 return 0; -- No changes, skip write
245 setmetatable(items
, nil);
247 do -- re-index by key
248 for k
in pairs(items
) do
249 if type(k
) == "string" then
255 items
[ items
[i
].key
] = i
;
262 archive_store
.purge
= _purge_store
;
265 keyval
= keyval_store
;
266 archive
= archive_store
;
271 function driver
:open(store
, typ
) -- luacheck: ignore 212/self
272 local store_mt
= stores
[typ
or "keyval"];
274 return setmetatable({ store
= memory
[store
] }, store_mt
);
276 return nil, "unsupported-store";
279 function driver
:purge(user
) -- luacheck: ignore 212/self
280 for _
, store
in pairs(memory
) do
285 if auto_purge_enabled
then
286 module
:hook("resource-unbind", function (event
)
287 local user_bare_jid
= event
.session
.username
.."@"..event
.session
.host
;
288 if not prosody
.bare_sessions
[user_bare_jid
] then -- User went offline
289 module
:log("debug", "Clearing store for offline user %s", user_bare_jid
);
291 if auto_purge_stores
:empty() then
292 f
, s
, v
= pairs(memory
);
294 f
, s
, v
= auto_purge_stores
:items();
297 for store_name
in f
, s
, v
do
298 if memory
[store_name
] then
299 memory
[store_name
][event
.session
.username
] = nil;
306 module
:provides("storage", driver
);