--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_storage_appendmap/README.markdown Sun Mar 06 17:03:19 2016 +0100
@@ -0,0 +1,16 @@
+---
+labels:
+- 'Stage-Alpha'
+- 'Type-Storage'
+summary: Experimental map store optimized for small incremental changes
+...
+
+This is an experimental storage driver where changed data is appended.
+Data is simply written as `key = value` pairs to the end of the file.
+This allows changes to individual keys to be written without needing to
+write out the entire object again, but reads would grow gradually larger
+as it still needs to read old overwritten keys. This may be suitable for
+eg rosters where individual contacts are changed at a time. In theory,
+this could also allow rolling back changes.
+
+Requires 0.10
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_storage_appendmap/mod_storage_appendmap.lua Sun Mar 06 17:03:19 2016 +0100
@@ -0,0 +1,94 @@
+local dump = require "util.serialization".serialize;
+local load = require "util.envload".envloadfile;
+local dm = require "core.storagemanager".olddm;
+
+local driver = {};
+
+local map = {};
+local map_mt = { __index = map };
+map.remove = {};
+
+function map:get(user, key)
+ module:log("debug", "map:get(%s, %s)", tostring(user), tostring(key))
+ local filename = dm.getpath(user, module.host, self.store, "map");
+ module:log("debug", "File is %s", filename);
+ local env = {};
+ if _VERSION == "Lua 5.1" then -- HACK
+ env._ENV = env; -- HACK
+ end -- SO MANY HACKS
+ local chunk, err = load(filename, env);
+ if not chunk then return chunk, err; end
+ local ok, err = pcall(chunk);
+ if not ok then return ok, err; end
+ if _VERSION == "Lua 5.1" then -- HACK
+ env._ENV = nil; -- HACK
+ end -- HACKS EVERYWHERE
+ if key == nil then
+ return env;
+ end
+ return env[key];
+end
+
+function map:set_keys(user, keyvalues)
+ local keys, values = {}, {};
+ if _VERSION == "Lua 5.1" then
+ assert(not keyvalues._ENV, "'_ENV' is a restricted key");
+ end
+ for key, value in pairs(keyvalues) do
+ module:log("debug", "user %s sets %q to %s", user, key, tostring(value))
+ if type(key) ~= "string" or not key:find("^[%w_][%w%d_]*$") or key == "_ENV" then
+ key = "_ENV[" .. dump(key) .. "]";
+ end
+ table.insert(keys, key);
+ if value == self.remove then
+ table.insert(values, "nil")
+ else
+ table.insert(values, dump(value))
+ end
+ end
+ local data = table.concat(keys, ", ") .. " = " .. table.concat(values, ", ") .. ";\n";
+ return dm.append_raw(user, module.host, self.store, "map", data);
+end
+
+function map:set(user, key, value)
+ if _VERSION == "Lua 5.1" then
+ assert(key ~= "_ENV", "'_ENV' is a restricted key");
+ end
+ if key == nil then
+ local filename = dm.getpath(user, module.host, self.store, "map");
+ os.remove(filename);
+ return true;
+ end
+ if type(key) ~= "string" or not key:find("^[%w_][%w%d_]*$") or key == "_ENV" then
+ key = "_ENV[" .. dump(key) .. "]";
+ end
+ local data = key .. " = " .. dump(value) .. ";\n";
+ return dm.append_raw(user, module.host, self.store, "map", data);
+end
+
+local keyval = {};
+local keyval_mt = { __index = keyval };
+
+function keyval:get(user)
+ return map.get(self, user);
+end
+
+keyval.set = map.set_keys;
+
+-- TODO some kind of periodic compaction thing?
+function map:_compact(user)
+ local data = self:get(user);
+ return keyval.set(self, user, data);
+end
+
+function driver:open(store, typ)
+ if typ == "map" then
+ return setmetatable({ store = store, }, map_mt);
+ elseif typ == nil or typ == "keyval" then
+ return setmetatable({ store = store, }, keyval_mt);
+ end
+ return nil, "unsupported-store";
+end
+
+module:provides("storage", driver);
+