aboutsummaryrefslogtreecommitdiffstats
path: root/util/datamanager.lua
blob: f25fffb36effbfed38215f8c4afc6b6f67aa2162 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
local format = string.format;
local setmetatable, type = setmetatable, type;
local pairs, ipairs = pairs, ipairs;
local char = string.char;
local loadfile, setfenv, pcall = loadfile, setfenv, pcall;
local log = log;
local io_open = io.open;
local os_remove = os.remove;
local tostring, tonumber = tostring, tonumber;
local error = error;
local next = next;
local t_insert = table.insert;

local indent = function(f, i)
	for n = 1, i do
		f:write("\t");
	end
end

local data_path = "data";

module "datamanager"


---- utils -----
local encode, decode;

local log = function (type, msg) return log(type, "datamanager", msg); end

do 
	local urlcodes = setmetatable({}, { __index = function (t, k) t[k] = char(tonumber("0x"..k)); return t[k]; end });

	decode = function (s)
		return s and (s:gsub("+", " "):gsub("%%([a-fA-F0-9][a-fA-F0-9])", urlcodes));
	end

	encode = function (s)
		return s and (s:gsub("%W", function (c) return format("%%%x", c:byte()); end));
	end
end

local function basicSerialize (o)
	if type(o) == "number" or type(o) == "boolean" then
		return tostring(o);
	else -- assume it is a string -- FIXME make sure it's a string. throw an error otherwise.
		return (format("%q", tostring(o)):gsub("\\\n", "\\n"));
	end
end


local function simplesave (f, o, ind)
	if type(o) == "number" then
		f:write(o)
	elseif type(o) == "string" then
		f:write((format("%q", o):gsub("\\\n", "\\n")))
	elseif type(o) == "table" then
		f:write("{\n")
		for k,v in pairs(o) do
			indent(f, ind);
			f:write("[", basicSerialize(k), "] = ")
			simplesave(f, v, ind+1)
			f:write(",\n")
		end
		indent(f, ind-1);
		f:write("}")
	elseif type(o) == "boolean" then
		f:write(o and "true" or "false");
	else
		error("cannot serialize a " .. type(o))
	end
end

------- API -------------

function set_data_path(path)
	data_path = path;
end

function getpath(username, host, datastore, ext)
	ext = ext or "dat";
	if username then
		return format("%s/%s/%s/%s.%s", data_path, encode(host), datastore, encode(username), ext);
	elseif host then
		return format("%s/%s/%s.%s", data_path, encode(host), datastore, ext);
	else
		return format("%s/%s.%s", data_path, datastore, ext);
	end
end

function load(username, host, datastore)
	local data, ret = loadfile(getpath(username, host, datastore));
	if not data then
		log("warn", "Failed to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil"));
		return nil;
	end
	setfenv(data, {});
	local success, ret = pcall(data);
	if not success then
		log("error", "Unable to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil"));
		return nil;
	end
	return ret;
end

function store(username, host, datastore, data)
	if not data then
		data = {};
	end
	-- save the datastore
	local f, msg = io_open(getpath(username, host, datastore), "w+");
	if not f then
		log("error", "Unable to write to "..datastore.." storage ('"..msg.."') for user: "..(username or "nil").."@"..(host or "nil"));
		return;
	end
	f:write("return ");
	simplesave(f, data, 1);
	f:close();
	if not next(data) then -- try to delete empty datastore
		os_remove(getpath(username, host, datastore));
	end
	-- we write data even when we are deleting because lua doesn't have a
	-- platform independent way of checking for non-exisitng files
	return true;
end

function list_append(username, host, datastore, data)
	if not data then return; end
	-- save the datastore
	local f, msg = io_open(getpath(username, host, datastore, "list"), "a+");
	if not f then
		log("error", "Unable to write to "..datastore.." storage ('"..msg.."') for user: "..(username or "nil").."@"..(host or "nil"));
		return;
	end
	f:write("item(");
	simplesave(f, data, 1);
	f:write(");\n");
	f:close();
	return true;
end

function list_store(username, host, datastore, data)
	if not data then
		data = {};
	end
	-- save the datastore
	local f, msg = io_open(getpath(username, host, datastore, "list"), "w+");
	if not f then
		log("error", "Unable to write to "..datastore.." storage ('"..msg.."') for user: "..(username or "nil").."@"..(host or "nil"));
		return;
	end
	for _, d in ipairs(data) do
		f:write("item(");
		simplesave(f, d, 1);
		f:write(");\n");
	end
	f:close();
	if not next(data) then -- try to delete empty datastore
		os_remove(getpath(username, host, datastore, "list"));
	end
	-- we write data even when we are deleting because lua doesn't have a
	-- platform independent way of checking for non-exisitng files
	return true;
end

function list_load(username, host, datastore)
	local data, ret = loadfile(getpath(username, host, datastore, "list"));
	if not data then
		log("warn", "Failed to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil"));
		return nil;
	end
	local items = {};
	setfenv(data, {item = function(i) t_insert(items, i); end});
	local success, ret = pcall(data);
	if not success then
		log("error", "Unable to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil"));
		return nil;
	end
	return items;
end

return _M;