diff options
author | Kaarle Ritvanen <kaarle.ritvanen@datakunkku.fi> | 2013-10-08 18:50:56 +0300 |
---|---|---|
committer | Kaarle Ritvanen <kaarle.ritvanen@datakunkku.fi> | 2013-10-08 18:50:56 +0300 |
commit | 33728ad3382d74281412d4556561d479bb88832b (patch) | |
tree | e8a9b2798dec96d820715f729989e91f9e7d5e12 /acf2/persistence | |
parent | 3e48dd63e8bdf0c2641cfb73e6b20bea8c466ff8 (diff) | |
download | acf2-33728ad3382d74281412d4556561d479bb88832b.tar.bz2 acf2-33728ad3382d74281412d4556561d479bb88832b.tar.xz |
changed module paths from acf to acf2v0.1.0
Diffstat (limited to 'acf2/persistence')
-rw-r--r-- | acf2/persistence/backends/augeas.lua | 153 | ||||
-rw-r--r-- | acf2/persistence/backends/files.lua | 107 | ||||
-rw-r--r-- | acf2/persistence/backends/json.lua | 79 | ||||
-rw-r--r-- | acf2/persistence/backends/null.lua | 10 | ||||
-rw-r--r-- | acf2/persistence/backends/volatile.lua | 46 | ||||
-rw-r--r-- | acf2/persistence/init.lua | 79 | ||||
-rw-r--r-- | acf2/persistence/util.lua | 22 |
7 files changed, 496 insertions, 0 deletions
diff --git a/acf2/persistence/backends/augeas.lua b/acf2/persistence/backends/augeas.lua new file mode 100644 index 0000000..010a52b --- /dev/null +++ b/acf2/persistence/backends/augeas.lua @@ -0,0 +1,153 @@ +--[[ +Copyright (c) 2012-2013 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local topology = require('acf2.model.root').topology +local pth = require('acf2.path') + +local util = require('acf2.util') +local copy = util.copy + + +local function aug_path(path) return pth.join('/files', unpack(path)) end + +local function strip_name(name) + return type(name) == 'string' and name:match('^[^][/=)%s]+') or name +end + +local function ipath(path, index) return path..'['..index..']' end + + +local backend = require('acf2.object').class() + +function backend:init() self.aug = require('augeas').init() end + +function backend:find(path, leaf) + util.map( + function(comp) + assert( + comp == strip_name(comp) and ( + type(comp) == 'number' or not comp:match('^%.+$') + ) + ) + end, + path + ) + local res = aug_path(path) + + if #self.aug:match(res) == 0 and #path > 1 and leaf then + local index = path[#path] + if type(index) == 'number' then + local ppath = copy(path) + table.remove(ppath) + ppath = aug_path(ppath) + + if #self.aug:match(ppath) > 0 and #self.aug:match( + ppath..'/*' + ) == 0 then + return ipath(ppath, index), ppath, index + end + end + end + + return res +end + +function backend:get(path, top) + local tpe = top and top.type + local leaf = tpe and tpe ~= 'table' + local apath, mvpath = self:find(path, leaf or not tpe) + + local matches = self.aug:match(apath) + if mvpath then + assert(#matches < 2) + leaf = true + end + + if #matches == 0 then return end + + if #matches > 1 then + assert(not leaf) + local res = {} + path = copy(path) + for i, _ in ipairs(matches) do + table.insert(path, i) + if self:get(path) then table.insert(res, i) end + table.remove(path) + end + return res + end + + local value = self.aug:get(matches[1]) + if value then return tpe == 'table' and {1} or value end + if leaf then return end + + local names = {} + for _, child in ipairs(self.aug:match(apath..'/*')) do + names[strip_name(pth.name(child))] = true + end + return util.keys(names) +end + +function backend:set(mods) + local gcpaths = {} + + for _, mod in ipairs(mods) do + local path, value = unpack(mod) + + local delete = value == nil + self.aug:rm(aug_path(path)..(delete and '' or '/*')) + + local apath, mvpath, index = self:find(path, type(value) ~= 'table') + local mpath = mvpath or apath + + if not delete then + if #self.aug:match(mpath) == 0 then + + local function order(path) + return topology('/augeas'..path).order + end + local ord = order(pth.join('/', unpack(path))) + + for _, sibling in ipairs(self.aug:match(pth.parent(mpath)..'/*')) do + if order(strip_name(sibling):sub(7, -1)) > ord then + self.aug:insert(sibling, pth.name(mpath), true) + break + end + end + end + + if mvpath then + local size = #self.aug:match(mvpath) + while size < index do + self.aug:insert(ipath(mvpath, size), pth.name(mvpath)) + size = size + 1 + end + end + end + + if type(value) == 'table' then value = nil end + if not delete or mvpath then self.aug:set(apath, value) + elseif apath > '/' then apath = pth.parent(apath) end + + if delete or value == '' then gcpaths[mpath] = true end + end + + local function gc(path) + if path == '/' or #self.aug:match(path..'/*') > 0 then return end + if self.aug:rm(path.."[. = '']") > 0 then gc(pth.parent(path)) end + end + for p, _ in pairs(gcpaths) do gc(p) end + + if self.aug:save() ~= 0 then + print('Augeas save failed') + for _, ep in ipairs(self.aug:match('/augeas//error')) do + print(ep, self.aug:get(ep)) + end + assert(false) + end +end + + +return backend diff --git a/acf2/persistence/backends/files.lua b/acf2/persistence/backends/files.lua new file mode 100644 index 0000000..70ff33e --- /dev/null +++ b/acf2/persistence/backends/files.lua @@ -0,0 +1,107 @@ +--[[ +Copyright (c) 2012-2013 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local topology = require('acf2.model.root').topology +local pth = require('acf2.path') +local util = require('acf2.persistence.util') +local copy = require('acf2.util').copy + +local posix = require('posix') +local stringy = require('stringy') + + +local function get_scope(top) + if not top or top.type ~= 'reference' or not pth.is_unique(top.scope) then + return + end + + return stringy.startswith( + top.scope, '/files/' + ) and top.scope:sub(7, -1) or nil +end + + +local backend = require('acf2.object').class() + +-- TODO cache expiration +function backend:init() self.cache = {} end + +function backend:get(path, top) + local name = pth.join('/', unpack(path)) + + if not self.cache[name] then + local t = posix.stat(name, 'type') + if not t then return end + + if t == 'regular' then + self.cache[name] = util.read_file(name) + + elseif t == 'link' then + -- TODO handle relative symlinks + local target = posix.readlink(name) + assert(target) + + local scope = get_scope(top) + assert(scope) + scope = scope..'/' + + local slen = scope:len() + assert(target:sub(1, slen) == scope) + return target:sub(slen + 1, -1) + + elseif t == 'directory' then + local res = {} + for _, fname in ipairs(posix.dir(name)) do + if not ({['.']=true, ['..']=true})[fname] then + table.insert(res, pth.name(fname)) + end + end + return res + + else error('Unsupported file type: '..name) end + end + + return self.cache[name] +end + +function backend:set(mods) + for _, mod in pairs(mods) do + local path, value = unpack(mod) + local name = pth.join('/', unpack(path)) + + if value == nil then + print('DEL', name) + + local t = posix.stat(name, 'type') + if t == 'directory' then + assert(posix.rmdir(name)) + elseif t then assert(os.remove(name)) end + + self.cache[name] = nil + + elseif type(value) == 'table' then + assert(posix.mkdir(name)) + + else + local scope = get_scope(topology('/files'..name)) + + if scope then + -- TODO use relative symlink + os.remove(name) + assert(posix.link(pth.to_absolute(value, scope), name, true)) + + else + local file = util.open_file(name, 'w') + file:write(tostring(value)) + file:close() + + self.cache[name] = value + end + end + end +end + + +return backend diff --git a/acf2/persistence/backends/json.lua b/acf2/persistence/backends/json.lua new file mode 100644 index 0000000..809d947 --- /dev/null +++ b/acf2/persistence/backends/json.lua @@ -0,0 +1,79 @@ +--[[ +Copyright (c) 2012-2013 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local pth = require('acf2.path') +local Cache = require('acf2.persistence.backends.volatile') +local util = require('acf2.persistence.util') +local copy = require('acf2.util').copy + +local json = require('cjson') +local posix = require('posix') + + +local backend = require('acf2.object').class() + +function backend:init() + -- TODO cache expiration + self.cache = {} + self.dirty = {} +end + +function backend:split_path(path) + local fpath = copy(path) + local jpath = {} + local res + + while #fpath > 0 do + local fp = pth.join('/', unpack(fpath)) + if self.cache[fp] then return fp, jpath end + table.insert(jpath, 1, fpath[#fpath]) + table.remove(fpath) + end + + fpath = '/' + + while true do + fpath = pth.join(fpath, jpath[1]) + table.remove(jpath, 1) + + local t = posix.stat(fpath, 'type') + if t == 'link' then t = posix.stat(posix.readlink(fpath), 'type') end + if not t or not ({directory=true, regular=true})[t] then + error('File or directory does not exist: '..fpath) + end + + if t == 'regular' then return fpath, jpath end + + assert(#jpath > 0) + end +end + +function backend:get(path, top) + local fpath, jpath = self:split_path(path) + if not self.cache[fpath] then + self.cache[fpath] = Cache(json.decode(util.read_file(fpath))) + end + return self.cache[fpath]:get(jpath, top) +end + +function backend:set(mods) + local dirty = {} + + for _, mod in ipairs(mods) do + local path, value = unpack(mod) + local fpath, jpath = self:split_path(path) + self.cache[fpath]:_set(jpath, value) + dirty[fpath] = true + end + + for path, _ in pairs(dirty) do + local file = util.open_file(path, 'w') + file:write(json.encode(self.cache[path]:_get{})) + file:close() + end +end + + +return backend diff --git a/acf2/persistence/backends/null.lua b/acf2/persistence/backends/null.lua new file mode 100644 index 0000000..118f35b --- /dev/null +++ b/acf2/persistence/backends/null.lua @@ -0,0 +1,10 @@ +--[[ +Copyright (c) 2012-2013 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local backend = require('acf2.object').class() +function backend:get(path, top) if #path == 0 then return {} end end +function backend:set(mods) end + +return backend diff --git a/acf2/persistence/backends/volatile.lua b/acf2/persistence/backends/volatile.lua new file mode 100644 index 0000000..e33068d --- /dev/null +++ b/acf2/persistence/backends/volatile.lua @@ -0,0 +1,46 @@ +--[[ +Copyright (c) 2012-2013 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local util = require('acf2.util') + + +local backend = require('acf2.object').class() + +function backend:init(data) self.data = data or {} end + +function backend:_get(path) + local res = self.data + for _, comp in ipairs(path) do + if res == nil then return end + assert(type(res) == 'table') + res = res[comp] + end + return res +end + +function backend:get(path, top) + local res = self:_get(path) + return type(res) == 'table' and util.keys(res) or res +end + +function backend:_set(path, value) + if type(value) == 'table' then value = {} end + + if #path == 0 then self.data = value + + else + local comps = util.copy(path) + local name = comps[#comps] + table.remove(comps) + self:_get(comps)[name] = value + end +end + +function backend:set(mods) + for _, mod in ipairs(mods) do self:_set(unpack(mod)) end +end + + +return backend diff --git a/acf2/persistence/init.lua b/acf2/persistence/init.lua new file mode 100644 index 0000000..1dca61d --- /dev/null +++ b/acf2/persistence/init.lua @@ -0,0 +1,79 @@ +--[[ +Copyright (c) 2012-2013 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local M = {} + +local loadmods = require('acf2.loader') +local topology = require('acf2.model.root').topology +local object = require('acf2.object') +local pth = require('acf2.path') +local util = require('acf2.util') + +local stringy = require('stringy') + + +local DataStore = object.class( + require('acf2.transaction.backend').TransactionBackend +) + +function DataStore:init() + object.super(self, DataStore):init() + self.backends = util.map( + function(m) return m() end, + loadmods('persistence/backends') + ) +end + +function DataStore:split_path(path) + local comps = pth.split(path) + local backend = self.backends[comps[1]] + assert(backend) + table.remove(comps, 1) + return backend, comps +end + +function DataStore:get(path) + local backend, comps = self:split_path(path) + local top = topology(path) + + local res = backend:get(comps, top) + + if top then + local t = top.type + if t and res ~= nil then + local atype = type(res) + + if t == 'table' then assert(atype == 'table') + + else + assert(atype ~= 'table') + + if t == 'string' then res = tostring(res) + elseif t == 'number' then res = tonumber(res) + elseif t == 'boolean' then + res = (res and res ~= 'false') and true or false + elseif t == 'reference' then assert(atype == 'string') + else assert(false) end + end + end + end + + return util.copy(res), self.mod_time[path] or 0 +end + +function DataStore:_set_multiple(mods) + local bms = {} + + for _, mod in ipairs(mods) do + local path, value = unpack(mod) + local backend, comps = self:split_path(path) + table.insert(util.setdefault(bms, backend, {}), {comps, value}) + end + + for backend, bm in pairs(bms) do backend:set(bm) end +end + + +return DataStore() diff --git a/acf2/persistence/util.lua b/acf2/persistence/util.lua new file mode 100644 index 0000000..a657411 --- /dev/null +++ b/acf2/persistence/util.lua @@ -0,0 +1,22 @@ +--[[ +Copyright (c) 2012-2013 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local M = {} + +function M.open_file(path, mode) + local file = io.open(path, mode) + if not file then error('Cannot open file: '..path) end + return file +end + +function M.read_file(path) + local file = M.open_file(path) + local data = '' + for line in file:lines() do data = data..line end + file:close() + return data +end + +return M |