Software /
code /
prosody-modules
Changeset
4876:0f5f2d4475b9
mod_http_xep227: Add support for import via APIs rather than direct store manipulation
In particular this transitions PEP nodes and data to be imported via mod_pep's
APIs, fixing issues with importing at runtime while PEP data may already be
live in RAM.
Next obvious candidate for this approach is rosters, so clients get immediate
roster pushes and other special handling (such as emitting subscribes to reach
the desired subscription state).
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Tue, 18 Jan 2022 17:01:18 +0000 |
parents | 4875:c8a7cb6fa1a7 |
children | 4877:adc6241e5d16 |
files | mod_http_xep227/mod_http_xep227.lua |
diffstat | 1 files changed, 124 insertions(+), 29 deletions(-) [+] |
line wrap: on
line diff
--- a/mod_http_xep227/mod_http_xep227.lua Tue Jan 18 09:39:15 2022 +0100 +++ b/mod_http_xep227/mod_http_xep227.lua Tue Jan 18 17:01:18 2022 +0000 @@ -4,6 +4,9 @@ local st = require "util.stanza"; local xml = require "util.xml"; +local jid_join = require "util.jid".join; + +local mod_pep = module:depends("pep"); local tokens = module:depends("tokenauth"); module:depends("storage_xep0227"); @@ -20,6 +23,8 @@ pep_data = "archive"; }; +local xmlns_pubsub = "http://jabber.org/protocol/pubsub"; + local function new_user_xml(username, host) local user_xml = st.stanza("server-data", {xmlns='urn:xmpp:pie:0'}) :tag("host", { jid = host }) @@ -152,6 +157,111 @@ return [[<?xml version="1.0" encoding="utf-8" ?>]]..tostring(xml_data); end +local function generic_keyval_importer(username, host, store_name, source_store) + -- Read the current data + local data, err = source_store:get(username); + if data ~= nil or not err then + local target_store = sm.open(host, store_name); + -- Transform the data and update user_xml (via the _set_user_xml callback) + if not target_store:set(username, data == nil and {} or data) then + return 500; + end + module:log("debug", "Imported data for '%s' store", store_name); + elseif err then + return nil, err; + else + module:log("debug", "No data for store '%s'", store_name); + end + return true; +end + +local function generic_archive_importer(username, host, store_name, source_archive) + local dest_driver = get_config_driver(store_name, host); + local dest_archive = dest_driver:open(store_name, "archive"); + local results_iter, results_err = source_archive:find(username); + if results_iter then + local count, errs = 0, 0; + for id, item, when, with in source_archive:find(username) do + local ok, err = dest_archive:append(username, id, item, when, with); + if ok then + count = count + 1; + else + module:log("warn", "Error: %s", err); + errs = errs + 1; + end + if ( count + errs ) % 100 == 0 then + module:log("info", "%d items migrated, %d errors", count, errs); + end + end + elseif results_err then + module:log("warn", "Unable to read from '%s': %s", store_name, results_err); + return nil, "error reading from source archive"; + end + return true; +end + +local special_keyval_importers = {}; + +function special_keyval_importers.pep(username, host, store_name, store) --luacheck: ignore 212/store_name + local user_jid = jid_join(username, host); + local pep_service = mod_pep.get_pep_service(username); + local pep_nodes, store_err = store:get(username); + if not pep_nodes and store_err then + return nil, store_err; + end + + local all_ok = true; + for node_name, node_config in pairs(pep_nodes) do + local ok, ret = pep_service:get_node_config(node_name, user_jid); + if not ok and ret == "item-not-found" then + -- Create node according to imported data + if node_config == true then node_config = {}; end + local create_ok, create_err = pep_service:create(node_name, user_jid, node_config.config); + if not create_ok then + module:log("warn", "Failed to create PEP node: %s", create_err); + all_ok = false; + end + end + end + + return all_ok; +end + +local special_archive_importers = setmetatable({}, { + __index = function (t, k) + if k:match("^pep_") then + return t.pep_data; + end + end; +}); + +function special_archive_importers.pep_data(username, host, store_name, source_archive) + local user_jid = jid_join(username, host); + local pep_service = mod_pep.get_pep_service(username); + + local node_name = store_name:match("^pep_(.+)$"); + if not node_name then + return nil, "invalid store name"; + end + + local results_iter, results_err = source_archive:find(username); + if results_iter then + local count, errs = 0, 0; + for id, item in source_archive:find(username) do + local wrapped_item = st.stanza("item", { xmlns = xmlns_pubsub, id = id }) + :add_child(item); + local ok, err = pep_service:publish(node_name, user_jid, id, wrapped_item); + if not ok then + module:log("warn", "Failed to publish PEP item to '%s': %s", node_name, err, tostring(wrapped_item)); + end + end + module:log("debug", "Imported %d PEP items (%d errors)", count, errs); + elseif results_err then + return nil, "store access error"; + end + return true; +end + local function is_looking_like_xep227(xml_data) if not xml_data or xml_data.name ~= "server-data" or xml_data.attr.xmlns ~= "urn:xmpp:pie:0" then @@ -187,19 +297,16 @@ local query_params = http.formdecode(event.request.url.query or ""); local selected_stores = get_selected_stores(query_params); + module:log("debug", "Importing %d keyval stores (%s)...", #selected_stores.keyval, table.concat(selected_stores.keyval, ", ")); for _, store_name in ipairs(selected_stores.keyval) do + module:log("debug", "Importing keyval store %s...", store_name); -- Initialize the destination store (XEP-0227 backed) - local store = xep227_driver:open_xep0227(store_name, nil, user_xml); + local source_store = xep227_driver:open_xep0227(store_name, nil, user_xml); - -- Read the current data - local data, err = store:get(username); - if data ~= nil or not err then - local target_store = sm.open(session.host, store_name); - -- Transform the data and update user_xml (via the _set_user_xml callback) - if not target_store:set(username, data == nil and {} or data) then - return 500; - end - elseif err then + local importer = special_keyval_importers[store_name] or generic_keyval_importer; + local ok, err = importer(username, session.host, store_name, source_store); + if not ok then + module:log("warn", "Importer for keyval store '%s' encountered error: %s", store_name, err or "<no error returned>"); return 500; end end @@ -214,27 +321,15 @@ end end + module:log("debug", "Importing %d archive stores (%s)...", #selected_stores.archive, table.concat(selected_stores.archive, ", ")); for store_name in it.values(selected_stores.archive) do + module:log("debug", "Importing archive store %s...", store_name); local source_archive = xep227_driver:open_xep0227(store_name, "archive", user_xml); - local dest_driver = get_config_driver(store_name, session.host); - local dest_archive = dest_driver:open(store_name, "archive"); - local results_iter, results_err = source_archive:find(username); - if results_iter then - local count, errs = 0, 0; - for id, item, when, with in source_archive:find(username) do - local ok, err = dest_archive:append(username, id, item, when, with); - if ok then - count = count + 1; - else - module:log("warn", "Error: %s", err); - errs = errs + 1; - end - if ( count + errs ) % 100 == 0 then - module:log("info", "%d items migrated, %d errors", count, errs); - end - end - elseif results_err then - module:log("warn", "Unable to read from '%s': %s", store_name, results_err); + + local importer = special_archive_importers[store_name] or generic_archive_importer; + local ok, err = importer(username, session.host, store_name, source_archive); + if not ok then + module:log("warn", "Importer for archive store '%s' encountered error: %s", err or "<no error returned>"); return 500; end end