local dir = require "pl.dir" local path = require "pl.path" local store = require "volksdata.store" local term = require "volksdata.term" local nsm = require "volksdata.namespace" -- Read only module properties. local PROTECTED = { store = true, reset_store = true, } local fpath = debug.getinfo(1, "S").source:sub(2) local root_path = path.dirname(path.dirname(fpath)) local config_path = os.getenv("PKAR_CONFIG_DIR") or path.join(root_path .. "/config") local config = dofile(path.join(config_path, "app.lua")) local store_id = "file://" .. (os.getenv("PKAR_BASE") or config.fs.dres_path) -- Fill namespace map from config. for pfx, ns in pairs(config.namespace) do nsm.add(pfx, ns) end -- Initialize the store. `clear` wipes all data. YOU HAVE BEEN WARNED! local store_init = function(clear) return store.new(store.MDB, store_id, clear) end local M = { -- Project root path. root = root_path, config = config, default_title = "Pocket Archive", store_id = store_id, --Logger config. logger = require "sllog":init{ {"err", "%T PKAR %-5L ", "%n", io.stderr}, {"warn", "%T PKAR %-5L ", "%n", io.stderr}, {"info", "%T PKAR %-5L ", "%n", io.stderr}, {"debug", "%T PKAR %-5L%f (%S) ", "%n", io.stderr}, timefn=(socket or {}).gettime, -- use socket.gettime if available report="debug", -- to which level should internal log events be passed? hookrequire=true, -- also report calls to require() level="debug", -- output levels up to and including "dbg" }, -- Commonly used terms. RDF_TYPE = term.new_iriref_ns("rdf:type"), DC_TITLE_P = term.new_iriref_ns("dc:title"), DC_CREATED_P = term.new_iriref_ns("dc:created"), TN_P = term.new_iriref_ns("pas:thumbnail"), FIRST_P = term.new_iriref_ns("pas:first"), NEXT_P = term.new_iriref_ns("pas:next"), PATH_P = term.new_iriref_ns("pas:sourcePath"), CONTENT_TYPE_P = term.new_iriref_ns("pas:contentType"), ART_T = term.new_iriref_ns("pas:Artifact"), PART_T = term.new_iriref_ns("pas:Part"), FILE_T = term.new_iriref_ns("pas:File"), -- Common namespaces PAR_NS = nsm.get_ns("par"), PAS_NS = nsm.get_ns("pas"), -- Methods. -- Escape strings for use in gsub patterns. escape_ptn = function(src) return src:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%0") end, } --[[ Generate pairtree directory and file path from an ID string and prefix. The directory is created if not existing. The ID string can include the `par:` namespace prefix or the fully qualified namespace. ext is optional, and is appended to the raw path. if no_create is not nil or false, the directory will not be checked for existence or created. return: full file path, with the optional extension if provided. --]] M.gen_pairtree = function (pfx, id_str, ext, no_create) local bare_id = id_str:gsub(M.PAR_NS, ""):gsub("^par:", "") local res_dir = path.join(pfx, bare_id:sub(1,2), bare_id:sub(3,4)) local created, err if (path.isdir(res_dir)) or no_create then created = false else created, err = dir.makepath(res_dir) if not created then error(err) end end local fpath if (res_dir) then fpath = path.join(res_dir, bare_id .. (ext or "")) end return fpath, created end -- No more assignments to the module afte here. local mt = { __index = function(t, k) if (k == "store") then if not rawget(t, "_store") then rawset(t, "_store", store.new(store.MDB, store_id)) end return rawget(t, "_store") -- WIPE ALL DATA from the store and returns the new empty store handle. elseif (k == "reset_store") then rawset(t, "_store", store.new(store.MDB, store_id, true)) return rawget(t, "_store") end end, __newindex = function () return nil, "Module is read only." end } setmetatable (M, mt) -- Initialize random ID generator. math.randomseed(M.config.id.seed[1], M.config.id.seed[2]) return M