# HG changeset patch # User Emmanuel Gil Peyrot # Date 1467260532 -3600 # Node ID 51596d73157e7ad88becb8c5908b46e562f6c060 # Parent 3d80f8dba886649527b1c3eee425f537cecfa168 mod_storage_muconference_readonly: Initial commit diff -r 3d80f8dba886 -r 51596d73157e mod_storage_muconference_readonly/README.markdown --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_storage_muconference_readonly/README.markdown Thu Jun 30 05:22:12 2016 +0100 @@ -0,0 +1,45 @@ +--- +labels: +- 'Type-Storage' +- 'Stage-Alpha' +summary: MU-Conference SQL Read-only Storage Module +... + +Introduction +============ + +This is a storage backend using MU-Conference’s SQL storage. It depends +on [LuaDBI](https://prosody.im/doc/depends#luadbi) + +This module only works in read-only, and was made to be used by +[mod\_migrate](https://modules.prosody.im/mod_migrate.html) to migrate +from MU-Conference’s SQL storage. + +You may need to convert your 'rooms' and 'rooms\_lists' tables to +utf8mb4 before running that script, in order not to end up with +mojibake. Note that MySQL doesn’t support having more than +191 characters in the jid field in this case, so you may have to change +the table schema as well. + +Configuration +============= + +Copy the module to the prosody modules/plugins directory. + +In Prosody's configuration file, set: + + storage = "muconference_readonly" + +MUConferenceSQL options are the same as the +[https://prosody.im/doc/modules/mod\_storage\_sql](SQL ones): + +Compatibility +============= + + ------- --------------------------- + trunk Works + ------- --------------------------- + 0.10 Untested, but should work + ------- --------------------------- + 0.9 Does not work + ------- --------------------------- diff -r 3d80f8dba886 -r 51596d73157e mod_storage_muconference_readonly/mod_storage_muconference_readonly.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_storage_muconference_readonly/mod_storage_muconference_readonly.lua Thu Jun 30 05:22:12 2016 +0100 @@ -0,0 +1,155 @@ + +-- luacheck: ignore 212/self + +local sql = require "util.sql"; +local xml_parse = require "util.xml".parse; +local resolve_relative_path = require "util.paths".resolve_relative_path; +local stanza_preserialize = require "util.stanza".preserialize; + +local unpack = unpack +local function iterator(result) + return function(result_) + local row = result_(); + if row ~= nil then + return unpack(row); + end + end, result, nil; +end + +local default_params = { driver = "MySQL" }; + +local engine; + +local host = module.host; +local room, store; + +local function get_best_affiliation(a, b) + if a == 'owner' or b == 'owner' then + return 'owner'; + elseif a == 'administrator' or b == 'administrator' then + return 'administrator'; + elseif a == 'outcast' or b == 'outcast' then + return 'outcast'; + elseif a == 'member' or b == 'member' then + return 'member'; + end + assert(false); +end + +local function keyval_store_get() + if store == "muc" then + local room_jid = room.."@"..host; + local result; + for row in engine:select("SELECT `name`,`desc`,`topic`,`public`,`secret` FROM `rooms` WHERE `jid`=? LIMIT 1", room_jid or "") do result = row end + local name = result[1]; + local desc = result[2]; + local subject = result[3]; + local public = result[4]; + local hidden = public == 0 and true or nil; + local secret = result[5]; + if secret == '' then secret = nil end + local affiliations = {}; + for row in engine:select("SELECT `jid_user`,`affil` FROM `rooms_lists` WHERE `jid_room`=?", room_jid or "") do + local jid_user = row[1]; + local affil = row[2]; + -- mu-conference has a bug where full JIDs get stored… + local bare_jid = jid_user:gsub('/.*', ''); + local old_affil = affiliations[bare_jid]; + -- mu-conference has a bug where it can record multiple affiliations… + if old_affil ~= nil and old_affil ~= affil then + affil = get_best_affiliation(old_affil, affil); + end + -- terminology is clearly “admin”, not “administrator”. + if affil == 'administrator' then + affil = 'admin'; + end + affiliations[bare_jid] = affil; + end + return { + jid = room_jid, + _data = { + persistent = true, + name = name, + subject = subject, + password = secret, + hidden = hidden, + }, + _affiliations = affiliations, + }; + end +end + +--- Key/value store API (default store type) + +local keyval_store = {}; +keyval_store.__index = keyval_store; +function keyval_store:get(roomname) + room, store = roomname, self.store; + local ok, result = engine:transaction(keyval_store_get); + if not ok then + module:log("error", "Unable to read from database %s store for %s: %s", store, roomname or "", result); + return nil, result; + end + return result; +end + +function keyval_store:users() + local host_length = host:len() + 1; + local ok, result = engine:transaction(function() + return engine:select("SELECT SUBSTRING_INDEX(jid, '@', 1) FROM `rooms`"); + end); + if not ok then return ok, result end + return iterator(result); +end + +local stores = { + keyval = keyval_store; +}; + +--- Implement storage driver API + +-- FIXME: Some of these operations need to operate on the archive store(s) too + +local driver = {}; + +function driver:open(store, typ) + local store_mt = stores[typ or "keyval"]; + if store_mt then + return setmetatable({ store = store }, store_mt); + end + return nil, "unsupported-store"; +end + +function driver:stores(roomname) + local query = "SELECT 'config'"; + if roomname == true or not roomname then + roomname = ""; + end + local ok, result = engine:transaction(function() + return engine:select(query, host, roomname); + end); + if not ok then return ok, result end + return iterator(result); +end + +--- Initialization + + +local function normalize_params(params) + assert(params.driver and params.database, "Configuration error: Both the SQL driver and the database need to be specified"); + return params; +end + +function module.load() + if prosody.prosodyctl then return; end + local engines = module:shared("/*/sql/connections"); + local params = normalize_params(module:get_option("sql", default_params)); + engine = engines[sql.db2uri(params)]; + if not engine then + module:log("debug", "Creating new engine"); + engine = sql:create_engine(params); + engines[sql.db2uri(params)] = engine; + end + + module:provides("storage", driver); +end