# HG changeset patch # User Kim Alvefur # Date 1415626861 -3600 # Node ID 9158882dd9a1a355c887daac1bbfbaf898387215 # Parent f9cb09d451c75d6fc2b7761fee58ed2fe75d1d68 mod_storage_muc_log: Provides an archive API to mod_muc_log data diff -r f9cb09d451c7 -r 9158882dd9a1 mod_storage_muc_log/mod_storage_muc_log.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_storage_muc_log/mod_storage_muc_log.lua Mon Nov 10 14:41:01 2014 +0100 @@ -0,0 +1,154 @@ + +local datamanager = require"core.storagemanager".olddm; +local xml_parse = require"util.xml".parse; +local data_load, data_store = datamanager.load, datamanager.store; +local datastore = "muc_log"; +local datetime = require"util.datetime" +local lfs = require"lfs"; +local noop = function () end; +local os_date = os.date; + +local timef, datef = "!%X", "!%y%m%d"; +local host = module.host; + +local driver = {}; +local driver_mt = { __index = driver }; + +do + -- Sanity check + -- Fun fact: 09:00 and 21:00 en_HK are both "09:00:00 UTC" + local t = os_date("!*t"); + t.hour = 9; + local am = os_date(timef, os.time(t)); + t.hour = 21; + local pm = os_date(timef, os.time(t)); + if am == pm then + module:log("warn", "Timestamps in AM and PM are identical in your locale, expect timestamps to be wrong"); + end +end + +local function parse_silly(date, time) + local year, month, day = date:match("^(%d%d)(%d%d)(%d%d)"); + year = "20"..year; + -- year = (year < "70" and "20" or "19") .. year; + local hour, min, sec = time:match("(%d%d)%D+(%d%d)%D+(%d%d)"); + if hour == "12" and time:find("[Aa][Mm]") then + hour = "00"; + elseif hour < "12" and time:find("[Pp][Mm]") then + hour = tostring(tonumber(hour) % 12 + 12); + end + return datetime.parse(("%s-%s-%sT%s:%s:%sZ"):format(year, month, day, hour or "00", min or "00", sec or "00")); +end + +local function st_with(tag) + local with = tag.attr.type; + return with and tag.name .. "<" .. with or tag.name; +end + +function driver:append(node, key, when, with, stanza) + local today = os_date(datef, when); + local now = os_date(timef, when); + local data = data_load(node, host, datastore .. "/" .. today) or {}; + data[#data + 1] = "" .. tostring(stanza) .. "\n"; + datamanager.getpath(node, host, datastore, nil, true); -- create the datastore dir + local ok, err = data_store(node, host, datastore .. "/" .. today, data); + if not ok then + return ok, err; + end + return today .. "_" .. #data; +end + +function driver:find(node, query) + local path = datamanager.getpath(node, host, datastore):match("(.*)/"); + + local ok, iter, state, var = pcall(lfs.dir, path); + if not ok then + module:log("warn", iter); + return nil, iter; + end + + local dates, i = {}, 1; + for dir in iter, state, var do + if lfs.attributes(datamanager.getpath(node, host, datastore .. "/" .. dir), "mode") == "file" then + dates[i], i = dir, i+1; + end + end + if dates[1] == nil then return noop, 0; end + table.sort(dates); + + return coroutine.wrap(function () + local query = query; + local dates = dates; + local start_date = query and query.start and os_date(datef, query.start) or dates[1]; + local end_date = query and query["end"] and os_date(datef, query["end"]) or dates[#dates]; + local start_time = query and query.start and os_date(timef, query.start) or dates[1]; + local end_time = query and query["end"] and os_date(timef, query["end"]) or dates[#dates]; + local query_with = query and query.with; + local query_limit = query and query.limit; + local seek_once = query and query.after; + + local today, time, data, err, item; + local inner_start, inner_stop, inner_step; + local outer_start, outer_stop, outer_step = 1, #dates, 1; + if query and query.reverse then + outer_start, outer_stop, outer_step = outer_stop, outer_start, -outer_step; + seek_once = query.before; + if seek_once then + end_date = seek_once:match"^(%d+)_%d"; + end + elseif seek_once then + start_date = seek_once:match"^(%d+)_%d"; + end + local matches = 0; + for i = outer_start, outer_stop, outer_step do + today = dates[i]; + if today >= start_date and today <= end_date then + data, err = data_load(node, host, datastore .. "/" .. today); + if data then + inner_start, inner_stop, inner_step = 1, #data, 1; + if query and query.reverse then + inner_start, inner_stop, inner_step = inner_stop, inner_start, -inner_step; + end + if seek_once then + inner_start = tonumber(seek_once:match("_(%d+)$")); + inner_start = inner_start + (query and query.reverse and -1 or 1); + seek_once = nil; + end + for i = inner_start, inner_stop, inner_step do + item, err = data[i]; + if item then + item, err = xml_parse(item); + end + if item then + time = item.attr.time; + item = item.tags[1]; + if (today >= start_date or time >= start_time) and + (today <= end_date or time <= end_time) and + (not query_with or query_with == st_with(item)) and + item:get_child_text("alreadyJoined") ~= "true" then + matches = matches + 1; + coroutine.yield(today.."_"..i, item, parse_silly(today, time)); + if query_limit and matches >= query_limit then + return; + end + end + elseif err then + module:log("warn", err); + end + end + elseif err then + module:log("warn", err); + end + end + end + end); +end + +function open(_, store, typ) + if typ ~= "archive" then + return nil, "unsupported-store"; + end + return setmetatable({ store = store, type = typ }, driver_mt); +end + +module:provides "storage";