1 diff --git src/gen_logdb.erl src/gen_logdb.erl
7 +%%%----------------------------------------------------------------------
8 +%%% File : gen_logdb.erl
9 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
10 +%%% Purpose : Describes generic behaviour for mod_logdb backends.
12 +%%% Id : $Id: gen_logdb.erl 1273 2009-02-05 18:12:57Z malik $
13 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
14 +%%%----------------------------------------------------------------------
17 +-author('o.palij@gmail.com').
19 +-export([behaviour_info/1]).
21 +behaviour_info(callbacks) ->
23 + % called from handle_info(start, _)
24 + % it should logon database and return reference to started instance
25 + % start(VHost, Opts) -> {ok, SPid} | error
26 + % Options - list of options to connect to db
27 + % Types: Options = list() -> [] |
30 + % {db, "logdb"}] | ...
31 + % VHost = list() -> "jabber.example.org"
34 + % called from cleanup/1
35 + % it should logoff database and do cleanup
37 + % Types: VHost = list() -> "jabber.example.org"
40 + % called from handle_call({addlog, _}, _, _)
41 + % it should log messages to database
42 + % log_message(VHost, Msg) -> ok | error
44 + % VHost = list() -> "jabber.example.org"
45 + % Msg = record() -> #msg
48 + % called from ejabberdctl rebuild_stats
49 + % it should rebuild stats table (if used) for vhost
50 + % rebuild_stats(VHost)
52 + % VHost = list() -> "jabber.example.org"
55 + % it should rebuild stats table (if used) for vhost at Date
56 + % rebuild_stats_at(VHost, Date)
58 + % VHost = list() -> "jabber.example.org"
59 + % Date = list() -> "2007-02-12"
60 + {rebuild_stats_at, 2},
62 + % called from user_messages_at_parse_query/5
63 + % it should delete selected user messages at date
64 + % delete_messages_by_user_at(VHost, Msgs, Date) -> ok | error
66 + % VHost = list() -> "jabber.example.org"
67 + % Msgs = list() -> [ #msg1, msg2, ... ]
68 + % Date = list() -> "2007-02-12"
69 + {delete_messages_by_user_at, 3},
71 + % called from user_messages_parse_query/4 | vhost_messages_at_parse_query/4
72 + % it should delete all user messages at date
73 + % delete_all_messages_by_user_at(User, VHost, Date) -> ok | error
75 + % User = list() -> "admin"
76 + % VHost = list() -> "jabber.example.org"
77 + % Date = list() -> "2007-02-12"
78 + {delete_all_messages_by_user_at, 3},
80 + % called from vhost_messages_parse_query/3
81 + % it should delete messages for vhost at date and update stats
82 + % delete_messages_at(VHost, Date) -> ok | error
84 + % VHost = list() -> "jabber.example.org"
85 + % Date = list() -> "2007-02-12"
86 + {delete_messages_at, 2},
88 + % called from ejabberd_web_admin:vhost_messages_stats/3
89 + % it should return sorted list of count of messages by dates for vhost
90 + % get_vhost_stats(VHost) -> {ok, [{Date1, Msgs_count1}, {Date2, Msgs_count2}, ... ]} |
93 + % VHost = list() -> "jabber.example.org"
94 + % DateN = list() -> "2007-02-12"
95 + % Msgs_countN = number() -> 241
96 + {get_vhost_stats, 1},
98 + % called from ejabberd_web_admin:vhost_messages_stats_at/4
99 + % it should return sorted list of count of messages by users at date for vhost
100 + % get_vhost_stats_at(VHost, Date) -> {ok, [{User1, Msgs_count1}, {User2, Msgs_count2}, ....]} |
103 + % VHost = list() -> "jabber.example.org"
104 + % Date = list() -> "2007-02-12"
105 + % UserN = list() -> "admin"
106 + % Msgs_countN = number() -> 241
107 + {get_vhost_stats_at, 2},
109 + % called from ejabberd_web_admin:user_messages_stats/4
110 + % it should return sorted list of count of messages by date for user at vhost
111 + % get_user_stats(User, VHost) -> {ok, [{Date1, Msgs_count1}, {Date2, Msgs_count2}, ...]} |
114 + % User = list() -> "admin"
115 + % VHost = list() -> "jabber.example.org"
116 + % DateN = list() -> "2007-02-12"
117 + % Msgs_countN = number() -> 241
118 + {get_user_stats, 2},
120 + % called from ejabberd_web_admin:user_messages_stats_at/5
121 + % it should return all user messages at date
122 + % get_user_messages_at(User, VHost, Date) -> {ok, Msgs} | {error, Reason}
124 + % User = list() -> "admin"
125 + % VHost = list() -> "jabber.example.org"
126 + % Date = list() -> "2007-02-12"
127 + % Msgs = list() -> [ #msg1, msg2, ... ]
128 + {get_user_messages_at, 3},
130 + % called from many places
131 + % it should return list of dates for vhost
132 + % get_dates(VHost) -> [Date1, Date2, ... ]
134 + % VHost = list() -> "jabber.example.org"
135 + % DateN = list() -> "2007-02-12"
138 + % called from start
139 + % it should return list with users settings for VHost in db
140 + % get_users_settings(VHost) -> [#user_settings1, #user_settings2, ... ] | error
142 + % VHost = list() -> "jabber.example.org"
143 + {get_users_settings, 1},
145 + % called from many places
146 + % it should return User settings at VHost from db
147 + % get_user_settings(User, VHost) -> error | {ok, #user_settings}
149 + % User = list() -> "admin"
150 + % VHost = list() -> "jabber.example.org"
151 + {get_user_settings, 2},
153 + % called from web admin
154 + % it should set User settings at VHost
155 + % set_user_settings(User, VHost, #user_settings) -> ok | error
157 + % User = list() -> "admin"
158 + % VHost = list() -> "jabber.example.org"
159 + {set_user_settings, 3},
161 + % called from remove_user (ejabberd hook)
162 + % it should remove user messages and settings at VHost
163 + % drop_user(User, VHost) -> ok | error
165 + % User = list() -> "admin"
166 + % VHost = list() -> "jabber.example.org"
169 +behaviour_info(_) ->
171 diff --git src/mod_logdb.erl src/mod_logdb.erl
173 index 0000000..7de346f
175 +++ src/mod_logdb.erl
177 +%%%----------------------------------------------------------------------
178 +%%% File : mod_logdb.erl
179 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
180 +%%% Purpose : Frontend for log user messages to db
182 +%%% Id : $Id: mod_logdb.erl 1360 2009-07-30 06:00:14Z malik $
183 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
184 +%%%----------------------------------------------------------------------
187 +-author('o.palij@gmail.com').
189 +-behaviour(gen_server).
190 +-behaviour(gen_mod).
193 +-export([start_link/2]).
195 +-export([start/2,stop/1]).
197 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
199 +-export([send_packet/3, receive_packet/4, remove_user/2]).
200 +-export([get_local_identity/5,
201 + get_local_features/5,
203 + adhoc_local_items/4,
204 + adhoc_local_commands/4
205 +% get_sm_identity/5,
206 +% get_sm_features/5,
209 +% adhoc_sm_commands/4]).
212 +-export([rebuild_stats/3,
213 + copy_messages/1, copy_messages_ctl/3, copy_messages_int_tc/1]).
215 +-export([get_vhost_stats/1, get_vhost_stats_at/2,
216 + get_user_stats/2, get_user_messages_at/3,
219 + convert_timestamp/1, convert_timestamp_brief/1,
220 + get_user_settings/2, set_user_settings/3,
221 + user_messages_at_parse_query/4, user_messages_parse_query/3,
222 + vhost_messages_parse_query/2, vhost_messages_at_parse_query/4,
223 + list_to_bool/1, bool_to_list/1,
224 + list_to_string/1, string_to_list/1,
225 + get_module_settings/1, set_module_settings/2,
226 + purge_old_records/2]).
228 +-export([webadmin_menu/3,
231 + user_parse_query/5]).
233 +-export([vhost_messages_stats/3,
234 + vhost_messages_stats_at/4,
235 + user_messages_stats/4,
236 + user_messages_stats_at/5]).
238 +-include("mod_logdb.hrl").
239 +-include("ejabberd.hrl").
240 +-include("mod_roster.hrl").
241 +-include("jlib.hrl").
242 +-include("ejabberd_ctl.hrl").
243 +-include("adhoc.hrl").
244 +-include("web/ejabberd_web_admin.hrl").
245 +-include("web/ejabberd_http.hrl").
247 +-define(PROCNAME, ejabberd_mod_logdb).
248 +% gen_server call timeout
249 +-define(CALL_TIMEOUT, 10000).
251 +-record(state, {vhost, dbmod, backendPid, monref, purgeRef, pollRef, dbopts, dbs, dolog_default, ignore_jids, groupchat, purge_older_days, poll_users_settings, drop_messages_on_user_removal}).
253 +ets_settings_table(VHost) -> list_to_atom("ets_logdb_settings_" ++ VHost).
255 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
257 +% gen_mod/gen_server callbacks
259 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
260 +% ejabberd starts module
261 +start(VHost, Opts) ->
263 + {gen_mod:get_module_proc(VHost, ?PROCNAME),
264 + {?MODULE, start_link, [VHost, Opts]},
269 + % add child to ejabberd_sup
270 + supervisor:start_child(ejabberd_sup, ChildSpec).
272 +% supervisor starts gen_server
273 +start_link(VHost, Opts) ->
274 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
275 + {ok, Pid} = gen_server:start_link({local, Proc}, ?MODULE, [VHost, Opts], []),
279 +init([VHost, Opts]) ->
280 + ?MYDEBUG("Starting mod_logdb", []),
281 + process_flag(trap_exit, true),
282 + DBs = gen_mod:get_opt(dbs, Opts, [{mnesia, []}]),
283 + VHostDB = gen_mod:get_opt(vhosts, Opts, [{VHost, mnesia}]),
284 + % 10 is default becouse of using in clustered environment
285 + PollUsersSettings = gen_mod:get_opt(poll_users_settings, Opts, 10),
287 + {value,{_, DBName}} = lists:keysearch(VHost, 1, VHostDB),
288 + {value, {DBName, DBOpts}} = lists:keysearch(DBName, 1, DBs),
290 + ?MYDEBUG("Starting mod_logdb for ~p with ~p backend", [VHost, DBName]),
292 + DBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(DBName)),
294 + {ok, #state{vhost=VHost,
297 + % dbs used for convert messages from one backend to other
299 + dolog_default=gen_mod:get_opt(dolog_default, Opts, true),
300 + drop_messages_on_user_removal=gen_mod:get_opt(drop_messages_on_user_removal, Opts, true),
301 + ignore_jids=gen_mod:get_opt(ignore_jids, Opts, []),
302 + groupchat=gen_mod:get_opt(groupchat, Opts, none),
303 + purge_older_days=gen_mod:get_opt(purge_older_days, Opts, never),
304 + poll_users_settings=PollUsersSettings}}.
306 +cleanup(#state{vhost=VHost} = _State) ->
307 + ?MYDEBUG("Stopping ~s for ~p", [?MODULE, VHost]),
309 + %ets:delete(ets_settings_table(VHost)),
311 + ejabberd_hooks:delete(remove_user, VHost, ?MODULE, remove_user, 90),
312 + ejabberd_hooks:delete(user_send_packet, VHost, ?MODULE, send_packet, 90),
313 + ejabberd_hooks:delete(user_receive_packet, VHost, ?MODULE, receive_packet, 90),
314 + %ejabberd_hooks:delete(adhoc_sm_commands, VHost, ?MODULE, adhoc_sm_commands, 110),
315 + %ejabberd_hooks:delete(adhoc_sm_items, VHost, ?MODULE, adhoc_sm_items, 110),
316 + ejabberd_hooks:delete(adhoc_local_commands, VHost, ?MODULE, adhoc_local_commands, 110),
317 + ejabberd_hooks:delete(adhoc_local_items, VHost, ?MODULE, adhoc_local_items, 110),
318 + %ejabberd_hooks:delete(disco_sm_identity, VHost, ?MODULE, get_sm_identity, 110),
319 + %ejabberd_hooks:delete(disco_sm_features, VHost, ?MODULE, get_sm_features, 110),
320 + %ejabberd_hooks:delete(disco_sm_items, VHost, ?MODULE, get_sm_items, 110),
321 + ejabberd_hooks:delete(disco_local_identity, VHost, ?MODULE, get_local_identity, 110),
322 + ejabberd_hooks:delete(disco_local_features, VHost, ?MODULE, get_local_features, 110),
323 + ejabberd_hooks:delete(disco_local_items, VHost, ?MODULE, get_local_items, 110),
325 + ejabberd_hooks:delete(webadmin_menu_host, VHost, ?MODULE, webadmin_menu, 70),
326 + ejabberd_hooks:delete(webadmin_user, VHost, ?MODULE, webadmin_user, 50),
327 + ejabberd_hooks:delete(webadmin_page_host, VHost, ?MODULE, webadmin_page, 50),
328 + ejabberd_hooks:delete(webadmin_user_parse_query, VHost, ?MODULE, user_parse_query, 50),
330 + ?MYDEBUG("Removed hooks for ~p", [VHost]),
332 + %ejabberd_ctl:unregister_commands(VHost, [{"rebuild_stats", "rebuild mod_logdb module stats for vhost"}], ?MODULE, rebuild_stats),
333 + %Supported_backends = lists:flatmap(fun({Backend, _Opts}) ->
334 + % [atom_to_list(Backend), " "]
335 + % end, State#state.dbs),
336 + %ejabberd_ctl:unregister_commands(
338 + % [{"copy_messages backend", "copy messages from backend to current backend. backends could be: " ++ Supported_backends }],
339 + % ?MODULE, copy_messages_ctl),
340 + ?MYDEBUG("Unregistered commands for ~p", [VHost]).
343 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
344 + %gen_server:call(Proc, {cleanup}),
345 + %?MYDEBUG("Cleanup in stop finished!!!!", []),
346 + %timer:sleep(10000),
347 + ok = supervisor:terminate_child(ejabberd_sup, Proc),
348 + ok = supervisor:delete_child(ejabberd_sup, Proc).
350 +handle_call({cleanup}, _From, State) ->
352 + ?MYDEBUG("Cleanup finished!!!!!", []),
353 + {reply, ok, State};
354 +handle_call({get_dates}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
355 + Reply = DBMod:get_dates(VHost),
356 + {reply, Reply, State};
357 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
358 +% ejabberd_web_admin callbacks
359 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
360 +handle_call({delete_messages_by_user_at, PMsgs, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
361 + Reply = DBMod:delete_messages_by_user_at(VHost, PMsgs, Date),
362 + {reply, Reply, State};
363 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
364 + Reply = DBMod:delete_all_messages_by_user_at(User, VHost, Date),
365 + {reply, Reply, State};
366 +handle_call({delete_messages_at, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
367 + Reply = DBMod:delete_messages_at(VHost, Date),
368 + {reply, Reply, State};
369 +handle_call({get_vhost_stats}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
370 + Reply = DBMod:get_vhost_stats(VHost),
371 + {reply, Reply, State};
372 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
373 + Reply = DBMod:get_vhost_stats_at(VHost, Date),
374 + {reply, Reply, State};
375 +handle_call({get_user_stats, User}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
376 + Reply = DBMod:get_user_stats(User, VHost),
377 + {reply, Reply, State};
378 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
379 + Reply = DBMod:get_user_messages_at(User, VHost, Date),
380 + {reply, Reply, State};
381 +handle_call({get_user_settings, User}, _From, #state{dbmod=_DBMod, vhost=VHost}=State) ->
382 + Reply = case ets:match_object(ets_settings_table(VHost),
383 + #user_settings{owner_name=User, _='_'}) of
385 + _ -> #user_settings{owner_name=User,
386 + dolog_default=State#state.dolog_default,
390 + {reply, Reply, State};
391 +% TODO: remove User ??
392 +handle_call({set_user_settings, User, GSet}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
393 + Set = GSet#user_settings{owner_name=User},
395 + case ets:match_object(ets_settings_table(VHost),
396 + #user_settings{owner_name=User, _='_'}) of
398 + ?MYDEBUG("Settings is equal", []),
401 + case DBMod:set_user_settings(User, VHost, Set) of
405 + true = ets:insert(ets_settings_table(VHost), Set),
409 + {reply, Reply, State};
410 +handle_call({get_module_settings}, _From, State) ->
411 + {reply, State, State};
412 +handle_call({set_module_settings, #state{purge_older_days=PurgeDays,
413 + poll_users_settings=PollSec} = Settings},
415 + #state{purgeRef=PurgeRefOld,
416 + pollRef=PollRefOld,
417 + purge_older_days=PurgeDaysOld,
418 + poll_users_settings=PollSecOld} = State) ->
420 + PurgeDays == never, PurgeDaysOld /= never ->
421 + {ok, cancel} = timer:cancel(PurgeRefOld),
423 + is_integer(PurgeDays), PurgeDaysOld == never ->
424 + set_purge_timer(PurgeDays);
430 + PollSec == PollSecOld ->
432 + PollSec == 0, PollSecOld /= 0 ->
433 + {ok, cancel} = timer:cancel(PollRefOld),
435 + is_integer(PollSec), PollSecOld == 0 ->
436 + set_poll_timer(PollSec);
437 + is_integer(PollSec), PollSecOld /= 0 ->
438 + {ok, cancel} = timer:cancel(PollRefOld),
439 + set_poll_timer(PollSec)
442 + NewState = State#state{dolog_default=Settings#state.dolog_default,
443 + ignore_jids=Settings#state.ignore_jids,
444 + groupchat=Settings#state.groupchat,
445 + drop_messages_on_user_removal=Settings#state.drop_messages_on_user_removal,
446 + purge_older_days=PurgeDays,
447 + poll_users_settings=PollSec,
450 + {reply, ok, NewState};
451 +handle_call(Msg, _From, State) ->
452 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
454 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
455 +% end ejabberd_web_admin callbacks
456 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
458 +% ejabberd_hooks call
459 +handle_cast({addlog, Direction, Owner, Peer, Packet}, #state{dbmod=DBMod, vhost=VHost}=State) ->
460 + case filter(Owner, Peer, State) of
462 + case catch packet_parse(Owner, Peer, Packet, Direction, State) of
465 + {'EXIT', Reason} ->
466 + ?ERROR_MSG("Failed to parse: ~p", [Reason]);
468 + DBMod:log_message(VHost, Msg)
474 +handle_cast({remove_user, User}, #state{dbmod=DBMod, vhost=VHost}=State) ->
475 + case State#state.drop_messages_on_user_removal of
477 + DBMod:drop_user(User, VHost),
478 + ?INFO_MSG("Launched ~s@~s removal", [User, VHost]);
480 + ?INFO_MSG("Message removing is disabled. Keeping messages for ~s@~s", [User, VHost])
483 +% ejabberdctl rebuild_stats/3
484 +handle_cast({rebuild_stats}, #state{dbmod=DBMod, vhost=VHost}=State) ->
485 + DBMod:rebuild_stats(VHost),
487 +handle_cast({copy_messages, Backend}, State) ->
488 + spawn(?MODULE, copy_messages, [[State, Backend]]),
490 +handle_cast({copy_messages, Backend, Date}, State) ->
491 + spawn(?MODULE, copy_messages, [[State, Backend, Date]]),
493 +handle_cast(Msg, State) ->
494 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
497 +% return: disabled | timer reference
498 +set_purge_timer(PurgeDays) ->
501 + Days when is_integer(Days) ->
502 + {ok, Ref1} = timer:send_interval(timer:hours(24), scheduled_purging),
506 +% return: disabled | timer reference
507 +set_poll_timer(PollSec) ->
510 + {ok, Ref2} = timer:send_interval(timer:seconds(PollSec), poll_users_settings),
512 + % db polling disabled
516 + {ok, Ref3} = timer:send_interval(timer:seconds(10), poll_users_settings),
520 +% actual starting of logging
521 +% from timer:send_after (in init)
522 +handle_info(start, #state{dbmod=DBMod, vhost=VHost}=State) ->
523 + case DBMod:start(VHost, State#state.dbopts) of
524 + {error,{already_started,_}} ->
525 + ?MYDEBUG("backend module already started - trying to stop it", []),
527 + {stop, already_started, State};
529 + timer:sleep(30000),
530 + ?ERROR_MSG("Failed to start: ~p", [Reason]),
531 + {stop, db_connection_failed, State};
533 + ?INFO_MSG("~p connection established", [DBMod]),
535 + MonRef = erlang:monitor(process, SPid),
537 + ets:new(ets_settings_table(VHost), [named_table,public,set,{keypos, #user_settings.owner_name}]),
538 + {ok, DoLog} = DBMod:get_users_settings(VHost),
539 + ets:insert(ets_settings_table(VHost), DoLog),
541 + TrefPurge = set_purge_timer(State#state.purge_older_days),
542 + TrefPoll = set_poll_timer(State#state.poll_users_settings),
544 + ejabberd_hooks:add(remove_user, VHost, ?MODULE, remove_user, 90),
545 + ejabberd_hooks:add(user_send_packet, VHost, ?MODULE, send_packet, 90),
546 + ejabberd_hooks:add(user_receive_packet, VHost, ?MODULE, receive_packet, 90),
548 + ejabberd_hooks:add(disco_local_items, VHost, ?MODULE, get_local_items, 110),
549 + ejabberd_hooks:add(disco_local_features, VHost, ?MODULE, get_local_features, 110),
550 + ejabberd_hooks:add(disco_local_identity, VHost, ?MODULE, get_local_identity, 110),
551 + %ejabberd_hooks:add(disco_sm_items, VHost, ?MODULE, get_sm_items, 110),
552 + %ejabberd_hooks:add(disco_sm_features, VHost, ?MODULE, get_sm_features, 110),
553 + %ejabberd_hooks:add(disco_sm_identity, VHost, ?MODULE, get_sm_identity, 110),
554 + ejabberd_hooks:add(adhoc_local_items, VHost, ?MODULE, adhoc_local_items, 110),
555 + ejabberd_hooks:add(adhoc_local_commands, VHost, ?MODULE, adhoc_local_commands, 110),
556 + %ejabberd_hooks:add(adhoc_sm_items, VHost, ?MODULE, adhoc_sm_items, 110),
557 + %ejabberd_hooks:add(adhoc_sm_commands, VHost, ?MODULE, adhoc_sm_commands, 110),
559 + ejabberd_hooks:add(webadmin_menu_host, VHost, ?MODULE, webadmin_menu, 70),
560 + ejabberd_hooks:add(webadmin_user, VHost, ?MODULE, webadmin_user, 50),
561 + ejabberd_hooks:add(webadmin_page_host, VHost, ?MODULE, webadmin_page, 50),
562 + ejabberd_hooks:add(webadmin_user_parse_query, VHost, ?MODULE, user_parse_query, 50),
564 + ?MYDEBUG("Added hooks for ~p", [VHost]),
566 + %ejabberd_ctl:register_commands(
568 + % [{"rebuild_stats", "rebuild mod_logdb module stats for vhost"}],
569 + % ?MODULE, rebuild_stats),
570 + %Supported_backends = lists:flatmap(fun({Backend, _Opts}) ->
571 + % [atom_to_list(Backend), " "]
572 + % end, State#state.dbs),
573 + %ejabberd_ctl:register_commands(
575 + % [{"copy_messages backend", "copy messages from backend to current backend. backends could be: " ++ Supported_backends }],
576 + % ?MODULE, copy_messages_ctl),
577 + ?MYDEBUG("Registered commands for ~p", [VHost]),
579 + NewState=State#state{monref = MonRef, backendPid=SPid, purgeRef=TrefPurge, pollRef=TrefPoll},
580 + {noreply, NewState};
582 + ?ERROR_MSG("Rez=~p", [Rez]),
583 + timer:sleep(30000),
584 + {stop, db_connection_failed, State}
586 +% from timer:send_interval/2 (in start handle_info)
587 +handle_info(scheduled_purging, #state{vhost=VHost, purge_older_days=Days} = State) ->
588 + ?MYDEBUG("Starting scheduled purging of old records for ~p", [VHost]),
589 + spawn(?MODULE, purge_old_records, [VHost, integer_to_list(Days)]),
591 +% from timer:send_interval/2 (in start handle_info)
592 +handle_info(poll_users_settings, #state{dbmod=DBMod, vhost=VHost}=State) ->
593 + {ok, DoLog} = DBMod:get_users_settings(VHost),
594 + ?MYDEBUG("DoLog=~p", [DoLog]),
595 + true = ets:delete_all_objects(ets_settings_table(VHost)),
596 + ets:insert(ets_settings_table(VHost), DoLog),
598 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
599 + {stop, db_connection_dropped, State};
600 +handle_info({fetch_result, _, _}, State) ->
601 + ?MYDEBUG("Got timed out mysql fetch result", []),
603 +handle_info(Info, State) ->
604 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
607 +terminate(db_connection_failed, _State) ->
609 +terminate(db_connection_dropped, State) ->
610 + ?MYDEBUG("Got terminate with db_connection_dropped", []),
613 +terminate(Reason, #state{monref=undefined} = State) ->
614 + ?MYDEBUG("Got terminate with undefined monref.~nReason: ~p", [Reason]),
617 +terminate(Reason, #state{dbmod=DBMod, vhost=VHost, monref=MonRef, backendPid=Pid} = State) ->
618 + ?INFO_MSG("Reason: ~p", [Reason]),
619 + case erlang:is_process_alive(Pid) of
621 + erlang:demonitor(MonRef, [flush]),
629 +code_change(_OldVsn, State, _Extra) ->
632 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
634 +% ejabberd_hooks callbacks
636 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
637 +% TODO: change to/from to list as sql stores it as list
638 +send_packet(Owner, Peer, P) ->
639 + VHost = Owner#jid.lserver,
640 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
641 + gen_server:cast(Proc, {addlog, to, Owner, Peer, P}).
643 +receive_packet(_JID, Peer, Owner, P) ->
644 + VHost = Owner#jid.lserver,
645 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
646 + gen_server:cast(Proc, {addlog, from, Owner, Peer, P}).
648 +remove_user(User, Server) ->
649 + LUser = jlib:nodeprep(User),
650 + LServer = jlib:nameprep(Server),
651 + Proc = gen_mod:get_module_proc(LServer, ?PROCNAME),
652 + gen_server:cast(Proc, {remove_user, LUser}).
654 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
658 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
659 +rebuild_stats(_Val, VHost, ["rebuild_stats"]) ->
660 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
661 + gen_server:cast(Proc, {rebuild_stats}),
662 + {stop, ?STATUS_SUCCESS};
663 +rebuild_stats(Val, _VHost, _Args) ->
666 +copy_messages_ctl(_Val, VHost, ["copy_messages", Backend]) ->
667 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
668 + gen_server:cast(Proc, {copy_messages, Backend}),
669 + {stop, ?STATUS_SUCCESS};
670 +copy_messages_ctl(_Val, VHost, ["copy_messages", Backend, Date]) ->
671 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
672 + gen_server:cast(Proc, {copy_messages, Backend, Date}),
673 + {stop, ?STATUS_SUCCESS};
674 +copy_messages_ctl(Val, _VHost, _Args) ->
676 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
680 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
682 +% handle_cast({addlog, E}, _)
683 +% raw packet -> #msg
684 +packet_parse(Owner, Peer, Packet, Direction, State) ->
685 + case xml:get_subtag(Packet, "body") of
690 + case xml:get_tag_attr_s("type", Packet) of
695 + case Message_type of
696 + "groupchat" when State#state.groupchat == send, Direction == to ->
698 + "groupchat" when State#state.groupchat == send, Direction == from ->
700 + "groupchat" when State#state.groupchat == half ->
701 + Rooms = ets:match(muc_online_room, '$1'),
702 + Ni=lists:foldl(fun([{muc_online_room, {GName, GHost}, Pid}], Names) ->
703 + case gen_fsm:sync_send_all_state_event(Pid, {get_jid_nick,Owner}) of
706 + lists:append(Names, [jlib:jid_to_string({GName, GHost, Nick})])
709 + case lists:member(jlib:jid_to_string(Peer), Ni) of
710 + true when Direction == from ->
715 + "groupchat" when State#state.groupchat == none ->
721 + Message_body = xml:get_tag_cdata(Body_xml),
723 + case xml:get_subtag(Packet, "subject") of
727 + xml:get_tag_cdata(Subject_xml)
730 + OwnerName = stringprep:tolower(Owner#jid.user),
731 + PName = stringprep:tolower(Peer#jid.user),
732 + PServer = stringprep:tolower(Peer#jid.server),
733 + PResource = Peer#jid.resource,
735 + #msg{timestamp=get_timestamp(),
736 + owner_name=OwnerName,
738 + peer_server=PServer,
739 + peer_resource=PResource,
740 + direction=Direction,
742 + subject=Message_subject,
746 +% called from handle_cast({addlog, _}, _) -> true (log messages) | false (do not log messages)
747 +filter(Owner, Peer, State) ->
748 + OwnerStr = Owner#jid.luser++"@"++Owner#jid.lserver,
749 + OwnerServ = "@"++Owner#jid.lserver,
750 + PeerStr = Peer#jid.luser++"@"++Peer#jid.lserver,
751 + PeerServ = "@"++Peer#jid.lserver,
753 + LogTo = case ets:match_object(ets_settings_table(State#state.vhost),
754 + #user_settings{owner_name=Owner#jid.luser, _='_'}) of
755 + [#user_settings{dolog_default=Default,
757 + donotlog_list=DNLL}] ->
758 + A = lists:member(PeerStr, DLL),
759 + B = lists:member(PeerStr, DNLL),
763 + Default == true -> true;
764 + Default == false -> false;
765 + true -> State#state.dolog_default
767 + _ -> State#state.dolog_default
770 + lists:all(fun(O) -> O end,
771 + [not lists:member(OwnerStr, State#state.ignore_jids),
772 + not lists:member(PeerStr, State#state.ignore_jids),
773 + not lists:member(OwnerServ, State#state.ignore_jids),
774 + not lists:member(PeerServ, State#state.ignore_jids),
777 +purge_old_records(VHost, Days) ->
778 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
780 + Dates = ?MODULE:get_dates(VHost),
781 + DateNow = calendar:datetime_to_gregorian_seconds({date(), {0,0,1}}),
782 + DateDiff = list_to_integer(Days)*24*60*60,
783 + ?MYDEBUG("Purging tables older than ~s days", [Days]),
784 + lists:foreach(fun(Date) ->
785 + [Year, Month, Day] = ejabberd_regexp:split(Date, "[^0-9]+"),
786 + DateInSec = calendar:datetime_to_gregorian_seconds({{list_to_integer(Year), list_to_integer(Month), list_to_integer(Day)}, {0,0,1}}),
788 + (DateNow - DateInSec) > DateDiff ->
789 + gen_server:call(Proc, {delete_messages_at, Date});
791 + ?MYDEBUG("Skipping messages at ~p", [Date])
795 +% called from get_vhost_stats/2, get_user_stats/3
796 +sort_stats(Stats) ->
797 + % Stats = [{"2003-4-15",1}, {"2006-8-18",1}, ... ]
798 + CFun = fun({TableName, Count}) ->
799 + [Year, Month, Day] = ejabberd_regexp:split(TableName, "[^0-9]+"),
800 + { calendar:datetime_to_gregorian_seconds({{list_to_integer(Year), list_to_integer(Month), list_to_integer(Day)}, {0,0,1}}), Count }
802 + % convert to [{63364377601,1}, {63360662401,1}, ... ]
803 + CStats = lists:map(CFun, Stats),
805 + SortedStats = lists:reverse(lists:keysort(1, CStats)),
806 + % convert to [{"2007-12-9",1}, {"2007-10-27",1}, ... ] sorted list
807 + [{mod_logdb:convert_timestamp_brief(TableSec), Count} || {TableSec, Count} <- SortedStats].
809 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
811 +% Date/Time operations
813 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
814 +% return float seconds elapsed from "zero hour" as list
816 + {MegaSec, Sec, MicroSec} = now(),
817 + [List] = io_lib:format("~.5f", [MegaSec*1000000 + Sec + MicroSec/1000000]),
820 +% convert float seconds elapsed from "zero hour" to local time "%Y-%m-%d %H:%M:%S" string
821 +convert_timestamp(Seconds) when is_list(Seconds) ->
822 + case string:to_float(Seconds++".0") of
823 + {F,_} when is_float(F) -> convert_timestamp(F);
824 + _ -> erlang:error(badarg, [Seconds])
826 +convert_timestamp(Seconds) when is_float(Seconds) ->
827 + GregSec = trunc(Seconds + 719528*86400),
828 + UnivDT = calendar:gregorian_seconds_to_datetime(GregSec),
829 + {{Year, Month, Day},{Hour, Minute, Sec}} = calendar:universal_time_to_local_time(UnivDT),
830 + integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day) ++ " " ++ integer_to_list(Hour) ++ ":" ++ integer_to_list(Minute) ++ ":" ++ integer_to_list(Sec).
832 +% convert float seconds elapsed from "zero hour" to local time "%Y-%m-%d" string
833 +convert_timestamp_brief(Seconds) when is_list(Seconds) ->
834 + convert_timestamp_brief(list_to_float(Seconds));
835 +convert_timestamp_brief(Seconds) when is_float(Seconds) ->
836 + GregSec = trunc(Seconds + 719528*86400),
837 + UnivDT = calendar:gregorian_seconds_to_datetime(GregSec),
838 + {{Year, Month, Day},{_Hour, _Minute, _Sec}} = calendar:universal_time_to_local_time(UnivDT),
839 + integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day);
840 +convert_timestamp_brief(Seconds) when is_integer(Seconds) ->
841 + {{Year, Month, Day},{_Hour, _Minute, _Sec}} = calendar:gregorian_seconds_to_datetime(Seconds),
842 + integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day).
844 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
846 +% DB operations (get)
848 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
849 +get_vhost_stats(VHost) ->
850 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
851 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
853 +get_vhost_stats_at(VHost, Date) ->
854 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
855 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
857 +get_user_stats(User, VHost) ->
858 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
859 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
861 +get_user_messages_at(User, VHost, Date) ->
862 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
863 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
866 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
867 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
869 +get_user_settings(User, VHost) ->
870 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
871 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
873 +set_user_settings(User, VHost, Set) ->
874 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
875 + gen_server:call(Proc, {set_user_settings, User, Set}).
877 +get_module_settings(VHost) ->
878 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
879 + gen_server:call(Proc, {get_module_settings}).
881 +set_module_settings(VHost, Settings) ->
882 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
883 + gen_server:call(Proc, {set_module_settings, Settings}).
885 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
887 +% Web admin callbacks (delete)
889 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
890 +user_messages_at_parse_query(VHost, Date, Msgs, Query) ->
891 + case lists:keysearch("delete", 1, Query) of
893 + PMsgs = lists:filter(
895 + ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg#msg.timestamp))),
896 + lists:member({"selected", ID}, Query)
898 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
899 + gen_server:call(Proc, {delete_messages_by_user_at, PMsgs, Date}, ?CALL_TIMEOUT);
904 +user_messages_parse_query(User, VHost, Query) ->
905 + case lists:keysearch("delete", 1, Query) of
907 + Dates = get_dates(VHost),
908 + PDates = lists:filter(
910 + ID = jlib:encode_base64(binary_to_list(term_to_binary(User++Date))),
911 + lists:member({"selected", ID}, Query)
913 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
917 + [gen_server:call(Proc,
918 + {delete_all_messages_by_user_at, User, Date},
921 + case lists:member(error, Rez) of
931 +vhost_messages_parse_query(VHost, Query) ->
932 + case lists:keysearch("delete", 1, Query) of
934 + Dates = get_dates(VHost),
935 + PDates = lists:filter(
937 + ID = jlib:encode_base64(binary_to_list(term_to_binary(VHost++Date))),
938 + lists:member({"selected", ID}, Query)
940 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
941 + Rez = lists:foldl(fun(Date, Acc) ->
942 + lists:append(Acc, [gen_server:call(Proc,
943 + {delete_messages_at, Date},
946 + case lists:member(error, Rez) of
956 +vhost_messages_at_parse_query(VHost, Date, Stats, Query) ->
957 + case lists:keysearch("delete", 1, Query) of
959 + PStats = lists:filter(
960 + fun({User, _Count}) ->
961 + ID = jlib:encode_base64(binary_to_list(term_to_binary(User++VHost))),
962 + lists:member({"selected", ID}, Query)
964 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
965 + Rez = lists:foldl(fun({User, _Count}, Acc) ->
966 + lists:append(Acc, [gen_server:call(Proc,
967 + {delete_all_messages_by_user_at,
971 + case lists:member(error, Rez) of
981 +copy_messages([#state{vhost=VHost}=State, From]) ->
982 + ?INFO_MSG("Going to copy messages from ~p for ~p", [From, VHost]),
984 + {FromDBName, FromDBOpts} =
985 + case lists:keysearch(list_to_atom(From), 1, State#state.dbs) of
986 + {value, {FN, FO}} ->
989 + ?ERROR_MSG("Failed to find record for ~p in dbs", [From]),
993 + FromDBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(FromDBName)),
995 + {ok, _FromPid} = FromDBMod:start(VHost, FromDBOpts),
997 + Dates = FromDBMod:get_dates(VHost),
998 + DatesLength = length(Dates),
1000 + lists:foldl(fun(Date, Acc) ->
1001 + case copy_messages_int([FromDBMod, State#state.dbmod, VHost, Date]) of
1003 + ?INFO_MSG("Copied messages at ~p (~p/~p)", [Date, Acc, DatesLength]);
1005 + ?ERROR_MSG("Failed to copy messages at ~p (~p/~p): ~p", [Date, Acc, DatesLength, Value]),
1006 + FromDBMod:stop(VHost),
1011 + ?INFO_MSG("Copied messages from ~p", [From]),
1012 + FromDBMod:stop(VHost);
1013 +copy_messages([#state{vhost=VHost}=State, From, Date]) ->
1014 + {value, {FromDBName, FromDBOpts}} = lists:keysearch(list_to_atom(From), 1, State#state.dbs),
1015 + FromDBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(FromDBName)),
1016 + {ok, _FromPid} = FromDBMod:start(VHost, FromDBOpts),
1017 + case catch copy_messages_int([FromDBMod, State#state.dbmod, VHost, Date]) of
1018 + {'exit', Reason} ->
1019 + ?ERROR_MSG("Failed to copy messages at ~p: ~p", [Date, Reason]);
1021 + ?INFO_MSG("Copied messages at ~p", [Date]);
1023 + ?ERROR_MSG("Failed to copy messages at ~p: ~p", [Date, Value])
1025 + FromDBMod:stop(VHost).
1027 +copy_messages_int([FromDBMod, ToDBMod, VHost, Date]) ->
1028 + ets:new(mod_logdb_temp, [named_table, set, public]),
1029 + {Time, Value} = timer:tc(?MODULE, copy_messages_int_tc, [[FromDBMod, ToDBMod, VHost, Date]]),
1030 + ets:delete_all_objects(mod_logdb_temp),
1031 + ets:delete(mod_logdb_temp),
1032 + ?INFO_MSG("copy_messages at ~p elapsed ~p sec", [Date, Time/1000000]),
1035 +copy_messages_int_tc([FromDBMod, ToDBMod, VHost, Date]) ->
1036 + ?INFO_MSG("Going to copy messages from ~p for ~p at ~p", [FromDBMod, VHost, Date]),
1038 + ok = FromDBMod:rebuild_stats_at(VHost, Date),
1039 + catch mod_logdb:rebuild_stats_at(VHost, Date),
1040 + {ok, FromStats} = FromDBMod:get_vhost_stats_at(VHost, Date),
1041 + ToStats = case mod_logdb:get_vhost_stats_at(VHost, Date) of
1042 + {ok, Stats} -> Stats;
1046 + FromStatsS = lists:keysort(1, FromStats),
1047 + ToStatsS = lists:keysort(1, ToStats),
1049 + StatsLength = length(FromStats),
1052 + % destination table is empty
1053 + FromDBMod /= mod_logdb_mnesia_old, ToStats == [] ->
1054 + fun({User, _Count}, Acc) ->
1055 + {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date),
1057 + lists:foldl(fun(Msg, MFAcc) ->
1058 + ok = ToDBMod:log_message(VHost, Msg),
1062 + ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
1063 + %timer:sleep(100),
1066 + % destination table is not empty
1067 + FromDBMod /= mod_logdb_mnesia_old, ToStats /= [] ->
1068 + fun({User, _Count}, Acc) ->
1069 + {ok, ToMsgs} = ToDBMod:get_user_messages_at(User, VHost, Date),
1070 + lists:foreach(fun(#msg{timestamp=Tst}) when length(Tst) == 16 ->
1071 + ets:insert(mod_logdb_temp, {Tst});
1072 + % mysql, pgsql removes final zeros after decimal point
1073 + (#msg{timestamp=Tst}) when length(Tst) < 16 ->
1074 + {F, _} = string:to_float(Tst++".0"),
1075 + [T] = io_lib:format("~.5f", [F]),
1076 + ets:insert(mod_logdb_temp, {T})
1078 + {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date),
1080 + lists:foldl(fun(#msg{timestamp=ToTimestamp} = Msg, MFAcc) ->
1081 + case ets:member(mod_logdb_temp, ToTimestamp) of
1083 + ok = ToDBMod:log_message(VHost, Msg),
1084 + ets:insert(mod_logdb_temp, {ToTimestamp}),
1091 + ets:delete_all_objects(mod_logdb_temp),
1092 + ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
1093 + %timer:sleep(100),
1096 + % copying from mod_logmnesia
1098 + fun({User, _Count}, Acc) ->
1100 + case ToDBMod:get_user_messages_at(User, VHost, Date) of
1104 + lists:foreach(fun(#msg{timestamp=Tst}) when length(Tst) == 16 ->
1105 + ets:insert(mod_logdb_temp, {Tst});
1106 + % mysql, pgsql removes final zeros after decimal point
1107 + (#msg{timestamp=Tst}) when length(Tst) < 15 ->
1108 + {F, _} = string:to_float(Tst++".0"),
1109 + [T] = io_lib:format("~.5f", [F]),
1110 + ets:insert(mod_logdb_temp, {T})
1115 + {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date),
1119 + fun({msg, TU, TS, TR, FU, FS, FR, Type, Subj, Body, Timest},
1121 + [Timestamp] = if is_float(Timest) == true ->
1122 + io_lib:format("~.5f", [Timest]);
1123 + % early versions of mod_logmnesia
1124 + is_integer(Timest) == true ->
1125 + io_lib:format("~.5f", [Timest-719528*86400.0]);
1127 + ?ERROR_MSG("Incorrect timestamp ~p", [Timest]),
1130 + case ets:member(mod_logdb_temp, Timestamp) of
1135 + TMsg = #msg{timestamp=Timestamp,
1137 + peer_name=FU, peer_server=FS, peer_resource=FR,
1140 + subject=Subj, body=Body},
1141 + ok = ToDBMod:log_message(VHost, TMsg);
1147 + FMsg = #msg{timestamp=Timestamp,
1149 + peer_name=TU, peer_server=TS, peer_resource=TR,
1152 + subject=Subj, body=Body},
1153 + ok = ToDBMod:log_message(VHost, FMsg);
1156 + ets:insert(mod_logdb_temp, {Timestamp}),
1158 + true -> % not ets:member
1161 + end, 0, Msgs), % foldl
1163 + ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
1164 + %timer:sleep(100),
1167 + end, % if FromDBMod /= mod_logdb_mnesia_old
1170 + FromStats == [] ->
1171 + ?INFO_MSG("No messages were found at ~p", [Date]);
1172 + FromStatsS == ToStatsS ->
1173 + ?INFO_MSG("Stats are equal at ~p", [Date]);
1174 + FromStatsS /= ToStatsS ->
1175 + lists:foldl(CopyFun, 0, FromStats),
1176 + ok = ToDBMod:rebuild_stats_at(VHost, Date)
1177 + %timer:sleep(1000)
1182 +list_to_bool(Num) ->
1183 + case lists:member(Num, ["t", "true", "y", "yes", "1"]) of
1187 + case lists:member(Num, ["f", "false", "n", "no", "0"]) of
1195 +bool_to_list(true) ->
1197 +bool_to_list(false) ->
1200 +list_to_string([]) ->
1202 +list_to_string(List) when is_list(List) ->
1203 + Str = lists:flatmap(fun(Elm) -> Elm ++ "\n" end, List),
1204 + lists:sublist(Str, length(Str)-1).
1206 +string_to_list(null) ->
1208 +string_to_list([]) ->
1210 +string_to_list(String) ->
1211 + ejabberd_regexp:split(String, "\n").
1213 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1215 +% ad-hoc (copy/pasted from mod_configure.erl)
1217 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1218 +-define(ITEMS_RESULT(Allow, LNode, Fallback),
1223 + case get_local_items(LServer, LNode,
1224 + jlib:jid_to_string(To), Lang) of
1232 +get_local_items(Acc, From, #jid{lserver = LServer} = To, "", Lang) ->
1233 + case gen_mod:is_loaded(LServer, mod_adhoc) of
1237 + Items = case Acc of
1238 + {result, Its} -> Its;
1241 + AllowUser = acl:match_rule(LServer, mod_logdb, From),
1242 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1244 + AllowUser == allow; AllowAdmin == allow ->
1245 + case get_local_items(LServer, [],
1246 + jlib:jid_to_string(To), Lang) of
1248 + {result, Items ++ Res};
1249 + {error, _Error} ->
1256 +get_local_items(Acc, From, #jid{lserver = LServer} = To, Node, Lang) ->
1257 + case gen_mod:is_loaded(LServer, mod_adhoc) of
1261 + LNode = string:tokens(Node, "/"),
1262 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1265 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1266 + ["mod_logdb_users"] ->
1267 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1268 + ["mod_logdb_users", [$@ | _]] ->
1269 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1270 + ["mod_logdb_users", _User] ->
1271 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1272 + ["mod_logdb_settings"] ->
1273 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1279 +-define(NODE(Name, Node),
1280 + {xmlelement, "item",
1282 + {"name", translate:translate(Lang, Name)},
1283 + {"node", Node}], []}).
1285 +get_local_items(_Host, [], Server, Lang) ->
1287 + [?NODE("Messages logging engine", "mod_logdb")]
1289 +get_local_items(_Host, ["mod_logdb"], Server, Lang) ->
1291 + [?NODE("Messages logging engine users", "mod_logdb_users"),
1292 + ?NODE("Messages logging engine settings", "mod_logdb_settings")]
1294 +get_local_items(Host, ["mod_logdb_users"], Server, Lang) ->
1295 + {result, get_all_vh_users(Host, Server, Lang)};
1296 +get_local_items(_Host, ["mod_logdb_users", [$@ | Diap]], Server, Lang) ->
1297 + case catch ejabberd_auth:dirty_get_registered_users() of
1298 + {'EXIT', _Reason} ->
1299 + ?ERR_INTERNAL_SERVER_ERROR;
1301 + SUsers = lists:sort([{S, U} || {U, S} <- Users]),
1303 + [S1, S2] = ejabberd_regexp:split(Diap, "-"),
1304 + N1 = list_to_integer(S1),
1305 + N2 = list_to_integer(S2),
1306 + Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
1307 + lists:map(fun({S, U}) ->
1308 + ?NODE(U ++ "@" ++ S, "mod_logdb_users/" ++ U ++ "@" ++ S)
1311 + {'EXIT', _Reason} ->
1312 + ?ERR_NOT_ACCEPTABLE;
1317 +get_local_items(_Host, ["mod_logdb_users", _User], _Server, _Lang) ->
1319 +get_local_items(_Host, ["mod_logdb_settings"], _Server, _Lang) ->
1321 +get_local_items(_Host, Item, _Server, _Lang) ->
1322 + ?MYDEBUG("asked for items in ~p", [Item]),
1323 + {error, ?ERR_ITEM_NOT_FOUND}.
1325 +-define(INFO_RESULT(Allow, Feats),
1328 + {error, ?ERR_FORBIDDEN};
1333 +get_local_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
1334 + case gen_mod:is_loaded(LServer, mod_adhoc) of
1338 + LNode = string:tokens(Node, "/"),
1339 + AllowUser = acl:match_rule(LServer, mod_logdb, From),
1340 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1342 + ["mod_logdb"] when AllowUser == allow; AllowAdmin == allow ->
1343 + ?INFO_RESULT(allow, [?NS_COMMANDS]);
1345 + ?INFO_RESULT(deny, [?NS_COMMANDS]);
1346 + ["mod_logdb_users"] ->
1347 + ?INFO_RESULT(AllowAdmin, []);
1348 + ["mod_logdb_users", [$@ | _]] ->
1349 + ?INFO_RESULT(AllowAdmin, []);
1350 + ["mod_logdb_users", _User] ->
1351 + ?INFO_RESULT(AllowAdmin, [?NS_COMMANDS]);
1352 + ["mod_logdb_settings"] ->
1353 + ?INFO_RESULT(AllowAdmin, [?NS_COMMANDS]);
1357 + %?MYDEBUG("asked for ~p features: ~p", [LNode, Allow]),
1362 +-define(INFO_IDENTITY(Category, Type, Name, Lang),
1363 + [{xmlelement, "identity",
1364 + [{"category", Category},
1366 + {"name", translate:translate(Lang, Name)}], []}]).
1368 +-define(INFO_COMMAND(Name, Lang),
1369 + ?INFO_IDENTITY("automation", "command-node", Name, Lang)).
1371 +get_local_identity(Acc, _From, _To, Node, Lang) ->
1372 + LNode = string:tokens(Node, "/"),
1375 + ?INFO_COMMAND("Messages logging engine", Lang);
1376 + ["mod_logdb_users"] ->
1377 + ?INFO_COMMAND("Messages logging engine users", Lang);
1378 + ["mod_logdb_users", [$@ | _]] ->
1380 + ["mod_logdb_users", User] ->
1381 + ?INFO_COMMAND(User, Lang);
1382 + ["mod_logdb_settings"] ->
1383 + ?INFO_COMMAND("Messages logging engine settings", Lang);
1390 +%get_sm_items(Acc, From, To, Node, Lang) ->
1391 +% ?MYDEBUG("get_sm_items Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
1394 +%get_sm_features(Acc, From, To, Node, Lang) ->
1395 +% ?MYDEBUG("get_sm_features Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
1398 +%get_sm_identity(Acc, From, To, Node, Lang) ->
1399 +% ?MYDEBUG("get_sm_identity Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
1402 +adhoc_local_items(Acc, From, #jid{lserver = LServer, server = Server} = To,
1404 + Items = case Acc of
1405 + {result, Its} -> Its;
1408 + Nodes = recursively_get_local_items(LServer, "", Server, Lang),
1409 + Nodes1 = lists:filter(
1411 + Nd = xml:get_tag_attr_s("node", N),
1412 + F = get_local_features([], From, To, Nd, Lang),
1414 + {result, [?NS_COMMANDS]} ->
1420 + {result, Items ++ Nodes1}.
1422 +recursively_get_local_items(_LServer, "mod_logdb_users", _Server, _Lang) ->
1424 +recursively_get_local_items(LServer, Node, Server, Lang) ->
1425 + LNode = string:tokens(Node, "/"),
1426 + Items = case get_local_items(LServer, LNode, Server, Lang) of
1429 + {error, _Error} ->
1432 + Nodes = lists:flatten(
1435 + S = xml:get_tag_attr_s("jid", N),
1436 + Nd = xml:get_tag_attr_s("node", N),
1437 + if (S /= Server) or (Nd == "") ->
1440 + [N, recursively_get_local_items(
1441 + LServer, Nd, Server, Lang)]
1446 +-define(COMMANDS_RESULT(Allow, From, To, Request),
1449 + {error, ?ERR_FORBIDDEN};
1451 + adhoc_local_commands(From, To, Request)
1454 +adhoc_local_commands(Acc, From, #jid{lserver = LServer} = To,
1455 + #adhoc_request{node = Node} = Request) ->
1456 + LNode = string:tokens(Node, "/"),
1457 + AllowUser = acl:match_rule(LServer, mod_logdb, From),
1458 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1460 + ["mod_logdb"] when AllowUser == allow; AllowAdmin == allow ->
1461 + ?COMMANDS_RESULT(allow, From, To, Request);
1462 + ["mod_logdb_users", _User] when AllowAdmin == allow ->
1463 + ?COMMANDS_RESULT(allow, From, To, Request);
1464 + ["mod_logdb_settings"] when AllowAdmin == allow ->
1465 + ?COMMANDS_RESULT(allow, From, To, Request);
1470 +adhoc_local_commands(From, #jid{lserver = LServer} = _To,
1471 + #adhoc_request{lang = Lang,
1473 + sessionid = SessionID,
1475 + xdata = XData} = Request) ->
1476 + LNode = string:tokens(Node, "/"),
1477 + %% If the "action" attribute is not present, it is
1478 + %% understood as "execute". If there was no <actions/>
1479 + %% element in the first response (which there isn't in our
1480 + %% case), "execute" and "complete" are equivalent.
1481 + ActionIsExecute = lists:member(Action,
1482 + ["", "execute", "complete"]),
1483 + if Action == "cancel" ->
1484 + %% User cancels request
1485 + adhoc:produce_response(
1487 + #adhoc_response{status = canceled});
1488 + XData == false, ActionIsExecute ->
1489 + %% User requests form
1490 + case get_form(LServer, LNode, From, Lang) of
1492 + adhoc:produce_response(
1494 + #adhoc_response{status = executing,
1495 + elements = Form});
1499 + XData /= false, ActionIsExecute ->
1500 + %% User returns form.
1501 + case jlib:parse_xdata_submit(XData) of
1503 + {error, ?ERR_BAD_REQUEST};
1505 + case set_form(From, LServer, LNode, Lang, Fields) of
1507 + adhoc:produce_response(
1508 + #adhoc_response{lang = Lang,
1510 + sessionid = SessionID,
1511 + status = completed});
1517 + {error, ?ERR_BAD_REQUEST}
1520 +-define(LISTLINE(Label, Value),
1521 + {xmlelement, "option", [{"label", Label}],
1522 + [{xmlelement, "value", [], [{xmlcdata, Value}]}]}).
1523 +-define(DEFVAL(Value), {xmlelement, "value", [], [{xmlcdata, Value}]}).
1525 +get_user_form(LUser, LServer, Lang) ->
1526 + %From = jlib:jid_to_string(jlib:jid_remove_resource(Jid)),
1527 + #user_settings{dolog_default=DLD,
1529 + donotlog_list=DNLL} = get_user_settings(LUser, LServer),
1530 + {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
1531 + [{xmlelement, "title", [],
1533 + translate:translate(
1534 + Lang, "Messages logging engine settings")}]},
1535 + {xmlelement, "instructions", [],
1537 + translate:translate(
1538 + Lang, "Set logging preferences")++ ": " ++ LUser ++ "@" ++ LServer}]},
1540 + {xmlelement, "field", [{"type", "list-single"},
1542 + translate:translate(Lang, "Default")},
1543 + {"var", "dolog_default"}],
1544 + [?DEFVAL(atom_to_list(DLD)),
1545 + ?LISTLINE(translate:translate(Lang, "Log Messages"), "true"),
1546 + ?LISTLINE(translate:translate(Lang, "Do Not Log Messages"), "false")
1549 + {xmlelement, "field", [{"type", "text-multi"},
1551 + translate:translate(
1552 + Lang, "Log Messages")},
1553 + {"var", "dolog_list"}],
1554 + [{xmlelement, "value", [], [{xmlcdata, list_to_string(DLL)}]}]},
1556 + {xmlelement, "field", [{"type", "text-multi"},
1558 + translate:translate(
1559 + Lang, "Do Not Log Messages")},
1560 + {"var", "donotlog_list"}],
1561 + [{xmlelement, "value", [], [{xmlcdata, list_to_string(DNLL)}]}]}
1564 +get_settings_form(Host, Lang) ->
1565 + #state{dbmod=DBMod,
1567 + dolog_default=DLD,
1568 + ignore_jids=IgnoreJids,
1569 + groupchat=GroupChat,
1570 + purge_older_days=PurgeDaysT,
1571 + drop_messages_on_user_removal=MRemoval,
1572 + poll_users_settings=PollTime} = mod_logdb:get_module_settings(Host),
1574 + Backends = lists:map(fun({Backend, _Opts}) ->
1575 + ?LISTLINE(atom_to_list(Backend), atom_to_list(Backend))
1577 + DB = lists:sublist(atom_to_list(DBMod), length(atom_to_list(?MODULE)) + 2, length(atom_to_list(DBMod))),
1578 + DBsL = lists:append([?DEFVAL(DB)], Backends),
1581 + case PurgeDaysT of
1583 + Num when is_integer(Num) -> integer_to_list(Num);
1586 + {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
1587 + [{xmlelement, "title", [],
1589 + translate:translate(
1590 + Lang, "Messages logging engine settings") ++ " (run-time)"}]},
1591 + {xmlelement, "instructions", [],
1593 + translate:translate(
1594 + Lang, "Set run-time settings")}]},
1596 + {xmlelement, "field", [{"type", "list-single"},
1598 + translate:translate(Lang, "Backend")},
1599 + {"var", "backend"}],
1602 + {xmlelement, "field", [{"type", "text-multi"},
1604 + translate:translate(
1607 + [{xmlelement, "value", [], [{xmlcdata, lists:flatten(io_lib:format("~p.",[DBs]))}]}]},
1609 + {xmlelement, "field", [{"type", "list-single"},
1611 + translate:translate(Lang, "Default")},
1612 + {"var", "dolog_default"}],
1613 + [?DEFVAL(atom_to_list(DLD)),
1614 + ?LISTLINE(translate:translate(Lang, "Log Messages"), "true"),
1615 + ?LISTLINE(translate:translate(Lang, "Do Not Log Messages"), "false")
1617 + % drop_messages_on_user_removal
1618 + {xmlelement, "field", [{"type", "list-single"},
1620 + translate:translate(Lang, "Drop messages on user removal")},
1621 + {"var", "drop_messages_on_user_removal"}],
1622 + [?DEFVAL(atom_to_list(MRemoval)),
1623 + ?LISTLINE(translate:translate(Lang, "Drop"), "true"),
1624 + ?LISTLINE(translate:translate(Lang, "Do not drop"), "false")
1627 + {xmlelement, "field", [{"type", "list-single"},
1629 + translate:translate(Lang, "Groupchat messages logging")},
1630 + {"var", "groupchat"}],
1631 + [?DEFVAL(atom_to_list(GroupChat)),
1632 + ?LISTLINE("all", "all"),
1633 + ?LISTLINE("none", "none"),
1634 + ?LISTLINE("send", "send"),
1635 + ?LISTLINE("half", "half")
1638 + {xmlelement, "field", [{"type", "text-multi"},
1640 + translate:translate(
1641 + Lang, "Jids/Domains to ignore")},
1642 + {"var", "ignore_list"}],
1643 + [{xmlelement, "value", [], [{xmlcdata, list_to_string(IgnoreJids)}]}]},
1644 + % purge older days
1645 + {xmlelement, "field", [{"type", "text-single"},
1647 + translate:translate(
1648 + Lang, "Purge messages older than (days)")},
1649 + {"var", "purge_older_days"}],
1650 + [{xmlelement, "value", [], [{xmlcdata, PurgeDays}]}]},
1651 + % poll users settings
1652 + {xmlelement, "field", [{"type", "text-single"},
1654 + translate:translate(
1655 + Lang, "Poll users settings (seconds)")},
1656 + {"var", "poll_users_settings"}],
1657 + [{xmlelement, "value", [], [{xmlcdata, integer_to_list(PollTime)}]}]}
1660 +get_form(_Host, ["mod_logdb"], #jid{luser = LUser, lserver = LServer} = _Jid, Lang) ->
1661 + get_user_form(LUser, LServer, Lang);
1662 +get_form(_Host, ["mod_logdb_users", User], _JidFrom, Lang) ->
1663 + #jid{luser=LUser, lserver=LServer} = jlib:string_to_jid(User),
1664 + get_user_form(LUser, LServer, Lang);
1665 +get_form(Host, ["mod_logdb_settings"], _JidFrom, Lang) ->
1666 + get_settings_form(Host, Lang);
1667 +get_form(_Host, Command, _, _Lang) ->
1668 + ?MYDEBUG("asked for form ~p", [Command]),
1669 + {error, ?ERR_SERVICE_UNAVAILABLE}.
1671 +check_log_list([Head | Tail]) ->
1672 + case lists:member($@, Head) of
1674 + false -> throw(error)
1676 + % this check for Head to be valid jid
1677 + case jlib:string_to_jid(Head) of
1681 + check_log_list(Tail)
1683 +check_log_list([]) ->
1686 +check_ignore_list([Head | Tail]) ->
1687 + case lists:member($@, Head) of
1689 + false -> throw(error)
1691 + % this check for Head to be valid jid
1692 + case jlib:string_to_jid(Head) of
1694 + % this check for Head to be valid domain "@domain.org"
1695 + case lists:nth(1, Head) of
1697 + % TODO: this allows spaces and special characters in Head. May be change to nodeprep?
1698 + case jlib:nameprep(lists:delete($@, Head)) of
1699 + error -> throw(error);
1700 + _ -> check_log_list(Tail)
1705 + check_ignore_list(Tail)
1707 +check_ignore_list([]) ->
1710 +parse_users_settings(XData) ->
1711 + DLD = case lists:keysearch("dolog_default", 1, XData) of
1712 + {value, {_, [String]}} when String == "true"; String == "false" ->
1713 + list_to_bool(String);
1715 + throw(bad_request)
1717 + DLL = case lists:keysearch("dolog_list", 1, XData) of
1719 + throw(bad_request);
1720 + {value, {_, [[]]}} ->
1722 + {value, {_, List1}} ->
1723 + case catch check_log_list(List1) of
1725 + throw(bad_request);
1730 + DNLL = case lists:keysearch("donotlog_list", 1, XData) of
1732 + throw(bad_request);
1733 + {value, {_, [[]]}} ->
1735 + {value, {_, List2}} ->
1736 + case catch check_log_list(List2) of
1738 + throw(bad_request);
1743 + #user_settings{dolog_default=DLD,
1745 + donotlog_list=DNLL}.
1747 +parse_module_settings(XData) ->
1748 + DLD = case lists:keysearch("dolog_default", 1, XData) of
1749 + {value, {_, [Str1]}} when Str1 == "true"; Str1 == "false" ->
1750 + list_to_bool(Str1);
1752 + throw(bad_request)
1754 + MRemoval = case lists:keysearch("drop_messages_on_user_removal", 1, XData) of
1755 + {value, {_, [Str5]}} when Str5 == "true"; Str5 == "false" ->
1756 + list_to_bool(Str5);
1758 + throw(bad_request)
1760 + GroupChat = case lists:keysearch("groupchat", 1, XData) of
1761 + {value, {_, [Str2]}} when Str2 == "none";
1765 + list_to_atom(Str2);
1767 + throw(bad_request)
1769 + Ignore = case lists:keysearch("ignore_list", 1, XData) of
1770 + {value, {_, List}} ->
1771 + case catch check_ignore_list(List) of
1775 + throw(bad_request)
1778 + throw(bad_request)
1780 + Purge = case lists:keysearch("purge_older_days", 1, XData) of
1781 + {value, {_, ["never"]}} ->
1783 + {value, {_, [Str3]}} ->
1784 + case catch list_to_integer(Str3) of
1785 + {'EXIT', {badarg, _}} -> throw(bad_request);
1789 + throw(bad_request)
1791 + Poll = case lists:keysearch("poll_users_settings", 1, XData) of
1792 + {value, {_, [Str4]}} ->
1793 + case catch list_to_integer(Str4) of
1794 + {'EXIT', {badarg, _}} -> throw(bad_request);
1798 + throw(bad_request)
1800 + #state{dolog_default=DLD,
1801 + groupchat=GroupChat,
1802 + ignore_jids=Ignore,
1803 + purge_older_days=Purge,
1804 + drop_messages_on_user_removal=MRemoval,
1805 + poll_users_settings=Poll}.
1807 +set_form(From, _Host, ["mod_logdb"], _Lang, XData) ->
1808 + #jid{luser=LUser, lserver=LServer} = From,
1809 + case catch parse_users_settings(XData) of
1811 + {error, ?ERR_BAD_REQUEST};
1813 + case mod_logdb:set_user_settings(LUser, LServer, UserSettings) of
1817 + {error, ?ERR_INTERNAL_SERVER_ERROR}
1820 +set_form(_From, _Host, ["mod_logdb_users", User], _Lang, XData) ->
1821 + #jid{luser=LUser, lserver=LServer} = jlib:string_to_jid(User),
1822 + case catch parse_users_settings(XData) of
1823 + bad_request -> {error, ?ERR_BAD_REQUEST};
1825 + case mod_logdb:set_user_settings(LUser, LServer, UserSettings) of
1829 + {error, ?ERR_INTERNAL_SERVER_ERROR}
1832 +set_form(_From, Host, ["mod_logdb_settings"], _Lang, XData) ->
1833 + case catch parse_module_settings(XData) of
1834 + bad_request -> {error, ?ERR_BAD_REQUEST};
1836 + case mod_logdb:set_module_settings(Host, Settings) of
1840 + {error, ?ERR_INTERNAL_SERVER_ERROR}
1843 +set_form(From, _Host, Node, _Lang, XData) ->
1844 + User = jlib:jid_to_string(jlib:jid_remove_resource(From)),
1845 + ?MYDEBUG("set form for ~p at ~p XData=~p", [User, Node, XData]),
1846 + {error, ?ERR_SERVICE_UNAVAILABLE}.
1848 +%adhoc_sm_items(Acc, From, To, Request) ->
1849 +% ?MYDEBUG("adhoc_sm_items Acc=~p From=~p To=~p Request=~p", [Acc, From, To, Request]),
1852 +%adhoc_sm_commands(Acc, From, To, Request) ->
1853 +% ?MYDEBUG("adhoc_sm_commands Acc=~p From=~p To=~p Request=~p", [Acc, From, To, Request]),
1856 +get_all_vh_users(Host, Server, Lang) ->
1857 + case catch ejabberd_auth:get_vh_registered_users(Host) of
1858 + {'EXIT', _Reason} ->
1861 + SUsers = lists:sort([{S, U} || {U, S} <- Users]),
1862 + case length(SUsers) of
1863 + N when N =< 100 ->
1864 + lists:map(fun({S, U}) ->
1865 + ?NODE(U ++ "@" ++ S, "mod_logdb_users/" ++ U ++ "@" ++ S)
1868 + NParts = trunc(math:sqrt(N * 0.618)) + 1,
1869 + M = trunc(N / NParts) + 1,
1870 + lists:map(fun(K) ->
1873 + "@" ++ integer_to_list(K) ++
1874 + "-" ++ integer_to_list(L),
1875 + {FS, FU} = lists:nth(K, SUsers),
1877 + if L < N -> lists:nth(L, SUsers);
1878 + true -> lists:last(SUsers)
1881 + FU ++ "@" ++ FS ++
1884 + ?NODE(Name, "mod_logdb_users/" ++ Node)
1885 + end, lists:seq(1, N, M))
1889 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1893 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1894 +webadmin_menu(Acc, _Host, Lang) ->
1895 + [{"messages", ?T("Users Messages")} | Acc].
1897 +webadmin_user(Acc, User, Server, Lang) ->
1898 + Sett = get_user_settings(User, Server),
1900 + case Sett#user_settings.dolog_default of
1902 + ?INPUTT("submit", "dolog", "Log Messages");
1904 + ?INPUTT("submit", "donotlog", "Do Not Log Messages");
1907 + Acc ++ [?XE("h3", [?ACT("messages/", "Messages"), ?C(" "), Log])].
1909 +webadmin_page(_, Host,
1910 + #request{path = ["messages"],
1912 + lang = Lang}) when is_list(Host) ->
1913 + Res = vhost_messages_stats(Host, Query, Lang),
1915 +webadmin_page(_, Host,
1916 + #request{path = ["messages", Date],
1918 + lang = Lang}) when is_list(Host) ->
1919 + Res = vhost_messages_stats_at(Host, Query, Lang, Date),
1921 +webadmin_page(_, Host,
1922 + #request{path = ["user", U, "messages"],
1925 + Res = user_messages_stats(U, Host, Query, Lang),
1927 +webadmin_page(_, Host,
1928 + #request{path = ["user", U, "messages", Date],
1931 + Res = mod_logdb:user_messages_stats_at(U, Host, Query, Lang, Date),
1933 +webadmin_page(Acc, _, _) -> Acc.
1935 +user_parse_query(_, "dolog", User, Server, _Query) ->
1936 + Sett = get_user_settings(User, Server),
1937 + % TODO: check returned value
1938 + set_user_settings(User, Server, Sett#user_settings{dolog_default=true}),
1940 +user_parse_query(_, "donotlog", User, Server, _Query) ->
1941 + Sett = get_user_settings(User, Server),
1942 + % TODO: check returned value
1943 + set_user_settings(User, Server, Sett#user_settings{dolog_default=false}),
1945 +user_parse_query(Acc, _Action, _User, _Server, _Query) ->
1948 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1952 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1953 +vhost_messages_stats(Server, Query, Lang) ->
1954 + Res = case catch vhost_messages_parse_query(Server, Query) of
1955 + {'EXIT', Reason} ->
1956 + ?ERROR_MSG("~p", [Reason]),
1958 + VResult -> VResult
1960 + {Time, Value} = timer:tc(mod_logdb, get_vhost_stats, [Server]),
1961 + ?INFO_MSG("get_vhost_stats(~p) elapsed ~p sec", [Server, Time/1000000]),
1962 + %case get_vhost_stats(Server) of
1964 + {'EXIT', CReason} ->
1965 + ?ERROR_MSG("Failed to get_vhost_stats: ~p", [CReason]),
1966 + [?XC("h1", ?T("Error occupied while fetching list"))];
1967 + {error, GReason} ->
1968 + ?ERROR_MSG("Failed to get_vhost_stats: ~p", [GReason]),
1969 + [?XC("h1", ?T("Error occupied while fetching list"))];
1971 + [?XC("h1", ?T("No logged messages for ") ++ Server)];
1973 + Fun = fun({Date, Count}) ->
1974 + ID = jlib:encode_base64(binary_to_list(term_to_binary(Server++Date))),
1976 + [?XE("td", [?INPUT("checkbox", "selected", ID)]),
1977 + ?XE("td", [?AC(Date, Date)]),
1978 + ?XC("td", integer_to_list(Count))
1981 + [?XC("h1", ?T("Logged messages for ") ++ Server)] ++
1983 + ok -> [?CT("Submitted"), ?P];
1984 + error -> [?CT("Bad format"), ?P];
1987 + [?XAE("form", [{"action", ""}, {"method", "post"}],
1992 + ?XCT("td", "Date"),
1993 + ?XCT("td", "Count")
1996 + lists:map(Fun, Dates)
1999 + ?INPUTT("submit", "delete", "Delete Selected")
2003 +vhost_messages_stats_at(Server, Query, Lang, Date) ->
2004 + {Time, Value} = timer:tc(mod_logdb, get_vhost_stats_at, [Server, Date]),
2005 + ?INFO_MSG("get_vhost_stats_at(~p,~p) elapsed ~p sec", [Server, Date, Time/1000000]),
2006 + %case get_vhost_stats_at(Server, Date) of
2008 + {'EXIT', CReason} ->
2009 + ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [CReason]),
2010 + [?XC("h1", ?T("Error occupied while fetching list"))];
2011 + {error, GReason} ->
2012 + ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [GReason]),
2013 + [?XC("h1", ?T("Error occupied while fetching list"))];
2015 + [?XC("h1", ?T("No logged messages for ") ++ Server ++ ?T(" at ") ++ Date)];
2017 + Res = case catch vhost_messages_at_parse_query(Server, Date, Users, Query) of
2018 + {'EXIT', Reason} ->
2019 + ?ERROR_MSG("~p", [Reason]),
2021 + VResult -> VResult
2023 + Fun = fun({User, Count}) ->
2024 + ID = jlib:encode_base64(binary_to_list(term_to_binary(User++Server))),
2026 + [?XE("td", [?INPUT("checkbox", "selected", ID)]),
2027 + ?XE("td", [?AC("../user/"++User++"/messages/"++Date, User)]),
2028 + ?XC("td", integer_to_list(Count))
2031 + [?XC("h1", ?T("Logged messages for ") ++ Server ++ ?T(" at ") ++ Date)] ++
2033 + ok -> [?CT("Submitted"), ?P];
2034 + error -> [?CT("Bad format"), ?P];
2037 + [?XAE("form", [{"action", ""}, {"method", "post"}],
2042 + ?XCT("td", "User"),
2043 + ?XCT("td", "Count")
2046 + lists:map(Fun, Users)
2049 + ?INPUTT("submit", "delete", "Delete Selected")
2053 +user_messages_stats(User, Server, Query, Lang) ->
2054 + Jid = jlib:jid_to_string({User, Server, ""}),
2056 + Res = case catch user_messages_parse_query(User, Server, Query) of
2057 + {'EXIT', Reason} ->
2058 + ?ERROR_MSG("~p", [Reason]),
2060 + VResult -> VResult
2063 + {Time, Value} = timer:tc(mod_logdb, get_user_stats, [User, Server]),
2064 + ?INFO_MSG("get_user_stats(~p,~p) elapsed ~p sec", [User, Server, Time/1000000]),
2067 + {'EXIT', CReason} ->
2068 + ?ERROR_MSG("Failed to get_user_stats: ~p", [CReason]),
2069 + [?XC("h1", ?T("Error occupied while fetching days"))];
2070 + {error, GReason} ->
2071 + ?ERROR_MSG("Failed to get_user_stats: ~p", [GReason]),
2072 + [?XC("h1", ?T("Error occupied while fetching days"))];
2074 + [?XC("h1", ?T("No logged messages for ") ++ Jid)];
2076 + Fun = fun({Date, Count}) ->
2077 + ID = jlib:encode_base64(binary_to_list(term_to_binary(User++Date))),
2079 + [?XE("td", [?INPUT("checkbox", "selected", ID)]),
2080 + ?XE("td", [?AC(Date, Date)]),
2081 + ?XC("td", integer_to_list(Count))
2083 + %[?AC(Date, Date ++ " (" ++ integer_to_list(Count) ++ ")"), ?BR]
2085 + [?XC("h1", ?T("Logged messages for ") ++ Jid)] ++
2087 + ok -> [?CT("Submitted"), ?P];
2088 + error -> [?CT("Bad format"), ?P];
2091 + [?XAE("form", [{"action", ""}, {"method", "post"}],
2096 + ?XCT("td", "Date"),
2097 + ?XCT("td", "Count")
2100 + lists:map(Fun, Dates)
2103 + ?INPUTT("submit", "delete", "Delete Selected")
2107 +search_user_nick(User, List) ->
2108 + case lists:keysearch(User, 1, List) of
2109 + {value,{User, []}} ->
2111 + {value,{User, Nick}} ->
2117 +user_messages_stats_at(User, Server, Query, Lang, Date) ->
2118 + Jid = jlib:jid_to_string({User, Server, ""}),
2120 + {Time, Value} = timer:tc(mod_logdb, get_user_messages_at, [User, Server, Date]),
2121 + ?INFO_MSG("get_user_messages_at(~p,~p,~p) elapsed ~p sec", [User, Server, Date, Time/1000000]),
2123 + {'EXIT', CReason} ->
2124 + ?ERROR_MSG("Failed to get_user_messages_at: ~p", [CReason]),
2125 + [?XC("h1", ?T("Error occupied while fetching messages"))];
2126 + {error, GReason} ->
2127 + ?ERROR_MSG("Failed to get_user_messages_at: ~p", [GReason]),
2128 + [?XC("h1", ?T("Error occupied while fetching messages"))];
2130 + [?XC("h1", ?T("No logged messages for ") ++ Jid ++ ?T(" at ") ++ Date)];
2131 + {ok, User_messages} ->
2132 + Res = case catch user_messages_at_parse_query(Server,
2136 + {'EXIT', Reason} ->
2137 + ?ERROR_MSG("~p", [Reason]),
2139 + VResult -> VResult
2142 + UR = ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]),
2144 + lists:map(fun(Item) ->
2145 + {jlib:jid_to_string(Item#roster.jid), Item#roster.name}
2148 + UniqUsers = lists:foldl(fun(#msg{peer_name=PName, peer_server=PServer}, List) ->
2149 + ToAdd = PName++"@"++PServer,
2150 + case lists:member(ToAdd, List) of
2152 + false -> lists:append([ToAdd], List)
2154 + end, [], User_messages),
2156 + % Users to filter (sublist of UniqUsers)
2157 + CheckedUsers = case lists:keysearch("filter", 1, Query) of
2159 + lists:filter(fun(UFUser) ->
2160 + ID = jlib:encode_base64(binary_to_list(term_to_binary(UFUser))),
2161 + lists:member({"selected", ID}, Query)
2166 + % UniqUsers in html (noone selected -> everyone selected)
2167 + Users = lists:map(fun(UHUser) ->
2168 + ID = jlib:encode_base64(binary_to_list(term_to_binary(UHUser))),
2169 + Input = case lists:member(UHUser, CheckedUsers) of
2170 + true -> [?INPUTC("checkbox", "selected", ID)];
2171 + false when CheckedUsers == [] -> [?INPUTC("checkbox", "selected", ID)];
2172 + false -> [?INPUT("checkbox", "selected", ID)]
2175 + case search_user_nick(UHUser, UserRoster) of
2177 + N -> " ("++ N ++")"
2180 + [?XE("td", Input),
2181 + ?XC("td", UHUser++Nick)])
2182 + end, lists:sort(UniqUsers)),
2183 + % Messages to show (based on Users)
2184 + User_messages_filtered = case CheckedUsers of
2185 + [] -> User_messages;
2186 + _ -> lists:filter(fun(#msg{peer_name=PName, peer_server=PServer}) ->
2187 + lists:member(PName++"@"++PServer, CheckedUsers)
2188 + end, User_messages)
2191 + Msgs_Fun = fun(#msg{timestamp=Timestamp,
2193 + direction=Direction,
2194 + peer_name=PName, peer_server=PServer, peer_resource=PRes,
2197 + TextRaw = case Subject of
2199 + _ -> [?T("Subject"),": ",Subject,"<br>", Body]
2201 + ID = jlib:encode_base64(binary_to_list(term_to_binary(Timestamp))),
2202 + % replace \n with <br>
2203 + Text = lists:map(fun(10) -> "<br>";
2206 + Resource = case PRes of
2212 + case search_user_nick(PName++"@"++PServer, UserRoster) of
2213 + nothing when PServer == Server ->
2215 + nothing when Type == "groupchat", Direction == from ->
2216 + PName++"@"++PServer++Resource;
2218 + PName++"@"++PServer;
2222 + [?XE("td", [?INPUT("checkbox", "selected", ID)]),
2223 + ?XC("td", convert_timestamp(Timestamp)),
2224 + ?XC("td", atom_to_list(Direction)++": "++UserNick),
2227 + % Filtered user messages in html
2228 + Msgs = lists:map(Msgs_Fun, lists:sort(User_messages_filtered)),
2230 + [?XC("h1", ?T("Logged messages for ") ++ Jid ++ ?T(" at ") ++ Date)] ++
2232 + ok -> [?CT("Submitted"), ?P];
2233 + error -> [?CT("Bad format"), ?P];
2236 + [?XAE("form", [{"action", ""}, {"method", "post"}],
2240 + ?XCT("td", "User")
2246 + ?INPUTT("submit", "filter", "Filter Selected")
2252 + ?XCT("td", "Date, Time"),
2253 + ?XCT("td", "Direction: Jid"),
2254 + ?XCT("td", "Body")
2259 + ?INPUTT("submit", "delete", "Delete Selected"),
2264 diff --git src/mod_logdb.hrl src/mod_logdb.hrl
2265 new file mode 100644
2266 index 0000000..50db897
2268 +++ src/mod_logdb.hrl
2270 +%%%----------------------------------------------------------------------
2271 +%%% File : mod_logdb.hrl
2272 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
2274 +%%% Version : trunk
2275 +%%% Id : $Id: mod_logdb.hrl 1273 2009-02-05 18:12:57Z malik $
2276 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
2277 +%%%----------------------------------------------------------------------
2279 +-define(logdb_debug, true).
2281 +-ifdef(logdb_debug).
2282 +-define(MYDEBUG(Format, Args), io:format("D(~p:~p:~p) : "++Format++"~n",
2283 + [calendar:local_time(),?MODULE,?LINE]++Args)).
2285 +-define(MYDEBUG(_F,_A),[]).
2288 +-record(msg, {timestamp,
2290 + peer_name, peer_server, peer_resource,
2295 +-record(user_settings, {owner_name,
2298 + donotlog_list=[]}).
2300 +-define(INPUTC(Type, Name, Value),
2301 + ?XA("input", [{"type", Type},
2304 + {"checked", "true"}])).
2305 diff --git src/mod_logdb_mnesia.erl src/mod_logdb_mnesia.erl
2306 new file mode 100644
2307 index 0000000..783aaeb
2309 +++ src/mod_logdb_mnesia.erl
2311 +%%%----------------------------------------------------------------------
2312 +%%% File : mod_logdb_mnesia.erl
2313 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
2314 +%%% Purpose : mnesia backend for mod_logdb
2315 +%%% Version : trunk
2316 +%%% Id : $Id: mod_logdb_mnesia.erl 1273 2009-02-05 18:12:57Z malik $
2317 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
2318 +%%%----------------------------------------------------------------------
2320 +-module(mod_logdb_mnesia).
2321 +-author('o.palij@gmail.com').
2323 +-include("mod_logdb.hrl").
2324 +-include("ejabberd.hrl").
2325 +-include("jlib.hrl").
2327 +-behaviour(gen_logdb).
2328 +-behaviour(gen_server).
2331 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
2333 +-export([start/2, stop/1]).
2335 +-export([log_message/2,
2337 + rebuild_stats_at/2,
2338 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
2339 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
2341 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
2344 +-define(PROCNAME, mod_logdb_mnesia).
2345 +-define(CALL_TIMEOUT, 10000).
2347 +-record(state, {vhost}).
2349 +-record(stats, {user, at, count}).
2357 +stats_table(VHost) ->
2358 + list_to_atom(prefix() ++ "stats" ++ suffix(VHost)).
2360 +table_name(VHost, Date) ->
2361 + list_to_atom(prefix() ++ "messages_" ++ Date ++ suffix(VHost)).
2363 +settings_table(VHost) ->
2364 + list_to_atom(prefix() ++ "settings" ++ suffix(VHost)).
2366 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2368 +% gen_mod callbacks
2370 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2371 +start(VHost, Opts) ->
2372 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2373 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
2376 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2377 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
2379 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2381 +% gen_server callbacks
2383 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2384 +init([VHost, _Opts]) ->
2385 + case mnesia:system_info(is_running) of
2387 + ok = create_stats_table(VHost),
2388 + ok = create_settings_table(VHost),
2389 + {ok, #state{vhost=VHost}};
2391 + ?ERROR_MSG("Mnesia not running", []),
2392 + {stop, db_connection_failed};
2394 + ?ERROR_MSG("Mnesia status: ~p", [Status]),
2395 + {stop, db_connection_failed}
2398 +handle_call({log_message, Msg}, _From, #state{vhost=VHost}=State) ->
2399 + {reply, log_message_int(VHost, Msg), State};
2400 +handle_call({rebuild_stats}, _From, #state{vhost=VHost}=State) ->
2401 + {atomic, ok} = delete_nonexistent_stats(VHost),
2403 + lists:foreach(fun(Date) ->
2404 + rebuild_stats_at_int(VHost, Date)
2405 + end, get_dates_int(VHost)),
2406 + {reply, Reply, State};
2407 +handle_call({rebuild_stats_at, Date}, _From, #state{vhost=VHost}=State) ->
2408 + Reply = rebuild_stats_at_int(VHost, Date),
2409 + {reply, Reply, State};
2410 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{vhost=VHost}=State) ->
2411 + Table = table_name(VHost, Date),
2415 + mnesia:write_lock_table(stats_table(VHost)),
2416 + mnesia:write_lock_table(Table),
2417 + mnesia:delete_object(Table, Msg, write)
2420 + DRez = case mnesia:transaction(Fun) of
2421 + {aborted, Reason} ->
2422 + ?ERROR_MSG("Failed to delete_messages_by_user_at at ~p for ~p: ~p", [Date, VHost, Reason]),
2428 + case rebuild_stats_at_int(VHost, Date) of
2434 + {reply, Reply, State};
2435 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{vhost=VHost}=State) ->
2436 + {reply, delete_all_messages_by_user_at_int(User, VHost, Date), State};
2437 +handle_call({delete_messages_at, Date}, _From, #state{vhost=VHost}=State) ->
2439 + case mnesia:delete_table(table_name(VHost, Date)) of
2441 + delete_stats_by_vhost_at_int(VHost, Date);
2442 + {aborted, Reason} ->
2443 + ?ERROR_MSG("Failed to delete_messages_at for ~p at ~p", [VHost, Date, Reason]),
2446 + {reply, Reply, State};
2447 +handle_call({get_vhost_stats}, _From, #state{vhost=VHost}=State) ->
2448 + Fun = fun(#stats{at=Date, count=Count}, Stats) ->
2449 + case lists:keysearch(Date, 1, Stats) of
2451 + lists:append(Stats, [{Date, Count}]);
2452 + {value, {_, TempCount}} ->
2453 + lists:keyreplace(Date, 1, Stats, {Date, TempCount+Count})
2457 + case mnesia:transaction(fun() ->
2458 + mnesia:foldl(Fun, [], stats_table(VHost))
2460 + {atomic, Result} -> {ok, mod_logdb:sort_stats(Result)};
2461 + {aborted, Reason} -> {error, Reason}
2463 + {reply, Reply, State};
2464 +handle_call({get_vhost_stats_at, Date}, _From, #state{vhost=VHost}=State) ->
2466 + Pat = #stats{user='$1', at=Date, count='$2'},
2467 + mnesia:select(stats_table(VHost), [{Pat, [], [['$1', '$2']]}])
2470 + case mnesia:transaction(Fun) of
2471 + {atomic, Result} ->
2472 + {ok, lists:reverse(lists:keysort(2, [{User, Count} || [User, Count] <- Result]))};
2473 + {aborted, Reason} ->
2476 + {reply, Reply, State};
2477 +handle_call({get_user_stats, User}, _From, #state{vhost=VHost}=State) ->
2478 + {reply, get_user_stats_int(User, VHost), State};
2479 +handle_call({get_user_messages_at, User, Date}, _From, #state{vhost=VHost}=State) ->
2481 + case mnesia:transaction(fun() ->
2482 + Pat = #msg{owner_name=User, _='_'},
2483 + mnesia:select(table_name(VHost, Date),
2484 + [{Pat, [], ['$_']}])
2486 + {atomic, Result} -> {ok, Result};
2487 + {aborted, Reason} ->
2490 + {reply, Reply, State};
2491 +handle_call({get_dates}, _From, #state{vhost=VHost}=State) ->
2492 + {reply, get_dates_int(VHost), State};
2493 +handle_call({get_users_settings}, _From, #state{vhost=VHost}=State) ->
2494 + Reply = mnesia:dirty_match_object(settings_table(VHost), #user_settings{_='_'}),
2495 + {reply, {ok, Reply}, State};
2496 +handle_call({get_user_settings, User}, _From, #state{vhost=VHost}=State) ->
2498 + case mnesia:dirty_match_object(settings_table(VHost), #user_settings{owner_name=User, _='_'}) of
2503 + {reply, Reply, State};
2504 +handle_call({set_user_settings, _User, Set}, _From, #state{vhost=VHost}=State) ->
2505 + ?MYDEBUG("~p~n~p", [settings_table(VHost), Set]),
2506 + Reply = mnesia:dirty_write(settings_table(VHost), Set),
2507 + ?MYDEBUG("~p", [Reply]),
2508 + {reply, Reply, State};
2509 +handle_call({drop_user, User}, _From, #state{vhost=VHost}=State) ->
2510 + {ok, Dates} = get_user_stats_int(User, VHost),
2511 + MDResult = lists:map(fun({Date, _}) ->
2512 + delete_all_messages_by_user_at_int(User, VHost, Date)
2514 + SDResult = delete_user_settings_int(User, VHost),
2516 + case lists:all(fun(Result) when Result == ok ->
2518 + (Result) when Result == error ->
2520 + end, lists:append(MDResult, [SDResult])) of
2526 + {reply, Reply, State};
2527 +handle_call({stop}, _From, State) ->
2528 + {stop, normal, ok, State};
2529 +handle_call(Msg, _From, State) ->
2530 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
2533 +handle_cast(Msg, State) ->
2534 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
2537 +handle_info(Info, State) ->
2538 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
2541 +terminate(_Reason, _State) ->
2544 +code_change(_OldVsn, State, _Extra) ->
2547 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2549 +% gen_logdb callbacks
2551 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2552 +log_message(VHost, Msg) ->
2553 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2554 + gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
2555 +rebuild_stats(VHost) ->
2556 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2557 + gen_server:call(Proc, {rebuild_stats}, ?CALL_TIMEOUT).
2558 +rebuild_stats_at(VHost, Date) ->
2559 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2560 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
2561 +delete_messages_by_user_at(VHost, Msgs, Date) ->
2562 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2563 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
2564 +delete_all_messages_by_user_at(User, VHost, Date) ->
2565 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2566 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
2567 +delete_messages_at(VHost, Date) ->
2568 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2569 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
2570 +get_vhost_stats(VHost) ->
2571 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2572 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
2573 +get_vhost_stats_at(VHost, Date) ->
2574 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2575 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
2576 +get_user_stats(User, VHost) ->
2577 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2578 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
2579 +get_user_messages_at(User, VHost, Date) ->
2580 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2581 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
2582 +get_dates(VHost) ->
2583 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2584 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
2585 +get_user_settings(User, VHost) ->
2586 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2587 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
2588 +get_users_settings(VHost) ->
2589 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2590 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
2591 +set_user_settings(User, VHost, Set) ->
2592 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2593 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
2594 +drop_user(User, VHost) ->
2595 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2596 + gen_server:call(Proc, {drop_user, User}, ?CALL_TIMEOUT).
2598 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2602 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2603 +log_message_int(VHost, #msg{timestamp=Timestamp}=Msg) ->
2604 + Date = mod_logdb:convert_timestamp_brief(Timestamp),
2606 + ATable = table_name(VHost, Date),
2608 + mnesia:write_lock_table(ATable),
2609 + mnesia:write(ATable, Msg, write)
2611 + % log message, increment stats for both users
2612 + case mnesia:transaction(Fun) of
2613 + % if table does not exists - create it and try to log message again
2614 + {aborted,{no_exists, _Table}} ->
2615 + case create_msg_table(VHost, Date) of
2616 + {aborted, CReason} ->
2617 + ?ERROR_MSG("Failed to log message: ~p", [CReason]),
2620 + ?MYDEBUG("Created msg table for ~p at ~p", [VHost, Date]),
2621 + log_message_int(VHost, Msg)
2623 + {aborted, TReason} ->
2624 + ?ERROR_MSG("Failed to log message: ~p", [TReason]),
2627 + ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
2628 + Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
2629 + increment_user_stats(Msg#msg.owner_name, VHost, Date)
2632 +increment_user_stats(Owner, VHost, Date) ->
2634 + Pat = #stats{user=Owner, at=Date, count='$1'},
2635 + mnesia:write_lock_table(stats_table(VHost)),
2636 + case mnesia:select(stats_table(VHost), [{Pat, [], ['$_']}]) of
2638 + mnesia:write(stats_table(VHost),
2639 + #stats{user=Owner,
2644 + mnesia:delete_object(stats_table(VHost),
2645 + #stats{user=Owner,
2647 + count=Stats#stats.count},
2649 + New = Stats#stats{count = Stats#stats.count+1},
2651 + New#stats.count > 0 -> mnesia:write(stats_table(VHost),
2658 + case mnesia:transaction(Fun) of
2659 + {aborted, Reason} ->
2660 + ?ERROR_MSG("Failed to update stats for ~s@~s: ~p", [Owner, VHost, Reason]),
2663 + ?MYDEBUG("Updated stats for ~s@~s", [Owner, VHost]),
2667 +get_dates_int(VHost) ->
2668 + Tables = mnesia:system_info(tables),
2669 + lists:foldl(fun(ATable, Dates) ->
2670 + Table = atom_to_list(ATable),
2671 + case ejabberd_regexp:run(Table, VHost++"$") of
2673 + case re:run(Table, "_[0-9]+-[0-9]+-[0-9]+_") of
2674 + {match, [{S, E}]} ->
2675 + lists:append(Dates, [lists:sublist(Table,S+1,E-2)]);
2684 +rebuild_stats_at_int(VHost, Date) ->
2685 + Table = table_name(VHost, Date),
2686 + STable = stats_table(VHost),
2687 + CFun = fun(Msg, Stats) ->
2688 + Owner = Msg#msg.owner_name,
2689 + case lists:keysearch(Owner, 1, Stats) of
2690 + {value, {_, Count}} ->
2691 + lists:keyreplace(Owner, 1, Stats, {Owner, Count + 1});
2693 + lists:append(Stats, [{Owner, 1}])
2696 + DFun = fun(#stats{at=SDate} = Stat, _Acc)
2697 + when SDate == Date ->
2698 + mnesia:delete_object(stats_table(VHost), Stat, write);
2699 + (_Stat, _Acc) -> ok
2701 + % TODO: Maybe unregister hooks ?
2702 + case mnesia:transaction(fun() ->
2703 + mnesia:write_lock_table(Table),
2704 + mnesia:write_lock_table(STable),
2705 + % Calc stats for VHost at Date
2706 + case mnesia:foldl(CFun, [], Table) of
2709 + % Delete all stats for VHost at Date
2710 + mnesia:foldl(DFun, [], STable),
2711 + % Write new calc'ed stats
2712 + lists:foreach(fun({Owner, Count}) ->
2713 + WStat = #stats{user=Owner, at=Date, count=Count},
2714 + mnesia:write(stats_table(VHost), WStat, write)
2719 + {aborted, Reason} ->
2720 + ?ERROR_MSG("Failed to rebuild_stats_at for ~p at ~p: ~p", [VHost, Date, Reason]),
2724 + {atomic, empty} ->
2725 + {atomic,ok} = mnesia:delete_table(Table),
2726 + ?MYDEBUG("Dropped table at ~p", [Date]),
2730 +delete_nonexistent_stats(VHost) ->
2731 + Dates = get_dates_int(VHost),
2732 + mnesia:transaction(fun() ->
2733 + mnesia:foldl(fun(#stats{at=Date} = Stat, _Acc) ->
2734 + case lists:member(Date, Dates) of
2735 + false -> mnesia:delete_object(Stat);
2738 + end, ok, stats_table(VHost))
2741 +delete_stats_by_vhost_at_int(VHost, Date) ->
2742 + StatsDelete = fun(#stats{at=SDate} = Stat, _Acc)
2743 + when SDate == Date ->
2744 + mnesia:delete_object(stats_table(VHost), Stat, write),
2746 + (_Msg, _Acc) -> ok
2748 + case mnesia:transaction(fun() ->
2749 + mnesia:write_lock_table(stats_table(VHost)),
2750 + mnesia:foldl(StatsDelete, ok, stats_table(VHost))
2752 + {aborted, Reason} ->
2753 + ?ERROR_MSG("Failed to update stats at ~p for ~p: ~p", [Date, VHost, Reason]),
2754 + rebuild_stats_at_int(VHost, Date);
2756 + ?INFO_MSG("Updated stats at ~p for ~p", [Date, VHost]),
2760 +get_user_stats_int(User, VHost) ->
2761 + case mnesia:transaction(fun() ->
2762 + Pat = #stats{user=User, at='$1', count='$2'},
2763 + mnesia:select(stats_table(VHost), [{Pat, [], [['$1', '$2']]}])
2765 + {atomic, Result} ->
2766 + {ok, mod_logdb:sort_stats([{Date, Count} || [Date, Count] <- Result])};
2767 + {aborted, Reason} ->
2771 +delete_all_messages_by_user_at_int(User, VHost, Date) ->
2772 + Table = table_name(VHost, Date),
2773 + MsgDelete = fun(#msg{owner_name=Owner} = Msg, _Acc)
2774 + when Owner == User ->
2775 + mnesia:delete_object(Table, Msg, write),
2777 + (_Msg, _Acc) -> ok
2779 + DRez = case mnesia:transaction(fun() ->
2780 + mnesia:foldl(MsgDelete, ok, Table)
2782 + {aborted, Reason} ->
2783 + ?ERROR_MSG("Failed to delete_all_messages_by_user_at for ~p@~p at ~p: ~p", [User, VHost, Date, Reason]),
2788 + case rebuild_stats_at_int(VHost, Date) of
2795 +delete_user_settings_int(User, VHost) ->
2796 + STable = settings_table(VHost),
2797 + case mnesia:dirty_match_object(STable, #user_settings{owner_name=User, _='_'}) of
2801 + mnesia:dirty_delete_object(STable, UserSettings)
2804 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2808 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2809 +create_stats_table(VHost) ->
2810 + SName = stats_table(VHost),
2811 + case mnesia:create_table(SName,
2812 + [{disc_only_copies, [node()]},
2814 + {attributes, record_info(fields, stats)},
2815 + {record_name, stats}
2818 + ?MYDEBUG("Created stats table for ~p", [VHost]),
2819 + lists:foreach(fun(Date) ->
2820 + rebuild_stats_at_int(VHost, Date)
2821 + end, get_dates_int(VHost)),
2823 + {aborted, {already_exists, _}} ->
2824 + ?MYDEBUG("Stats table for ~p already exists", [VHost]),
2826 + {aborted, Reason} ->
2827 + ?ERROR_MSG("Failed to create stats table: ~p", [Reason]),
2831 +create_settings_table(VHost) ->
2832 + SName = settings_table(VHost),
2833 + case mnesia:create_table(SName,
2834 + [{disc_copies, [node()]},
2836 + {attributes, record_info(fields, user_settings)},
2837 + {record_name, user_settings}
2840 + ?MYDEBUG("Created settings table for ~p", [VHost]),
2842 + {aborted, {already_exists, _}} ->
2843 + ?MYDEBUG("Settings table for ~p already exists", [VHost]),
2845 + {aborted, Reason} ->
2846 + ?ERROR_MSG("Failed to create settings table: ~p", [Reason]),
2850 +create_msg_table(VHost, Date) ->
2851 + mnesia:create_table(
2852 + table_name(VHost, Date),
2853 + [{disc_only_copies, [node()]},
2855 + {attributes, record_info(fields, msg)},
2856 + {record_name, msg}]).
2857 diff --git src/mod_logdb_mnesia_old.erl src/mod_logdb_mnesia_old.erl
2858 new file mode 100644
2859 index 0000000..aef9956
2861 +++ src/mod_logdb_mnesia_old.erl
2863 +%%%----------------------------------------------------------------------
2864 +%%% File : mod_logdb_mnesia_old.erl
2865 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
2866 +%%% Purpose : mod_logmnesia backend for mod_logdb (should be used only for copy_tables functionality)
2867 +%%% Version : trunk
2868 +%%% Id : $Id: mod_logdb_mnesia_old.erl 1273 2009-02-05 18:12:57Z malik $
2869 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
2870 +%%%----------------------------------------------------------------------
2872 +-module(mod_logdb_mnesia_old).
2873 +-author('o.palij@gmail.com').
2875 +-include("ejabberd.hrl").
2876 +-include("jlib.hrl").
2878 +-behaviour(gen_logdb).
2880 +-export([start/2, stop/1,
2883 + rebuild_stats_at/2,
2884 + rebuild_stats_at1/2,
2885 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
2886 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
2888 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
2891 +-record(stats, {user, server, table, count}).
2892 +-record(msg, {to_user, to_server, to_resource, from_user, from_server, from_resource, id, type, subject, body, timestamp}).
2894 +tables_prefix() -> "messages_".
2895 +% stats_table should not start with tables_prefix(VHost) !
2896 +% i.e. lists:prefix(tables_prefix(VHost), atom_to_list(stats_table())) must be /= true
2897 +stats_table() -> list_to_atom("messages-stats").
2898 +% table name as atom from Date
2899 +-define(ATABLE(Date), list_to_atom(tables_prefix() ++ Date)).
2900 +-define(LTABLE(Date), tables_prefix() ++ Date).
2902 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2904 +% gen_logdb callbacks
2906 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2907 +start(_Opts, _VHost) ->
2908 + case mnesia:system_info(is_running) of
2910 + ok = create_stats_table(),
2913 + ?ERROR_MSG("Mnesia not running", []),
2916 + ?ERROR_MSG("Mnesia status: ~p", [Status]),
2923 +log_message(_VHost, _Msg) ->
2926 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2928 +% gen_logdb callbacks (maintaince)
2930 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2931 +rebuild_stats(_VHost) ->
2934 +rebuild_stats_at(VHost, Date) ->
2935 + Table = ?LTABLE(Date),
2936 + {Time, Value}=timer:tc(?MODULE, rebuild_stats_at1, [VHost, Table]),
2937 + ?INFO_MSG("rebuild_stats_at ~p elapsed ~p sec: ~p~n", [Date, Time/1000000, Value]),
2939 +rebuild_stats_at1(VHost, Table) ->
2940 + CFun = fun(Msg, Stats) ->
2941 + To = Msg#msg.to_user ++ "@" ++ Msg#msg.to_server,
2943 + Msg#msg.to_server == VHost ->
2944 + case lists:keysearch(To, 1, Stats) of
2945 + {value, {Who_to, Count_to}} ->
2946 + lists:keyreplace(To, 1, Stats, {Who_to, Count_to + 1});
2948 + lists:append(Stats, [{To, 1}])
2953 + From = Msg#msg.from_user ++ "@" ++ Msg#msg.from_server,
2955 + Msg#msg.from_server == VHost ->
2956 + case lists:keysearch(From, 1, Stats_to) of
2957 + {value, {Who_from, Count_from}} ->
2958 + lists:keyreplace(From, 1, Stats_to, {Who_from, Count_from + 1});
2960 + lists:append(Stats_to, [{From, 1}])
2967 + DFun = fun(#stats{table=STable, server=Server} = Stat, _Acc)
2968 + when STable == Table, Server == VHost ->
2969 + mnesia:delete_object(stats_table(), Stat, write);
2970 + (_Stat, _Acc) -> ok
2972 + case mnesia:transaction(fun() ->
2973 + mnesia:write_lock_table(list_to_atom(Table)),
2974 + mnesia:write_lock_table(stats_table()),
2975 + % Calc stats for VHost at Date
2976 + AStats = mnesia:foldl(CFun, [], list_to_atom(Table)),
2977 + % Delete all stats for VHost at Date
2978 + mnesia:foldl(DFun, [], stats_table()),
2979 + % Write new calc'ed stats
2980 + lists:foreach(fun({Who, Count}) ->
2981 + Jid = jlib:string_to_jid(Who),
2982 + JUser = Jid#jid.user,
2983 + WStat = #stats{user=JUser, server=VHost, table=Table, count=Count},
2984 + mnesia:write(stats_table(), WStat, write)
2987 + {aborted, Reason} ->
2988 + ?ERROR_MSG("Failed to rebuild_stats_at for ~p at ~p: ~p", [VHost, Table, Reason]),
2994 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2996 +% gen_logdb callbacks (delete)
2998 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2999 +delete_messages_by_user_at(_VHost, _Msgs, _Date) ->
3002 +delete_all_messages_by_user_at(_User, _VHost, _Date) ->
3005 +delete_messages_at(VHost, Date) ->
3006 + Table = list_to_atom(tables_prefix() ++ Date),
3008 + DFun = fun(#msg{to_server=To_server, from_server=From_server}=Msg, _Acc)
3009 + when To_server == VHost; From_server == VHost ->
3010 + mnesia:delete_object(Table, Msg, write);
3011 + (_Msg, _Acc) -> ok
3014 + case mnesia:transaction(fun() ->
3015 + mnesia:foldl(DFun, [], Table)
3017 + {aborted, Reason} ->
3018 + ?ERROR_MSG("Failed to delete_messages_at for ~p at ~p: ~p", [VHost, Date, Reason]),
3024 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3026 +% gen_logdb callbacks (get)
3028 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3029 +get_vhost_stats(_VHost) ->
3030 + {error, "does not emplemented"}.
3032 +get_vhost_stats_at(VHost, Date) ->
3034 + Pat = #stats{user='$1', server=VHost, table=tables_prefix()++Date, count = '$2'},
3035 + mnesia:select(stats_table(), [{Pat, [], [['$1', '$2']]}])
3037 + case mnesia:transaction(Fun) of
3038 + {atomic, Result} ->
3039 + RFun = fun([User, Count]) ->
3042 + {ok, lists:reverse(lists:keysort(2, lists:map(RFun, Result)))};
3043 + {aborted, Reason} -> {error, Reason}
3046 +get_user_stats(_User, _VHost) ->
3047 + {error, "does not emplemented"}.
3049 +get_user_messages_at(User, VHost, Date) ->
3050 + Table_name = tables_prefix() ++ Date,
3051 + case mnesia:transaction(fun() ->
3052 + Pat_to = #msg{to_user=User, to_server=VHost, _='_'},
3053 + Pat_from = #msg{from_user=User, from_server=VHost, _='_'},
3054 + mnesia:select(list_to_atom(Table_name),
3055 + [{Pat_to, [], ['$_']},
3056 + {Pat_from, [], ['$_']}])
3058 + {atomic, Result} ->
3059 + Msgs = lists:map(fun(#msg{to_user=To_user, to_server=To_server, to_resource=To_res,
3060 + from_user=From_user, from_server=From_server, from_resource=From_res,
3063 + body=Body, timestamp=Timestamp} = _Msg) ->
3064 + Subject = case Subj of
3068 + {msg, To_user, To_server, To_res, From_user, From_server, From_res, Type, Subject, Body, Timestamp}
3071 + {aborted, Reason} ->
3075 +get_dates(_VHost) ->
3076 + Tables = mnesia:system_info(tables),
3078 + lists:filter(fun(Table) ->
3079 + lists:prefix(tables_prefix(), atom_to_list(Table))
3082 + lists:map(fun(Table) ->
3083 + lists:sublist(atom_to_list(Table),
3084 + length(tables_prefix())+1,
3085 + length(atom_to_list(Table)))
3089 +get_users_settings(_VHost) ->
3091 +get_user_settings(_User, _VHost) ->
3093 +set_user_settings(_User, _VHost, _Set) ->
3095 +drop_user(_User, _VHost) ->
3098 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3102 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3103 +% called from db_logon/2
3104 +create_stats_table() ->
3105 + SName = stats_table(),
3106 + case mnesia:create_table(SName,
3107 + [{disc_only_copies, [node()]},
3109 + {attributes, record_info(fields, stats)},
3110 + {record_name, stats}
3113 + ?INFO_MSG("Created stats table", []),
3115 + {aborted, {already_exists, _}} ->
3117 + {aborted, Reason} ->
3118 + ?ERROR_MSG("Failed to create stats table: ~p", [Reason]),
3121 diff --git src/mod_logdb_mysql.erl src/mod_logdb_mysql.erl
3122 new file mode 100644
3123 index 0000000..7c473ce
3125 +++ src/mod_logdb_mysql.erl
3127 +%%%----------------------------------------------------------------------
3128 +%%% File : mod_logdb_mysql.erl
3129 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
3130 +%%% Purpose : MySQL backend for mod_logdb
3131 +%%% Version : trunk
3132 +%%% Id : $Id: mod_logdb_mysql.erl 1360 2009-07-30 06:00:14Z malik $
3133 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
3134 +%%%----------------------------------------------------------------------
3136 +-module(mod_logdb_mysql).
3137 +-author('o.palij@gmail.com').
3139 +-include("mod_logdb.hrl").
3140 +-include("ejabberd.hrl").
3141 +-include("jlib.hrl").
3143 +-behaviour(gen_logdb).
3144 +-behaviour(gen_server).
3147 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
3149 +-export([start/2, stop/1]).
3151 +-export([log_message/2,
3153 + rebuild_stats_at/2,
3154 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
3155 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
3157 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
3160 +% gen_server call timeout
3161 +-define(CALL_TIMEOUT, 30000).
3162 +-define(MYSQL_TIMEOUT, 60000).
3163 +-define(INDEX_SIZE, integer_to_list(170)).
3164 +-define(PROCNAME, mod_logdb_mysql).
3166 +-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
3167 + list_to_string/1, string_to_list/1,
3168 + convert_timestamp_brief/1]).
3170 +-record(state, {dbref, vhost, server, port, db, user, password}).
3172 +% replace "." with "_"
3173 +escape_vhost(VHost) -> lists:map(fun(46) -> 95;
3180 + "_" ++ escape_vhost(VHost) ++ "`".
3182 +messages_table(VHost, Date) ->
3183 + prefix() ++ "messages_" ++ Date ++ suffix(VHost).
3185 +stats_table(VHost) ->
3186 + prefix() ++ "stats" ++ suffix(VHost).
3188 +temp_table(VHost) ->
3189 + prefix() ++ "temp" ++ suffix(VHost).
3191 +settings_table(VHost) ->
3192 + prefix() ++ "settings" ++ suffix(VHost).
3194 +users_table(VHost) ->
3195 + prefix() ++ "users" ++ suffix(VHost).
3196 +servers_table(VHost) ->
3197 + prefix() ++ "servers" ++ suffix(VHost).
3198 +resources_table(VHost) ->
3199 + prefix() ++ "resources" ++ suffix(VHost).
3201 +ets_users_table(VHost) -> list_to_atom("logdb_users_" ++ VHost).
3202 +ets_servers_table(VHost) -> list_to_atom("logdb_servers_" ++ VHost).
3203 +ets_resources_table(VHost) -> list_to_atom("logdb_resources_" ++ VHost).
3205 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3207 +% gen_mod callbacks
3209 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3210 +start(VHost, Opts) ->
3211 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3212 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
3215 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3216 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
3218 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3220 +% gen_server callbacks
3222 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3223 +init([VHost, Opts]) ->
3226 + Server = gen_mod:get_opt(server, Opts, "localhost"),
3227 + Port = gen_mod:get_opt(port, Opts, 3306),
3228 + DB = gen_mod:get_opt(db, Opts, "logdb"),
3229 + User = gen_mod:get_opt(user, Opts, "root"),
3230 + Password = gen_mod:get_opt(password, Opts, ""),
3232 + St = #state{vhost=VHost,
3233 + server=Server, port=Port, db=DB,
3234 + user=User, password=Password},
3236 + case open_mysql_connection(St) of
3238 + State = St#state{dbref=DBRef},
3239 + ok = create_stats_table(State),
3240 + ok = create_settings_table(State),
3241 + ok = create_users_table(State),
3242 + % clear ets cache every ...
3243 + timer:send_interval(timer:hours(12), clear_ets_tables),
3244 + ok = create_servers_table(State),
3245 + ok = create_resources_table(State),
3246 + erlang:monitor(process, DBRef),
3248 + {error, Reason} ->
3249 + ?ERROR_MSG("MySQL connection failed: ~p~n", [Reason]),
3250 + {stop, db_connection_failed}
3253 +open_mysql_connection(#state{server=Server, port=Port, db=DB,
3254 + user=DBUser, password=Password} = _State) ->
3255 + LogFun = fun(debug, _Format, _Argument) ->
3256 + %?MYDEBUG(Format, Argument);
3258 + (error, Format, Argument) ->
3259 + ?ERROR_MSG(Format, Argument);
3260 + (Level, Format, Argument) ->
3261 + ?MYDEBUG("MySQL (~p)~n", [Level]),
3262 + ?MYDEBUG(Format, Argument)
3264 + ?INFO_MSG("Opening mysql connection ~s@~s:~p/~s", [DBUser, Server, Port, DB]),
3265 + mysql_conn:start(Server, Port, DBUser, Password, DB, LogFun).
3267 +close_mysql_connection(DBRef) ->
3268 + ?MYDEBUG("Closing ~p mysql connection", [DBRef]),
3269 + mysql_conn:stop(DBRef).
3271 +handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3272 + Date = convert_timestamp_brief(Msg#msg.timestamp),
3274 + Table = messages_table(VHost, Date),
3275 + Owner_id = get_user_id(DBRef, VHost, Msg#msg.owner_name),
3276 + Peer_name_id = get_user_id(DBRef, VHost, Msg#msg.peer_name),
3277 + Peer_server_id = get_server_id(DBRef, VHost, Msg#msg.peer_server),
3278 + Peer_resource_id = get_resource_id(DBRef, VHost, Msg#msg.peer_resource),
3280 + Query = ["INSERT INTO ",Table," ",
3283 + "peer_server_id,",
3284 + "peer_resource_id,",
3291 + "('", Owner_id, "',",
3292 + "'", Peer_name_id, "',",
3293 + "'", Peer_server_id, "',",
3294 + "'", Peer_resource_id, "',",
3295 + "'", atom_to_list(Msg#msg.direction), "',",
3296 + "'", Msg#msg.type, "',",
3297 + "'", ejabberd_odbc:escape(Msg#msg.subject), "',",
3298 + "'", ejabberd_odbc:escape(Msg#msg.body), "',",
3299 + "'", Msg#msg.timestamp, "');"],
3302 + case sql_query_internal_silent(DBRef, Query) of
3304 + ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
3305 + Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
3306 + increment_user_stats(DBRef, Msg#msg.owner_name, Owner_id, VHost, Peer_name_id, Peer_server_id, Date);
3307 + {error, Reason} ->
3308 + case ejabberd_regexp:run(Reason, "#42S02") of
3309 + % Table doesn't exist
3311 + case create_msg_table(DBRef, VHost, Date) of
3315 + {updated, _} = sql_query_internal(DBRef, Query),
3316 + increment_user_stats(DBRef, Msg#msg.owner_name, Owner_id, VHost, Peer_name_id, Peer_server_id, Date)
3319 + ?ERROR_MSG("Failed to log message: ~p", [Reason]),
3323 + {reply, Reply, State};
3324 +handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3325 + Reply = rebuild_stats_at_int(DBRef, VHost, Date),
3326 + {reply, Reply, State};
3327 +handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
3328 + {reply, error, State};
3329 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3330 + Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
3331 + ["\"",Timestamp,"\"",","]
3334 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
3336 + Query = ["DELETE FROM ",messages_table(VHost, Date)," ",
3337 + "WHERE timestamp IN (", Temp1],
3340 + case sql_query_internal(DBRef, Query) of
3342 + ?MYDEBUG("Aff=~p", [Aff]),
3343 + rebuild_stats_at_int(DBRef, VHost, Date);
3347 + {reply, Reply, State};
3348 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3349 + ok = delete_all_messages_by_user_at_int(DBRef, User, VHost, Date),
3350 + ok = delete_stats_by_user_at_int(DBRef, User, VHost, Date),
3351 + {reply, ok, State};
3352 +handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3354 + case sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Date),";"]) of
3356 + Query = ["DELETE FROM ",stats_table(VHost)," "
3357 + "WHERE at=\"",Date,"\";"],
3358 + case sql_query_internal(DBRef, Query) of
3367 + {reply, Reply, State};
3368 +handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3369 + SName = stats_table(VHost),
3370 + Query = ["SELECT at, sum(count) ",
3371 + "FROM ",SName," ",
3373 + "ORDER BY DATE(at) DESC;"
3376 + case sql_query_internal(DBRef, Query) of
3378 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
3379 + {error, Reason} ->
3380 + % TODO: Duplicate error message ?
3383 + {reply, Reply, State};
3384 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3385 + SName = stats_table(VHost),
3386 + Query = ["SELECT username, sum(count) AS allcount ",
3387 + "FROM ",SName," ",
3388 + "JOIN ",users_table(VHost)," ON owner_id=user_id "
3389 + "WHERE at=\"",Date,"\" "
3390 + "GROUP BY username ",
3391 + "ORDER BY allcount DESC;"
3394 + case sql_query_internal(DBRef, Query) of
3396 + {ok, lists:reverse(
3398 + [ {User, list_to_integer(Count)} || [User, Count] <- Result]))};
3399 + {error, Reason} ->
3403 + {reply, Reply, State};
3404 +handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3405 + {reply, get_user_stats_int(DBRef, User, VHost), State};
3406 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3407 + TName = messages_table(VHost, Date),
3408 + UName = users_table(VHost),
3409 + SName = servers_table(VHost),
3410 + RName = resources_table(VHost),
3411 + Query = ["SELECT users.username,",
3412 + "servers.server,",
3413 + "resources.resource,",
3414 + "messages.direction,"
3416 + "messages.subject,"
3418 + "messages.timestamp "
3419 + "FROM ",TName," AS messages "
3420 + "JOIN ",UName," AS users ON peer_name_id=user_id ",
3421 + "JOIN ",SName," AS servers ON peer_server_id=server_id ",
3422 + "JOIN ",RName," AS resources ON peer_resource_id=resource_id ",
3423 + "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\" ",
3424 + "ORDER BY timestamp ASC;"],
3426 + case sql_query_internal(DBRef, Query) of
3428 + Fun = fun([Peer_name, Peer_server, Peer_resource,
3433 + #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
3434 + direction=list_to_atom(Direction),
3436 + subject=Subject, body=Body,
3437 + timestamp=Timestamp}
3439 + {ok, lists:map(Fun, Result)};
3440 + {error, Reason} ->
3443 + {reply, Reply, State};
3444 +handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3445 + SName = stats_table(VHost),
3446 + Query = ["SELECT at ",
3447 + "FROM ",SName," ",
3449 + "ORDER BY DATE(at) DESC;"
3452 + case sql_query_internal(DBRef, Query) of
3454 + [ Date || [Date] <- Result ];
3455 + {error, Reason} ->
3458 + {reply, Reply, State};
3459 +handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3460 + Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
3461 + "FROM ",settings_table(VHost)," ",
3462 + "JOIN ",users_table(VHost)," ON user_id=owner_id;"],
3464 + case sql_query_internal(DBRef, Query) of
3466 + {ok, lists:map(fun([Owner, DoLogDef, DoLogL, DoNotLogL]) ->
3467 + #user_settings{owner_name=Owner,
3468 + dolog_default=list_to_bool(DoLogDef),
3469 + dolog_list=string_to_list(DoLogL),
3470 + donotlog_list=string_to_list(DoNotLogL)
3476 + {reply, Reply, State};
3477 +handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3478 + Query = ["SELECT dolog_default,dolog_list,donotlog_list FROM ",settings_table(VHost)," ",
3479 + "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\";"],
3481 + case sql_query_internal(DBRef, Query) of
3484 + {data, [[Owner, DoLogDef, DoLogL, DoNotLogL]]} ->
3485 + {ok, #user_settings{owner_name=Owner,
3486 + dolog_default=list_to_bool(DoLogDef),
3487 + dolog_list=string_to_list(DoLogL),
3488 + donotlog_list=string_to_list(DoNotLogL)}};
3492 + {reply, Reply, State};
3493 +handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
3494 + dolog_list=DoLogL,
3495 + donotlog_list=DoNotLogL}},
3496 + _From, #state{dbref=DBRef, vhost=VHost} = State) ->
3497 + User_id = get_user_id(DBRef, VHost, User),
3499 + Query = ["UPDATE ",settings_table(VHost)," ",
3500 + "SET dolog_default=",bool_to_list(DoLogDef),", ",
3501 + "dolog_list='",list_to_string(DoLogL),"', ",
3502 + "donotlog_list='",list_to_string(DoNotLogL),"' ",
3503 + "WHERE owner_id=\"",User_id,"\";"],
3506 + case sql_query_internal(DBRef, Query) of
3508 + IQuery = ["INSERT INTO ",settings_table(VHost)," ",
3509 + "(owner_id, dolog_default, dolog_list, donotlog_list) ",
3511 + "('",User_id,"', ",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
3512 + case sql_query_internal_silent(DBRef, IQuery) of
3514 + ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
3516 + {error, Reason} ->
3517 + case ejabberd_regexp:run(Reason, "#23000") of
3522 + ?ERROR_MSG("Failed setup user ~p@~p: ~p", [User, VHost, Reason]),
3527 + ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
3532 + {reply, Reply, State};
3533 +handle_call({stop}, _From, #state{vhost=VHost}=State) ->
3534 + ets:delete(ets_users_table(VHost)),
3535 + ets:delete(ets_servers_table(VHost)),
3536 + ?MYDEBUG("Stoping mysql backend for ~p", [VHost]),
3537 + {stop, normal, ok, State};
3538 +handle_call(Msg, _From, State) ->
3539 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
3542 +handle_cast({rebuild_stats}, State) ->
3543 + rebuild_all_stats_int(State),
3545 +handle_cast({drop_user, User}, #state{vhost=VHost} = State) ->
3547 + {ok, DBRef} = open_mysql_connection(State),
3548 + {ok, Dates} = get_user_stats_int(DBRef, User, VHost),
3549 + MDResult = lists:map(fun({Date, _}) ->
3550 + delete_all_messages_by_user_at_int(DBRef, User, VHost, Date)
3552 + StDResult = delete_all_stats_by_user_int(DBRef, User, VHost),
3553 + SDResult = delete_user_settings_int(DBRef, User, VHost),
3554 + case lists:all(fun(Result) when Result == ok ->
3556 + (Result) when Result == error ->
3558 + end, lists:append([MDResult, [StDResult], [SDResult]])) of
3560 + ?INFO_MSG("Removed ~s@~s", [User, VHost]);
3562 + ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost])
3564 + close_mysql_connection(DBRef)
3568 +handle_cast(Msg, State) ->
3569 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
3572 +handle_info(clear_ets_tables, State) ->
3573 + ets:delete_all_objects(ets_users_table(State#state.vhost)),
3574 + ets:delete_all_objects(ets_resources_table(State#state.vhost)),
3576 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
3577 + {stop, connection_dropped, State};
3578 +handle_info(Info, State) ->
3579 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
3582 +terminate(_Reason, #state{dbref=DBRef}=_State) ->
3583 + close_mysql_connection(DBRef),
3586 +code_change(_OldVsn, State, _Extra) ->
3589 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3591 +% gen_logdb callbacks
3593 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3594 +log_message(VHost, Msg) ->
3595 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3596 + gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
3597 +rebuild_stats(VHost) ->
3598 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3599 + gen_server:cast(Proc, {rebuild_stats}).
3600 +rebuild_stats_at(VHost, Date) ->
3601 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3602 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
3603 +delete_messages_by_user_at(VHost, Msgs, Date) ->
3604 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3605 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
3606 +delete_all_messages_by_user_at(User, VHost, Date) ->
3607 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3608 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
3609 +delete_messages_at(VHost, Date) ->
3610 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3611 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
3612 +get_vhost_stats(VHost) ->
3613 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3614 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
3615 +get_vhost_stats_at(VHost, Date) ->
3616 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3617 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
3618 +get_user_stats(User, VHost) ->
3619 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3620 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
3621 +get_user_messages_at(User, VHost, Date) ->
3622 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3623 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
3624 +get_dates(VHost) ->
3625 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3626 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
3627 +get_users_settings(VHost) ->
3628 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3629 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
3630 +get_user_settings(User, VHost) ->
3631 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3632 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
3633 +set_user_settings(User, VHost, Set) ->
3634 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3635 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
3636 +drop_user(User, VHost) ->
3637 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3638 + gen_server:cast(Proc, {drop_user, User}).
3640 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3644 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3645 +increment_user_stats(DBRef, User_name, User_id, VHost, PNameID, PServerID, Date) ->
3646 + SName = stats_table(VHost),
3647 + UQuery = ["UPDATE ",SName," ",
3648 + "SET count=count+1 ",
3649 + "WHERE owner_id=\"",User_id,"\" AND peer_name_id=\"",PNameID,"\" AND peer_server_id=\"",PServerID,"\" AND at=\"",Date,"\";"],
3651 + case sql_query_internal(DBRef, UQuery) of
3653 + IQuery = ["INSERT INTO ",SName," ",
3654 + "(owner_id, peer_name_id, peer_server_id, at, count) ",
3656 + "('",User_id,"', '",PNameID,"', '",PServerID,"', '",Date,"', '1');"],
3657 + case sql_query_internal(DBRef, IQuery) of
3659 + ?MYDEBUG("New stats for ~s@~s at ~s", [User_name, VHost, Date]),
3665 + ?MYDEBUG("Updated stats for ~s@~s at ~s", [User_name, VHost, Date]),
3671 +get_dates_int(DBRef, VHost) ->
3672 + case sql_query_internal(DBRef, ["SHOW TABLES"]) of
3674 + lists:foldl(fun([Table], Dates) ->
3675 + Reg = lists:sublist(prefix(),2,length(prefix())) ++ ".*" ++ escape_vhost(VHost),
3676 + case re:run(Table, Reg) of
3677 + {match, [{1, _}]} ->
3678 + ?MYDEBUG("matched ~p against ~p", [Table, Reg]),
3679 + case re:run(Table,"[0-9]+-[0-9]+-[0-9]+") of
3680 + {match, [{S, E}]} ->
3681 + lists:append(Dates, [lists:sublist(Table,S,E)]);
3693 +rebuild_all_stats_int(#state{vhost=VHost}=State) ->
3695 + {ok, DBRef} = open_mysql_connection(State),
3696 + ok = delete_nonexistent_stats(DBRef, VHost),
3697 + case lists:filter(fun(Date) ->
3698 + case catch rebuild_stats_at_int(DBRef, VHost, Date) of
3701 + {'EXIT', _} -> true
3703 + end, get_dates_int(DBRef, VHost)) of
3706 + ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]),
3709 + close_mysql_connection(DBRef)
3713 +rebuild_stats_at_int(DBRef, VHost, Date) ->
3714 + TempTable = temp_table(VHost),
3716 + Table = messages_table(VHost, Date),
3717 + STable = stats_table(VHost),
3719 + DQuery = [ "DELETE FROM ",STable," ",
3720 + "WHERE at='",Date,"';"],
3722 + ok = create_temp_table(DBRef, TempTable),
3723 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," WRITE, ",TempTable," WRITE;"]),
3724 + SQuery = ["INSERT INTO ",TempTable," ",
3725 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
3726 + "SELECT owner_id,peer_name_id,peer_server_id,\"",Date,"\",count(*) ",
3727 + "FROM ",Table," GROUP BY owner_id,peer_name_id,peer_server_id;"],
3728 + case sql_query_internal(DBRef, SQuery) of
3730 + Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]),
3732 + {data, [["0"]]} ->
3733 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]),
3734 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE;"]),
3735 + {updated, _} = sql_query_internal(DBRef, DQuery),
3738 + ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]),
3742 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE, ",TempTable," WRITE;"]),
3743 + {updated, _} = sql_query_internal(DBRef, DQuery),
3744 + SQuery1 = ["INSERT INTO ",STable," ",
3745 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
3746 + "SELECT owner_id,peer_name_id,peer_server_id,at,count ",
3747 + "FROM ",TempTable,";"],
3748 + case sql_query_internal(DBRef, SQuery1) of
3749 + {updated, _} -> ok;
3750 + {error, _} -> error
3752 + {error, _} -> error
3756 + case catch apply(Fun, []) of
3758 + ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
3762 + {'EXIT', Reason} ->
3763 + ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]),
3766 + sql_query_internal(DBRef, ["UNLOCK TABLES;"]),
3767 + sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]),
3771 +delete_nonexistent_stats(DBRef, VHost) ->
3772 + Dates = get_dates_int(DBRef, VHost),
3773 + STable = stats_table(VHost),
3775 + Temp = lists:flatmap(fun(Date) ->
3776 + ["\"",Date,"\"",","]
3783 + % replace last "," with ");"
3784 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
3785 + Query = ["DELETE FROM ",STable," ",
3786 + "WHERE at NOT IN (", Temp1],
3787 + case sql_query_internal(DBRef, Query) of
3795 +get_user_stats_int(DBRef, User, VHost) ->
3796 + SName = stats_table(VHost),
3797 + Query = ["SELECT at, sum(count) as allcount ",
3798 + "FROM ",SName," ",
3799 + "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\" ",
3801 + "ORDER BY DATE(at) DESC;"
3803 + case sql_query_internal(DBRef, Query) of
3805 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result]};
3806 + {error, Result} ->
3810 +delete_all_messages_by_user_at_int(DBRef, User, VHost, Date) ->
3811 + DQuery = ["DELETE FROM ",messages_table(VHost, Date)," ",
3812 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
3813 + case sql_query_internal(DBRef, DQuery) of
3815 + ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]),
3821 +delete_all_stats_by_user_int(DBRef, User, VHost) ->
3822 + SQuery = ["DELETE FROM ",stats_table(VHost)," ",
3823 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
3824 + case sql_query_internal(DBRef, SQuery) of
3826 + ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]),
3828 + {error, _} -> error
3831 +delete_stats_by_user_at_int(DBRef, User, VHost, Date) ->
3832 + SQuery = ["DELETE FROM ",stats_table(VHost)," ",
3833 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\") ",
3834 + "AND at=\"",Date,"\";"],
3835 + case sql_query_internal(DBRef, SQuery) of
3837 + ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]),
3839 + {error, _} -> error
3842 +delete_user_settings_int(DBRef, User, VHost) ->
3843 + Query = ["DELETE FROM ",settings_table(VHost)," ",
3844 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
3845 + case sql_query_internal(DBRef, Query) of
3847 + ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]),
3849 + {error, Reason} ->
3850 + ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]),
3854 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3858 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3859 +create_temp_table(DBRef, Name) ->
3860 + Query = ["CREATE TABLE ",Name," (",
3861 + "owner_id MEDIUMINT UNSIGNED, ",
3862 + "peer_name_id MEDIUMINT UNSIGNED, ",
3863 + "peer_server_id MEDIUMINT UNSIGNED, ",
3864 + "at VARCHAR(11), ",
3866 + ") ENGINE=MyISAM CHARACTER SET utf8;"
3868 + case sql_query_internal(DBRef, Query) of
3869 + {updated, _} -> ok;
3870 + {error, _Reason} -> error
3873 +create_stats_table(#state{dbref=DBRef, vhost=VHost}=State) ->
3874 + SName = stats_table(VHost),
3875 + Query = ["CREATE TABLE ",SName," (",
3876 + "owner_id MEDIUMINT UNSIGNED, ",
3877 + "peer_name_id MEDIUMINT UNSIGNED, ",
3878 + "peer_server_id MEDIUMINT UNSIGNED, ",
3879 + "at varchar(20), ",
3880 + "count int(11), ",
3881 + "INDEX(owner_id, peer_name_id, peer_server_id), ",
3883 + ") ENGINE=InnoDB CHARACTER SET utf8;"
3885 + case sql_query_internal_silent(DBRef, Query) of
3887 + ?INFO_MSG("Created stats table for ~p", [VHost]),
3888 + rebuild_all_stats_int(State),
3890 + {error, Reason} ->
3891 + case ejabberd_regexp:run(Reason, "#42S01") of
3893 + ?MYDEBUG("Stats table for ~p already exists", [VHost]),
3894 + CheckQuery = ["SHOW COLUMNS FROM ",SName," LIKE 'peer_%_id';"],
3895 + case sql_query_internal(DBRef, CheckQuery) of
3896 + {data, Elems} when length(Elems) == 2 ->
3897 + ?MYDEBUG("Stats table structure is ok", []),
3900 + ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []),
3901 + case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of
3903 + ?INFO_MSG("Successfully dropped ~p", [SName]);
3905 + ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName])
3910 + ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
3915 +create_settings_table(#state{dbref=DBRef, vhost=VHost}) ->
3916 + SName = settings_table(VHost),
3917 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
3918 + "owner_id MEDIUMINT UNSIGNED PRIMARY KEY, ",
3919 + "dolog_default TINYINT(1) NOT NULL DEFAULT 1, ",
3920 + "dolog_list TEXT, ",
3921 + "donotlog_list TEXT ",
3922 + ") ENGINE=InnoDB CHARACTER SET utf8;"
3924 + case sql_query_internal(DBRef, Query) of
3926 + ?MYDEBUG("Created settings table for ~p", [VHost]),
3932 +create_users_table(#state{dbref=DBRef, vhost=VHost}) ->
3933 + SName = users_table(VHost),
3934 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
3935 + "username TEXT NOT NULL, ",
3936 + "user_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
3937 + "UNIQUE INDEX(username(",?INDEX_SIZE,")) ",
3938 + ") ENGINE=InnoDB CHARACTER SET utf8;"
3940 + case sql_query_internal(DBRef, Query) of
3942 + ?MYDEBUG("Created users table for ~p", [VHost]),
3943 + ets:new(ets_users_table(VHost), [named_table, set, public]),
3944 + %update_users_from_db(DBRef, VHost),
3950 +create_servers_table(#state{dbref=DBRef, vhost=VHost}) ->
3951 + SName = servers_table(VHost),
3952 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
3953 + "server TEXT NOT NULL, ",
3954 + "server_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
3955 + "UNIQUE INDEX(server(",?INDEX_SIZE,")) ",
3956 + ") ENGINE=InnoDB CHARACTER SET utf8;"
3958 + case sql_query_internal(DBRef, Query) of
3960 + ?MYDEBUG("Created servers table for ~p", [VHost]),
3961 + ets:new(ets_servers_table(VHost), [named_table, set, public]),
3962 + update_servers_from_db(DBRef, VHost),
3968 +create_resources_table(#state{dbref=DBRef, vhost=VHost}) ->
3969 + RName = resources_table(VHost),
3970 + Query = ["CREATE TABLE IF NOT EXISTS ",RName," (",
3971 + "resource TEXT NOT NULL, ",
3972 + "resource_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
3973 + "UNIQUE INDEX(resource(",?INDEX_SIZE,")) ",
3974 + ") ENGINE=InnoDB CHARACTER SET utf8;"
3976 + case sql_query_internal(DBRef, Query) of
3978 + ?MYDEBUG("Created resources table for ~p", [VHost]),
3979 + ets:new(ets_resources_table(VHost), [named_table, set, public]),
3985 +create_msg_table(DBRef, VHost, Date) ->
3986 + TName = messages_table(VHost, Date),
3987 + Query = ["CREATE TABLE ",TName," (",
3988 + "owner_id MEDIUMINT UNSIGNED, ",
3989 + "peer_name_id MEDIUMINT UNSIGNED, ",
3990 + "peer_server_id MEDIUMINT UNSIGNED, ",
3991 + "peer_resource_id MEDIUMINT(8) UNSIGNED, ",
3992 + "direction ENUM('to', 'from'), ",
3993 + "type ENUM('chat','error','groupchat','headline','normal') NOT NULL, ",
3996 + "timestamp DOUBLE, ",
3997 + "INDEX search_i (owner_id, peer_name_id, peer_server_id, peer_resource_id), ",
3998 + "FULLTEXT (body) "
3999 + ") ENGINE=MyISAM CHARACTER SET utf8;"
4001 + case sql_query_internal(DBRef, Query) of
4002 + {updated, _MySQLRes} ->
4003 + ?MYDEBUG("Created msg table for ~p at ~p", [VHost, Date]),
4009 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4011 +% internal ets cache (users, servers, resources)
4013 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4014 +update_servers_from_db(DBRef, VHost) ->
4015 + ?INFO_MSG("Reading servers from db for ~p", [VHost]),
4016 + SQuery = ["SELECT server, server_id FROM ",servers_table(VHost),";"],
4017 + {data, Result} = sql_query_internal(DBRef, SQuery),
4018 + true = ets:delete_all_objects(ets_servers_table(VHost)),
4019 + true = ets:insert(ets_servers_table(VHost), [ {Server, Server_id} || [Server, Server_id] <- Result]).
4021 +%update_users_from_db(DBRef, VHost) ->
4022 +% ?INFO_MSG("Reading users from db for ~p", [VHost]),
4023 +% SQuery = ["SELECT username, user_id FROM ",users_table(VHost),";"],
4024 +% {data, Result} = sql_query_internal(DBRef, SQuery),
4025 +% true = ets:delete_all_objects(ets_users_table(VHost)),
4026 +% true = ets:insert(ets_users_table(VHost), [ {Username, User_id} || [Username, User_id] <- Result]).
4028 +%get_user_name(DBRef, VHost, User_id) ->
4029 +% case ets:match(ets_users_table(VHost), {'$1', User_id}) of
4030 +% [[User]] -> User;
4031 +% % this can be in clustered environment
4033 +% %update_users_from_db(DBRef, VHost),
4034 +% SQuery = ["SELECT username FROM ",users_table(VHost)," ",
4035 +% "WHERE user_id=\"",User_id,"\";"],
4036 +% {data, [[Name]]} = sql_query_internal(DBRef, SQuery),
4037 +% % cache {user, id} pair
4038 +% ets:insert(ets_users_table(VHost), {Name, User_id}),
4042 +%get_server_name(DBRef, VHost, Server_id) ->
4043 +% case ets:match(ets_servers_table(VHost), {'$1', Server_id}) of
4044 +% [[Server]] -> Server;
4045 + % this can be in clustered environment
4047 +% update_servers_from_db(DBRef, VHost),
4048 +% [[Server1]] = ets:match(ets_servers_table(VHost), {'$1', Server_id}),
4052 +get_user_id_from_db(DBRef, VHost, User) ->
4053 + SQuery = ["SELECT user_id FROM ",users_table(VHost)," ",
4054 + "WHERE username=\"",User,"\";"],
4055 + case sql_query_internal(DBRef, SQuery) of
4056 + % no such user in db
4059 + {data, [[DBId]]} ->
4060 + % cache {user, id} pair
4061 + ets:insert(ets_users_table(VHost), {User, DBId}),
4064 +get_user_id(DBRef, VHost, User) ->
4066 + case ets:match(ets_users_table(VHost), {User, '$1'}) of
4069 + case get_user_id_from_db(DBRef, VHost, User) of
4070 + % no such user in db
4072 + IQuery = ["INSERT INTO ",users_table(VHost)," ",
4073 + "SET username=\"",User,"\";"],
4074 + case sql_query_internal_silent(DBRef, IQuery) of
4076 + {ok, NewId} = get_user_id_from_db(DBRef, VHost, User),
4078 + {error, Reason} ->
4079 + % this can be in clustered environment
4080 + match = ejabberd_regexp:run(Reason, "#23000"),
4081 + ?ERROR_MSG("Duplicate key name for ~p", [User]),
4082 + {ok, ClID} = get_user_id_from_db(DBRef, VHost, User),
4088 + [[EtsId]] -> EtsId
4091 +get_server_id(DBRef, VHost, Server) ->
4092 + case ets:match(ets_servers_table(VHost), {Server, '$1'}) of
4094 + IQuery = ["INSERT INTO ",servers_table(VHost)," ",
4095 + "SET server=\"",Server,"\";"],
4096 + case sql_query_internal_silent(DBRef, IQuery) of
4098 + SQuery = ["SELECT server_id FROM ",servers_table(VHost)," ",
4099 + "WHERE server=\"",Server,"\";"],
4100 + {data, [[Id]]} = sql_query_internal(DBRef, SQuery),
4101 + ets:insert(ets_servers_table(VHost), {Server, Id}),
4103 + {error, Reason} ->
4104 + % this can be in clustered environment
4105 + match = ejabberd_regexp:run(Reason, "#23000"),
4106 + ?ERROR_MSG("Duplicate key name for ~p", [Server]),
4107 + update_servers_from_db(DBRef, VHost),
4108 + [[Id1]] = ets:match(ets_servers_table(VHost), {Server, '$1'}),
4114 +get_resource_id_from_db(DBRef, VHost, Resource) ->
4115 + SQuery = ["SELECT resource_id FROM ",resources_table(VHost)," ",
4116 + "WHERE resource=\"",ejabberd_odbc:escape(Resource),"\";"],
4117 + case sql_query_internal(DBRef, SQuery) of
4118 + % no such resource in db
4121 + {data, [[DBId]]} ->
4122 + % cache {resource, id} pair
4123 + ets:insert(ets_resources_table(VHost), {Resource, DBId}),
4126 +get_resource_id(DBRef, VHost, Resource) ->
4128 + case ets:match(ets_resources_table(VHost), {Resource, '$1'}) of
4131 + case get_resource_id_from_db(DBRef, VHost, Resource) of
4132 + % no such resource in db
4134 + IQuery = ["INSERT INTO ",resources_table(VHost)," ",
4135 + "SET resource=\"",ejabberd_odbc:escape(Resource),"\";"],
4136 + case sql_query_internal_silent(DBRef, IQuery) of
4138 + {ok, NewId} = get_resource_id_from_db(DBRef, VHost, Resource),
4140 + {error, Reason} ->
4141 + % this can be in clustered environment
4142 + match = ejabberd_regexp:run(Reason, "#23000"),
4143 + ?ERROR_MSG("Duplicate key name for ~p", [Resource]),
4144 + {ok, ClID} = get_resource_id_from_db(DBRef, VHost, Resource),
4150 + [[EtsId]] -> EtsId
4153 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4157 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4158 +sql_query_internal(DBRef, Query) ->
4159 + case sql_query_internal_silent(DBRef, Query) of
4160 + {error, Reason} ->
4161 + ?ERROR_MSG("~p while ~p", [Reason, lists:append(Query)]),
4166 +sql_query_internal_silent(DBRef, Query) ->
4167 + ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
4168 + get_result(mysql_conn:fetch(DBRef, Query, self(), ?MYSQL_TIMEOUT)).
4170 +get_result({updated, MySQLRes}) ->
4171 + {updated, mysql:get_result_affected_rows(MySQLRes)};
4172 +get_result({data, MySQLRes}) ->
4173 + {data, mysql:get_result_rows(MySQLRes)};
4174 +get_result({error, "query timed out"}) ->
4175 + {error, "query timed out"};
4176 +get_result({error, MySQLRes}) ->
4177 + Reason = mysql:get_result_reason(MySQLRes),
4179 diff --git src/mod_logdb_mysql5.erl src/mod_logdb_mysql5.erl
4180 new file mode 100644
4181 index 0000000..59efc77
4183 +++ src/mod_logdb_mysql5.erl
4185 +%%%----------------------------------------------------------------------
4186 +%%% File : mod_logdb_mysql5.erl
4187 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
4188 +%%% Purpose : MySQL 5 backend for mod_logdb
4189 +%%% Version : trunk
4190 +%%% Id : $Id: mod_logdb_mysql5.erl 1360 2009-07-30 06:00:14Z malik $
4191 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
4192 +%%%----------------------------------------------------------------------
4194 +-module(mod_logdb_mysql5).
4195 +-author('o.palij@gmail.com').
4197 +-include("mod_logdb.hrl").
4198 +-include("ejabberd.hrl").
4199 +-include("jlib.hrl").
4201 +-behaviour(gen_logdb).
4202 +-behaviour(gen_server).
4205 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
4207 +-export([start/2, stop/1]).
4209 +-export([log_message/2,
4211 + rebuild_stats_at/2,
4212 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
4213 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
4215 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
4218 +% gen_server call timeout
4219 +-define(CALL_TIMEOUT, 30000).
4220 +-define(MYSQL_TIMEOUT, 60000).
4221 +-define(INDEX_SIZE, integer_to_list(170)).
4222 +-define(PROCNAME, mod_logdb_mysql5).
4224 +-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
4225 + list_to_string/1, string_to_list/1,
4226 + convert_timestamp_brief/1]).
4228 +-record(state, {dbref, vhost, server, port, db, user, password}).
4230 +% replace "." with "_"
4231 +escape_vhost(VHost) -> lists:map(fun(46) -> 95;
4238 + "_" ++ escape_vhost(VHost) ++ "`".
4240 +messages_table(VHost, Date) ->
4241 + prefix() ++ "messages_" ++ Date ++ suffix(VHost).
4243 +% TODO: this needs to be redone to unify view name in stored procedure and in delete_messages_at/2
4244 +view_table(VHost, Date) ->
4245 + Table = messages_table(VHost, Date),
4246 + TablewoQ = lists:sublist(Table, 2, length(Table) - 2),
4247 + lists:append(["`v_", TablewoQ, "`"]).
4249 +stats_table(VHost) ->
4250 + prefix() ++ "stats" ++ suffix(VHost).
4252 +temp_table(VHost) ->
4253 + prefix() ++ "temp" ++ suffix(VHost).
4255 +settings_table(VHost) ->
4256 + prefix() ++ "settings" ++ suffix(VHost).
4258 +users_table(VHost) ->
4259 + prefix() ++ "users" ++ suffix(VHost).
4260 +servers_table(VHost) ->
4261 + prefix() ++ "servers" ++ suffix(VHost).
4262 +resources_table(VHost) ->
4263 + prefix() ++ "resources" ++ suffix(VHost).
4265 +logmessage_name(VHost) ->
4266 + prefix() ++ "logmessage" ++ suffix(VHost).
4268 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4270 +% gen_mod callbacks
4272 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4273 +start(VHost, Opts) ->
4274 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4275 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
4278 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4279 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
4281 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4283 +% gen_server callbacks
4285 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4286 +init([VHost, Opts]) ->
4289 + Server = gen_mod:get_opt(server, Opts, "localhost"),
4290 + Port = gen_mod:get_opt(port, Opts, 3306),
4291 + DB = gen_mod:get_opt(db, Opts, "logdb"),
4292 + User = gen_mod:get_opt(user, Opts, "root"),
4293 + Password = gen_mod:get_opt(password, Opts, ""),
4295 + St = #state{vhost=VHost,
4296 + server=Server, port=Port, db=DB,
4297 + user=User, password=Password},
4299 + case open_mysql_connection(St) of
4301 + State = St#state{dbref=DBRef},
4302 + ok = create_internals(State),
4303 + ok = create_stats_table(State),
4304 + ok = create_settings_table(State),
4305 + ok = create_users_table(State),
4306 + ok = create_servers_table(State),
4307 + ok = create_resources_table(State),
4308 + erlang:monitor(process, DBRef),
4310 + {error, Reason} ->
4311 + ?ERROR_MSG("MySQL connection failed: ~p~n", [Reason]),
4312 + {stop, db_connection_failed}
4315 +open_mysql_connection(#state{server=Server, port=Port, db=DB,
4316 + user=DBUser, password=Password} = _State) ->
4317 + LogFun = fun(debug, _Format, _Argument) ->
4318 + %?MYDEBUG(Format, Argument);
4320 + (error, Format, Argument) ->
4321 + ?ERROR_MSG(Format, Argument);
4322 + (Level, Format, Argument) ->
4323 + ?MYDEBUG("MySQL (~p)~n", [Level]),
4324 + ?MYDEBUG(Format, Argument)
4326 + ?INFO_MSG("Opening mysql connection ~s@~s:~p/~s", [DBUser, Server, Port, DB]),
4327 + mysql_conn:start(Server, Port, DBUser, Password, DB, [65536, 131072], LogFun).
4329 +close_mysql_connection(DBRef) ->
4330 + ?MYDEBUG("Closing ~p mysql connection", [DBRef]),
4331 + mysql_conn:stop(DBRef).
4333 +handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4334 + Reply = rebuild_stats_at_int(DBRef, VHost, Date),
4335 + {reply, Reply, State};
4336 +handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
4337 + {reply, error, State};
4338 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4339 + Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
4340 + ["\"",Timestamp,"\"",","]
4343 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
4345 + Query = ["DELETE FROM ",messages_table(VHost, Date)," ",
4346 + "WHERE timestamp IN (", Temp1],
4349 + case sql_query_internal(DBRef, Query) of
4351 + ?MYDEBUG("Aff=~p", [Aff]),
4352 + rebuild_stats_at_int(DBRef, VHost, Date);
4356 + {reply, Reply, State};
4357 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4358 + ok = delete_all_messages_by_user_at_int(DBRef, User, VHost, Date),
4359 + ok = delete_stats_by_user_at_int(DBRef, User, VHost, Date),
4360 + {reply, ok, State};
4361 +handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4363 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Date),";"]),
4364 + TQuery = ["DELETE FROM ",stats_table(VHost)," "
4365 + "WHERE at=\"",Date,"\";"],
4366 + {updated, _} = sql_query_internal(DBRef, TQuery),
4367 + VQuery = ["DROP VIEW IF EXISTS ",view_table(VHost,Date),";"],
4368 + {updated, _} = sql_query_internal(DBRef, VQuery),
4372 + case catch apply(Fun, []) of
4378 + {reply, Reply, State};
4379 +handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4380 + SName = stats_table(VHost),
4381 + Query = ["SELECT at, sum(count) ",
4382 + "FROM ",SName," ",
4384 + "ORDER BY DATE(at) DESC;"
4387 + case sql_query_internal(DBRef, Query) of
4389 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
4390 + {error, Reason} ->
4391 + % TODO: Duplicate error message ?
4394 + {reply, Reply, State};
4395 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4396 + SName = stats_table(VHost),
4397 + Query = ["SELECT username, sum(count) as allcount ",
4398 + "FROM ",SName," ",
4399 + "JOIN ",users_table(VHost)," ON owner_id=user_id "
4400 + "WHERE at=\"",Date,"\" ",
4401 + "GROUP BY username ",
4402 + "ORDER BY allcount DESC;"
4405 + case sql_query_internal(DBRef, Query) of
4407 + {ok, [ {User, list_to_integer(Count)} || [User, Count] <- Result ]};
4408 + {error, Reason} ->
4411 + {reply, Reply, State};
4412 +handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4413 + {reply, get_user_stats_int(DBRef, User, VHost), State};
4414 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4415 + Query = ["SELECT peer_name,",
4423 + "FROM ",view_table(VHost, Date)," "
4424 + "WHERE owner_name=\"",User,"\";"],
4426 + case sql_query_internal(DBRef, Query) of
4428 + Fun = fun([Peer_name, Peer_server, Peer_resource,
4433 + #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
4434 + direction=list_to_atom(Direction),
4436 + subject=Subject, body=Body,
4437 + timestamp=Timestamp}
4439 + {ok, lists:map(Fun, Result)};
4440 + {error, Reason} ->
4443 + {reply, Reply, State};
4444 +handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4445 + SName = stats_table(VHost),
4446 + Query = ["SELECT at ",
4447 + "FROM ",SName," ",
4449 + "ORDER BY DATE(at) DESC;"
4452 + case sql_query_internal(DBRef, Query) of
4454 + [ Date || [Date] <- Result ];
4455 + {error, Reason} ->
4458 + {reply, Reply, State};
4459 +handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4460 + Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
4461 + "FROM ",settings_table(VHost)," ",
4462 + "JOIN ",users_table(VHost)," ON user_id=owner_id;"],
4464 + case sql_query_internal(DBRef, Query) of
4466 + {ok, lists:map(fun([Owner, DoLogDef, DoLogL, DoNotLogL]) ->
4467 + #user_settings{owner_name=Owner,
4468 + dolog_default=list_to_bool(DoLogDef),
4469 + dolog_list=string_to_list(DoLogL),
4470 + donotlog_list=string_to_list(DoNotLogL)
4476 + {reply, Reply, State};
4477 +handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4478 + Query = ["SELECT dolog_default,dolog_list,donotlog_list FROM ",settings_table(VHost)," ",
4479 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4481 + case sql_query_internal(DBRef, Query) of
4484 + {data, [[Owner, DoLogDef, DoLogL, DoNotLogL]]} ->
4485 + {ok, #user_settings{owner_name=Owner,
4486 + dolog_default=list_to_bool(DoLogDef),
4487 + dolog_list=string_to_list(DoLogL),
4488 + donotlog_list=string_to_list(DoNotLogL)}};
4492 + {reply, Reply, State};
4493 +handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
4494 + dolog_list=DoLogL,
4495 + donotlog_list=DoNotLogL}},
4496 + _From, #state{dbref=DBRef, vhost=VHost} = State) ->
4497 + User_id = get_user_id(DBRef, VHost, User),
4498 + Query = ["UPDATE ",settings_table(VHost)," ",
4499 + "SET dolog_default=",bool_to_list(DoLogDef),", ",
4500 + "dolog_list='",list_to_string(DoLogL),"', ",
4501 + "donotlog_list='",list_to_string(DoNotLogL),"' ",
4502 + "WHERE owner_id=",User_id,";"],
4505 + case sql_query_internal(DBRef, Query) of
4507 + IQuery = ["INSERT INTO ",settings_table(VHost)," ",
4508 + "(owner_id, dolog_default, dolog_list, donotlog_list) ",
4510 + "(",User_id,",",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
4511 + case sql_query_internal_silent(DBRef, IQuery) of
4513 + ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
4515 + {error, Reason} ->
4516 + case ejabberd_regexp:run(Reason, "#23000") of
4521 + ?ERROR_MSG("Failed setup user ~p@~p: ~p", [User, VHost, Reason]),
4526 + ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
4531 + {reply, Reply, State};
4532 +handle_call({stop}, _From, #state{vhost=VHost}=State) ->
4533 + ?MYDEBUG("Stoping mysql5 backend for ~p", [VHost]),
4534 + {stop, normal, ok, State};
4535 +handle_call(Msg, _From, State) ->
4536 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
4539 +handle_cast({log_message, Msg}, #state{dbref=DBRef, vhost=VHost}=State) ->
4541 + Date = convert_timestamp_brief(Msg#msg.timestamp),
4542 + TableName = messages_table(VHost, Date),
4544 + Query = [ "CALL ",logmessage_name(VHost)," "
4545 + "('", TableName, "',",
4547 + "'", Msg#msg.owner_name, "',",
4548 + "'", Msg#msg.peer_name, "',",
4549 + "'", Msg#msg.peer_server, "',",
4550 + "'", ejabberd_odbc:escape(Msg#msg.peer_resource), "',",
4551 + "'", atom_to_list(Msg#msg.direction), "',",
4552 + "'", Msg#msg.type, "',",
4553 + "'", ejabberd_odbc:escape(Msg#msg.subject), "',",
4554 + "'", ejabberd_odbc:escape(Msg#msg.body), "',",
4555 + "'", Msg#msg.timestamp, "');"],
4557 + case sql_query_internal(DBRef, Query) of
4559 + ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
4560 + Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
4562 + {error, _Reason} ->
4568 +handle_cast({rebuild_stats}, State) ->
4569 + rebuild_all_stats_int(State),
4571 +handle_cast({drop_user, User}, #state{vhost=VHost} = State) ->
4573 + {ok, DBRef} = open_mysql_connection(State),
4574 + {ok, Dates} = get_user_stats_int(DBRef, User, VHost),
4575 + MDResult = lists:map(fun({Date, _}) ->
4576 + delete_all_messages_by_user_at_int(DBRef, User, VHost, Date)
4578 + StDResult = delete_all_stats_by_user_int(DBRef, User, VHost),
4579 + SDResult = delete_user_settings_int(DBRef, User, VHost),
4580 + case lists:all(fun(Result) when Result == ok ->
4582 + (Result) when Result == error ->
4584 + end, lists:append([MDResult, [StDResult], [SDResult]])) of
4586 + ?INFO_MSG("Removed ~s@~s", [User, VHost]);
4588 + ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost])
4590 + close_mysql_connection(DBRef)
4594 +handle_cast(Msg, State) ->
4595 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
4598 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
4599 + {stop, connection_dropped, State};
4600 +handle_info(Info, State) ->
4601 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
4604 +terminate(_Reason, #state{dbref=DBRef}=_State) ->
4605 + close_mysql_connection(DBRef),
4608 +code_change(_OldVsn, State, _Extra) ->
4611 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4613 +% gen_logdb callbacks
4615 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4616 +log_message(VHost, Msg) ->
4617 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4618 + gen_server:cast(Proc, {log_message, Msg}).
4619 +rebuild_stats(VHost) ->
4620 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4621 + gen_server:cast(Proc, {rebuild_stats}).
4622 +rebuild_stats_at(VHost, Date) ->
4623 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4624 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
4625 +delete_messages_by_user_at(VHost, Msgs, Date) ->
4626 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4627 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
4628 +delete_all_messages_by_user_at(User, VHost, Date) ->
4629 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4630 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
4631 +delete_messages_at(VHost, Date) ->
4632 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4633 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
4634 +get_vhost_stats(VHost) ->
4635 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4636 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
4637 +get_vhost_stats_at(VHost, Date) ->
4638 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4639 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
4640 +get_user_stats(User, VHost) ->
4641 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4642 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
4643 +get_user_messages_at(User, VHost, Date) ->
4644 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4645 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
4646 +get_dates(VHost) ->
4647 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4648 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
4649 +get_users_settings(VHost) ->
4650 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4651 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
4652 +get_user_settings(User, VHost) ->
4653 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4654 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
4655 +set_user_settings(User, VHost, Set) ->
4656 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4657 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
4658 +drop_user(User, VHost) ->
4659 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4660 + gen_server:cast(Proc, {drop_user, User}).
4662 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4666 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4667 +get_dates_int(DBRef, VHost) ->
4668 + case sql_query_internal(DBRef, ["SHOW TABLES"]) of
4670 + lists:foldl(fun([Table], Dates) ->
4671 + Reg = lists:sublist(prefix(),2,length(prefix())) ++ ".*" ++ escape_vhost(VHost),
4672 + case re:run(Table, Reg) of
4673 + {match, [{1, _}]} ->
4674 + case re:run(Table,"[0-9]+-[0-9]+-[0-9]+") of
4675 + {match, [{S, E}]} ->
4676 + lists:append(Dates, [lists:sublist(Table,S,E)]);
4688 +rebuild_all_stats_int(#state{vhost=VHost}=State) ->
4690 + {ok, DBRef} = open_mysql_connection(State),
4691 + ok = delete_nonexistent_stats(DBRef, VHost),
4692 + case lists:filter(fun(Date) ->
4693 + case catch rebuild_stats_at_int(DBRef, VHost, Date) of
4696 + {'EXIT', _} -> true
4698 + end, get_dates_int(DBRef, VHost)) of
4701 + ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]),
4704 + close_mysql_connection(DBRef)
4708 +rebuild_stats_at_int(DBRef, VHost, Date) ->
4709 + TempTable = temp_table(VHost),
4711 + Table = messages_table(VHost, Date),
4712 + STable = stats_table(VHost),
4714 + DQuery = [ "DELETE FROM ",STable," ",
4715 + "WHERE at='",Date,"';"],
4717 + ok = create_temp_table(DBRef, TempTable),
4718 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," WRITE, ",TempTable," WRITE;"]),
4719 + SQuery = ["INSERT INTO ",TempTable," ",
4720 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
4721 + "SELECT owner_id,peer_name_id,peer_server_id,\"",Date,"\",count(*) ",
4722 + "FROM ",Table," WHERE ext is NULL GROUP BY owner_id,peer_name_id,peer_server_id;"],
4723 + case sql_query_internal(DBRef, SQuery) of
4725 + Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]),
4727 + {data, [["0"]]} ->
4728 + {updated, _} = sql_query_internal(DBRef, ["DROP VIEW IF EXISTS ",view_table(VHost,Date),";"]),
4729 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]),
4730 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE;"]),
4731 + {updated, _} = sql_query_internal(DBRef, DQuery),
4734 + ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]),
4738 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE, ",TempTable," WRITE;"]),
4739 + {updated, _} = sql_query_internal(DBRef, DQuery),
4740 + SQuery1 = ["INSERT INTO ",STable," ",
4741 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
4742 + "SELECT owner_id,peer_name_id,peer_server_id,at,count ",
4743 + "FROM ",TempTable,";"],
4744 + case sql_query_internal(DBRef, SQuery1) of
4745 + {updated, _} -> ok;
4746 + {error, _} -> error
4748 + {error, _} -> error
4752 + case catch apply(Fun, []) of
4754 + ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
4758 + {'EXIT', Reason} ->
4759 + ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]),
4762 + sql_query_internal(DBRef, ["UNLOCK TABLES;"]),
4763 + sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]),
4766 +delete_nonexistent_stats(DBRef, VHost) ->
4767 + Dates = get_dates_int(DBRef, VHost),
4768 + STable = stats_table(VHost),
4770 + Temp = lists:flatmap(fun(Date) ->
4771 + ["\"",Date,"\"",","]
4777 + % replace last "," with ");"
4778 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
4779 + Query = ["DELETE FROM ",STable," ",
4780 + "WHERE at NOT IN (", Temp1],
4781 + case sql_query_internal(DBRef, Query) of
4789 +get_user_stats_int(DBRef, User, VHost) ->
4790 + SName = stats_table(VHost),
4791 + UName = users_table(VHost),
4792 + Query = ["SELECT stats.at, sum(stats.count) ",
4793 + "FROM ",UName," AS users ",
4794 + "JOIN ",SName," AS stats ON owner_id=user_id "
4795 + "WHERE users.username=\"",User,"\" ",
4796 + "GROUP BY stats.at "
4797 + "ORDER BY DATE(stats.at) DESC;"
4799 + case sql_query_internal(DBRef, Query) of
4801 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
4802 + {error, Result} ->
4806 +delete_all_messages_by_user_at_int(DBRef, User, VHost, Date) ->
4807 + DQuery = ["DELETE FROM ",messages_table(VHost, Date)," ",
4808 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4809 + case sql_query_internal(DBRef, DQuery) of
4811 + ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]),
4817 +delete_all_stats_by_user_int(DBRef, User, VHost) ->
4818 + SQuery = ["DELETE FROM ",stats_table(VHost)," ",
4819 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4820 + case sql_query_internal(DBRef, SQuery) of
4822 + ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]),
4824 + {error, _} -> error
4827 +delete_stats_by_user_at_int(DBRef, User, VHost, Date) ->
4828 + SQuery = ["DELETE FROM ",stats_table(VHost)," ",
4829 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\") ",
4830 + "AND at=\"",Date,"\";"],
4831 + case sql_query_internal(DBRef, SQuery) of
4833 + ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]),
4835 + {error, _} -> error
4838 +delete_user_settings_int(DBRef, User, VHost) ->
4839 + Query = ["DELETE FROM ",settings_table(VHost)," ",
4840 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4841 + case sql_query_internal(DBRef, Query) of
4843 + ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]),
4845 + {error, Reason} ->
4846 + ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]),
4850 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4854 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4855 +create_temp_table(DBRef, Name) ->
4856 + Query = ["CREATE TABLE ",Name," (",
4857 + "owner_id MEDIUMINT UNSIGNED, ",
4858 + "peer_name_id MEDIUMINT UNSIGNED, ",
4859 + "peer_server_id MEDIUMINT UNSIGNED, ",
4860 + "at VARCHAR(11), ",
4862 + ") ENGINE=MyISAM CHARACTER SET utf8;"
4864 + case sql_query_internal(DBRef, Query) of
4865 + {updated, _} -> ok;
4866 + {error, _Reason} -> error
4869 +create_stats_table(#state{dbref=DBRef, vhost=VHost}=State) ->
4870 + SName = stats_table(VHost),
4871 + Query = ["CREATE TABLE ",SName," (",
4872 + "owner_id MEDIUMINT UNSIGNED, ",
4873 + "peer_name_id MEDIUMINT UNSIGNED, ",
4874 + "peer_server_id MEDIUMINT UNSIGNED, ",
4875 + "at VARCHAR(11), ",
4876 + "count INT(11), ",
4877 + "ext INTEGER DEFAULT NULL, "
4878 + "INDEX ext_i (ext), "
4879 + "INDEX(owner_id,peer_name_id,peer_server_id), ",
4881 + ") ENGINE=MyISAM CHARACTER SET utf8;"
4883 + case sql_query_internal_silent(DBRef, Query) of
4885 + ?MYDEBUG("Created stats table for ~p", [VHost]),
4886 + rebuild_all_stats_int(State),
4888 + {error, Reason} ->
4889 + case ejabberd_regexp:run(Reason, "#42S01") of
4891 + ?MYDEBUG("Stats table for ~p already exists", [VHost]),
4892 + CheckQuery = ["SHOW COLUMNS FROM ",SName," LIKE 'peer_%_id';"],
4893 + case sql_query_internal(DBRef, CheckQuery) of
4894 + {data, Elems} when length(Elems) == 2 ->
4895 + ?MYDEBUG("Stats table structure is ok", []),
4898 + ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []),
4899 + case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of
4901 + ?INFO_MSG("Successfully dropped ~p", [SName]);
4903 + ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName])
4908 + ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
4913 +create_settings_table(#state{dbref=DBRef, vhost=VHost}) ->
4914 + SName = settings_table(VHost),
4915 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
4916 + "owner_id MEDIUMINT UNSIGNED PRIMARY KEY, ",
4917 + "dolog_default TINYINT(1) NOT NULL DEFAULT 1, ",
4918 + "dolog_list TEXT, ",
4919 + "donotlog_list TEXT ",
4920 + ") ENGINE=InnoDB CHARACTER SET utf8;"
4922 + case sql_query_internal(DBRef, Query) of
4924 + ?MYDEBUG("Created settings table for ~p", [VHost]),
4930 +create_users_table(#state{dbref=DBRef, vhost=VHost}) ->
4931 + SName = users_table(VHost),
4932 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
4933 + "username TEXT NOT NULL, ",
4934 + "user_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
4935 + "UNIQUE INDEX(username(",?INDEX_SIZE,")) ",
4936 + ") ENGINE=InnoDB CHARACTER SET utf8;"
4938 + case sql_query_internal(DBRef, Query) of
4940 + ?MYDEBUG("Created users table for ~p", [VHost]),
4946 +create_servers_table(#state{dbref=DBRef, vhost=VHost}) ->
4947 + SName = servers_table(VHost),
4948 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
4949 + "server TEXT NOT NULL, ",
4950 + "server_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
4951 + "UNIQUE INDEX(server(",?INDEX_SIZE,")) ",
4952 + ") ENGINE=InnoDB CHARACTER SET utf8;"
4954 + case sql_query_internal(DBRef, Query) of
4956 + ?MYDEBUG("Created servers table for ~p", [VHost]),
4962 +create_resources_table(#state{dbref=DBRef, vhost=VHost}) ->
4963 + RName = resources_table(VHost),
4964 + Query = ["CREATE TABLE IF NOT EXISTS ",RName," (",
4965 + "resource TEXT NOT NULL, ",
4966 + "resource_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
4967 + "UNIQUE INDEX(resource(",?INDEX_SIZE,")) ",
4968 + ") ENGINE=InnoDB CHARACTER SET utf8;"
4970 + case sql_query_internal(DBRef, Query) of
4972 + ?MYDEBUG("Created resources table for ~p", [VHost]),
4978 +create_internals(#state{dbref=DBRef, vhost=VHost}) ->
4979 + sql_query_internal(DBRef, ["DROP PROCEDURE IF EXISTS ",logmessage_name(VHost),";"]),
4980 + case sql_query_internal(DBRef, [get_logmessage(VHost)]) of
4982 + ?MYDEBUG("Created logmessage for ~p", [VHost]),
4988 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4992 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4993 +sql_query_internal(DBRef, Query) ->
4994 + case sql_query_internal_silent(DBRef, Query) of
4995 + {error, Reason} ->
4996 + ?ERROR_MSG("~p while ~p", [Reason, lists:append(Query)]),
5001 +sql_query_internal_silent(DBRef, Query) ->
5002 + ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
5003 + get_result(mysql_conn:fetch(DBRef, Query, self(), ?MYSQL_TIMEOUT)).
5005 +get_result({updated, MySQLRes}) ->
5006 + {updated, mysql:get_result_affected_rows(MySQLRes)};
5007 +get_result({data, MySQLRes}) ->
5008 + {data, mysql:get_result_rows(MySQLRes)};
5009 +get_result({error, "query timed out"}) ->
5010 + {error, "query timed out"};
5011 +get_result({error, MySQLRes}) ->
5012 + Reason = mysql:get_result_reason(MySQLRes),
5015 +get_user_id(DBRef, VHost, User) ->
5016 + SQuery = ["SELECT user_id FROM ",users_table(VHost)," ",
5017 + "WHERE username=\"",User,"\";"],
5018 + case sql_query_internal(DBRef, SQuery) of
5020 + IQuery = ["INSERT INTO ",users_table(VHost)," ",
5021 + "SET username=\"",User,"\";"],
5022 + case sql_query_internal_silent(DBRef, IQuery) of
5024 + {data, [[DBIdNew]]} = sql_query_internal(DBRef, SQuery),
5026 + {error, Reason} ->
5027 + % this can be in clustered environment
5028 + match = ejabberd_regexp:run(Reason, "#23000"),
5029 + ?ERROR_MSG("Duplicate key name for ~p", [User]),
5030 + {data, [[ClID]]} = sql_query_internal(DBRef, SQuery),
5033 + {data, [[DBId]]} ->
5037 +get_logmessage(VHost) ->
5038 + UName = users_table(VHost),
5039 + SName = servers_table(VHost),
5040 + RName = resources_table(VHost),
5041 + StName = stats_table(VHost),
5043 +CREATE PROCEDURE ~s(tablename TEXT, atdate TEXT, owner TEXT, peer_name TEXT, peer_server TEXT, peer_resource TEXT, mdirection VARCHAR(4), mtype VARCHAR(10), msubject TEXT, mbody TEXT, mtimestamp DOUBLE)
5045 + DECLARE ownerID MEDIUMINT UNSIGNED;
5046 + DECLARE peer_nameID MEDIUMINT UNSIGNED;
5047 + DECLARE peer_serverID MEDIUMINT UNSIGNED;
5048 + DECLARE peer_resourceID MEDIUMINT UNSIGNED;
5049 + DECLARE Vmtype VARCHAR(10);
5050 + DECLARE Vmtimestamp DOUBLE;
5051 + DECLARE Vmdirection VARCHAR(4);
5052 + DECLARE Vmbody TEXT;
5053 + DECLARE Vmsubject TEXT;
5056 + DECLARE viewname TEXT;
5057 + DECLARE notable INT;
5058 + DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' SET @notable = 1;
5061 + SET @ownerID = NULL;
5062 + SET @peer_nameID = NULL;
5063 + SET @peer_serverID = NULL;
5064 + SET @peer_resourceID = NULL;
5066 + SET @Vmtype = mtype;
5067 + SET @Vmtimestamp = mtimestamp;
5068 + SET @Vmdirection = mdirection;
5069 + SET @Vmbody = mbody;
5070 + SET @Vmsubject = msubject;
5072 + SELECT user_id INTO @ownerID FROM ~s WHERE username=owner;
5073 + IF @ownerID IS NULL THEN
5074 + INSERT INTO ~s SET username=owner;
5075 + SET @ownerID = LAST_INSERT_ID();
5078 + SELECT user_id INTO @peer_nameID FROM ~s WHERE username=peer_name;
5079 + IF @peer_nameID IS NULL THEN
5080 + INSERT INTO ~s SET username=peer_name;
5081 + SET @peer_nameID = LAST_INSERT_ID();
5084 + SELECT server_id INTO @peer_serverID FROM ~s WHERE server=peer_server;
5085 + IF @peer_serverID IS NULL THEN
5086 + INSERT INTO ~s SET server=peer_server;
5087 + SET @peer_serverID = LAST_INSERT_ID();
5090 + SELECT resource_id INTO @peer_resourceID FROM ~s WHERE resource=peer_resource;
5091 + IF @peer_resourceID IS NULL THEN
5092 + INSERT INTO ~s SET resource=peer_resource;
5093 + SET @peer_resourceID = LAST_INSERT_ID();
5096 + SET @iq = CONCAT(\"INSERT INTO \",tablename,\" (owner_id, peer_name_id, peer_server_id, peer_resource_id, direction, type, subject, body, timestamp) VALUES (@ownerID,@peer_nameID,@peer_serverID,@peer_resourceID,@Vmdirection,@Vmtype,@Vmsubject,@Vmbody,@Vmtimestamp);\");
5097 + PREPARE insertmsg FROM @iq;
5099 + IF @notable = 1 THEN
5100 + SET @cq = CONCAT(\"CREATE TABLE \",tablename,\" (
5101 + owner_id MEDIUMINT UNSIGNED NOT NULL,
5102 + peer_name_id MEDIUMINT UNSIGNED NOT NULL,
5103 + peer_server_id MEDIUMINT UNSIGNED NOT NULL,
5104 + peer_resource_id MEDIUMINT(8) UNSIGNED NOT NULL,
5105 + direction ENUM('to', 'from') NOT NULL,
5106 + type ENUM('chat','error','groupchat','headline','normal') NOT NULL,
5109 + timestamp DOUBLE NOT NULL,
5110 + ext INTEGER DEFAULT NULL,
5111 + INDEX search_i (owner_id, peer_name_id, peer_server_id, peer_resource_id),
5112 + INDEX ext_i (ext),
5116 + CHARACTER SET utf8;\");
5117 + PREPARE createtable FROM @cq;
5118 + EXECUTE createtable;
5119 + DEALLOCATE PREPARE createtable;
5121 + SET @viewname = CONCAT(\"`v_\", TRIM(BOTH '`' FROM tablename), \"`\");
5122 + SET @cq = CONCAT(\"CREATE OR REPLACE VIEW \",@viewname,\" AS
5123 + SELECT owner.username AS owner_name,
5124 + peer.username AS peer_name,
5125 + servers.server AS peer_server,
5126 + resources.resource AS peer_resource,
5127 + messages.direction,
5131 + messages.timestamp
5137 + \", tablename,\" messages
5139 + owner.user_id=messages.owner_id and
5140 + peer.user_id=messages.peer_name_id and
5141 + servers.server_id=messages.peer_server_id and
5142 + resources.resource_id=messages.peer_resource_id
5143 + ORDER BY messages.timestamp;\");
5144 + PREPARE createview FROM @cq;
5145 + EXECUTE createview;
5146 + DEALLOCATE PREPARE createview;
5149 + PREPARE insertmsg FROM @iq;
5150 + EXECUTE insertmsg;
5151 + ELSEIF @notable = 0 THEN
5152 + EXECUTE insertmsg;
5155 + DEALLOCATE PREPARE insertmsg;
5157 + IF @notable = 0 THEN
5158 + UPDATE ~s SET count=count+1 WHERE owner_id=@ownerID AND peer_name_id=@peer_nameID AND peer_server_id=@peer_serverID AND at=atdate;
5159 + IF ROW_COUNT() = 0 THEN
5160 + INSERT INTO ~s (owner_id, peer_name_id, peer_server_id, at, count) VALUES (@ownerID, @peer_nameID, @peer_serverID, atdate, 1);
5163 +END;", [logmessage_name(VHost),UName,UName,UName,UName,SName,SName,RName,RName,UName,UName,SName,RName,StName,StName]).
5164 diff --git src/mod_logdb_pgsql.erl src/mod_logdb_pgsql.erl
5165 new file mode 100644
5166 index 0000000..1227519
5168 +++ src/mod_logdb_pgsql.erl
5170 +%%%----------------------------------------------------------------------
5171 +%%% File : mod_logdb_pgsql.erl
5172 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
5173 +%%% Purpose : Posgresql backend for mod_logdb
5174 +%%% Version : trunk
5175 +%%% Id : $Id: mod_logdb_pgsql.erl 1360 2009-07-30 06:00:14Z malik $
5176 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
5177 +%%%----------------------------------------------------------------------
5179 +-module(mod_logdb_pgsql).
5180 +-author('o.palij@gmail.com').
5182 +-include("mod_logdb.hrl").
5183 +-include("ejabberd.hrl").
5184 +-include("jlib.hrl").
5186 +-behaviour(gen_logdb).
5187 +-behaviour(gen_server).
5190 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
5192 +-export([start/2, stop/1]).
5194 +-export([log_message/2,
5196 + rebuild_stats_at/2,
5197 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
5198 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
5200 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
5203 +-export([view_table/3]).
5205 +% gen_server call timeout
5206 +-define(CALL_TIMEOUT, 30000).
5207 +-define(PGSQL_TIMEOUT, 60000).
5208 +-define(PROCNAME, mod_logdb_pgsql).
5210 +-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
5211 + list_to_string/1, string_to_list/1,
5212 + convert_timestamp_brief/1]).
5214 +-record(state, {dbref, vhost, server, port, db, user, password, schema}).
5216 +% replace "." with "_"
5217 +escape_vhost(VHost) -> lists:map(fun(46) -> 95;
5222 + Schema ++ ".\"" ++ "logdb_".
5225 + "_" ++ escape_vhost(VHost) ++ "\"".
5227 +messages_table(VHost, Schema, Date) ->
5228 + prefix(Schema) ++ "messages_" ++ Date ++ suffix(VHost).
5230 +view_table(VHost, Schema, Date) ->
5231 + Table = messages_table(VHost, Schema, Date),
5232 + TablewoS = lists:sublist(Table, length(Schema) + 3, length(Table) - length(Schema) - 3),
5233 + lists:append([Schema, ".\"v_", TablewoS, "\""]).
5235 +stats_table(VHost, Schema) ->
5236 + prefix(Schema) ++ "stats" ++ suffix(VHost).
5238 +temp_table(VHost, Schema) ->
5239 + prefix(Schema) ++ "temp" ++ suffix(VHost).
5241 +settings_table(VHost, Schema) ->
5242 + prefix(Schema) ++ "settings" ++ suffix(VHost).
5244 +users_table(VHost, Schema) ->
5245 + prefix(Schema) ++ "users" ++ suffix(VHost).
5246 +servers_table(VHost, Schema) ->
5247 + prefix(Schema) ++ "servers" ++ suffix(VHost).
5248 +resources_table(VHost, Schema) ->
5249 + prefix(Schema) ++ "resources" ++ suffix(VHost).
5251 +logmessage_name(VHost, Schema) ->
5252 + prefix(Schema) ++ "logmessage" ++ suffix(VHost).
5254 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5256 +% gen_mod callbacks
5258 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5259 +start(VHost, Opts) ->
5260 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5261 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
5264 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5265 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
5267 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5269 +% gen_server callbacks
5271 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5272 +init([VHost, Opts]) ->
5273 + Server = gen_mod:get_opt(server, Opts, "localhost"),
5274 + DB = gen_mod:get_opt(db, Opts, "ejabberd_logdb"),
5275 + User = gen_mod:get_opt(user, Opts, "root"),
5276 + Port = gen_mod:get_opt(port, Opts, 5432),
5277 + Password = gen_mod:get_opt(password, Opts, ""),
5278 + Schema = gen_mod:get_opt(schema, Opts, "public"),
5280 + ?MYDEBUG("Starting pgsql backend for ~p", [VHost]),
5282 + St = #state{vhost=VHost,
5283 + server=Server, port=Port, db=DB,
5284 + user=User, password=Password,
5287 + case open_pgsql_connection(St) of
5289 + State = St#state{dbref=DBRef},
5290 + ok = create_internals(State),
5291 + ok = create_stats_table(State),
5292 + ok = create_settings_table(State),
5293 + ok = create_users_table(State),
5294 + ok = create_servers_table(State),
5295 + ok = create_resources_table(State),
5296 + erlang:monitor(process, DBRef),
5298 + % this does not work
5299 + {error, Reason} ->
5300 + ?ERROR_MSG("PgSQL connection failed: ~p~n", [Reason]),
5301 + {stop, db_connection_failed};
5302 + % and this too, becouse pgsql_conn do exit() which can not be catched
5304 + ?ERROR_MSG("Rez: ~p~n", [Rez]),
5305 + {stop, db_connection_failed}
5308 +open_pgsql_connection(#state{server=Server, port=Port, db=DB, schema=Schema,
5309 + user=User, password=Password} = _State) ->
5310 + ?INFO_MSG("Opening pgsql connection ~s@~s:~p/~s", [User, Server, Port, DB]),
5311 + {ok, DBRef} = pgsql:connect(Server, DB, User, Password, Port),
5312 + {updated, _} = sql_query_internal(DBRef, ["SET SEARCH_PATH TO ",Schema,";"]),
5315 +close_pgsql_connection(DBRef) ->
5316 + ?MYDEBUG("Closing ~p pgsql connection", [DBRef]),
5317 + pgsql:terminate(DBRef).
5319 +handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5320 + Date = convert_timestamp_brief(Msg#msg.timestamp),
5321 + TableName = messages_table(VHost, Schema, Date),
5322 + ViewName = view_table(VHost, Schema, Date),
5324 + Query = [ "SELECT ", logmessage_name(VHost, Schema)," "
5325 + "('", TableName, "',",
5326 + "'", ViewName, "',",
5328 + "'", Msg#msg.owner_name, "',",
5329 + "'", Msg#msg.peer_name, "',",
5330 + "'", Msg#msg.peer_server, "',",
5331 + "'", ejabberd_odbc:escape(Msg#msg.peer_resource), "',",
5332 + "'", atom_to_list(Msg#msg.direction), "',",
5333 + "'", Msg#msg.type, "',",
5334 + "'", ejabberd_odbc:escape(Msg#msg.subject), "',",
5335 + "'", ejabberd_odbc:escape(Msg#msg.body), "',",
5336 + "'", Msg#msg.timestamp, "');"],
5338 + case sql_query_internal_silent(DBRef, Query) of
5339 + % TODO: change this
5340 + {data, [{"0"}]} ->
5341 + ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
5342 + Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
5344 + {error, _Reason} ->
5347 + {reply, ok, State};
5348 +handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5349 + Reply = rebuild_stats_at_int(DBRef, VHost, Schema, Date),
5350 + {reply, Reply, State};
5351 +handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
5352 + {reply, error, State};
5353 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5354 + Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
5355 + ["'",Timestamp,"'",","]
5358 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
5360 + Query = ["DELETE FROM ",messages_table(VHost, Schema, Date)," ",
5361 + "WHERE timestamp IN (", Temp1],
5364 + case sql_query_internal(DBRef, Query) of
5366 + rebuild_stats_at_int(DBRef, VHost, Schema, Date);
5370 + {reply, Reply, State};
5371 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5372 + ok = delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date),
5373 + ok = delete_stats_by_user_at_int(DBRef, Schema, User, VHost, Date),
5374 + {reply, ok, State};
5375 +handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5376 + {updated, _} = sql_query_internal(DBRef, ["DROP VIEW ",view_table(VHost, Schema, Date),";"]),
5378 + case sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Schema, Date)," CASCADE;"]) of
5380 + Query = ["DELETE FROM ",stats_table(VHost, Schema)," "
5381 + "WHERE at='",Date,"';"],
5382 + case sql_query_internal(DBRef, Query) of
5391 + {reply, Reply, State};
5392 +handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5393 + SName = stats_table(VHost, Schema),
5394 + Query = ["SELECT at, sum(count) ",
5395 + "FROM ",SName," ",
5397 + "ORDER BY DATE(at) DESC;"
5400 + case sql_query_internal(DBRef, Query) of
5402 + {ok, [ {Date, list_to_integer(Count)} || {Date, Count} <- Recs]};
5403 + {error, Reason} ->
5404 + % TODO: Duplicate error message ?
5407 + {reply, Reply, State};
5408 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5409 + SName = stats_table(VHost, Schema),
5410 + Query = ["SELECT username, sum(count) AS allcount ",
5411 + "FROM ",SName," ",
5412 + "JOIN ",users_table(VHost, Schema)," ON owner_id=user_id ",
5413 + "WHERE at='",Date,"' ",
5414 + "GROUP BY username ",
5415 + "ORDER BY allcount DESC;"
5418 + case sql_query_internal(DBRef, Query) of
5420 + RFun = fun({User, Count}) ->
5421 + {User, list_to_integer(Count)}
5423 + {ok, lists:reverse(lists:keysort(2, lists:map(RFun, Recs)))};
5424 + {error, Reason} ->
5428 + {reply, Reply, State};
5429 +handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5430 + {reply, get_user_stats_int(DBRef, Schema, User, VHost), State};
5431 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5432 + Query = ["SELECT peer_name,",
5440 + "FROM ",view_table(VHost, Schema, Date)," "
5441 + "WHERE owner_name='",User,"';"],
5443 + case sql_query_internal(DBRef, Query) of
5445 + Fun = fun({Peer_name, Peer_server, Peer_resource,
5450 + #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
5451 + direction=list_to_atom(Direction),
5453 + subject=Subject, body=Body,
5454 + timestamp=Timestamp}
5456 + {ok, lists:map(Fun, Recs)};
5457 + {error, Reason} ->
5460 + {reply, Reply, State};
5461 +handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5462 + SName = stats_table(VHost, Schema),
5463 + Query = ["SELECT at ",
5464 + "FROM ",SName," ",
5466 + "ORDER BY at DESC;"
5469 + case sql_query_internal(DBRef, Query) of
5471 + [ Date || {Date} <- Result ];
5472 + {error, Reason} ->
5475 + {reply, Reply, State};
5476 +handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5477 + Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
5478 + "FROM ",settings_table(VHost, Schema)," ",
5479 + "JOIN ",users_table(VHost, Schema)," ON user_id=owner_id;"],
5481 + case sql_query_internal(DBRef, Query) of
5483 + {ok, [#user_settings{owner_name=Owner,
5484 + dolog_default=list_to_bool(DoLogDef),
5485 + dolog_list=string_to_list(DoLogL),
5486 + donotlog_list=string_to_list(DoNotLogL)
5487 + } || {Owner, DoLogDef, DoLogL, DoNotLogL} <- Recs]};
5488 + {error, Reason} ->
5491 + {reply, Reply, State};
5492 +handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5493 + Query = ["SELECT dolog_default,dolog_list,donotlog_list ",
5494 + "FROM ",settings_table(VHost, Schema)," ",
5495 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
5497 + case sql_query_internal_silent(DBRef, Query) of
5500 + {data, [{DoLogDef, DoLogL, DoNotLogL}]} ->
5501 + {ok, #user_settings{owner_name=User,
5502 + dolog_default=list_to_bool(DoLogDef),
5503 + dolog_list=string_to_list(DoLogL),
5504 + donotlog_list=string_to_list(DoNotLogL)}};
5505 + {error, Reason} ->
5506 + ?ERROR_MSG("Failed to get_user_settings for ~p@~p: ~p", [User, VHost, Reason]),
5509 + {reply, Reply, State};
5510 +handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
5511 + dolog_list=DoLogL,
5512 + donotlog_list=DoNotLogL}},
5513 + _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5514 + User_id = get_user_id(DBRef, VHost, Schema, User),
5515 + Query = ["UPDATE ",settings_table(VHost, Schema)," ",
5516 + "SET dolog_default=",bool_to_list(DoLogDef),", ",
5517 + "dolog_list='",list_to_string(DoLogL),"', ",
5518 + "donotlog_list='",list_to_string(DoNotLogL),"' ",
5519 + "WHERE owner_id=",User_id,";"],
5522 + case sql_query_internal(DBRef, Query) of
5524 + IQuery = ["INSERT INTO ",settings_table(VHost, Schema)," ",
5525 + "(owner_id, dolog_default, dolog_list, donotlog_list) ",
5527 + "(",User_id,", ",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
5528 + case sql_query_internal(DBRef, IQuery) of
5530 + ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
5536 + ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
5541 + {reply, Reply, State};
5542 +handle_call({stop}, _From, State) ->
5543 + ?MYDEBUG("Stoping pgsql backend for ~p", [State#state.vhost]),
5544 + {stop, normal, ok, State};
5545 +handle_call(Msg, _From, State) ->
5546 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
5550 +handle_cast({rebuild_stats}, State) ->
5551 + rebuild_all_stats_int(State),
5553 +handle_cast({drop_user, User}, #state{vhost=VHost, schema=Schema}=State) ->
5555 + {ok, DBRef} = open_pgsql_connection(State),
5556 + {ok, Dates} = get_user_stats_int(DBRef, Schema, User, VHost),
5557 + MDResult = lists:map(fun({Date, _}) ->
5558 + delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date)
5560 + StDResult = delete_all_stats_by_user_int(DBRef, Schema, User, VHost),
5561 + SDResult = delete_user_settings_int(DBRef, Schema, User, VHost),
5562 + case lists:all(fun(Result) when Result == ok ->
5564 + (Result) when Result == error ->
5566 + end, lists:append([MDResult, [StDResult], [SDResult]])) of
5568 + ?INFO_MSG("Removed ~s@~s", [User, VHost]);
5570 + ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost])
5572 + close_pgsql_connection(DBRef)
5576 +handle_cast(Msg, State) ->
5577 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
5580 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
5581 + {stop, connection_dropped, State};
5582 +handle_info(Info, State) ->
5583 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
5586 +terminate(_Reason, #state{dbref=DBRef}=_State) ->
5587 + close_pgsql_connection(DBRef),
5590 +code_change(_OldVsn, State, _Extra) ->
5593 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5595 +% gen_logdb callbacks
5597 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5598 +log_message(VHost, Msg) ->
5599 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5600 + gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
5601 +rebuild_stats(VHost) ->
5602 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5603 + gen_server:cast(Proc, {rebuild_stats}).
5604 +rebuild_stats_at(VHost, Date) ->
5605 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5606 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
5607 +delete_messages_by_user_at(VHost, Msgs, Date) ->
5608 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5609 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
5610 +delete_all_messages_by_user_at(User, VHost, Date) ->
5611 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5612 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
5613 +delete_messages_at(VHost, Date) ->
5614 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5615 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
5616 +get_vhost_stats(VHost) ->
5617 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5618 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
5619 +get_vhost_stats_at(VHost, Date) ->
5620 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5621 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
5622 +get_user_stats(User, VHost) ->
5623 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5624 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
5625 +get_user_messages_at(User, VHost, Date) ->
5626 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5627 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
5628 +get_dates(VHost) ->
5629 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5630 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
5631 +get_users_settings(VHost) ->
5632 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5633 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
5634 +get_user_settings(User, VHost) ->
5635 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5636 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
5637 +set_user_settings(User, VHost, Set) ->
5638 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5639 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
5640 +drop_user(User, VHost) ->
5641 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5642 + gen_server:cast(Proc, {drop_user, User}).
5644 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5648 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5649 +get_dates_int(DBRef, VHost) ->
5650 + Query = ["SELECT n.nspname as \"Schema\",
5651 + c.relname as \"Name\",
5652 + CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' END as \"Type\",
5653 + r.rolname as \"Owner\"
5654 + FROM pg_catalog.pg_class c
5655 + JOIN pg_catalog.pg_roles r ON r.oid = c.relowner
5656 + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
5657 + WHERE c.relkind IN ('r','')
5658 + AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
5659 + AND c.relname ~ '^(.*",escape_vhost(VHost),".*)$'
5660 + AND pg_catalog.pg_table_is_visible(c.oid)
5662 + case sql_query_internal(DBRef, Query) of
5664 + lists:foldl(fun({_Schema, Table, _Type, _Owner}, Dates) ->
5665 + case re:run(Table,"[0-9]+-[0-9]+-[0-9]+") of
5666 + {match, [{S, E}]} ->
5667 + lists:append(Dates, [lists:sublist(Table,S,E)]);
5676 +rebuild_all_stats_int(#state{vhost=VHost, schema=Schema}=State) ->
5678 + {ok, DBRef} = open_pgsql_connection(State),
5679 + ok = delete_nonexistent_stats(DBRef, Schema, VHost),
5680 + case lists:filter(fun(Date) ->
5681 + case catch rebuild_stats_at_int(DBRef, VHost, Schema, Date) of
5684 + {'EXIT', _} -> true
5686 + end, get_dates_int(DBRef, VHost)) of
5689 + ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]),
5692 + close_pgsql_connection(DBRef)
5696 +rebuild_stats_at_int(DBRef, VHost, Schema, Date) ->
5697 + TempTable = temp_table(VHost, Schema),
5700 + Table = messages_table(VHost, Schema, Date),
5701 + STable = stats_table(VHost, Schema),
5703 + DQuery = [ "DELETE FROM ",STable," ",
5704 + "WHERE at='",Date,"';"],
5706 + ok = create_temp_table(DBRef, VHost, Schema),
5707 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," IN ACCESS EXCLUSIVE MODE;"]),
5708 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",TempTable," IN ACCESS EXCLUSIVE MODE;"]),
5709 + SQuery = ["INSERT INTO ",TempTable," ",
5710 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
5711 + "SELECT owner_id,peer_name_id,peer_server_id,'",Date,"'",",count(*) ",
5712 + "FROM ",Table," GROUP BY owner_id,peer_name_id,peer_server_id;"],
5713 + case sql_query_internal(DBRef, SQuery) of
5715 + Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]),
5717 + {data, [{"0"}]} ->
5718 + {updated, _} = sql_query_internal(DBRef, ["DROP VIEW ",view_table(VHost, Schema, Date),";"]),
5719 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table," CASCADE;"]),
5720 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," IN ACCESS EXCLUSIVE MODE;"]),
5721 + {updated, _} = sql_query_internal(DBRef, DQuery),
5724 + ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]),
5728 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," IN ACCESS EXCLUSIVE MODE;"]),
5729 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",TempTable," IN ACCESS EXCLUSIVE MODE;"]),
5730 + {updated, _} = sql_query_internal(DBRef, DQuery),
5731 + SQuery1 = ["INSERT INTO ",STable," ",
5732 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
5733 + "SELECT owner_id,peer_name_id,peer_server_id,at,count ",
5734 + "FROM ",TempTable,";"],
5735 + case sql_query_internal(DBRef, SQuery1) of
5736 + {updated, _} -> ok;
5737 + {error, _} -> error
5739 + {error, _} -> error
5743 + case sql_transaction_internal(DBRef, Fun) of
5745 + ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
5747 + {aborted, Reason} ->
5748 + ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]),
5751 + sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]),
5754 +delete_nonexistent_stats(DBRef, Schema, VHost) ->
5755 + Dates = get_dates_int(DBRef, VHost),
5756 + STable = stats_table(VHost, Schema),
5758 + Temp = lists:flatmap(fun(Date) ->
5759 + ["'",Date,"'",","]
5766 + % replace last "," with ");"
5767 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
5768 + Query = ["DELETE FROM ",STable," ",
5769 + "WHERE at NOT IN (", Temp1],
5770 + case sql_query_internal(DBRef, Query) of
5778 +get_user_stats_int(DBRef, Schema, User, VHost) ->
5779 + SName = stats_table(VHost, Schema),
5780 + UName = users_table(VHost, Schema),
5781 + Query = ["SELECT stats.at, sum(stats.count) ",
5782 + "FROM ",UName," AS users ",
5783 + "JOIN ",SName," AS stats ON owner_id=user_id "
5784 + "WHERE users.username='",User,"' ",
5785 + "GROUP BY stats.at "
5786 + "ORDER BY DATE(at) DESC;"
5788 + case sql_query_internal(DBRef, Query) of
5790 + {ok, [ {Date, list_to_integer(Count)} || {Date, Count} <- Recs ]};
5791 + {error, Result} ->
5795 +delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date) ->
5796 + DQuery = ["DELETE FROM ",messages_table(VHost, Schema, Date)," ",
5797 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
5798 + case sql_query_internal(DBRef, DQuery) of
5800 + ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]),
5806 +delete_all_stats_by_user_int(DBRef, Schema, User, VHost) ->
5807 + SQuery = ["DELETE FROM ",stats_table(VHost, Schema)," ",
5808 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
5809 + case sql_query_internal(DBRef, SQuery) of
5811 + ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]),
5813 + {error, _} -> error
5816 +delete_stats_by_user_at_int(DBRef, Schema, User, VHost, Date) ->
5817 + SQuery = ["DELETE FROM ",stats_table(VHost, Schema)," ",
5818 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"') ",
5819 + "AND at='",Date,"';"],
5820 + case sql_query_internal(DBRef, SQuery) of
5822 + ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]),
5824 + {error, _} -> error
5827 +delete_user_settings_int(DBRef, Schema, User, VHost) ->
5828 + Query = ["DELETE FROM ",settings_table(VHost, Schema)," ",
5829 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
5830 + case sql_query_internal(DBRef, Query) of
5832 + ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]),
5834 + {error, Reason} ->
5835 + ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]),
5839 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5843 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5844 +create_temp_table(DBRef, VHost, Schema) ->
5845 + TName = temp_table(VHost, Schema),
5846 + Query = ["CREATE TABLE ",TName," (",
5847 + "owner_id INTEGER, ",
5848 + "peer_name_id INTEGER, ",
5849 + "peer_server_id INTEGER, ",
5850 + "at VARCHAR(20), ",
5854 + case sql_query_internal(DBRef, Query) of
5855 + {updated, _} -> ok;
5856 + {error, _Reason} -> error
5859 +create_stats_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5860 + SName = stats_table(VHost, Schema),
5864 + Query = ["CREATE TABLE ",SName," (",
5865 + "owner_id INTEGER, ",
5866 + "peer_name_id INTEGER, ",
5867 + "peer_server_id INTEGER, ",
5868 + "at VARCHAR(20), ",
5872 + case sql_query_internal_silent(DBRef, Query) of
5874 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"s_search_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (owner_id, peer_name_id, peer_server_id);"]),
5875 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"s_at_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (at);"]),
5877 + {error, Reason} ->
5878 + case lists:keysearch(code, 1, Reason) of
5879 + {value, {code, "42P07"}} ->
5882 + ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
5887 + case sql_transaction_internal(DBRef, Fun) of
5888 + {atomic, created} ->
5889 + ?MYDEBUG("Created stats table for ~p", [VHost]),
5890 + rebuild_all_stats_int(State),
5892 + {atomic, exists} ->
5893 + ?MYDEBUG("Stats table for ~p already exists", [VHost]),
5894 + {match, [{F, L}]} = re:run(SName, "\".*\""),
5895 + QTable = lists:sublist(SName, F+1, L-2),
5896 + OIDQuery = ["SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname='",QTable,"' AND pg_catalog.pg_table_is_visible(c.oid);"],
5897 + {data,[{OID}]} = sql_query_internal(DBRef, OIDQuery),
5898 + CheckQuery = ["SELECT a.attname FROM pg_catalog.pg_attribute a WHERE a.attrelid = '",OID,"' AND a.attnum > 0 AND NOT a.attisdropped AND a.attname ~ '^peer_.*_id$';"],
5899 + case sql_query_internal(DBRef, CheckQuery) of
5900 + {data, Elems} when length(Elems) == 2 ->
5901 + ?MYDEBUG("Stats table structure is ok", []),
5904 + ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []),
5905 + case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of
5907 + ?INFO_MSG("Successfully dropped ~p", [SName]);
5909 + ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName])
5913 + {error, _} -> error
5916 +create_settings_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
5917 + SName = settings_table(VHost, Schema),
5918 + Query = ["CREATE TABLE ",SName," (",
5919 + "owner_id INTEGER PRIMARY KEY, ",
5920 + "dolog_default BOOLEAN, ",
5921 + "dolog_list TEXT DEFAULT '', ",
5922 + "donotlog_list TEXT DEFAULT ''",
5925 + case sql_query_internal_silent(DBRef, Query) of
5927 + ?MYDEBUG("Created settings table for ~p", [VHost]),
5929 + {error, Reason} ->
5930 + case lists:keysearch(code, 1, Reason) of
5931 + {value, {code, "42P07"}} ->
5932 + ?MYDEBUG("Settings table for ~p already exists", [VHost]),
5935 + ?ERROR_MSG("Failed to create settings table for ~p: ~p", [VHost, Reason]),
5940 +create_users_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
5941 + SName = users_table(VHost, Schema),
5945 + Query = ["CREATE TABLE ",SName," (",
5946 + "username TEXT UNIQUE, ",
5947 + "user_id SERIAL PRIMARY KEY",
5950 + case sql_query_internal_silent(DBRef, Query) of
5952 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"username_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (username);"]),
5954 + {error, Reason} ->
5955 + case lists:keysearch(code, 1, Reason) of
5956 + {value, {code, "42P07"}} ->
5959 + ?ERROR_MSG("Failed to create users table for ~p: ~p", [VHost, Reason]),
5964 + case sql_transaction_internal(DBRef, Fun) of
5965 + {atomic, created} ->
5966 + ?MYDEBUG("Created users table for ~p", [VHost]),
5968 + {atomic, exists} ->
5969 + ?MYDEBUG("Users table for ~p already exists", [VHost]),
5971 + {aborted, _} -> error
5974 +create_servers_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
5975 + SName = servers_table(VHost, Schema),
5978 + Query = ["CREATE TABLE ",SName," (",
5979 + "server TEXT UNIQUE, ",
5980 + "server_id SERIAL PRIMARY KEY",
5983 + case sql_query_internal_silent(DBRef, Query) of
5985 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"server_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (server);"]),
5987 + {error, Reason} ->
5988 + case lists:keysearch(code, 1, Reason) of
5989 + {value, {code, "42P07"}} ->
5992 + ?ERROR_MSG("Failed to create servers table for ~p: ~p", [VHost, Reason]),
5997 + case sql_transaction_internal(DBRef, Fun) of
5998 + {atomic, created} ->
5999 + ?MYDEBUG("Created servers table for ~p", [VHost]),
6001 + {atomic, exists} ->
6002 + ?MYDEBUG("Servers table for ~p already exists", [VHost]),
6004 + {aborted, _} -> error
6007 +create_resources_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
6008 + RName = resources_table(VHost, Schema),
6010 + Query = ["CREATE TABLE ",RName," (",
6011 + "resource TEXT UNIQUE, ",
6012 + "resource_id SERIAL PRIMARY KEY",
6015 + case sql_query_internal_silent(DBRef, Query) of
6017 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"resource_i_",Schema,"_",escape_vhost(VHost),"\" ON ",RName," (resource);"]),
6019 + {error, Reason} ->
6020 + case lists:keysearch(code, 1, Reason) of
6021 + {value, {code, "42P07"}} ->
6024 + ?ERROR_MSG("Failed to create users table for ~p: ~p", [VHost, Reason]),
6029 + case sql_transaction_internal(DBRef, Fun) of
6030 + {atomic, created} ->
6031 + ?MYDEBUG("Created resources table for ~p", [VHost]),
6033 + {atomic, exists} ->
6034 + ?MYDEBUG("Resources table for ~p already exists", [VHost]),
6036 + {aborted, _} -> error
6039 +create_internals(#state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
6040 + sql_query_internal(DBRef, ["DROP FUNCTION IF EXISTS ",logmessage_name(VHost,Schema)," (tbname TEXT, atdt TEXT, owner TEXT, peer_name TEXT, peer_server TEXT, peer_resource TEXT, mdirection VARCHAR(4), mtype VARCHAR(9), msubj TEXT, mbody TEXT, mtimestamp DOUBLE PRECISION);"]),
6041 + case sql_query_internal(DBRef, [get_logmessage(VHost, Schema)]) of
6043 + ?MYDEBUG("Created logmessage for ~p", [VHost]),
6045 + {error, Reason} ->
6046 + case lists:keysearch(code, 1, Reason) of
6047 + {value, {code, "42704"}} ->
6048 + ?ERROR_MSG("plpgsql language must be installed into database '~s'. Use CREATE LANGUAGE...", [State#state.db]),
6055 +get_user_id(DBRef, VHost, Schema, User) ->
6056 + SQuery = ["SELECT user_id FROM ",users_table(VHost, Schema)," ",
6057 + "WHERE username='",User,"';"],
6058 + case sql_query_internal(DBRef, SQuery) of
6060 + IQuery = ["INSERT INTO ",users_table(VHost, Schema)," ",
6061 + "VALUES ('",User,"');"],
6062 + case sql_query_internal_silent(DBRef, IQuery) of
6064 + {data, [{DBIdNew}]} = sql_query_internal(DBRef, SQuery),
6066 + {error, Reason} ->
6067 + % this can be in clustered environment
6068 + {value, {code, "23505"}} = lists:keysearch(code, 1, Reason),
6069 + ?ERROR_MSG("Duplicate key name for ~p", [User]),
6070 + {data, [{ClID}]} = sql_query_internal(DBRef, SQuery),
6073 + {data, [{DBId}]} ->
6077 +get_logmessage(VHost,Schema) ->
6078 + UName = users_table(VHost,Schema),
6079 + SName = servers_table(VHost,Schema),
6080 + RName = resources_table(VHost,Schema),
6081 + StName = stats_table(VHost,Schema),
6082 + io_lib:format("CREATE OR REPLACE FUNCTION ~s (tbname TEXT, vname TEXT, atdt TEXT, owner TEXT, peer_name TEXT, peer_server TEXT, peer_resource TEXT, mdirection VARCHAR(4), mtype VARCHAR(9), msubj TEXT, mbody TEXT, mtimestamp DOUBLE PRECISION) RETURNS INTEGER AS $$
6085 + peer_nameID INTEGER;
6086 + peer_serverID INTEGER;
6087 + peer_resourceID INTEGER;
6088 + tablename ALIAS for $1;
6089 + viewname ALIAS for $2;
6090 + atdate ALIAS for $3;
6092 + SELECT INTO ownerID user_id FROM ~s WHERE username = owner;
6094 + INSERT INTO ~s (username) VALUES (owner);
6095 + ownerID := lastval();
6098 + SELECT INTO peer_nameID user_id FROM ~s WHERE username = peer_name;
6100 + INSERT INTO ~s (username) VALUES (peer_name);
6101 + peer_nameID := lastval();
6104 + SELECT INTO peer_serverID server_id FROM ~s WHERE server = peer_server;
6106 + INSERT INTO ~s (server) VALUES (peer_server);
6107 + peer_serverID := lastval();
6110 + SELECT INTO peer_resourceID resource_id FROM ~s WHERE resource = peer_resource;
6112 + INSERT INTO ~s (resource) VALUES (peer_resource);
6113 + peer_resourceID := lastval();
6117 + EXECUTE 'INSERT INTO ' || tablename || ' (owner_id, peer_name_id, peer_server_id, peer_resource_id, direction, type, subject, body, timestamp) VALUES (' || ownerID || ',' || peer_nameID || ',' || peer_serverID || ',' || peer_resourceID || ',''' || mdirection || ''',''' || mtype || ''',' || quote_literal(msubj) || ',' || quote_literal(mbody) || ',' || mtimestamp || ')';
6118 + EXCEPTION WHEN undefined_table THEN
6119 + EXECUTE 'CREATE TABLE ' || tablename || ' (' ||
6120 + 'owner_id INTEGER, ' ||
6121 + 'peer_name_id INTEGER, ' ||
6122 + 'peer_server_id INTEGER, ' ||
6123 + 'peer_resource_id INTEGER, ' ||
6124 + 'direction VARCHAR(4) CHECK (direction IN (''to'',''from'')), ' ||
6125 + 'type VARCHAR(9) CHECK (type IN (''chat'',''error'',''groupchat'',''headline'',''normal'')), ' ||
6126 + 'subject TEXT, ' ||
6128 + 'timestamp DOUBLE PRECISION)';
6129 + EXECUTE 'CREATE INDEX \"search_i_' || '~s' || '_' || atdate || '_' || '~s' || '\"' || ' ON ' || tablename || ' (owner_id, peer_name_id, peer_server_id, peer_resource_id)';
6131 + EXECUTE 'CREATE OR REPLACE VIEW ' || viewname || ' AS ' ||
6132 + 'SELECT owner.username AS owner_name, ' ||
6133 + 'peer.username AS peer_name, ' ||
6134 + 'servers.server AS peer_server, ' ||
6135 + 'resources.resource AS peer_resource, ' ||
6136 + 'messages.direction, ' ||
6137 + 'messages.type, ' ||
6138 + 'messages.subject, ' ||
6139 + 'messages.body, ' ||
6140 + 'messages.timestamp ' ||
6145 + '~s resources, ' ||
6146 + tablename || ' messages ' ||
6148 + 'owner.user_id=messages.owner_id and ' ||
6149 + 'peer.user_id=messages.peer_name_id and ' ||
6150 + 'servers.server_id=messages.peer_server_id and ' ||
6151 + 'resources.resource_id=messages.peer_resource_id ' ||
6152 + 'ORDER BY messages.timestamp';
6154 + EXECUTE 'INSERT INTO ' || tablename || ' (owner_id, peer_name_id, peer_server_id, peer_resource_id, direction, type, subject, body, timestamp) VALUES (' || ownerID || ',' || peer_nameID || ',' || peer_serverID || ',' || peer_resourceID || ',''' || mdirection || ''',''' || mtype || ''',' || quote_literal(msubj) || ',' || quote_literal(mbody) || ',' || mtimestamp || ')';
6157 + UPDATE ~s SET count=count+1 where at=atdate and owner_id=ownerID and peer_name_id=peer_nameID and peer_server_id=peer_serverID;
6159 + INSERT INTO ~s (owner_id, peer_name_id, peer_server_id, at, count) VALUES (ownerID, peer_nameID, peer_serverID, atdate, 1);
6163 +$$ LANGUAGE plpgsql;
6164 +", [logmessage_name(VHost,Schema),UName,UName,UName,UName,SName,SName,RName,RName,Schema,escape_vhost(VHost),UName,UName,SName,RName,StName,StName]).
6166 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6170 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6171 +% like do_transaction/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
6172 +sql_transaction_internal(DBRef, Fun) ->
6173 + case sql_query_internal(DBRef, ["BEGIN;"]) of
6175 + case catch Fun() of
6177 + rollback_internal(DBRef, Err);
6178 + {error, _} = Err ->
6179 + rollback_internal(DBRef, Err);
6180 + {'EXIT', _} = Err ->
6181 + rollback_internal(DBRef, Err);
6183 + case sql_query_internal(DBRef, ["COMMIT;"]) of
6184 + {error, _} -> rollback_internal(DBRef, {commit_error});
6187 + {atomic, _} -> Res;
6188 + _ -> {atomic, Res}
6193 + {aborted, {begin_error}}
6196 +% like rollback/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
6197 +rollback_internal(DBRef, Reason) ->
6198 + Res = sql_query_internal(DBRef, ["ROLLBACK;"]),
6199 + {aborted, {Reason, {rollback_result, Res}}}.
6201 +sql_query_internal(DBRef, Query) ->
6202 + case sql_query_internal_silent(DBRef, Query) of
6203 + {error, undefined, Rez} ->
6204 + ?ERROR_MSG("Got undefined result: ~p while ~p", [Rez, lists:append(Query)]),
6205 + {error, undefined};
6207 + ?ERROR_MSG("Failed: ~p while ~p", [Error, lists:append(Query)]),
6212 +sql_query_internal_silent(DBRef, Query) ->
6213 + ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
6214 + % TODO: use pquery?
6215 + get_result(pgsql:squery(DBRef, Query)).
6217 +get_result({ok, ["CREATE TABLE"]}) ->
6219 +get_result({ok, ["DROP TABLE"]}) ->
6221 +get_result({ok, ["ALTER TABLE"]}) ->
6223 +get_result({ok,["DROP VIEW"]}) ->
6225 +get_result({ok,["DROP FUNCTION"]}) ->
6227 +get_result({ok, ["CREATE INDEX"]}) ->
6229 +get_result({ok, ["CREATE FUNCTION"]}) ->
6231 +get_result({ok, [{"SELECT", _Rows, Recs}]}) ->
6234 + lists:map(fun(Elem) when is_binary(Elem) ->
6235 + binary_to_list(Elem);
6236 + (Elem) when is_list(Elem) ->
6238 + (Elem) when is_integer(Elem) ->
6239 + integer_to_list(Elem);
6240 + (Elem) when is_float(Elem) ->
6241 + float_to_list(Elem);
6242 + (Elem) when is_boolean(Elem) ->
6243 + atom_to_list(Elem);
6245 + ?ERROR_MSG("Unknown element type ~p", [Elem]),
6249 + Res = lists:map(Fun, Recs),
6250 + %{data, [list_to_tuple(Rec) || Rec <- Recs]};
6252 +get_result({ok, ["INSERT " ++ OIDN]}) ->
6253 + [_OID, N] = string:tokens(OIDN, " "),
6254 + {updated, list_to_integer(N)};
6255 +get_result({ok, ["DELETE " ++ N]}) ->
6256 + {updated, list_to_integer(N)};
6257 +get_result({ok, ["UPDATE " ++ N]}) ->
6258 + {updated, list_to_integer(N)};
6259 +get_result({ok, ["BEGIN"]}) ->
6261 +get_result({ok, ["LOCK TABLE"]}) ->
6263 +get_result({ok, ["ROLLBACK"]}) ->
6265 +get_result({ok, ["COMMIT"]}) ->
6267 +get_result({ok, ["SET"]}) ->
6269 +get_result({ok, [{error, Error}]}) ->
6272 + {error, undefined, Rez}.
6274 diff --git src/mod_muc/mod_muc_room.erl src/mod_muc/mod_muc_room.erl
6275 index 02c83ed..7693b66 100644
6276 --- src/mod_muc/mod_muc_room.erl
6277 +++ src/mod_muc/mod_muc_room.erl
6278 @@ -726,6 +726,12 @@ handle_sync_event({change_config, Config}, _From, StateName, StateData) ->
6279 {reply, {ok, NSD#state.config}, StateName, NSD};
6280 handle_sync_event({change_state, NewStateData}, _From, StateName, _StateData) ->
6281 {reply, {ok, NewStateData}, StateName, NewStateData};
6282 +handle_sync_event({get_jid_nick, Jid}, _From, StateName, StateData) ->
6283 + R = case ?DICT:find(jlib:jid_tolower(Jid), StateData#state.users) of
6285 + {ok, {user, _, Nick, _, _}} -> Nick
6287 + {reply, R, StateName, StateData};
6288 handle_sync_event(_Event, _From, StateName, StateData) ->
6290 {reply, Reply, StateName, StateData}.
6291 diff --git src/mod_roster.erl src/mod_roster.erl
6292 index b15497f..ace8ba7 100644
6293 --- src/mod_roster.erl
6294 +++ src/mod_roster.erl
6296 -include("web/ejabberd_http.hrl").
6297 -include("web/ejabberd_web_admin.hrl").
6299 +-include("mod_logdb.hrl").
6302 start(Host, Opts) ->
6303 IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
6304 @@ -1334,6 +1336,14 @@ user_roster(User, Server, Query, Lang) ->
6305 Res = user_roster_parse_query(User, Server, Items1, Query),
6306 Items = get_roster(LUser, LServer),
6307 SItems = lists:sort(Items),
6309 + Settings = case gen_mod:is_loaded(Server, mod_logdb) of
6311 + mod_logdb:get_user_settings(User, Server);
6319 @@ -1381,7 +1391,33 @@ user_roster(User, Server, Query, Lang) ->
6322 ejabberd_web_admin:term_to_id(R#roster.jid),
6325 + case gen_mod:is_loaded(Server, mod_logdb) of
6327 + Peer = jlib:jid_to_string(R#roster.jid),
6328 + A = lists:member(Peer, Settings#user_settings.dolog_list),
6329 + B = lists:member(Peer, Settings#user_settings.donotlog_list),
6333 + {"donotlog", "Do Not Log Messages"};
6335 + {"dolog", "Log Messages"};
6336 + Settings#user_settings.dolog_default == true ->
6337 + {"donotlog", "Do Not Log Messages"};
6338 + Settings#user_settings.dolog_default == false ->
6339 + {"dolog", "Log Messages"}
6342 + ?XAE("td", [{"class", "valign"}],
6343 + [?INPUTT("submit",
6345 + ejabberd_web_admin:term_to_id(R#roster.jid),
6353 [?XC("h1", ?T("Roster of ") ++ us_to_list(US))] ++
6354 @@ -1481,11 +1517,42 @@ user_roster_item_parse_query(User, Server, Items, Query) ->
6355 {"subscription", "remove"}],
6364 + case lists:keysearch(
6365 + "donotlog" ++ ejabberd_web_admin:term_to_id(JID), 1, Query) of
6367 + Peer = jlib:jid_to_string(JID),
6368 + Settings = mod_logdb:get_user_settings(User, Server),
6369 + DNLL = case lists:member(Peer, Settings#user_settings.donotlog_list) of
6370 + false -> lists:append(Settings#user_settings.donotlog_list, [Peer]);
6371 + true -> Settings#user_settings.donotlog_list
6373 + DLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.dolog_list),
6374 + Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
6375 + % TODO: check returned value
6376 + ok = mod_logdb:set_user_settings(User, Server, Sett),
6379 + case lists:keysearch(
6380 + "dolog" ++ ejabberd_web_admin:term_to_id(JID), 1, Query) of
6382 + Peer = jlib:jid_to_string(JID),
6383 + Settings = mod_logdb:get_user_settings(User, Server),
6384 + DLL = case lists:member(Peer, Settings#user_settings.dolog_list) of
6385 + false -> lists:append(Settings#user_settings.dolog_list, [Peer]);
6386 + true -> Settings#user_settings.dolog_list
6388 + DNLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.donotlog_list),
6389 + Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
6390 + % TODO: check returned value
6391 + ok = mod_logdb:set_user_settings(User, Server, Sett),
6402 diff --git src/msgs/nl.msg src/msgs/nl.msg
6403 index 70e739f..019b7b4 100644
6406 @@ -419,3 +419,15 @@
6407 {"Your Jabber account was successfully created.","Uw Jabber-account is succesvol gecreeerd."}.
6408 {"Your Jabber account was successfully deleted.","Uw Jabber-account is succesvol verwijderd."}.
6409 {"Your messages to ~s are being blocked. To unblock them, visit ~s","Uw berichten aan ~s worden geblokkeerd. Om ze te deblokkeren, ga naar ~s"}.
6411 +{"Users Messages", "Gebruikersberichten"}.
6413 +{"Count", "Aantal"}.
6414 +{"Logged messages for ", "Gelogde berichten van "}.
6416 +{"No logged messages for ", "Geen gelogde berichten van "}.
6417 +{"Date, Time", "Datum en tijd"}.
6418 +{"Direction: Jid", "Richting: Jabber ID"}.
6419 +{"Subject", "Onderwerp"}.
6420 +{"Body", "Berichtveld"}.
6421 +{"Messages", "Berichten"}.
6422 diff --git src/msgs/pl.msg src/msgs/pl.msg
6423 index 4bc2063..4395f3c 100644
6426 @@ -419,3 +419,27 @@
6427 {"Your Jabber account was successfully created.","Twoje konto zostało stworzone."}.
6428 {"Your Jabber account was successfully deleted.","Twoje konto zostało usunięte."}.
6429 {"Your messages to ~s are being blocked. To unblock them, visit ~s","Twoje wiadomości do ~s są blokowane. Aby je odblokować, odwiedź ~s"}.
6431 +{"Users Messages", "Wiadomości użytkownika"}.
6433 +{"Count", "Liczba"}.
6434 +{"Logged messages for ", "Zapisane wiadomości dla "}.
6436 +{"No logged messages for ", "Brak zapisanych wiadomości dla "}.
6437 +{"Date, Time", "Data, Godzina"}.
6438 +{"Direction: Jid", "Kierunek: Jid"}.
6439 +{"Subject", "Temat"}.
6441 +{"Messages","Wiadomości"}.
6442 +{"Filter Selected", "Odfiltruj zaznaczone"}.
6443 +{"Do Not Log Messages", "Nie zapisuj wiadomości"}.
6444 +{"Log Messages", "Zapisuj wiadomości"}.
6445 +{"Messages logging engine", "System zapisywania historii rozmów"}.
6446 +{"Default", "Domyślne"}.
6447 +{"Set logging preferences", "Ustaw preferencje zapisywania"}.
6448 +{"Messages logging engine settings", "Ustawienia systemu logowania"}.
6449 +{"Set run-time settings", "Zapisz ustawienia systemu logowania"}.
6450 +{"Groupchat messages logging", "Zapisywanie rozmów z konferencji"}.
6451 +{"Jids/Domains to ignore", "JID/Domena która ma być ignorowana"}.
6452 +{"Purge messages older than (days)", "Usuń wiadomości starsze niż (w dniach)"}.
6453 +{"Poll users settings (seconds)", "Czas aktualizacji preferencji użytkowników (sekundy)"}.
6454 diff --git src/msgs/ru.msg src/msgs/ru.msg
6455 index ece7348..99879ec 100644
6458 @@ -419,3 +419,31 @@
6459 {"Your Jabber account was successfully created.","Ваш Jabber-аккаунт был успешно создан."}.
6460 {"Your Jabber account was successfully deleted.","Ваш Jabber-аккаунт был успешно удален."}.
6461 {"Your messages to ~s are being blocked. To unblock them, visit ~s","Ваши сообщения к ~s блокируются. Для снятия блокировки перейдите по ссылке ~s"}.
6463 +{"Users Messages", "Сообщения пользователей"}.
6465 +{"Count", "Количество"}.
6466 +{"Logged messages for ", "Сохранённые cообщения для "}.
6468 +{"No logged messages for ", "Отсутствуют сообщения для "}.
6469 +{"Date, Time", "Дата, Время"}.
6470 +{"Direction: Jid", "Направление: Jid"}.
6471 +{"Subject", "Тема"}.
6473 +{"Messages", "Сообщения"}.
6474 +{"Filter Selected", "Отфильтровать выделенные"}.
6475 +{"Do Not Log Messages", "Не сохранять сообщения"}.
6476 +{"Log Messages", "Сохранять сообщения"}.
6477 +{"Messages logging engine", "Система логирования сообщений"}.
6478 +{"Default", "По умолчанию"}.
6479 +{"Set logging preferences", "Задайте настройки логирования"}.
6480 +{"Messages logging engine users", "Пользователи системы логирования сообщений"}.
6481 +{"Messages logging engine settings", "Настройки системы логирования сообщений"}.
6482 +{"Set run-time settings", "Задайте текущие настройки"}.
6483 +{"Groupchat messages logging", "Логирование сообщений типа groupchat"}.
6484 +{"Jids/Domains to ignore", "Игнорировать следующие jids/домены"}.
6485 +{"Purge messages older than (days)", "Удалять сообщения старее чем (дни)"}.
6486 +{"Poll users settings (seconds)", "Обновлять настройки пользователей через (секунд)"}.
6487 +{"Drop", "Удалять"}.
6488 +{"Do not drop", "Не удалять"}.
6489 +{"Drop messages on user removal", "Удалять сообщения при удалении пользователя"}.
6490 diff --git src/msgs/uk.msg src/msgs/uk.msg
6491 index 6e21c90..1cdd1ea 100644
6494 @@ -407,3 +407,31 @@
6495 {"Your Jabber account was successfully created.","Ваш Jabber-акаунт було успішно створено."}.
6496 {"Your Jabber account was successfully deleted.","Ваш Jabber-акаунт було успішно видалено."}.
6497 {"Your messages to ~s are being blocked. To unblock them, visit ~s","Ваші повідомлення до ~s блокуються. Для розблокування відвідайте ~s"}.
6499 +{"Users Messages", "Повідомлення користувачів"}.
6501 +{"Count", "Кількість"}.
6502 +{"Logged messages for ", "Збережені повідомлення для "}.
6504 +{"No logged messages for ", "Відсутні повідомлення для "}.
6505 +{"Date, Time", "Дата, Час"}.
6506 +{"Direction: Jid", "Напрямок: Jid"}.
6507 +{"Subject", "Тема"}.
6509 +{"Messages", "Повідомлення"}.
6510 +{"Filter Selected", "Відфільтрувати виділені"}.
6511 +{"Do Not Log Messages", "Не зберігати повідомлення"}.
6512 +{"Log Messages", "Зберігати повідомлення"}.
6513 +{"Messages logging engine", "Система збереження повідомлень"}.
6514 +{"Default", "За замовчуванням"}.
6515 +{"Set logging preferences", "Вкажіть налагоджування збереження повідомлень"}.
6516 +{"Messages logging engine users", "Користувачі системи збереження повідомлень"}.
6517 +{"Messages logging engine settings", "Налагоджування системи збереження повідомлень"}.
6518 +{"Set run-time settings", "Вкажіть поточні налагоджування"}.
6519 +{"Groupchat messages logging", "Збереження повідомлень типу groupchat"}.
6520 +{"Jids/Domains to ignore", "Ігнорувати наступні jids/домени"}.
6521 +{"Purge messages older than (days)", "Видаляти повідомлення старіші ніж (дні)"}.
6522 +{"Poll users settings (seconds)", "Оновлювати налагоджування користувачів кожні (секунд)"}.
6523 +{"Drop", "Видаляти"}.
6524 +{"Do not drop", "Не видаляти"}.
6525 +{"Drop messages on user removal", "Видаляти повідомлення під час видалення користувача"}.