--[[ Copyright (c) 2012-2015 Kaarle Ritvanen See LICENSE file for license details --]] local loadmods = require('aconf.loader') local topology = require('aconf.model.root').topology local object = require('aconf.object') local pth = require('aconf.path') local util = require('aconf.util') local contains = util.contains local copy = util.copy local setdefault = util.setdefault local stringy = require('stringy') local DataStore = object.class( require('aconf.transaction.base').TransactionBackend ) function DataStore:init() object.super(self, DataStore):init() self.backends = util.map( function(m) return m() end, loadmods('persistence/backends') ) self.triggers = {pre={}, post={}} self:flush_cache() end function DataStore:flush_cache() self.cache = {} end function DataStore:trigger(phase, path, func) local funcs = setdefault(self.triggers[phase], path, {}) if not contains(funcs, func) then table.insert(funcs, func) end 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) if not self.cache[path] then local backend, comps = self:split_path(path) local top = topology(path) local value, ts = backend:get(comps, top) if top then local t = top.type if t and value ~= nil then local atype = type(value) if t == 'table' then assert(atype == 'table') else assert(atype ~= 'table') if t == 'string' then value = tostring(value) elseif t == 'number' then value = tonumber(value) elseif t == 'boolean' then if atype == 'string' then value = value:lower() end if value == 1 or contains( {'1', 't', 'true', 'y', 'yes'}, value ) then value = true elseif value == 0 or contains( {'0', 'f', 'false', 'n', 'no'}, value ) then value = false else value = value and true or false end elseif contains({'binary', 'reference'}, t) then assert(atype == 'string') else assert(false) end end end end self.cache[path] = {copy(value), ts or self.mod_time[path] or 0} end local value, ts = table.unpack(self.cache[path]) return copy(value), ts end function DataStore:_set_multiple(mods) self:flush_cache() local bms = {} local trigger = {} for _, mod in ipairs(mods) do local path, value = table.unpack(mod) local tp = path while not trigger[tp] do trigger[tp] = true tp = pth.parent(tp) end local backend, comps = self:split_path(path) table.insert(setdefault(bms, backend, {}), {comps, value}) end local function exec_triggers(phase) for path, _ in pairs(trigger) do for _, func in ipairs(self.triggers[phase][path] or {}) do func() end end end exec_triggers('pre') for backend, bm in pairs(bms) do backend:set(bm) end exec_triggers('post') end return DataStore()