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
7 local auto_purge_enabled
= module
:get_option_boolean("storage_memory_temporary", false);
8 local auto_purge_stores
= module
:get_option_set("storage_memory_temporary_stores", {});
10 local memory
= setmetatable({}, {
11 __index
= function(t
, k
)
12 local store
= module
:shared(k
)
18 local function NULL() return nil end
20 local function _purge_store(self
, username
)
21 self
.store
[username
or NULL
] = nil;
25 local keyval_store
= {};
26 keyval_store
.__index
= keyval_store
;
28 function keyval_store
:get(username
)
29 return (self
.store
[username
or NULL
] or NULL
)();
32 function keyval_store
:set(username
, data
)
34 data
= envload("return "..serialize(data
), "=(data)", {});
36 self
.store
[username
or NULL
] = data
;
40 keyval_store
.purge
= _purge_store
;
42 local archive_store
= {};
43 archive_store
.__index
= archive_store
;
45 function archive_store
:append(username
, key
, value
, when
, with
)
46 if is_stanza(value
) then
47 value
= st
.preserialize(value
);
48 value
= envload("return xml"..serialize(value
), "=(stanza)", { xml
= st
.deserialize
})
50 value
= envload("return "..serialize(value
), "=(data)", {});
52 local a
= self
.store
[username
or NULL
];
55 self
.store
[username
or NULL
] = a
;
57 local v
= { key
= key
, when
= when
, with
= with
, value
= value
};
59 key
= tostring(a
):match
"%x+$"..tostring(v
):match
"%x+$";
63 table.remove(a
, a
[key
]);
71 local function archive_iter (a
, start
, stop
, step
, limit
, when_start
, when_end
, match_with
)
72 local item
, when
, with
;
74 coroutine
.yield(true); -- Ready
75 for i
= start
, stop
, step
do
77 when
, with
= item
.when
, item
.with
;
78 if when
>= when_start
and when_end
>= when
and (not match_with
or match_with
== with
) then
79 coroutine
.yield(item
.key
, item
.value(), when
, with
);
81 if limit
and count
>= limit
then return end
86 function archive_store
:find(username
, query
)
87 local a
= self
.store
[username
or NULL
] or {};
88 local start
, stop
, step
= 1, #a
, 1;
89 local qstart
, qend
, qwith
= -math
.huge
, math
.huge
;
92 module
:log("debug", "query included")
94 start
, stop
, step
= stop
, start
, -1;
96 start
= a
[query
.before
];
98 elseif query
.after
then
99 start
= a
[query
.after
];
102 qstart
= query
.start
or qstart
;
103 qend
= query
["end"] or qend
;
106 if not start
then return nil, "invalid-key"; end
107 local iter
= coroutine
.wrap(archive_iter
);
108 iter(a
, start
, stop
, step
, limit
, qstart
, qend
, qwith
);
112 function archive_store
:delete(username
, query
)
113 if not query
or next(query
) == nil then
114 self
.store
[username
or NULL
] = nil;
117 local items
= self
.store
[username
or NULL
];
122 items
= array(items
);
123 local count_before
= #items
;
126 items
:filter(function (item
)
127 return item
.key
~= query
.key
;
131 items
:filter(function (item
)
132 return item
.with
~= query
.with
;
136 items
:filter(function (item
)
137 return item
.when
< query
.start
;
141 items
:filter(function (item
)
142 return item
.when
> query
["end"];
145 if query
.truncate
and #items
> query
.truncate
then
146 if query
.reverse
then
147 -- Before: { 1, 2, 3, 4, 5, }
148 -- After: { 1, 2, 3 }
149 for i
= #items
, query
.truncate
+ 1, -1 do
153 -- Before: { 1, 2, 3, 4, 5, }
154 -- After: { 3, 4, 5 }
155 local offset
= #items
- query
.truncate
;
157 items
[i
] = items
[i
+offset
];
162 local count
= count_before
- #items
;
164 return 0; -- No changes, skip write
166 setmetatable(items
, nil);
168 do -- re-index by key
169 for k
in pairs(items
) do
170 if type(k
) == "string" then
176 items
[ items
[i
].key
] = i
;
183 archive_store
.purge
= _purge_store
;
186 keyval
= keyval_store
;
187 archive
= archive_store
;
192 function driver
:open(store
, typ
) -- luacheck: ignore 212/self
193 local store_mt
= stores
[typ
or "keyval"];
195 return setmetatable({ store
= memory
[store
] }, store_mt
);
197 return nil, "unsupported-store";
200 function driver
:purge(user
) -- luacheck: ignore 212/self
201 for _
, store
in pairs(memory
) do
206 if auto_purge_enabled
then
207 module
:hook("resource-unbind", function (event
)
208 local user_bare_jid
= event
.session
.username
.."@"..event
.session
.host
;
209 if not prosody
.bare_sessions
[user_bare_jid
] then -- User went offline
210 module
:log("debug", "Clearing store for offline user %s", user_bare_jid
);
212 if auto_purge_stores
:empty() then
213 f
, s
, v
= pairs(memory
);
215 f
, s
, v
= auto_purge_stores
:items();
218 for store_name
in f
, s
, v
do
219 if memory
[store_name
] then
220 memory
[store_name
][event
.session
.username
] = nil;
227 module
:provides("storage", driver
);