Merge from waqas
[prosody.git] / util / datamanager.lua
1 local format = string.format;
2 local setmetatable, type = setmetatable, type;
3 local pairs, ipairs = pairs, ipairs;
4 local char = string.char;
5 local loadfile, setfenv, pcall = loadfile, setfenv, pcall;
6 local log = log;
7 local io_open = io.open;
8 local os_remove = os.remove;
9 local tostring, tonumber = tostring, tonumber;
10 local error = error;
11 local next = next;
12 local t_insert = table.insert;
13
14 local indent = function(f, i)
15         for n = 1, i do
16                 f:write("\t");
17         end
18 end
19
20 local data_path = "data";
21
22 module "datamanager"
23
24
25 ---- utils -----
26 local encode, decode;
27
28 local log = function (type, msg) return log(type, "datamanager", msg); end
29
30 do 
31         local urlcodes = setmetatable({}, { __index = function (t, k) t[k] = char(tonumber("0x"..k)); return t[k]; end });
32
33         decode = function (s)
34                 return s and (s:gsub("+", " "):gsub("%%([a-fA-F0-9][a-fA-F0-9])", urlcodes));
35         end
36
37         encode = function (s)
38                 return s and (s:gsub("%W", function (c) return format("%%%x", c:byte()); end));
39         end
40 end
41
42 local function basicSerialize (o)
43         if type(o) == "number" or type(o) == "boolean" then
44                 return tostring(o);
45         else -- assume it is a string -- FIXME make sure it's a string. throw an error otherwise.
46                 return (format("%q", tostring(o)):gsub("\\\n", "\\n"));
47         end
48 end
49
50
51 local function simplesave (f, o, ind)
52         if type(o) == "number" then
53                 f:write(o)
54         elseif type(o) == "string" then
55                 f:write((format("%q", o):gsub("\\\n", "\\n")))
56         elseif type(o) == "table" then
57                 f:write("{\n")
58                 for k,v in pairs(o) do
59                         indent(f, ind);
60                         f:write("[", basicSerialize(k), "] = ")
61                         simplesave(f, v, ind+1)
62                         f:write(",\n")
63                 end
64                 indent(f, ind-1);
65                 f:write("}")
66         elseif type(o) == "boolean" then
67                 f:write(o and "true" or "false");
68         else
69                 error("cannot serialize a " .. type(o))
70         end
71 end
72
73 ------- API -------------
74
75 function set_data_path(path)
76         data_path = path;
77 end
78
79 function getpath(username, host, datastore, ext)
80         ext = ext or "dat";
81         if username then
82                 return format("%s/%s/%s/%s.%s", data_path, encode(host), datastore, encode(username), ext);
83         elseif host then
84                 return format("%s/%s/%s.%s", data_path, encode(host), datastore, ext);
85         else
86                 return format("%s/%s.%s", data_path, datastore, ext);
87         end
88 end
89
90 function load(username, host, datastore)
91         local data, ret = loadfile(getpath(username, host, datastore));
92         if not data then
93                 log("warn", "Failed to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil"));
94                 return nil;
95         end
96         setfenv(data, {});
97         local success, ret = pcall(data);
98         if not success then
99                 log("error", "Unable to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil"));
100                 return nil;
101         end
102         return ret;
103 end
104
105 function store(username, host, datastore, data)
106         if not data then
107                 data = {};
108         end
109         -- save the datastore
110         local f, msg = io_open(getpath(username, host, datastore), "w+");
111         if not f then
112                 log("error", "Unable to write to "..datastore.." storage ('"..msg.."') for user: "..(username or "nil").."@"..(host or "nil"));
113                 return;
114         end
115         f:write("return ");
116         simplesave(f, data, 1);
117         f:close();
118         if not next(data) then -- try to delete empty datastore
119                 os_remove(getpath(username, host, datastore));
120         end
121         -- we write data even when we are deleting because lua doesn't have a
122         -- platform independent way of checking for non-exisitng files
123         return true;
124 end
125
126 function list_append(username, host, datastore, data)
127         if not data then return; end
128         -- save the datastore
129         local f, msg = io_open(getpath(username, host, datastore, "list"), "a+");
130         if not f then
131                 log("error", "Unable to write to "..datastore.." storage ('"..msg.."') for user: "..(username or "nil").."@"..(host or "nil"));
132                 return;
133         end
134         f:write("item(");
135         simplesave(f, data, 1);
136         f:write(");\n");
137         f:close();
138         return true;
139 end
140
141 function list_store(username, host, datastore, data)
142         if not data then
143                 data = {};
144         end
145         -- save the datastore
146         local f, msg = io_open(getpath(username, host, datastore, "list"), "w+");
147         if not f then
148                 log("error", "Unable to write to "..datastore.." storage ('"..msg.."') for user: "..(username or "nil").."@"..(host or "nil"));
149                 return;
150         end
151         for _, d in ipairs(data) do
152                 f:write("item(");
153                 simplesave(f, d, 1);
154                 f:write(");\n");
155         end
156         f:close();
157         if not next(data) then -- try to delete empty datastore
158                 os_remove(getpath(username, host, datastore, "list"));
159         end
160         -- we write data even when we are deleting because lua doesn't have a
161         -- platform independent way of checking for non-exisitng files
162         return true;
163 end
164
165 function list_load(username, host, datastore)
166         local data, ret = loadfile(getpath(username, host, datastore, "list"));
167         if not data then
168                 log("warn", "Failed to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil"));
169                 return nil;
170         end
171         local items = {};
172         setfenv(data, {item = function(i) t_insert(items, i); end});
173         local success, ret = pcall(data);
174         if not success then
175                 log("error", "Unable to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil"));
176                 return nil;
177         end
178         return items;
179 end
180
181 return _M;