2 -- Copyright (C) 2014-2015 Kim Alvefur
4 -- This file is MIT/X11 licensed.
7 -- http://webserver2.tecgraf.puc-rio.br/~lhf/ftp/lua/#lgdbm
9 -- luacheck: globals open purge
11 local gdbm
= require
"gdbm";
12 local path
= require
"util.paths";
13 local lfs
= require
"lfs";
14 local st
= require
"util.stanza";
15 local uuid
= require
"util.uuid".generate
;
17 local serialization
= require
"util.serialization";
18 local serialize
= serialization
.serialize
;
19 local deserialize
= serialization
.deserialize
;
21 local g_set
, g_get
, g_del
= gdbm
.replace
, gdbm
.fetch
, gdbm
.delete
;
22 local g_first
, g_next
= gdbm
.firstkey
, gdbm
.nextkey
;
26 local function id(v
) return v
; end
28 local function is_stanza(s
)
29 return getmetatable(s
) == st
.stanza_mt
;
32 local base_path
= path
.resolve_relative_path(prosody
.paths
.data
, module
.host
);
38 local keyval_mt
= { __index
= keyval
, suffix
= ".db" };
40 function keyval
:set(user
, value
)
41 if type(value
) == "table" and next(value
) == nil then
45 value
= serialize(value
);
47 local ok
, err
= (value
and g_set
or g_del
)(self
._db
, user
or "@", value
);
48 if not ok
then return nil, err
; end
52 function keyval
:get(user
)
53 local data
, err
= g_get(self
._db
, user
or "@");
54 if not data
then return nil, err
; end
55 return deserialize(data
);
58 local function g_keys(db
, key
)
59 return (key
== nil and g_first
or g_next
)(db
, key
);
62 function keyval
:users()
63 return g_keys
, self
._db
, nil;
67 local archive_mt
= { __index
= archive
, suffix
= ".adb" };
69 archive
.get
= keyval
.get
;
70 archive
.set
= keyval
.set
;
72 function archive
:append(username
, key
, value
, when
, with
)
73 if type(when
) ~= "number" then
74 when
, with
, value
= value
, when
, with
;
78 local meta
= self
:get(username
);
82 local i
= meta
[key
] or #meta
+1;
84 if is_stanza(value
) then
85 type, value
= "stanza", st
.preserialize(value
);
87 meta
[i
] = { key
= key
, when
= when
, with
= with
, type = type };
89 local prefix
= (username
or "@") .. "#";
90 local ok
, err
= self
:set(prefix
..key
, value
);
91 if not ok
then return nil, err
; end
92 ok
, err
= self
:set(username
, meta
);
93 if not ok
then return nil, err
; end
97 local deserialize_map
= {
98 stanza
= st
.deserialize
;
101 function archive
:find(username
, query
)
102 query
= query
or empty
;
103 local meta
= self
:get(username
) or empty
;
104 local prefix
= (username
or "@") .. "#";
105 local reverse
= query
.reverse
;
106 local step
= reverse
and -1 or 1;
107 local first
= meta
[query
.after
];
108 local last
= meta
[query
.before
];
109 -- we don't want to include the actual 'after' or 'before' item
110 if first
then first
= first
+ 1; end
111 if last
then last
= last
- 1; end
112 first
, last
= first
or 1, last
or #meta
;
113 if reverse
then first
, last
= last
, first
; end
114 local limit
= query
.limit
;
117 if limit
and count
>= limit
then return end
119 for i
= first
, last
, step
do
121 if (not query
.with
or item
.with
== query
.with
)
122 and (not query
.start
or item
.when
>= query
.start
)
123 and (not query
["end"] or item
.when
<= query
["end"]) then
124 first
= i
+ step
; count
= count
+ 1;
125 value
= self
:get(prefix
..item
.key
);
126 return item
.key
, (deserialize_map
[item
.type] or id
)(value
), item
.when
, item
.with
;
134 archive
= archive_mt
;
137 function open(_
, store
, typ
)
138 typ
= typ
or "keyval";
139 local driver_mt
= drivers
[typ
];
140 if not driver_mt
then
141 return nil, "unsupported-store";
144 local db_path
= path
.join(base_path
, store
) .. driver_mt
.suffix
;
146 local db
= cache
[db_path
];
148 db
= assert(gdbm
.open(db_path
, "c"));
151 return setmetatable({ _db
= db
; _path
= db_path
; store
= store
, type = typ
}, driver_mt
);
154 function purge(_
, user
)
155 for dir
in lfs
.dir(base_path
) do
156 local name
, ext
= dir
:match("^(.-)%.a?db$");
158 open(_
, name
, "keyval"):set(user
, nil);
159 elseif ext
== ".adb" then
160 open(_
, name
, "archive"):delete(user
);
166 function module
.unload()
167 for db_path
, db
in pairs(cache
) do
168 module
:log("debug", "Closing db at %q", db_path
);
175 module
:provides
"storage";