2 local type, pairs
= type, pairs
;
3 local setmetatable
= setmetatable
;
6 local config
= require
"core.configmanager";
7 local datamanager
= require
"util.datamanager";
8 local modulemanager
= require
"core.modulemanager";
9 local multitable
= require
"util.multitable";
10 local log = require
"util.logger".init("storagemanager");
11 local async
= require
"util.async";
14 local prosody
= prosody
;
15 local hosts
= prosody
.hosts
;
20 local olddm
= {}; -- maintain old datamanager, for backwards compatibility
21 for k
,v
in pairs(datamanager
) do olddm
[k
] = v
; end
23 local null_storage_method
= function () return false, "no data storage active"; end
24 local null_storage_driver
= setmetatable(
27 open
= function (self
) return self
; end
29 __index
= function (self
, method
) --luacheck: ignore 212
30 return null_storage_method
;
35 local async_check
= config
.get("*", "storage_async_check") == true;
37 local stores_available
= multitable
.new();
39 local 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
);
48 return original_method
;
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());
55 return original_method(...);
57 rawset(t
, method_name
, wrapped_method
);
58 return wrapped_method
;
63 local 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
);
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);
75 host_session
.events
.add_handler("store-opened", check_async_wrapper
);
78 prosody
.events
.add_handler("host-activated", initialize_host
, 101);
80 local function load_driver(host
, driver_name
)
81 if driver_name
== "null" then
82 return null_storage_driver
;
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
);
88 log("error", "Failed to load storage driver plugin %s on %s: %s", driver_name
, host
, err
);
90 return stores_available
:get(host
, driver_name
);
93 local 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");
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";
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");
111 return storage_config
;
114 local function get_driver(host
, store
)
115 local storage
= get_storage_config(host
);
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
];
123 if not driver_name
then
124 driver_name
= config
.get(host
, "default_storage") or "internal";
127 local driver
= load_driver(host
, driver_name
);
129 log("warn", "Falling back to null driver for %s storage on %s", store
, host
);
130 driver_name
= "null";
131 driver
= null_storage_driver
;
133 return driver
, driver_name
;
136 local map_shim_mt
= {
138 get
= function(self
, username
, key
)
139 local ret
, err
= self
.keyval_store
:get(username
);
140 if ret
== nil then return nil, err
end
143 set
= function(self
, username
, key
, data
)
144 local current
, err
= self
.keyval_store
:get(username
);
145 if current
== nil then
153 return self
.keyval_store
:set(username
, current
);
155 set_keys
= function (self
, username
, keydatas
)
156 local current
, err
= self
.keyval_store
:get(username
);
157 if current
== nil then
163 for k
,v
in pairs(keydatas
) do
164 if v
== self
.remove then v
= nil; end
167 return self
.keyval_store
:set(username
, current
);
173 local open
; -- forward declaration
175 local 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
;
183 function open(host
, store
, typ
)
184 local driver
, driver_name
= get_driver(host
, store
);
185 local ret
, err
= driver
:open(store
, typ
);
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
);
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;
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
;
206 local 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
211 for store
, driver_name
in pairs(storage
) do
212 if not purged
[driver_name
] then
213 local driver
= get_driver(host
, store
);
215 purged
[driver_name
] = driver
:purge(user
);
217 log("warn", "Storage driver %s does not support removing all user data, "
218 .."you may need to delete it manually", driver_name
);
223 get_driver(host
):purge(user
); -- and the default driver
225 olddm
.purge(user
, host
); -- COMPAT list stores, like offline messages end up in the old datamanager
230 function datamanager
.load(username
, host
, datastore
)
231 return open(host
, datastore
):get(username
);
233 function datamanager
.store(username
, host
, datastore
, data
)
234 return open(host
, datastore
):set(username
, data
);
236 function 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
241 return driver
:users();
243 function datamanager
.stores(username
, host
, typ
)
244 return get_driver(host
):stores(username
, typ
);
246 function datamanager
.purge(username
, host
)
247 return purge(username
, host
);
251 initialize_host
= initialize_host
;
252 load_driver
= load_driver
;
253 get_driver
= get_driver
;