1 2local type, pairs = type, pairs; 3local setmetatable = setmetatable; 4local rawset = rawset; 5 6local config = require "core.configmanager"; 7local datamanager = require "util.datamanager"; 8local modulemanager = require "core.modulemanager"; 9local multitable = require "util.multitable"; 10local log = require "util.logger".init("storagemanager"); 11local async = require "util.async"; 12local debug = debug; 13 14local prosody = prosody; 15local hosts = prosody.hosts; 16 17local _ENV = nil; 18-- luacheck: std none 19 20local olddm = {}; -- maintain old datamanager, for backwards compatibility 21for k,v in pairs(datamanager) do olddm[k] = v; end 22 23local null_storage_method = function () return false, "no data storage active"; end 24local null_storage_driver = setmetatable( 25 { 26 name = "null", 27 open = function (self) return self; end 28 }, { 29 __index = function (self, method) --luacheck: ignore 212 30 return null_storage_method; 31 end 32 } 33); 34 35local async_check = config.get("*", "storage_async_check") == true; 36 37local stores_available = multitable.new(); 38 39local function check_async_wrapper(event) 40 local store = event.store; 41 event.store = setmetatable({}, { 42 __index = function (t, method_name) 43 local original_method = store[method_name]; 44 if type(original_method) ~= "function" then 45 if original_method then 46 rawset(t, method_name, original_method); 47 end 48 return original_method; 49 end 50 local wrapped_method = function (...) 51 if not async.ready() then 52 log("warn", "ASYNC-01: Attempt to access storage outside async context, " 53 .."see https://prosody.im/doc/developers/async - %s", debug.traceback()); 54 end 55 return original_method(...); 56 end 57 rawset(t, method_name, wrapped_method); 58 return wrapped_method; 59 end; 60 }); 61end 62 63local function initialize_host(host) 64 local host_session = hosts[host]; 65 host_session.events.add_handler("item-added/storage-provider", function (event) 66 local item = event.item; 67 stores_available:set(host, item.name, item); 68 end); 69 70 host_session.events.add_handler("item-removed/storage-provider", function (event) 71 local item = event.item; 72 stores_available:set(host, item.name, nil); 73 end); 74 if async_check then 75 host_session.events.add_handler("store-opened", check_async_wrapper); 76 end 77end 78prosody.events.add_handler("host-activated", initialize_host, 101); 79 80local function load_driver(host, driver_name) 81 if driver_name == "null" then 82 return null_storage_driver; 83 end 84 local driver = stores_available:get(host, driver_name); 85 if driver then return driver; end 86 local ok, err = modulemanager.load(host, "storage_"..driver_name); 87 if not ok then 88 log("error", "Failed to load storage driver plugin %s on %s: %s", driver_name, host, err); 89 end 90 return stores_available:get(host, driver_name); 91end 92 93local function get_storage_config(host) 94 -- COMPAT w/ unreleased Prosody 0.10 and the once-experimental mod_storage_sql2 in peoples' config files 95 local storage_config = config.get(host, "storage"); 96 local found_sql2; 97 if storage_config == "sql2" then 98 storage_config, found_sql2 = "sql", true; 99 elseif type(storage_config) == "table" then 100 for store_name, driver_name in pairs(storage_config) do 101 if driver_name == "sql2" then 102 storage_config[store_name] = "sql"; 103 found_sql2 = true; 104 end 105 end 106 end 107 if found_sql2 then 108 log("error", "The temporary 'sql2' storage module has now been renamed to 'sql', " 109 .."please update your config file: https://prosody.im/doc/modules/mod_storage_sql2"); 110 end 111 return storage_config; 112end 113 114local function get_driver(host, store) 115 local storage = get_storage_config(host); 116 local driver_name; 117 local option_type = type(storage); 118 if option_type == "string" then 119 driver_name = storage; 120 elseif option_type == "table" then 121 driver_name = storage[store]; 122 end 123 if not driver_name then 124 driver_name = config.get(host, "default_storage") or "internal"; 125 end 126 127 local driver = load_driver(host, driver_name); 128 if not driver then 129 log("warn", "Falling back to null driver for %s storage on %s", store, host); 130 driver_name = "null"; 131 driver = null_storage_driver; 132 end 133 return driver, driver_name; 134end 135 136local map_shim_mt = { 137 __index = { 138 get = function(self, username, key) 139 local ret, err = self.keyval_store:get(username); 140 if ret == nil then return nil, err end 141 return ret[key]; 142 end; 143 set = function(self, username, key, data) 144 local current, err = self.keyval_store:get(username); 145 if current == nil then 146 if err then 147 return nil, err; 148 else 149 current = {}; 150 end 151 end 152 current[key] = data; 153 return self.keyval_store:set(username, current); 154 end; 155 set_keys = function (self, username, keydatas) 156 local current, err = self.keyval_store:get(username); 157 if current == nil then 158 if err then 159 return nil, err; 160 end 161 current = {}; 162 end 163 for k,v in pairs(keydatas) do 164 if v == self.remove then v = nil; end 165 current[k] = v; 166 end 167 return self.keyval_store:set(username, current); 168 end; 169 remove = {}; 170 }; 171} 172 173local open; -- forward declaration 174 175local function create_map_shim(host, store) 176 local keyval_store, err = open(host, store, "keyval"); 177 if keyval_store == nil then return nil, err end 178 return setmetatable({ 179 keyval_store = keyval_store; 180 }, map_shim_mt); 181end 182 183function open(host, store, typ) 184 local driver, driver_name = get_driver(host, store); 185 local ret, err = driver:open(store, typ); 186 if not ret then 187 if err == "unsupported-store" then 188 if typ == "map" then -- Use shim on top of keyval store 189 log("debug", "map storage driver unavailable, using shim on top of keyval store."); 190 ret, err = create_map_shim(host, store); 191 else 192 log("debug", "Storage driver %s does not support store %s (%s), falling back to null driver", 193 driver_name, store, typ or "<nil>"); 194 ret, err = null_storage_driver, nil; 195 end 196 end 197 end 198 if ret then 199 local event_data = { host = host, store_name = store, store_type = typ, store = ret }; 200 hosts[host].events.fire_event("store-opened", event_data); 201 ret, err = event_data.store, event_data.store_err; 202 end 203 return ret, err; 204end 205 206local function purge(user, host) 207 local storage = get_storage_config(host); 208 if type(storage) == "table" then 209 -- multiple storage backends in use that we need to purge 210 local purged = {}; 211 for store, driver_name in pairs(storage) do 212 if not purged[driver_name] then 213 local driver = get_driver(host, store); 214 if driver.purge then 215 purged[driver_name] = driver:purge(user); 216 else 217 log("warn", "Storage driver %s does not support removing all user data, " 218 .."you may need to delete it manually", driver_name); 219 end 220 end 221 end 222 end 223 get_driver(host):purge(user); -- and the default driver 224 225 olddm.purge(user, host); -- COMPAT list stores, like offline messages end up in the old datamanager 226 227 return true; 228end 229 230function datamanager.load(username, host, datastore) 231 return open(host, datastore):get(username); 232end 233function datamanager.store(username, host, datastore, data) 234 return open(host, datastore):set(username, data); 235end 236function datamanager.users(host, datastore, typ) 237 local driver = open(host, datastore, typ); 238 if not driver.users then 239 return function() log("warn", "Storage driver %s does not support listing users", driver.name) end 240 end 241 return driver:users(); 242end 243function datamanager.stores(username, host, typ) 244 return get_driver(host):stores(username, typ); 245end 246function datamanager.purge(username, host) 247 return purge(username, host); 248end 249 250return { 251 initialize_host = initialize_host; 252 load_driver = load_driver; 253 get_driver = get_driver; 254 open = open; 255 purge = purge; 256 257 olddm = olddm; 258}; 259