summaryrefslogtreecommitdiffstats
path: root/acf/persistence/backends/json.lua
blob: 7300ce9e954363e44ed0f3b7d2ba31949cfd728d (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
--[[
Copyright (c) 2012 Kaarle Ritvanen
See LICENSE file for license details
--]]

module(..., package.seeall)

local pth = require('acf.path')
local util = require('acf.persistence.util')
local copy = require('acf.util').copy

require 'json'
require 'lfs'


local function keys(tbl)
   local res = {}
   for k, v in pairs(tbl) do table.insert(res, k) end
   return res
end


backend = require('acf.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 attrs = lfs.attributes(fpath)
      if not attrs or not ({directory=true, file=true})[attrs.mode] then
	 error('File or directory does not exist: '..fpath)
      end

      if attrs.mode == 'file' then return fpath, jpath end

      assert(#jpath > 0)
   end
end

function backend:_get(path)
   local fpath, jpath = self:split_path(path)

   if not self.cache[fpath] then
      self.cache[fpath] = json.decode(util.read_file(fpath))
   end

   local res = self.cache[fpath]

   while #jpath > 0 do
      if res == nil then return end
      assert(type(res) == 'table')
      local next = jpath[1]
      res = res[tonumber(next) or next]
      table.remove(jpath, 1)
   end

   return res
end

function backend:get(path)
   local res = self:_get(path)
   return type(res) == 'table' and keys(res) or res
end

function backend:set(mods)
   local dirty = {}

   for _, mod in ipairs(mods) do
      local p, t, value = unpack(mod)
      local fpath, jpath = self:split_path(p)
      if t == 'table' then value = {} end

      if #jpath == 0 then self.cache[fpath] = value

      else
	 local comps = copy(p)
	 local name = comps[#comps]
	 table.remove(comps)
	 self:_get(comps)[tonumber(name) or name] = value
      end

      dirty[fpath] = true
   end

   for path, _ in pairs(dirty) do
      local file = util.open_file(path, 'w')
      file:write(json.encode(self.cache[path]))
      file:close()
   end
end