1 --- src/mod_logdb.erl.orig Tue Dec 11 14:23:19 2007
2 +++ src/mod_logdb.erl Thu Sep 20 15:26:21 2007
4 +%%%----------------------------------------------------------------------
5 +%%% File : mod_logdb.erl
6 +%%% Author : Oleg Palij (mailto:o.palij@gmail.com xmpp://malik@jabber.te.ua)
7 +%%% Purpose : Frontend for log user messages to db
10 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
11 +%%%----------------------------------------------------------------------
14 +-author('o.palij@gmail.com').
17 +-behaviour(gen_server).
21 +-export([start_link/2]).
23 +-export([start/2,stop/1]).
25 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
27 +-export([send_packet/3, receive_packet/4, offline_packet/3]).
28 +-export([get_local_identity/5,
29 + get_local_features/5,
31 + adhoc_local_items/4,
32 + adhoc_local_commands/4
37 +% adhoc_sm_commands/4]).
40 +-export([rebuild_stats/3,
41 + copy_messages/1, copy_messages_ctl/3, copy_messages_int_tc/1]).
43 +-export([get_vhost_stats/1, get_vhost_stats_at/2,
44 + get_user_stats/2, get_user_messages_at/3,
47 + convert_timestamp/1, convert_timestamp_brief/1,
48 + get_user_settings/2, set_user_settings/3,
49 + user_messages_at_parse_query/4, user_messages_parse_query/3,
50 + vhost_messages_parse_query/2, vhost_messages_at_parse_query/4,
51 + list_to_bool/1, bool_to_list/1,
52 + list_to_string/1, string_to_list/1,
53 + get_module_settings/1, set_module_settings/2,
54 + purge_old_records/2]).
56 +-include("mod_logdb.hrl").
57 +-include("ejabberd.hrl").
58 +-include("jlib.hrl").
59 +-include("ejabberd_ctl.hrl").
60 +-include("adhoc.hrl").
62 +-define(PROCNAME, ejabberd_mod_logdb).
63 +% gen_server call timeout
64 +-define(CALL_TIMEOUT, 60000).
66 +-record(state, {vhost, dbmod, backendPid, monref, purgeRef, pollRef, dbopts, dbs, dolog_default, ignore_jids, groupchat, purge_older_days, poll_users_settings}).
68 +ets_settings_table(VHost) -> list_to_atom("ets_logdb_settings_" ++ VHost).
70 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
72 +% gen_mod/gen_server callbacks
74 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
75 +% ejabberd starts module
76 +start(VHost, Opts) ->
78 + {gen_mod:get_module_proc(VHost, ?PROCNAME),
79 + {?MODULE, start_link, [VHost, Opts]},
84 + % add child to ejabberd_sup
85 + supervisor:start_child(ejabberd_sup, ChildSpec).
87 +% supervisor starts gen_server
88 +start_link(VHost, Opts) ->
89 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
90 + gen_server:start_link({local, Proc}, ?MODULE, [VHost, Opts], []).
92 +init([VHost, Opts]) ->
93 + process_flag(trap_exit, true),
94 + DBs = gen_mod:get_opt(dbs, Opts, [{mnesia, []}]),
95 + VHostDB = gen_mod:get_opt(vhosts, Opts, [{VHost, mnesia}]),
96 + % 10 is default becouse of using in clustered environment
97 + PollUsersSettings = gen_mod:get_opt(poll_users_settings, Opts, 10),
99 + {value,{_, DBName}} = lists:keysearch(VHost, 1, VHostDB),
100 + {value, {DBName, DBOpts}} = lists:keysearch(DBName, 1, DBs),
102 + ?MYDEBUG("Starting mod_logdb for ~p with ~p backend", [VHost, DBName]),
104 + DBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(DBName)),
106 + % actually all work begin on receiving start signal
107 + timer:send_after(1000, start),
109 + {ok, #state{vhost=VHost,
112 + % dbs used for convert messages from one backend to other
114 + dolog_default=gen_mod:get_opt(dolog_default, Opts, true),
115 + ignore_jids=gen_mod:get_opt(ignore_jids, Opts, []),
116 + groupchat=gen_mod:get_opt(groupchat, Opts, none),
117 + purge_older_days=gen_mod:get_opt(purge_older_days, Opts, never),
118 + poll_users_settings=PollUsersSettings}}.
120 +cleanup(#state{vhost=VHost} = State) ->
121 + ?MYDEBUG("Stopping ~s for ~p", [?MODULE, VHost]),
123 + %ets:delete(ets_settings_table(VHost)),
125 + ejabberd_hooks:delete(user_send_packet, VHost, ?MODULE, send_packet, 90),
126 + ejabberd_hooks:delete(user_receive_packet, VHost, ?MODULE, receive_packet, 90),
127 + ejabberd_hooks:delete(offline_message_hook, VHost, ?MODULE, offline_packet, 10),
128 + %ejabberd_hooks:delete(adhoc_sm_commands, VHost, ?MODULE, adhoc_sm_commands, 110),
129 + %ejabberd_hooks:delete(adhoc_sm_items, VHost, ?MODULE, adhoc_sm_items, 110),
130 + ejabberd_hooks:delete(adhoc_local_commands, VHost, ?MODULE, adhoc_local_commands, 110),
131 + ejabberd_hooks:delete(adhoc_local_items, VHost, ?MODULE, adhoc_local_items, 110),
132 + %ejabberd_hooks:delete(disco_sm_identity, VHost, ?MODULE, get_sm_identity, 110),
133 + %ejabberd_hooks:delete(disco_sm_features, VHost, ?MODULE, get_sm_features, 110),
134 + %ejabberd_hooks:delete(disco_sm_items, VHost, ?MODULE, get_sm_items, 110),
135 + ejabberd_hooks:delete(disco_local_identity, VHost, ?MODULE, get_local_identity, 110),
136 + ejabberd_hooks:delete(disco_local_features, VHost, ?MODULE, get_local_features, 110),
137 + ejabberd_hooks:delete(disco_local_items, VHost, ?MODULE, get_local_items, 110),
139 + ?MYDEBUG("Removed hooks for ~p", [VHost]),
141 + ejabberd_ctl:unregister_commands(VHost, [{"rebuild_stats", "rebuild mod_logdb module stats for vhost"}], ?MODULE, rebuild_stats),
142 + Supported_backends = lists:flatmap(fun({Backend, _Opts}) ->
143 + [atom_to_list(Backend), " "]
144 + end, State#state.dbs),
145 + ejabberd_ctl:unregister_commands(
147 + [{"copy_messages backend", "copy messages from backend to current backend. backends could be: " ++ Supported_backends }],
148 + ?MODULE, copy_messages_ctl),
149 + ?MYDEBUG("Unregistered commands for ~p", [VHost]).
152 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
153 + %gen_server:call(Proc, {cleanup}),
154 + %?MYDEBUG("Cleanup in stop finished!!!!", []),
155 + %timer:sleep(10000),
156 + ok = supervisor:terminate_child(ejabberd_sup, Proc),
157 + ok = supervisor:delete_child(ejabberd_sup, Proc).
159 +handle_call({cleanup}, _From, State) ->
161 + ?MYDEBUG("Cleanup finished!!!!!", []),
162 + {reply, ok, State};
163 +handle_call({get_dates}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
164 + Reply = DBMod:get_dates(VHost),
165 + {reply, Reply, State};
166 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
167 +% ejabberd_web_admin callbacks
168 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
169 +handle_call({delete_messages_by_user_at, PMsgs, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
170 + Reply = DBMod:delete_messages_by_user_at(VHost, PMsgs, Date),
171 + {reply, Reply, State};
172 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
173 + Reply = DBMod:delete_all_messages_by_user_at(User, VHost, Date),
174 + {reply, Reply, State};
175 +handle_call({delete_messages_at, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
176 + Reply = DBMod:delete_messages_at(VHost, Date),
177 + {reply, Reply, State};
178 +handle_call({get_vhost_stats}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
179 + Reply = DBMod:get_vhost_stats(VHost),
180 + {reply, Reply, State};
181 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
182 + Reply = DBMod:get_vhost_stats_at(VHost, Date),
183 + {reply, Reply, State};
184 +handle_call({get_user_stats, User}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
185 + Reply = DBMod:get_user_stats(User, VHost),
186 + {reply, Reply, State};
187 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
188 + Reply = DBMod:get_user_messages_at(User, VHost, Date),
189 + {reply, Reply, State};
190 +handle_call({get_user_settings, User}, _From, #state{dbmod=_DBMod, vhost=VHost}=State) ->
191 + Reply = case ets:match_object(ets_settings_table(VHost),
192 + #user_settings{owner_name=User, _='_'}) of
194 + _ -> #user_settings{owner_name=User,
195 + dolog_default=State#state.dolog_default,
199 + {reply, Reply, State};
200 +% TODO: remove User ??
201 +handle_call({set_user_settings, User, GSet}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
202 + Set = GSet#user_settings{owner_name=User},
204 + case ets:match_object(ets_settings_table(VHost),
205 + #user_settings{owner_name=User, _='_'}) of
207 + ?MYDEBUG("Settings is equal", []),
210 + case DBMod:set_user_settings(User, VHost, Set) of
214 + true = ets:insert(ets_settings_table(VHost), Set),
218 + {reply, Reply, State};
219 +handle_call({get_module_settings}, _From, State) ->
220 + {reply, State, State};
221 +handle_call({set_module_settings, #state{purge_older_days=PurgeDays,
222 + poll_users_settings=PollSec} = Settings},
224 + #state{purgeRef=PurgeRefOld,
225 + pollRef=PollRefOld,
226 + purge_older_days=PurgeDaysOld,
227 + poll_users_settings=PollSecOld} = State) ->
229 + PurgeDays == never, PurgeDaysOld /= never ->
230 + {ok, cancel} = timer:cancel(PurgeRefOld),
232 + is_integer(PurgeDays), PurgeDaysOld == never ->
233 + set_purge_timer(PurgeDays);
239 + PollSec == PollSecOld ->
241 + PollSec == 0, PollSecOld /= 0 ->
242 + {ok, cancel} = timer:cancel(PollRefOld),
244 + is_integer(PollSec), PollSecOld == 0 ->
245 + set_poll_timer(PollSec);
246 + is_integer(PollSec), PollSecOld /= 0 ->
247 + {ok, cancel} = timer:cancel(PollRefOld),
248 + set_poll_timer(PollSec)
251 + NewState = State#state{dolog_default=Settings#state.dolog_default,
252 + ignore_jids=Settings#state.ignore_jids,
253 + groupchat=Settings#state.groupchat,
254 + purge_older_days=PurgeDays,
255 + poll_users_settings=PollSec,
258 + {reply, ok, NewState};
259 +handle_call(Msg, _From, State) ->
260 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
262 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
263 +% end ejabberd_web_admin callbacks
264 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
266 +% ejabberd_hooks call
267 +handle_cast({addlog, Direction, Owner, Peer, Packet}, #state{dbmod=DBMod, vhost=VHost}=State) ->
268 + case filter(Owner, Peer, State) of
270 + case catch packet_parse(Owner, Peer, Packet, Direction, State) of
273 + {'EXIT', Reason} ->
274 + ?ERROR_MSG("Failed to parse: ~p", [Reason]);
276 + DBMod:log_message(VHost, Msg)
282 +% ejabberdctl rebuild_stats/3
283 +handle_cast({rebuild_stats}, #state{dbmod=DBMod, vhost=VHost}=State) ->
284 + % TODO: maybe spawn?
285 + DBMod:rebuild_stats(VHost),
287 +handle_cast({copy_messages, Backend}, State) ->
288 + spawn(?MODULE, copy_messages, [[State, Backend]]),
290 +handle_cast({copy_messages, Backend, Date}, State) ->
291 + spawn(?MODULE, copy_messages, [[State, Backend, Date]]),
293 +handle_cast(Msg, State) ->
294 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
297 +% return: disabled | timer reference
298 +set_purge_timer(PurgeDays) ->
301 + Days when is_integer(Days) ->
302 + {ok, Ref1} = timer:send_interval(timer:hours(24), scheduled_purging),
306 +% return: disabled | timer reference
307 +set_poll_timer(PollSec) ->
310 + {ok, Ref2} = timer:send_interval(timer:seconds(PollSec), poll_users_settings),
312 + % db polling disabled
316 + {ok, Ref3} = timer:send_interval(timer:seconds(10), poll_users_settings),
320 +% actual starting of logging
321 +% from timer:send_after (in init)
322 +handle_info(start, #state{dbmod=DBMod, vhost=VHost}=State) ->
323 + case DBMod:start(VHost, State#state.dbopts) of
324 + {error, _Reason} ->
325 + timer:sleep(30000),
326 + {stop, db_connection_failed, State};
329 + ?INFO_MSG("~p connection established", [DBMod]),
331 + MonRef = erlang:monitor(process, SPid),
333 + ets:new(ets_settings_table(VHost), [named_table,public,set,{keypos, #user_settings.owner_name}]),
334 + {ok, DoLog} = DBMod:get_users_settings(VHost),
335 + ets:insert(ets_settings_table(VHost), DoLog),
337 + TrefPurge = set_purge_timer(State#state.purge_older_days),
338 + TrefPoll = set_poll_timer(State#state.poll_users_settings),
340 + ejabberd_hooks:add(user_send_packet, VHost, ?MODULE, send_packet, 90),
341 + ejabberd_hooks:add(user_receive_packet, VHost, ?MODULE, receive_packet, 90),
342 + ejabberd_hooks:add(offline_message_hook, VHost, ?MODULE, offline_packet, 10),
344 + ejabberd_hooks:add(disco_local_items, VHost, ?MODULE, get_local_items, 110),
345 + ejabberd_hooks:add(disco_local_features, VHost, ?MODULE, get_local_features, 110),
346 + ejabberd_hooks:add(disco_local_identity, VHost, ?MODULE, get_local_identity, 110),
347 + %ejabberd_hooks:add(disco_sm_items, VHost, ?MODULE, get_sm_items, 110),
348 + %ejabberd_hooks:add(disco_sm_features, VHost, ?MODULE, get_sm_features, 110),
349 + %ejabberd_hooks:add(disco_sm_identity, VHost, ?MODULE, get_sm_identity, 110),
350 + ejabberd_hooks:add(adhoc_local_items, VHost, ?MODULE, adhoc_local_items, 110),
351 + ejabberd_hooks:add(adhoc_local_commands, VHost, ?MODULE, adhoc_local_commands, 110),
352 + %ejabberd_hooks:add(adhoc_sm_items, VHost, ?MODULE, adhoc_sm_items, 110),
353 + %ejabberd_hooks:add(adhoc_sm_commands, VHost, ?MODULE, adhoc_sm_commands, 110),
355 + ?MYDEBUG("Added hooks for ~p", [VHost]),
357 + ejabberd_ctl:register_commands(
359 + [{"rebuild_stats", "rebuild mod_logdb module stats for vhost"}],
360 + ?MODULE, rebuild_stats),
361 + Supported_backends = lists:flatmap(fun({Backend, _Opts}) ->
362 + [atom_to_list(Backend), " "]
363 + end, State#state.dbs),
364 + ejabberd_ctl:register_commands(
366 + [{"copy_messages backend", "copy messages from backend to current backend. backends could be: " ++ Supported_backends }],
367 + ?MODULE, copy_messages_ctl),
368 + ?MYDEBUG("Registered commands for ~p", [VHost]),
370 + NewState=State#state{monref = MonRef, backendPid=SPid, purgeRef=TrefPurge, pollRef=TrefPoll},
371 + {noreply, NewState};
373 + ?ERROR_MSG("Rez=~p", [Rez]),
374 + timer:sleep(30000),
375 + {stop, db_connection_failed, State}
377 +% from timer:send_interval/2 (in start handle_info)
378 +handle_info(scheduled_purging, #state{vhost=VHost, purge_older_days=Days} = State) ->
379 + ?MYDEBUG("Starting scheduled purging of old records for ~p", [VHost]),
380 + spawn(?MODULE, purge_old_records, [VHost, integer_to_list(Days)]),
382 +% from timer:send_interval/2 (in start handle_info)
383 +handle_info(poll_users_settings, #state{dbmod=DBMod, vhost=VHost}=State) ->
384 + {ok, DoLog} = DBMod:get_users_settings(VHost),
385 + ?MYDEBUG("DoLog=~p", [DoLog]),
386 + true = ets:delete_all_objects(ets_settings_table(VHost)),
387 + ets:insert(ets_settings_table(VHost), DoLog),
389 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
390 + {stop, db_connection_dropped, State};
391 +handle_info({fetch_result, _, _}, State) ->
392 + ?MYDEBUG("Got timed out mysql fetch result", []),
394 +handle_info(Info, State) ->
395 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
398 +terminate(db_connection_failed, _State) ->
400 +terminate(db_connection_dropped, State) ->
403 +terminate(_Reason, #state{monref=undefined} = State) ->
406 +terminate(Reason, #state{dbmod=DBMod, vhost=VHost, monref=MonRef, backendPid=Pid} = State) ->
407 + ?INFO_MSG("Reason: ~p", [Reason]),
408 + case erlang:is_process_alive(Pid) of
410 + erlang:demonitor(MonRef, [flush]),
418 +code_change(_OldVsn, State, _Extra) ->
421 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
423 +% ejabberd_hooks callbacks
425 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
426 +% TODO: change to/from to list as sql stores it as list
427 +send_packet(Owner, Peer, P) ->
428 + VHost = Owner#jid.lserver,
429 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
430 + gen_server:cast(Proc, {addlog, to, Owner, Peer, P}).
432 +offline_packet(Peer, Owner, P) ->
433 + VHost = Owner#jid.lserver,
434 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
435 + gen_server:cast(Proc, {addlog, from, Owner, Peer, P}).
437 +receive_packet(_JID, Peer, Owner, P) ->
438 + VHost = Owner#jid.lserver,
439 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
440 + gen_server:cast(Proc, {addlog, from, Owner, Peer, P}).
442 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
446 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
447 +rebuild_stats(_Val, VHost, ["rebuild_stats"]) ->
448 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
449 + gen_server:cast(Proc, {rebuild_stats}),
450 + {stop, ?STATUS_SUCCESS};
451 +rebuild_stats(Val, _VHost, _Args) ->
454 +copy_messages_ctl(_Val, VHost, ["copy_messages", Backend]) ->
455 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
456 + gen_server:cast(Proc, {copy_messages, Backend}),
457 + {stop, ?STATUS_SUCCESS};
458 +copy_messages_ctl(_Val, VHost, ["copy_messages", Backend, Date]) ->
459 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
460 + gen_server:cast(Proc, {copy_messages, Backend, Date}),
461 + {stop, ?STATUS_SUCCESS};
462 +copy_messages_ctl(Val, _VHost, _Args) ->
464 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
468 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
470 +% handle_cast({addlog, E}, _)
471 +% raw packet -> #msg
472 +packet_parse(Owner, Peer, Packet, Direction, State) ->
473 + case xml:get_subtag(Packet, "body") of
478 + case xml:get_tag_attr_s("type", Packet) of
483 + case Message_type of
484 + "groupchat" when State#state.groupchat == send, Direction == to ->
486 + "groupchat" when State#state.groupchat == send, Direction == from ->
488 + "groupchat" when State#state.groupchat == half ->
489 + Rooms = ets:match(muc_online_room, '$1'),
490 + Ni=lists:foldl(fun([{muc_online_room, {GName, GHost}, Pid}], Names) ->
491 + case gen_fsm:sync_send_all_state_event(Pid, {get_jid_nick,Owner}) of
494 + lists:append(Names, [jlib:jid_to_string({GName, GHost, Nick})])
497 + case lists:member(jlib:jid_to_string(Peer), Ni) of
498 + true when Direction == from ->
503 + "groupchat" when State#state.groupchat == none ->
509 + Message_body = xml:get_tag_cdata(Body_xml),
511 + case xml:get_subtag(Packet, "subject") of
515 + xml:get_tag_cdata(Subject_xml)
518 + OwnerName = stringprep:tolower(Owner#jid.user),
519 + PName = stringprep:tolower(Peer#jid.user),
520 + PServer = stringprep:tolower(Peer#jid.server),
521 + PResource = Peer#jid.resource,
523 + #msg{timestamp=get_timestamp(),
524 + owner_name=OwnerName,
526 + peer_server=PServer,
527 + peer_resource=PResource,
528 + direction=Direction,
530 + subject=Message_subject,
534 +% called from handle_cast({addlog, _}, _) -> true (log messages) | false (do not log messages)
535 +filter(Owner, Peer, State) ->
536 + OwnerStr = Owner#jid.luser++"@"++Owner#jid.lserver,
537 + OwnerServ = "@"++Owner#jid.lserver,
538 + PeerStr = Peer#jid.luser++"@"++Peer#jid.lserver,
539 + PeerServ = "@"++Peer#jid.lserver,
541 + LogTo = case ets:match_object(ets_settings_table(State#state.vhost),
542 + #user_settings{owner_name=Owner#jid.luser, _='_'}) of
543 + [#user_settings{dolog_default=Default,
545 + donotlog_list=DNLL}] ->
546 + A = lists:member(PeerStr, DLL),
547 + B = lists:member(PeerStr, DNLL),
551 + Default == true -> true;
552 + Default == false -> false;
553 + true -> State#state.dolog_default
555 + _ -> State#state.dolog_default
558 + lists:all(fun(O) -> O end,
559 + [not lists:member(OwnerStr, State#state.ignore_jids),
560 + not lists:member(PeerStr, State#state.ignore_jids),
561 + not lists:member(OwnerServ, State#state.ignore_jids),
562 + not lists:member(PeerServ, State#state.ignore_jids),
565 +purge_old_records(VHost, Days) ->
566 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
568 + Dates = gen_server:call(Proc, {get_dates, {VHost}}),
569 + DateNow = calendar:datetime_to_gregorian_seconds({date(), {0,0,1}}),
570 + DateDiff = list_to_integer(Days)*24*60*60,
571 + ?MYDEBUG("Purging tables older than ~s days", [Days]),
572 + lists:foreach(fun(Date) ->
573 + {ok, [Year, Month, Day]} = regexp:split(Date, "[^0-9]+"),
574 + DateInSec = calendar:datetime_to_gregorian_seconds({{list_to_integer(Year), list_to_integer(Month), list_to_integer(Day)}, {0,0,1}}),
576 + (DateNow - DateInSec) > DateDiff ->
577 + gen_server:call(Proc, {delete_messages_at, Date});
579 + ?MYDEBUG("Skipping messages at ~p", [Date])
583 +% called from get_vhost_stats/2, get_user_stats/3
584 +sort_stats(Stats) ->
585 + % Stats = [{"2003-4-15",1}, {"2006-8-18",1}, ... ]
586 + CFun = fun({TableName, Count}) ->
587 + {ok, [Year, Month, Day]} = regexp:split(TableName, "[^0-9]+"),
588 + { calendar:datetime_to_gregorian_seconds({{list_to_integer(Year), list_to_integer(Month), list_to_integer(Day)}, {0,0,1}}), Count }
590 + % convert to [{63364377601,1}, {63360662401,1}, ... ]
591 + CStats = lists:map(CFun, Stats),
593 + SortedStats = lists:reverse(lists:keysort(1, CStats)),
594 + % convert to [{"2007-12-9",1}, {"2007-10-27",1}, ... ] sorted list
595 + [{mod_logdb:convert_timestamp_brief(TableSec), Count} || {TableSec, Count} <- SortedStats].
597 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
599 +% Date/Time operations
601 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
602 +% return float seconds elapsed from "zero hour" as list
604 + {MegaSec, Sec, MicroSec} = now(),
605 + [List] = io_lib:format("~.5f", [MegaSec*1000000 + Sec + MicroSec/1000000]),
608 +% convert float seconds elapsed from "zero hour" to local time "%Y-%m-%d %H:%M:%S" string
609 +convert_timestamp(Seconds) when is_list(Seconds) ->
610 + case string:to_float(Seconds++".0") of
611 + {F,_} when is_float(F) -> convert_timestamp(F);
612 + _ -> erlang:error(badarg, [Seconds])
614 +convert_timestamp(Seconds) when is_float(Seconds) ->
615 + GregSec = trunc(Seconds + 719528*86400),
616 + UnivDT = calendar:gregorian_seconds_to_datetime(GregSec),
617 + {{Year, Month, Day},{Hour, Minute, Sec}} = calendar:universal_time_to_local_time(UnivDT),
618 + integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day) ++ " " ++ integer_to_list(Hour) ++ ":" ++ integer_to_list(Minute) ++ ":" ++ integer_to_list(Sec).
620 +% convert float seconds elapsed from "zero hour" to local time "%Y-%m-%d" string
621 +convert_timestamp_brief(Seconds) when is_list(Seconds) ->
622 + convert_timestamp_brief(list_to_float(Seconds));
623 +convert_timestamp_brief(Seconds) when is_float(Seconds) ->
624 + GregSec = trunc(Seconds + 719528*86400),
625 + UnivDT = calendar:gregorian_seconds_to_datetime(GregSec),
626 + {{Year, Month, Day},{_Hour, _Minute, _Sec}} = calendar:universal_time_to_local_time(UnivDT),
627 + integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day);
628 +convert_timestamp_brief(Seconds) when is_integer(Seconds) ->
629 + {{Year, Month, Day},{_Hour, _Minute, _Sec}} = calendar:gregorian_seconds_to_datetime(Seconds),
630 + integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day).
632 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
634 +% DB operations (get)
636 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
637 +get_vhost_stats(VHost) ->
638 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
639 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
641 +get_vhost_stats_at(VHost, Date) ->
642 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
643 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
645 +get_user_stats(User, VHost) ->
646 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
647 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
649 +get_user_messages_at(User, VHost, Date) ->
650 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
651 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
654 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
655 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
657 +get_user_settings(User, VHost) ->
658 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
659 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
661 +set_user_settings(User, VHost, Set) ->
662 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
663 + gen_server:call(Proc, {set_user_settings, User, Set}).
665 +get_module_settings(VHost) ->
666 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
667 + gen_server:call(Proc, {get_module_settings}).
669 +set_module_settings(VHost, Settings) ->
670 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
671 + gen_server:call(Proc, {set_module_settings, Settings}).
673 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
675 +% Web admin callbacks (delete)
677 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
678 +user_messages_at_parse_query(VHost, Date, Msgs, Query) ->
679 + case lists:keysearch("delete", 1, Query) of
681 + PMsgs = lists:filter(
683 + ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg#msg.timestamp))),
684 + lists:member({"selected", ID}, Query)
686 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
687 + gen_server:call(Proc, {delete_messages_by_user_at, PMsgs, Date}, ?CALL_TIMEOUT);
692 +user_messages_parse_query(User, VHost, Query) ->
693 + Dates = get_dates(VHost),
694 + case lists:keysearch("delete", 1, Query) of
696 + PDates = lists:filter(
698 + ID = jlib:encode_base64(binary_to_list(term_to_binary(User++Date))),
699 + lists:member({"selected", ID}, Query)
701 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
705 + [gen_server:call(Proc,
706 + {delete_all_messages_by_user_at, User, Date},
709 + case lists:member(error, Rez) of
719 +vhost_messages_parse_query(VHost, Query) ->
720 + Dates = get_dates(VHost),
721 + case lists:keysearch("delete", 1, Query) of
723 + PDates = lists:filter(
725 + ID = jlib:encode_base64(binary_to_list(term_to_binary(VHost++Date))),
726 + lists:member({"selected", ID}, Query)
728 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
729 + Rez = lists:foldl(fun(Date, Acc) ->
730 + lists:append(Acc, [gen_server:call(Proc,
731 + {delete_messages_at, Date},
734 + case lists:member(error, Rez) of
744 +vhost_messages_at_parse_query(VHost, Date, Stats, Query) ->
745 + case lists:keysearch("delete", 1, Query) of
747 + PStats = lists:filter(
748 + fun({User, _Count}) ->
749 + ID = jlib:encode_base64(binary_to_list(term_to_binary(User++VHost))),
750 + lists:member({"selected", ID}, Query)
752 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
753 + Rez = lists:foldl(fun({User, _Count}, Acc) ->
754 + lists:append(Acc, [gen_server:call(Proc,
755 + {delete_all_messages_by_user_at,
759 + case lists:member(error, Rez) of
769 +copy_messages([#state{vhost=VHost}=State, From]) ->
770 + ?INFO_MSG("Going to copy messages from ~p for ~p", [From, VHost]),
772 + {FromDBName, FromDBOpts} =
773 + case lists:keysearch(list_to_atom(From), 1, State#state.dbs) of
774 + {value, {FN, FO}} ->
777 + ?ERROR_MSG("Failed to find record for ~p in dbs", [From]),
781 + FromDBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(FromDBName)),
783 + {ok, _FromPid} = FromDBMod:start(VHost, FromDBOpts),
785 + Dates = FromDBMod:get_dates(VHost),
786 + DatesLength = length(Dates),
788 + lists:foldl(fun(Date, Acc) ->
789 + case copy_messages_int([FromDBMod, State#state.dbmod, VHost, Date]) of
791 + ?INFO_MSG("Copied messages at ~p (~p/~p)", [Date, Acc, DatesLength]);
793 + ?ERROR_MSG("Failed to copy messages at ~p (~p/~p): ~p", [Date, Acc, DatesLength, Value]),
794 + FromDBMod:stop(VHost),
799 + ?INFO_MSG("Copied messages from ~p", [From]),
800 + FromDBMod:stop(VHost);
801 +copy_messages([#state{vhost=VHost}=State, From, Date]) ->
802 + {value, {FromDBName, FromDBOpts}} = lists:keysearch(list_to_atom(From), 1, State#state.dbs),
803 + FromDBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(FromDBName)),
804 + {ok, _FromPid} = FromDBMod:start(VHost, FromDBOpts),
805 + case catch copy_messages_int([FromDBMod, State#state.dbmod, VHost, Date]) of
806 + {'exit', Reason} ->
807 + ?ERROR_MSG("Failed to copy messages at ~p: ~p", [Date, Reason]);
809 + ?INFO_MSG("Copied messages at ~p", [Date]);
811 + ?ERROR_MSG("Failed to copy messages at ~p: ~p", [Date, Value])
813 + FromDBMod:stop(VHost).
815 +copy_messages_int([FromDBMod, ToDBMod, VHost, Date]) ->
816 + ets:new(mod_logdb_temp, [named_table, set, public]),
817 + {Time, Value} = timer:tc(?MODULE, copy_messages_int_tc, [[FromDBMod, ToDBMod, VHost, Date]]),
818 + ets:delete_all_objects(mod_logdb_temp),
819 + ets:delete(mod_logdb_temp),
820 + ?INFO_MSG("copy_messages at ~p elapsed ~p sec", [Date, Time/1000000]),
823 +copy_messages_int_tc([FromDBMod, ToDBMod, VHost, Date]) ->
824 + ?INFO_MSG("Going to copy messages from ~p for ~p at ~p", [FromDBMod, VHost, Date]),
826 + ok = FromDBMod:rebuild_stats_at(VHost, Date),
827 + catch mod_logdb:rebuild_stats_at(VHost, Date),
828 + {ok, FromStats} = FromDBMod:get_vhost_stats_at(VHost, Date),
829 + ToStats = case mod_logdb:get_vhost_stats_at(VHost, Date) of
830 + {ok, Stats} -> Stats;
834 + FromStatsS = lists:keysort(1, FromStats),
835 + ToStatsS = lists:keysort(1, ToStats),
837 + StatsLength = length(FromStats),
840 + % destination table is empty
841 + FromDBMod /= mod_logdb_mnesia_old, ToStats == [] ->
842 + fun({User, _Count}, Acc) ->
843 + {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date),
845 + lists:foldl(fun(Msg, MFAcc) ->
846 + ok = ToDBMod:log_message(VHost, Msg),
850 + ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
854 + % destination table is not empty
855 + FromDBMod /= mod_logdb_mnesia_old, ToStats /= [] ->
856 + fun({User, _Count}, Acc) ->
857 + {ok, ToMsgs} = ToDBMod:get_user_messages_at(User, VHost, Date),
858 + lists:foreach(fun(#msg{timestamp=Tst}) when length(Tst) == 16 ->
859 + ets:insert(mod_logdb_temp, {Tst});
860 + % mysql, pgsql removes final zeros after decimal point
861 + (#msg{timestamp=Tst}) when length(Tst) < 16 ->
862 + {F, _} = string:to_float(Tst++".0"),
863 + [T] = io_lib:format("~.5f", [F]),
864 + ets:insert(mod_logdb_temp, {T})
866 + {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date),
868 + lists:foldl(fun(#msg{timestamp=ToTimestamp} = Msg, MFAcc) ->
869 + case ets:member(mod_logdb_temp, ToTimestamp) of
871 + ok = ToDBMod:log_message(VHost, Msg),
872 + ets:insert(mod_logdb_temp, {ToTimestamp}),
879 + ets:delete_all_objects(mod_logdb_temp),
880 + ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
884 + % copying from mod_logmnesia
886 + fun({User, _Count}, Acc) ->
888 + case ToDBMod:get_user_messages_at(User, VHost, Date) of
892 + lists:foreach(fun(#msg{timestamp=Tst}) when length(Tst) == 16 ->
893 + ets:insert(mod_logdb_temp, {Tst});
894 + % mysql, pgsql removes final zeros after decimal point
895 + (#msg{timestamp=Tst}) when length(Tst) < 15 ->
896 + {F, _} = string:to_float(Tst++".0"),
897 + [T] = io_lib:format("~.5f", [F]),
898 + ets:insert(mod_logdb_temp, {T})
903 + {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date),
907 + fun({msg, TU, TS, TR, FU, FS, FR, Type, Subj, Body, Timest},
909 + [Timestamp] = if is_float(Timest) == true ->
910 + io_lib:format("~.5f", [Timest]);
911 + % early versions of mod_logmnesia
912 + is_integer(Timest) == true ->
913 + io_lib:format("~.5f", [Timest-719528*86400.0]);
915 + ?ERROR_MSG("Incorrect timestamp ~p", [Timest]),
918 + case ets:member(mod_logdb_temp, Timestamp) of
923 + TMsg = #msg{timestamp=Timestamp,
925 + peer_name=FU, peer_server=FS, peer_resource=FR,
928 + subject=Subj, body=Body},
929 + ok = ToDBMod:log_message(VHost, TMsg);
935 + FMsg = #msg{timestamp=Timestamp,
937 + peer_name=TU, peer_server=TS, peer_resource=TR,
940 + subject=Subj, body=Body},
941 + ok = ToDBMod:log_message(VHost, FMsg);
944 + ets:insert(mod_logdb_temp, {Timestamp}),
946 + true -> % not ets:member
949 + end, 0, Msgs), % foldl
951 + ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
955 + end, % if FromDBMod /= mod_logdb_mnesia_old
959 + ?INFO_MSG("No messages were found at ~p", [Date]);
960 + FromStatsS == ToStatsS ->
961 + ?INFO_MSG("Stats are equal at ~p", [Date]);
962 + FromStatsS /= ToStatsS ->
963 + lists:foldl(CopyFun, 0, FromStats),
964 + ok = ToDBMod:rebuild_stats_at(VHost, Date)
970 +list_to_bool(Num) ->
971 + case lists:member(Num, ["t", "true", "y", "yes", "1"]) of
975 + case lists:member(Num, ["f", "false", "n", "no", "0"]) of
983 +bool_to_list(true) ->
985 +bool_to_list(false) ->
988 +list_to_string([]) ->
990 +list_to_string(List) when is_list(List) ->
991 + Str = lists:flatmap(fun(Elm) -> Elm ++ "\n" end, List),
992 + lists:sublist(Str, length(Str)-1).
994 +string_to_list(null) ->
996 +string_to_list([]) ->
998 +string_to_list(String) ->
999 + {ok, List} = regexp:split(String, "\n"),
1002 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1004 +% ad-hoc (copy/pasted from mod_configure.erl)
1006 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1007 +-define(ITEMS_RESULT(Allow, LNode, Fallback),
1012 + case get_local_items(LServer, LNode,
1013 + jlib:jid_to_string(To), Lang) of
1021 +get_local_items(Acc, From, #jid{lserver = LServer} = To, "", Lang) ->
1022 + case gen_mod:is_loaded(LServer, mod_adhoc) of
1026 + Items = case Acc of
1027 + {result, Its} -> Its;
1030 + AllowUser = acl:match_rule(LServer, mod_logdb, From),
1031 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1033 + AllowUser == allow; AllowAdmin == allow ->
1034 + case get_local_items(LServer, [],
1035 + jlib:jid_to_string(To), Lang) of
1037 + {result, Items ++ Res};
1038 + {error, _Error} ->
1045 +get_local_items(Acc, From, #jid{lserver = LServer} = To, Node, Lang) ->
1046 + case gen_mod:is_loaded(LServer, mod_adhoc) of
1050 + LNode = string:tokens(Node, "/"),
1051 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1054 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1055 + ["mod_logdb_users"] ->
1056 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1057 + ["mod_logdb_users", [$@ | _]] ->
1058 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1059 + ["mod_logdb_users", _User] ->
1060 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1061 + ["mod_logdb_settings"] ->
1062 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1068 +-define(NODE(Name, Node),
1069 + {xmlelement, "item",
1071 + {"name", translate:translate(Lang, Name)},
1072 + {"node", Node}], []}).
1074 +get_local_items(_Host, [], Server, Lang) ->
1076 + [?NODE("Messages logging engine", "mod_logdb")]
1078 +get_local_items(_Host, ["mod_logdb"], Server, Lang) ->
1080 + [?NODE("Messages logging engine users", "mod_logdb_users"),
1081 + ?NODE("Messages logging engine settings", "mod_logdb_settings")]
1083 +get_local_items(Host, ["mod_logdb_users"], Server, Lang) ->
1084 + {result, get_all_vh_users(Host, Server, Lang)};
1085 +get_local_items(_Host, ["mod_logdb_users", [$@ | Diap]], Server, Lang) ->
1086 + case catch ejabberd_auth:dirty_get_registered_users() of
1087 + {'EXIT', _Reason} ->
1088 + ?ERR_INTERNAL_SERVER_ERROR;
1090 + SUsers = lists:sort([{S, U} || {U, S} <- Users]),
1092 + {ok, [S1, S2]} = regexp:split(Diap, "-"),
1093 + N1 = list_to_integer(S1),
1094 + N2 = list_to_integer(S2),
1095 + Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
1096 + lists:map(fun({S, U}) ->
1097 + ?NODE(U ++ "@" ++ S, "mod_logdb_users/" ++ U ++ "@" ++ S)
1100 + {'EXIT', _Reason} ->
1101 + ?ERR_NOT_ACCEPTABLE;
1106 +get_local_items(_Host, ["mod_logdb_users", _User], _Server, _Lang) ->
1108 +get_local_items(_Host, ["mod_logdb_settings"], _Server, _Lang) ->
1110 +get_local_items(_Host, Item, _Server, _Lang) ->
1111 + ?MYDEBUG("asked for items in ~p", [Item]),
1112 + {error, ?ERR_ITEM_NOT_FOUND}.
1114 +-define(INFO_RESULT(Allow, Feats),
1117 + {error, ?ERR_FORBIDDEN};
1122 +get_local_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
1123 + case gen_mod:is_loaded(LServer, mod_adhoc) of
1127 + LNode = string:tokens(Node, "/"),
1128 + AllowUser = acl:match_rule(LServer, mod_logdb, From),
1129 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1131 + ["mod_logdb"] when AllowUser == allow; AllowAdmin == allow ->
1132 + ?INFO_RESULT(allow, [?NS_COMMANDS]);
1134 + ?INFO_RESULT(deny, [?NS_COMMANDS]);
1135 + ["mod_logdb_users"] ->
1136 + ?INFO_RESULT(AllowAdmin, []);
1137 + ["mod_logdb_users", [$@ | _]] ->
1138 + ?INFO_RESULT(AllowAdmin, []);
1139 + ["mod_logdb_users", _User] ->
1140 + ?INFO_RESULT(AllowAdmin, [?NS_COMMANDS]);
1141 + ["mod_logdb_settings"] ->
1142 + ?INFO_RESULT(AllowAdmin, [?NS_COMMANDS]);
1146 + %?MYDEBUG("asked for ~p features: ~p", [LNode, Allow]),
1151 +-define(INFO_IDENTITY(Category, Type, Name, Lang),
1152 + [{xmlelement, "identity",
1153 + [{"category", Category},
1155 + {"name", translate:translate(Lang, Name)}], []}]).
1157 +-define(INFO_COMMAND(Name, Lang),
1158 + ?INFO_IDENTITY("automation", "command-node", Name, Lang)).
1160 +get_local_identity(Acc, _From, _To, Node, Lang) ->
1161 + LNode = string:tokens(Node, "/"),
1164 + ?INFO_COMMAND("Messages logging engine", Lang);
1165 + ["mod_logdb_users"] ->
1166 + ?INFO_COMMAND("Messages logging engine users", Lang);
1167 + ["mod_logdb_users", [$@ | _]] ->
1169 + ["mod_logdb_users", User] ->
1170 + ?INFO_COMMAND(User, Lang);
1171 + ["mod_logdb_settings"] ->
1172 + ?INFO_COMMAND("Messages logging engine settings", Lang);
1179 +%get_sm_items(Acc, From, To, Node, Lang) ->
1180 +% ?MYDEBUG("get_sm_items Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
1183 +%get_sm_features(Acc, From, To, Node, Lang) ->
1184 +% ?MYDEBUG("get_sm_features Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
1187 +%get_sm_identity(Acc, From, To, Node, Lang) ->
1188 +% ?MYDEBUG("get_sm_identity Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
1191 +adhoc_local_items(Acc, From, #jid{lserver = LServer, server = Server} = To,
1193 + Items = case Acc of
1194 + {result, Its} -> Its;
1197 + Nodes = recursively_get_local_items(LServer, "", Server, Lang),
1198 + Nodes1 = lists:filter(
1200 + Nd = xml:get_tag_attr_s("node", N),
1201 + F = get_local_features([], From, To, Nd, Lang),
1203 + {result, [?NS_COMMANDS]} ->
1209 + {result, Items ++ Nodes1}.
1211 +recursively_get_local_items(_LServer, "mod_logdb_users", _Server, _Lang) ->
1213 +recursively_get_local_items(LServer, Node, Server, Lang) ->
1214 + LNode = string:tokens(Node, "/"),
1215 + Items = case get_local_items(LServer, LNode, Server, Lang) of
1218 + {error, _Error} ->
1221 + Nodes = lists:flatten(
1224 + S = xml:get_tag_attr_s("jid", N),
1225 + Nd = xml:get_tag_attr_s("node", N),
1226 + if (S /= Server) or (Nd == "") ->
1229 + [N, recursively_get_local_items(
1230 + LServer, Nd, Server, Lang)]
1235 +-define(COMMANDS_RESULT(Allow, From, To, Request),
1238 + {error, ?ERR_FORBIDDEN};
1240 + adhoc_local_commands(From, To, Request)
1243 +adhoc_local_commands(Acc, From, #jid{lserver = LServer} = To,
1244 + #adhoc_request{node = Node} = Request) ->
1245 + LNode = string:tokens(Node, "/"),
1246 + AllowUser = acl:match_rule(LServer, mod_logdb, From),
1247 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1249 + ["mod_logdb"] when AllowUser == allow; AllowAdmin == allow ->
1250 + ?COMMANDS_RESULT(allow, From, To, Request);
1251 + ["mod_logdb_users", _User] when AllowAdmin == allow ->
1252 + ?COMMANDS_RESULT(allow, From, To, Request);
1253 + ["mod_logdb_settings"] when AllowAdmin == allow ->
1254 + ?COMMANDS_RESULT(allow, From, To, Request);
1259 +adhoc_local_commands(From, #jid{lserver = LServer} = _To,
1260 + #adhoc_request{lang = Lang,
1262 + sessionid = SessionID,
1264 + xdata = XData} = Request) ->
1265 + LNode = string:tokens(Node, "/"),
1266 + %% If the "action" attribute is not present, it is
1267 + %% understood as "execute". If there was no <actions/>
1268 + %% element in the first response (which there isn't in our
1269 + %% case), "execute" and "complete" are equivalent.
1270 + ActionIsExecute = lists:member(Action,
1271 + ["", "execute", "complete"]),
1272 + if Action == "cancel" ->
1273 + %% User cancels request
1274 + adhoc:produce_response(
1276 + #adhoc_response{status = canceled});
1277 + XData == false, ActionIsExecute ->
1278 + %% User requests form
1279 + case get_form(LServer, LNode, From, Lang) of
1281 + adhoc:produce_response(
1283 + #adhoc_response{status = executing,
1284 + elements = Form});
1288 + XData /= false, ActionIsExecute ->
1289 + %% User returns form.
1290 + case jlib:parse_xdata_submit(XData) of
1292 + {error, ?ERR_BAD_REQUEST};
1294 + case set_form(From, LServer, LNode, Lang, Fields) of
1296 + adhoc:produce_response(
1297 + #adhoc_response{lang = Lang,
1299 + sessionid = SessionID,
1300 + status = completed});
1306 + {error, ?ERR_BAD_REQUEST}
1309 +-define(LISTLINE(Label, Value),
1310 + {xmlelement, "option", [{"label", Label}],
1311 + [{xmlelement, "value", [], [{xmlcdata, Value}]}]}).
1312 +-define(DEFVAL(Value), {xmlelement, "value", [], [{xmlcdata, Value}]}).
1314 +get_user_form(LUser, LServer, Lang) ->
1315 + %From = jlib:jid_to_string(jlib:jid_remove_resource(Jid)),
1316 + #user_settings{dolog_default=DLD,
1318 + donotlog_list=DNLL} = get_user_settings(LUser, LServer),
1319 + {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
1320 + [{xmlelement, "title", [],
1322 + translate:translate(
1323 + Lang, "Messages logging engine settings")}]},
1324 + {xmlelement, "instructions", [],
1326 + translate:translate(
1327 + Lang, "Set logging preferences")++ ": " ++ LUser ++ "@" ++ LServer}]},
1329 + {xmlelement, "field", [{"type", "list-single"},
1331 + translate:translate(Lang, "Default")},
1332 + {"var", "dolog_default"}],
1333 + [?DEFVAL(atom_to_list(DLD)),
1334 + ?LISTLINE(translate:translate(Lang, "Log Messages"), "true"),
1335 + ?LISTLINE(translate:translate(Lang, "Do Not Log Messages"), "false")
1338 + {xmlelement, "field", [{"type", "text-multi"},
1340 + translate:translate(
1341 + Lang, "Log Messages")},
1342 + {"var", "dolog_list"}],
1343 + [{xmlelement, "value", [], [{xmlcdata, list_to_string(DLL)}]}]},
1345 + {xmlelement, "field", [{"type", "text-multi"},
1347 + translate:translate(
1348 + Lang, "Do Not Log Messages")},
1349 + {"var", "donotlog_list"}],
1350 + [{xmlelement, "value", [], [{xmlcdata, list_to_string(DNLL)}]}]}
1353 +get_settings_form(Host, Lang) ->
1354 + #state{dbmod=DBMod,
1356 + dolog_default=DLD,
1357 + ignore_jids=IgnoreJids,
1358 + groupchat=GroupChat,
1359 + purge_older_days=PurgeDaysT,
1360 + poll_users_settings=PollTime} = mod_logdb:get_module_settings(Host),
1362 + Backends = lists:map(fun({Backend, _Opts}) ->
1363 + ?LISTLINE(atom_to_list(Backend), atom_to_list(Backend))
1365 + DB = lists:sublist(atom_to_list(DBMod), length(atom_to_list(?MODULE)) + 2, length(atom_to_list(DBMod))),
1366 + DBsL = lists:append([?DEFVAL(DB)], Backends),
1369 + case PurgeDaysT of
1371 + Num when is_integer(Num) -> integer_to_list(Num);
1374 + {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
1375 + [{xmlelement, "title", [],
1377 + translate:translate(
1378 + Lang, "Messages logging engine settings") ++ " (run-time)"}]},
1379 + {xmlelement, "instructions", [],
1381 + translate:translate(
1382 + Lang, "Set run-time settings")}]},
1384 + {xmlelement, "field", [{"type", "list-single"},
1386 + translate:translate(Lang, "Backend")},
1387 + {"var", "backend"}],
1390 + {xmlelement, "field", [{"type", "text-multi"},
1392 + translate:translate(
1395 + [{xmlelement, "value", [], [{xmlcdata, lists:flatten(io_lib:format("~p.",[DBs]))}]}]},
1397 + {xmlelement, "field", [{"type", "list-single"},
1399 + translate:translate(Lang, "Default")},
1400 + {"var", "dolog_default"}],
1401 + [?DEFVAL(atom_to_list(DLD)),
1402 + ?LISTLINE(translate:translate(Lang, "Log Messages"), "true"),
1403 + ?LISTLINE(translate:translate(Lang, "Do Not Log Messages"), "false")
1406 + {xmlelement, "field", [{"type", "list-single"},
1408 + translate:translate(Lang, "Groupchat messages logging")},
1409 + {"var", "groupchat"}],
1410 + [?DEFVAL(atom_to_list(GroupChat)),
1411 + ?LISTLINE("all", "all"),
1412 + ?LISTLINE("none", "none"),
1413 + ?LISTLINE("send", "send"),
1414 + ?LISTLINE("half", "half")
1417 + {xmlelement, "field", [{"type", "text-multi"},
1419 + translate:translate(
1420 + Lang, "Jids/Domains to ignore")},
1421 + {"var", "ignore_list"}],
1422 + [{xmlelement, "value", [], [{xmlcdata, list_to_string(IgnoreJids)}]}]},
1423 + % purge older days
1424 + {xmlelement, "field", [{"type", "text-single"},
1426 + translate:translate(
1427 + Lang, "Purge messages older than (days)")},
1428 + {"var", "purge_older_days"}],
1429 + [{xmlelement, "value", [], [{xmlcdata, PurgeDays}]}]},
1430 + % poll users settings
1431 + {xmlelement, "field", [{"type", "text-single"},
1433 + translate:translate(
1434 + Lang, "Poll users settings (seconds)")},
1435 + {"var", "poll_users_settings"}],
1436 + [{xmlelement, "value", [], [{xmlcdata, integer_to_list(PollTime)}]}]}
1439 +get_form(_Host, ["mod_logdb"], #jid{luser = LUser, lserver = LServer} = _Jid, Lang) ->
1440 + get_user_form(LUser, LServer, Lang);
1441 +get_form(_Host, ["mod_logdb_users", User], _JidFrom, Lang) ->
1442 + #jid{luser=LUser, lserver=LServer} = jlib:string_to_jid(User),
1443 + get_user_form(LUser, LServer, Lang);
1444 +get_form(Host, ["mod_logdb_settings"], _JidFrom, Lang) ->
1445 + get_settings_form(Host, Lang);
1446 +get_form(_Host, Command, _, _Lang) ->
1447 + ?MYDEBUG("asked for form ~p", [Command]),
1448 + {error, ?ERR_SERVICE_UNAVAILABLE}.
1450 +check_log_list([Head | Tail]) ->
1451 + case lists:member($@, Head) of
1453 + false -> throw(error)
1455 + % this check for Head to be valid jid
1456 + case jlib:string_to_jid(Head) of
1460 + check_log_list(Tail)
1462 +check_log_list([]) ->
1465 +check_ignore_list([Head | Tail]) ->
1466 + case lists:member($@, Head) of
1468 + false -> throw(error)
1470 + % this check for Head to be valid jid
1471 + case jlib:string_to_jid(Head) of
1473 + % this check for Head to be valid domain "@domain.org"
1474 + case lists:nth(1, Head) of
1476 + % TODO: this allows spaces and special characters in Head. May be change to nodeprep?
1477 + case jlib:nameprep(lists:delete($@, Head)) of
1478 + error -> throw(error);
1479 + _ -> check_log_list(Tail)
1484 + check_ignore_list(Tail)
1486 +check_ignore_list([]) ->
1489 +parse_users_settings(XData) ->
1490 + DLD = case lists:keysearch("dolog_default", 1, XData) of
1491 + {value, {_, [String]}} when String == "true"; String == "false" ->
1492 + list_to_bool(String);
1494 + throw(bad_request)
1496 + DLL = case lists:keysearch("dolog_list", 1, XData) of
1498 + throw(bad_request);
1499 + {value, {_, [[]]}} ->
1501 + {value, {_, List1}} ->
1502 + case catch check_log_list(List1) of
1504 + throw(bad_request);
1509 + DNLL = case lists:keysearch("donotlog_list", 1, XData) of
1511 + throw(bad_request);
1512 + {value, {_, [[]]}} ->
1514 + {value, {_, List2}} ->
1515 + case catch check_log_list(List2) of
1517 + throw(bad_request);
1522 + #user_settings{dolog_default=DLD,
1524 + donotlog_list=DNLL}.
1526 +parse_module_settings(XData) ->
1527 + DLD = case lists:keysearch("dolog_default", 1, XData) of
1528 + {value, {_, [Str1]}} when Str1 == "true"; Str1 == "false" ->
1529 + list_to_bool(Str1);
1531 + throw(bad_request)
1533 + GroupChat = case lists:keysearch("groupchat", 1, XData) of
1534 + {value, {_, [Str2]}} when Str2 == "none";
1538 + list_to_atom(Str2);
1540 + throw(bad_request)
1542 + Ignore = case lists:keysearch("ignore_list", 1, XData) of
1543 + {value, {_, List}} ->
1544 + case catch check_ignore_list(List) of
1548 + throw(bad_request)
1551 + throw(bad_request)
1553 + Purge = case lists:keysearch("purge_older_days", 1, XData) of
1554 + {value, {_, ["never"]}} ->
1556 + {value, {_, [Str3]}} ->
1557 + case catch list_to_integer(Str3) of
1558 + {'EXIT', {badarg, _}} -> throw(bad_request);
1562 + throw(bad_request)
1564 + Poll = case lists:keysearch("poll_users_settings", 1, XData) of
1565 + {value, {_, [Str4]}} ->
1566 + case catch list_to_integer(Str4) of
1567 + {'EXIT', {badarg, _}} -> throw(bad_request);
1571 + throw(bad_request)
1573 + #state{dolog_default=DLD,
1574 + groupchat=GroupChat,
1575 + ignore_jids=Ignore,
1576 + purge_older_days=Purge,
1577 + poll_users_settings=Poll}.
1579 +set_form(From, _Host, ["mod_logdb"], _Lang, XData) ->
1580 + #jid{luser=LUser, lserver=LServer} = From,
1581 + case catch parse_users_settings(XData) of
1583 + {error, ?ERR_BAD_REQUEST};
1585 + case mod_logdb:set_user_settings(LUser, LServer, UserSettings) of
1589 + {error, ?ERR_INTERNAL_SERVER_ERROR}
1592 +set_form(_From, _Host, ["mod_logdb_users", User], _Lang, XData) ->
1593 + #jid{luser=LUser, lserver=LServer} = jlib:string_to_jid(User),
1594 + case catch parse_users_settings(XData) of
1595 + bad_request -> {error, ?ERR_BAD_REQUEST};
1597 + case mod_logdb:set_user_settings(LUser, LServer, UserSettings) of
1601 + {error, ?ERR_INTERNAL_SERVER_ERROR}
1604 +set_form(_From, Host, ["mod_logdb_settings"], _Lang, XData) ->
1605 + case catch parse_module_settings(XData) of
1606 + bad_request -> {error, ?ERR_BAD_REQUEST};
1608 + case mod_logdb:set_module_settings(Host, Settings) of
1612 + {error, ?ERR_INTERNAL_SERVER_ERROR}
1615 +set_form(From, _Host, Node, _Lang, XData) ->
1616 + User = jlib:jid_to_string(jlib:jid_remove_resource(From)),
1617 + ?MYDEBUG("set form for ~p at ~p XData=~p", [User, Node, XData]),
1618 + {error, ?ERR_SERVICE_UNAVAILABLE}.
1620 +%adhoc_sm_items(Acc, From, To, Request) ->
1621 +% ?MYDEBUG("adhoc_sm_items Acc=~p From=~p To=~p Request=~p", [Acc, From, To, Request]),
1624 +%adhoc_sm_commands(Acc, From, To, Request) ->
1625 +% ?MYDEBUG("adhoc_sm_commands Acc=~p From=~p To=~p Request=~p", [Acc, From, To, Request]),
1628 +get_all_vh_users(Host, Server, Lang) ->
1629 + case catch ejabberd_auth:get_vh_registered_users(Host) of
1630 + {'EXIT', _Reason} ->
1633 + SUsers = lists:sort([{S, U} || {U, S} <- Users]),
1634 + case length(SUsers) of
1635 + N when N =< 100 ->
1636 + lists:map(fun({S, U}) ->
1637 + ?NODE(U ++ "@" ++ S, "mod_logdb_users/" ++ U ++ "@" ++ S)
1640 + NParts = trunc(math:sqrt(N * 0.618)) + 1,
1641 + M = trunc(N / NParts) + 1,
1642 + lists:map(fun(K) ->
1645 + "@" ++ integer_to_list(K) ++
1646 + "-" ++ integer_to_list(L),
1647 + {FS, FU} = lists:nth(K, SUsers),
1649 + if L < N -> lists:nth(L, SUsers);
1650 + true -> lists:last(SUsers)
1653 + FU ++ "@" ++ FS ++
1656 + ?NODE(Name, "mod_logdb_users/" ++ Node)
1657 + end, lists:seq(1, N, M))
1660 --- src/mod_logdb.hrl.orig Tue Dec 11 14:23:19 2007
1661 +++ src/mod_logdb.hrl Tue Aug 7 16:50:32 2007
1663 +%%%----------------------------------------------------------------------
1664 +%%% File : mod_logdb.hrl
1665 +%%% Author : Oleg Palij (mailto:o.palij@gmail.com xmpp://malik@jabber.te.ua)
1667 +%%% Version : trunk
1669 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
1670 +%%%----------------------------------------------------------------------
1672 +-define(logdb_debug, true).
1674 +-ifdef(logdb_debug).
1675 +-define(MYDEBUG(Format, Args), io:format("D(~p:~p:~p) : "++Format++"~n",
1676 + [calendar:local_time(),?MODULE,?LINE]++Args)).
1678 +-define(MYDEBUG(_F,_A),[]).
1681 +-record(msg, {timestamp,
1683 + peer_name, peer_server, peer_resource,
1688 +-record(user_settings, {owner_name,
1691 + donotlog_list=[]}).
1692 --- src/mod_logdb_mnesia.erl.orig Tue Dec 11 14:23:19 2007
1693 +++ src/mod_logdb_mnesia.erl Wed Aug 22 22:58:11 2007
1695 +%%%----------------------------------------------------------------------
1696 +%%% File : mod_logdb_mnesia.erl
1697 +%%% Author : Oleg Palij (mailto:o.palij@gmail.com xmpp://malik@jabber.te.ua)
1698 +%%% Purpose : mnesia backend for mod_logdb
1699 +%%% Version : trunk
1701 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
1702 +%%%----------------------------------------------------------------------
1704 +-module(mod_logdb_mnesia).
1705 +-author('o.palij@gmail.com').
1706 +-vsn('$Revision$').
1708 +-include("mod_logdb.hrl").
1709 +-include("ejabberd.hrl").
1710 +-include("jlib.hrl").
1712 +-behaviour(gen_logdb).
1713 +-behaviour(gen_server).
1716 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
1718 +-export([start/2, stop/1]).
1720 +-export([log_message/2,
1722 + rebuild_stats_at/2,
1723 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
1724 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
1726 + get_users_settings/1, get_user_settings/2, set_user_settings/3]).
1728 +-define(PROCNAME, mod_logdb_mnesia).
1729 +-define(CALL_TIMEOUT, 240000).
1731 +-record(state, {vhost}).
1733 +-record(stats, {user, at, count}).
1741 +stats_table(VHost) ->
1742 + list_to_atom(prefix() ++ "stats" ++ suffix(VHost)).
1744 +table_name(VHost, Date) ->
1745 + list_to_atom(prefix() ++ "messages_" ++ Date ++ suffix(VHost)).
1747 +settings_table(VHost) ->
1748 + list_to_atom(prefix() ++ "settings" ++ suffix(VHost)).
1750 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1752 +% gen_mod callbacks
1754 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1755 +start(VHost, Opts) ->
1756 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1757 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
1760 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1761 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
1763 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1765 +% gen_server callbacks
1767 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1768 +init([VHost, _Opts]) ->
1769 + case mnesia:system_info(is_running) of
1771 + ok = create_stats_table(VHost),
1772 + ok = create_settings_table(VHost),
1773 + {ok, #state{vhost=VHost}};
1775 + ?ERROR_MSG("Mnesia not running", []),
1776 + {stop, db_connection_failed};
1778 + ?ERROR_MSG("Mnesia status: ~p", [Status]),
1779 + {stop, db_connection_failed}
1782 +handle_call({log_message, Msg}, _From, #state{vhost=VHost}=State) ->
1783 + {reply, log_message_int(VHost, Msg), State};
1784 +handle_call({rebuild_stats}, _From, #state{vhost=VHost}=State) ->
1785 + {atomic, ok} = delete_nonexistent_stats(VHost),
1787 + lists:foreach(fun(Date) ->
1788 + rebuild_stats_at_int(VHost, Date)
1789 + end, get_dates_int(VHost)),
1790 + {reply, Reply, State};
1791 +handle_call({rebuild_stats_at, Date}, _From, #state{vhost=VHost}=State) ->
1792 + Reply = rebuild_stats_at_int(VHost, Date),
1793 + {reply, Reply, State};
1794 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{vhost=VHost}=State) ->
1795 + Table = table_name(VHost, Date),
1799 + mnesia:write_lock_table(stats_table(VHost)),
1800 + mnesia:write_lock_table(Table),
1801 + mnesia:delete_object(Table, Msg, write)
1804 + DRez = case mnesia:transaction(Fun) of
1805 + {aborted, Reason} ->
1806 + ?ERROR_MSG("Failed to delete_messages_by_user_at at ~p for ~p: ~p", [Date, VHost, Reason]),
1812 + case rebuild_stats_at_int(VHost, Date) of
1818 + {reply, Reply, State};
1819 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{vhost=VHost}=State) ->
1820 + Table = table_name(VHost, Date),
1821 + MsgDelete = fun(#msg{owner_name=Owner} = Msg, _Acc)
1822 + when Owner == User ->
1823 + mnesia:delete_object(Table, Msg, write),
1825 + (_Msg, _Acc) -> ok
1827 + DRez = case mnesia:transaction(fun() ->
1828 + mnesia:foldl(MsgDelete, ok, Table)
1830 + {aborted, Reason} ->
1831 + ?ERROR_MSG("Failed to delete_all_messages_by_user_at for ~p@~p at ~p: ~p", [User, VHost, Date, Reason]),
1837 + case rebuild_stats_at_int(VHost, Date) of
1843 + {reply, Reply, State};
1844 +handle_call({delete_messages_at, Date}, _From, #state{vhost=VHost}=State) ->
1846 + case mnesia:delete_table(table_name(VHost, Date)) of
1848 + delete_stats_by_vhost_at_int(VHost, Date);
1849 + {aborted, Reason} ->
1850 + ?ERROR_MSG("Failed to delete_messages_at for ~p at ~p", [VHost, Date, Reason]),
1853 + {reply, Reply, State};
1854 +handle_call({get_vhost_stats}, _From, #state{vhost=VHost}=State) ->
1855 + Fun = fun(#stats{at=Date, count=Count}, Stats) ->
1856 + case lists:keysearch(Date, 1, Stats) of
1858 + lists:append(Stats, [{Date, Count}]);
1859 + {value, {_, TempCount}} ->
1860 + lists:keyreplace(Date, 1, Stats, {Date, TempCount+Count})
1864 + case mnesia:transaction(fun() ->
1865 + mnesia:foldl(Fun, [], stats_table(VHost))
1867 + {atomic, Result} -> {ok, mod_logdb:sort_stats(Result)};
1868 + {aborted, Reason} -> {error, Reason}
1870 + {reply, Reply, State};
1871 +handle_call({get_vhost_stats_at, Date}, _From, #state{vhost=VHost}=State) ->
1873 + Pat = #stats{user='$1', at=Date, count='$2'},
1874 + mnesia:select(stats_table(VHost), [{Pat, [], [['$1', '$2']]}])
1877 + case mnesia:transaction(Fun) of
1878 + {atomic, Result} ->
1879 + {ok, lists:reverse(lists:keysort(2, [{User, Count} || [User, Count] <- Result]))};
1880 + {aborted, Reason} ->
1883 + {reply, Reply, State};
1884 +handle_call({get_user_stats, User}, _From, #state{vhost=VHost}=State) ->
1886 + case mnesia:transaction(fun() ->
1887 + Pat = #stats{user=User, at='$1', count='$2'},
1888 + mnesia:select(stats_table(VHost), [{Pat, [], [['$1', '$2']]}])
1890 + {atomic, Result} ->
1891 + {ok, mod_logdb:sort_stats([{Date, Count} || [Date, Count] <- Result])};
1892 + {aborted, Reason} ->
1895 + {reply, Reply, State};
1896 +handle_call({get_user_messages_at, User, Date}, _From, #state{vhost=VHost}=State) ->
1898 + case mnesia:transaction(fun() ->
1899 + Pat = #msg{owner_name=User, _='_'},
1900 + mnesia:select(table_name(VHost, Date),
1901 + [{Pat, [], ['$_']}])
1903 + {atomic, Result} -> {ok, Result};
1904 + {aborted, Reason} ->
1907 + {reply, Reply, State};
1908 +handle_call({get_dates}, _From, #state{vhost=VHost}=State) ->
1909 + {reply, get_dates_int(VHost), State};
1910 +handle_call({get_users_settings}, _From, #state{vhost=VHost}=State) ->
1911 + Reply = mnesia:dirty_match_object(settings_table(VHost), #user_settings{_='_'}),
1912 + {reply, {ok, Reply}, State};
1913 +handle_call({get_user_settings, User}, _From, #state{vhost=VHost}=State) ->
1915 + case mnesia:dirty_match_object(settings_table(VHost), #user_settings{owner_name=User, _='_'}) of
1920 + {reply, Reply, State};
1921 +handle_call({set_user_settings, _User, Set}, _From, #state{vhost=VHost}=State) ->
1922 + ?MYDEBUG("~p~n~p", [settings_table(VHost), Set]),
1923 + Reply = mnesia:dirty_write(settings_table(VHost), Set),
1924 + {reply, Reply, State};
1925 +handle_call({stop}, _From, State) ->
1926 + {stop, normal, ok, State};
1927 +handle_call(Msg, _From, State) ->
1928 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
1931 +handle_cast(Msg, State) ->
1932 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
1935 +handle_info(Info, State) ->
1936 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
1939 +terminate(_Reason, _State) ->
1942 +code_change(_OldVsn, State, _Extra) ->
1945 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1947 +% gen_logdb callbacks
1949 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1950 +log_message(VHost, Msg) ->
1951 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1952 + gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
1953 +rebuild_stats(VHost) ->
1954 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1955 + gen_server:call(Proc, {rebuild_stats}, ?CALL_TIMEOUT).
1956 +rebuild_stats_at(VHost, Date) ->
1957 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1958 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
1959 +delete_messages_by_user_at(VHost, Msgs, Date) ->
1960 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1961 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
1962 +delete_all_messages_by_user_at(User, VHost, Date) ->
1963 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1964 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
1965 +delete_messages_at(VHost, Date) ->
1966 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1967 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
1968 +get_vhost_stats(VHost) ->
1969 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1970 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
1971 +get_vhost_stats_at(VHost, Date) ->
1972 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1973 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
1974 +get_user_stats(User, VHost) ->
1975 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1976 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
1977 +get_user_messages_at(User, VHost, Date) ->
1978 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1979 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
1980 +get_dates(VHost) ->
1981 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1982 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
1983 +get_user_settings(User, VHost) ->
1984 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1985 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
1986 +get_users_settings(VHost) ->
1987 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1988 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
1989 +set_user_settings(User, VHost, Set) ->
1990 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1991 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
1993 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1997 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1998 +log_message_int(VHost, #msg{timestamp=Timestamp}=Msg) ->
1999 + Date = mod_logdb:convert_timestamp_brief(Timestamp),
2001 + ATable = table_name(VHost, Date),
2003 + mnesia:write_lock_table(ATable),
2004 + mnesia:write(ATable, Msg, write)
2006 + % log message, increment stats for both users
2007 + case mnesia:transaction(Fun) of
2008 + % if table does not exists - create it and try to log message again
2009 + {aborted,{no_exists, _Table}} ->
2010 + case create_msg_table(VHost, Date) of
2011 + {aborted, CReason} ->
2012 + ?ERROR_MSG("Failed to log message: ~p", [CReason]),
2015 + ?MYDEBUG("Created msg table for ~p at ~p", [VHost, Date]),
2016 + log_message_int(VHost, Msg)
2018 + {aborted, TReason} ->
2019 + ?ERROR_MSG("Failed to log message: ~p", [TReason]),
2022 + ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
2023 + Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
2024 + increment_user_stats(Msg#msg.owner_name, VHost, Date)
2027 +increment_user_stats(Owner, VHost, Date) ->
2029 + Pat = #stats{user=Owner, at=Date, count='$1'},
2030 + mnesia:write_lock_table(stats_table(VHost)),
2031 + case mnesia:select(stats_table(VHost), [{Pat, [], ['$_']}]) of
2033 + mnesia:write(stats_table(VHost),
2034 + #stats{user=Owner,
2039 + mnesia:delete_object(stats_table(VHost),
2040 + #stats{user=Owner,
2042 + count=Stats#stats.count},
2044 + New = Stats#stats{count = Stats#stats.count+1},
2046 + New#stats.count > 0 -> mnesia:write(stats_table(VHost),
2053 + case mnesia:transaction(Fun) of
2054 + {aborted, Reason} ->
2055 + ?ERROR_MSG("Failed to update stats for ~s@~s: ~p", [Owner, VHost, Reason]),
2058 + ?MYDEBUG("Updated stats for ~s@~s", [Owner, VHost]),
2062 +get_dates_int(VHost) ->
2063 + Tables = mnesia:system_info(tables),
2064 + lists:foldl(fun(ATable, Dates) ->
2065 + Table = atom_to_list(ATable),
2066 + case regexp:match(Table, VHost++"$") of
2068 + case regexp:match(Table,"_[0-9]+-[0-9]+-[0-9]+_") of
2070 + lists:append(Dates, [lists:sublist(Table,S+1,E-2)]);
2079 +rebuild_stats_at_int(VHost, Date) ->
2080 + Table = table_name(VHost, Date),
2081 + STable = stats_table(VHost),
2082 + CFun = fun(Msg, Stats) ->
2083 + Owner = Msg#msg.owner_name,
2084 + case lists:keysearch(Owner, 1, Stats) of
2085 + {value, {_, Count}} ->
2086 + lists:keyreplace(Owner, 1, Stats, {Owner, Count + 1});
2088 + lists:append(Stats, [{Owner, 1}])
2091 + DFun = fun(#stats{at=SDate} = Stat, _Acc)
2092 + when SDate == Date ->
2093 + mnesia:delete_object(stats_table(VHost), Stat, write);
2094 + (_Stat, _Acc) -> ok
2096 + % TODO: Maybe unregister hooks ?
2097 + case mnesia:transaction(fun() ->
2098 + mnesia:write_lock_table(Table),
2099 + mnesia:write_lock_table(STable),
2100 + % Calc stats for VHost at Date
2101 + case mnesia:foldl(CFun, [], Table) of
2104 + % Delete all stats for VHost at Date
2105 + mnesia:foldl(DFun, [], STable),
2106 + % Write new calc'ed stats
2107 + lists:foreach(fun({Owner, Count}) ->
2108 + WStat = #stats{user=Owner, at=Date, count=Count},
2109 + mnesia:write(stats_table(VHost), WStat, write)
2114 + {aborted, Reason} ->
2115 + ?ERROR_MSG("Failed to rebuild_stats_at for ~p at ~p: ~p", [VHost, Date, Reason]),
2119 + {atomic, empty} ->
2120 + {atomic,ok} = mnesia:delete_table(Table),
2121 + ?MYDEBUG("Dropped table at ~p", [Date]),
2125 +delete_nonexistent_stats(VHost) ->
2126 + Dates = get_dates_int(VHost),
2127 + mnesia:transaction(fun() ->
2128 + mnesia:foldl(fun(#stats{at=Date} = Stat, _Acc) ->
2129 + case lists:member(Date, Dates) of
2130 + false -> mnesia:delete_object(Stat);
2133 + end, ok, stats_table(VHost))
2136 +delete_stats_by_vhost_at_int(VHost, Date) ->
2137 + StatsDelete = fun(#stats{at=SDate} = Stat, _Acc)
2138 + when SDate == Date ->
2139 + mnesia:delete_object(stats_table(VHost), Stat, write),
2141 + (_Msg, _Acc) -> ok
2143 + case mnesia:transaction(fun() ->
2144 + mnesia:write_lock_table(stats_table(VHost)),
2145 + mnesia:foldl(StatsDelete, ok, stats_table(VHost))
2147 + {aborted, Reason} ->
2148 + ?ERROR_MSG("Failed to update stats at ~p for ~p: ~p", [Date, VHost, Reason]),
2149 + rebuild_stats_at_int(VHost, Date);
2151 + ?INFO_MSG("Updated stats at ~p for ~p", [Date, VHost]),
2155 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2159 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2160 +create_stats_table(VHost) ->
2161 + SName = stats_table(VHost),
2162 + case mnesia:create_table(SName,
2163 + [{disc_only_copies, [node()]},
2165 + {attributes, record_info(fields, stats)},
2166 + {record_name, stats}
2169 + ?MYDEBUG("Created stats table for ~p", [VHost]),
2170 + lists:foreach(fun(Date) ->
2171 + rebuild_stats_at_int(VHost, Date)
2172 + end, get_dates_int(VHost)),
2174 + {aborted, {already_exists, _}} ->
2175 + ?MYDEBUG("Stats table for ~p already exists", [VHost]),
2177 + {aborted, Reason} ->
2178 + ?ERROR_MSG("Failed to create stats table: ~p", [Reason]),
2182 +create_settings_table(VHost) ->
2183 + SName = settings_table(VHost),
2184 + case mnesia:create_table(SName,
2185 + [{disc_copies, [node()]},
2187 + {attributes, record_info(fields, user_settings)},
2188 + {record_name, user_settings}
2191 + ?MYDEBUG("Created settings table for ~p", [VHost]),
2193 + {aborted, {already_exists, _}} ->
2194 + ?MYDEBUG("Settings table for ~p already exists", [VHost]),
2196 + {aborted, Reason} ->
2197 + ?ERROR_MSG("Failed to create settings table: ~p", [Reason]),
2201 +create_msg_table(VHost, Date) ->
2202 + mnesia:create_table(
2203 + table_name(VHost, Date),
2204 + [{disc_only_copies, [node()]},
2206 + {attributes, record_info(fields, msg)},
2207 + {record_name, msg}]).
2208 --- src/mod_logdb_mysql.erl.orig Tue Dec 11 14:23:19 2007
2209 +++ src/mod_logdb_mysql.erl Sun Nov 18 20:53:55 2007
2211 +%%%----------------------------------------------------------------------
2212 +%%% File : mod_logdb_mysql.erl
2213 +%%% Author : Oleg Palij (mailto:o.palij@gmail.com xmpp://malik@jabber.te.ua)
2214 +%%% Purpose : MySQL backend for mod_logdb
2215 +%%% Version : trunk
2217 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
2218 +%%%----------------------------------------------------------------------
2220 +-module(mod_logdb_mysql).
2221 +-author('o.palij@gmail.com').
2222 +-vsn('$Revision$').
2224 +-include("mod_logdb.hrl").
2225 +-include("ejabberd.hrl").
2226 +-include("jlib.hrl").
2228 +-behaviour(gen_logdb).
2229 +-behaviour(gen_server).
2232 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
2234 +-export([start/2, stop/1]).
2236 +-export([log_message/2,
2238 + rebuild_stats_at/2,
2239 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
2240 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
2242 + get_users_settings/1, get_user_settings/2, set_user_settings/3]).
2244 +% gen_server call timeout
2245 +-define(CALL_TIMEOUT, 60000).
2246 +-define(TIMEOUT, 60000).
2247 +-define(INDEX_SIZE, integer_to_list(170)).
2248 +-define(PROCNAME, mod_logdb_mysql).
2250 +-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
2251 + list_to_string/1, string_to_list/1,
2252 + convert_timestamp_brief/1]).
2254 +-record(state, {dbref, vhost}).
2256 +% replace "." with "_"
2257 +escape_vhost(VHost) -> lists:map(fun(46) -> 95;
2264 + "_" ++ escape_vhost(VHost) ++ "`".
2266 +messages_table(VHost, Date) ->
2267 + prefix() ++ "messages_" ++ Date ++ suffix(VHost).
2269 +stats_table(VHost) ->
2270 + prefix() ++ "stats" ++ suffix(VHost).
2272 +settings_table(VHost) ->
2273 + prefix() ++ "settings" ++ suffix(VHost).
2275 +users_table(VHost) ->
2276 + prefix() ++ "users" ++ suffix(VHost).
2277 +servers_table(VHost) ->
2278 + prefix() ++ "servers" ++ suffix(VHost).
2279 +resources_table(VHost) ->
2280 + prefix() ++ "resources" ++ suffix(VHost).
2282 +ets_users_table(VHost) -> list_to_atom("logdb_users_" ++ VHost).
2283 +ets_servers_table(VHost) -> list_to_atom("logdb_servers_" ++ VHost).
2284 +ets_resources_table(VHost) -> list_to_atom("logdb_resources_" ++ VHost).
2286 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2288 +% gen_mod callbacks
2290 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2291 +start(VHost, Opts) ->
2292 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2293 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
2296 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2297 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
2299 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2301 +% gen_server callbacks
2303 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2304 +init([VHost, Opts]) ->
2307 + Server = gen_mod:get_opt(server, Opts, "localhost"),
2308 + Port = gen_mod:get_opt(port, Opts, 3128),
2309 + DB = gen_mod:get_opt(db, Opts, "logdb"),
2310 + User = gen_mod:get_opt(user, Opts, "root"),
2311 + Password = gen_mod:get_opt(password, Opts, ""),
2313 + LogFun = fun(debug, Format, Argument) ->
2314 + ?MYDEBUG(Format, Argument);
2315 + (error, Format, Argument) ->
2316 + ?ERROR_MSG(Format, Argument);
2317 + (Level, Format, Argument) ->
2318 + ?MYDEBUG("MySQL (~p)~n", [Level]),
2319 + ?MYDEBUG(Format, Argument)
2321 + case mysql_conn:start(Server, Port, User, Password, DB, LogFun) of
2323 + ok = create_stats_table(DBRef, VHost),
2324 + ok = create_settings_table(DBRef, VHost),
2325 + ok = create_users_table(DBRef, VHost),
2326 + % clear ets cache every ...
2327 + timer:send_interval(timer:hours(12), clear_ets_tables),
2328 + ok = create_servers_table(DBRef, VHost),
2329 + ok = create_resources_table(DBRef, VHost),
2330 + erlang:monitor(process, DBRef),
2331 + {ok, #state{dbref=DBRef, vhost=VHost}};
2332 + {error, Reason} ->
2333 + ?ERROR_MSG("MySQL connection failed: ~p~n", [Reason]),
2334 + {stop, db_connection_failed}
2337 +handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2338 + {reply, log_message_int(DBRef, VHost, Msg), State};
2339 +handle_call({rebuild_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2340 + ok = delete_nonexistent_stats(DBRef, VHost),
2342 + lists:foreach(fun(Date) ->
2343 + catch rebuild_stats_at_int(DBRef, VHost, Date)
2344 + end, get_dates_int(DBRef, VHost)),
2345 + {reply, Reply, State};
2346 +handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2347 + Reply = rebuild_stats_at_int(DBRef, VHost, Date),
2348 + {reply, Reply, State};
2349 +handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
2350 + {reply, error, State};
2351 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2352 + Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
2353 + ["\"",Timestamp,"\"",","]
2356 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
2358 + Query = ["DELETE FROM ",messages_table(VHost, Date)," ",
2359 + "WHERE timestamp IN (", Temp1],
2362 + case sql_query_internal(DBRef, Query) of
2364 + ?MYDEBUG("Aff=~p", [Aff]),
2365 + rebuild_stats_at_int(DBRef, VHost, Date);
2369 + {reply, Reply, State};
2370 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2371 + Owner_id = get_user_id(DBRef, VHost, User),
2372 + DQuery = ["DELETE FROM ",messages_table(VHost, Date)," ",
2373 + "WHERE owner_id=\"",Owner_id,"\";"],
2375 + case sql_query_internal(DBRef, DQuery) of
2377 + rebuild_stats_at_int(DBRef, VHost, Date);
2381 + {reply, Reply, State};
2382 +handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2384 + case sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Date),";"]) of
2386 + Query = ["DELETE FROM ",stats_table(VHost)," "
2387 + "WHERE at=\"",Date,"\";"],
2388 + case sql_query_internal(DBRef, Query) of
2397 + {reply, Reply, State};
2398 +handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2399 + SName = stats_table(VHost),
2400 + Query = ["SELECT at, sum(count) ",
2401 + "FROM ",SName," ",
2403 + "ORDER BY DATE(at) DESC;"
2406 + case sql_query_internal(DBRef, Query) of
2408 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
2409 + {error, Reason} ->
2410 + % TODO: Duplicate error message ?
2413 + {reply, Reply, State};
2414 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2415 + SName = stats_table(VHost),
2416 + Query = ["SELECT username, count ",
2417 + "FROM ",SName," ",
2418 + "JOIN ",users_table(VHost)," ON owner_id=user_id "
2419 + "WHERE at=\"",Date,"\";"
2422 + case sql_query_internal(DBRef, Query) of
2424 + {ok, lists:reverse(
2426 + [ {User, list_to_integer(Count)} || [User, Count] <- Result]))};
2427 + {error, Reason} ->
2431 + {reply, Reply, State};
2432 +handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2433 + SName = stats_table(VHost),
2434 + Query = ["SELECT at, count ",
2435 + "FROM ",SName," ",
2436 + "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\" ",
2437 + "ORDER BY DATE(at) DESC;"
2440 + case sql_query_internal(DBRef, Query) of
2442 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result]};
2443 + {error, Result} ->
2446 + {reply, Reply, State};
2447 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2448 + TName = messages_table(VHost, Date),
2449 + UName = users_table(VHost),
2450 + SName = servers_table(VHost),
2451 + RName = resources_table(VHost),
2452 + Query = ["SELECT users.username,",
2453 + "servers.server,",
2454 + "resources.resource,",
2455 + "messages.direction,"
2457 + "messages.subject,"
2459 + "messages.timestamp "
2460 + "FROM ",TName," AS messages "
2461 + "JOIN ",UName," AS users ON peer_name_id=user_id ",
2462 + "JOIN ",SName," AS servers ON peer_server_id=server_id ",
2463 + "JOIN ",RName," AS resources ON peer_resource_id=resource_id ",
2464 + "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\" ",
2465 + "ORDER BY timestamp ASC;"],
2467 + case sql_query_internal(DBRef, Query) of
2469 + Fun = fun([Peer_name, Peer_server, Peer_resource,
2474 + #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
2475 + direction=list_to_atom(Direction),
2477 + subject=Subject, body=Body,
2478 + timestamp=Timestamp}
2480 + {ok, lists:map(Fun, Result)};
2481 + {error, Reason} ->
2484 + {reply, Reply, State};
2485 +handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2486 + SName = stats_table(VHost),
2487 + Query = ["SELECT at ",
2488 + "FROM ",SName," ",
2490 + "ORDER BY DATE(at) DESC;"
2493 + case sql_query_internal(DBRef, Query) of
2495 + [ Date || [Date] <- Result ];
2496 + {error, Reason} ->
2499 + {reply, Reply, State};
2500 +handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2501 + Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
2502 + "FROM ",settings_table(VHost)," ",
2503 + "JOIN ",users_table(VHost)," ON user_id=owner_id;"],
2505 + case sql_query_internal(DBRef, Query) of
2507 + {ok, lists:map(fun([Owner, DoLogDef, DoLogL, DoNotLogL]) ->
2508 + #user_settings{owner_name=Owner,
2509 + dolog_default=list_to_bool(DoLogDef),
2510 + dolog_list=string_to_list(DoLogL),
2511 + donotlog_list=string_to_list(DoNotLogL)
2517 + {reply, Reply, State};
2518 +handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2519 + Query = ["SELECT dolog_default,dolog_list,donotlog_list FROM ",settings_table(VHost)," ",
2520 + "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\";"],
2522 + case sql_query_internal(DBRef, Query) of
2525 + {data, [[Owner, DoLogDef, DoLogL, DoNotLogL]]} ->
2526 + {ok, #user_settings{owner_name=Owner,
2527 + dolog_default=list_to_bool(DoLogDef),
2528 + dolog_list=string_to_list(DoLogL),
2529 + donotlog_list=string_to_list(DoNotLogL)}};
2533 + {reply, Reply, State};
2534 +handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
2535 + dolog_list=DoLogL,
2536 + donotlog_list=DoNotLogL}},
2537 + _From, #state{dbref=DBRef, vhost=VHost} = State) ->
2538 + User_id = get_user_id(DBRef, VHost, User),
2540 + Query = ["UPDATE ",settings_table(VHost)," ",
2541 + "SET dolog_default=",bool_to_list(DoLogDef),", ",
2542 + "dolog_list='",list_to_string(DoLogL),"', ",
2543 + "donotlog_list='",list_to_string(DoNotLogL),"' ",
2544 + "WHERE owner_id=\"",User_id,"\";"],
2547 + case sql_query_internal(DBRef, Query) of
2549 + IQuery = ["INSERT INTO ",settings_table(VHost)," ",
2550 + "(owner_id, dolog_default, dolog_list, donotlog_list) ",
2552 + "('",User_id,"', ",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
2553 + case sql_query_internal_silent(DBRef, IQuery) of
2555 + ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
2557 + {error, Reason} ->
2558 + case regexp:match(Reason, "#23000") of
2563 + ?ERROR_MSG("Failed setup user ~p@~p: ~p", [User, VHost, Reason]),
2568 + ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
2573 + {reply, Reply, State};
2574 +handle_call({stop}, _From, #state{vhost=VHost}=State) ->
2575 + ets:delete(ets_users_table(VHost)),
2576 + ets:delete(ets_servers_table(VHost)),
2577 + ?MYDEBUG("Stoping mysql backend for ~p", [VHost]),
2578 + {stop, normal, ok, State};
2579 +handle_call(Msg, _From, State) ->
2580 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
2583 +handle_cast(Msg, State) ->
2584 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
2587 +handle_info(clear_ets_tables, State) ->
2588 + ets:delete_all_objects(ets_users_table(State#state.vhost)),
2589 + ets:delete_all_objects(ets_resources_table(State#state.vhost)),
2591 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
2592 + {stop, connection_dropped, State};
2593 +handle_info(Info, State) ->
2594 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
2597 +terminate(_Reason, _State) ->
2600 +code_change(_OldVsn, State, _Extra) ->
2603 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2605 +% gen_logdb callbacks
2607 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2608 +log_message(VHost, Msg) ->
2609 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2610 + gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
2611 +rebuild_stats(VHost) ->
2612 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2613 + gen_server:call(Proc, {rebuild_stats}, ?CALL_TIMEOUT).
2614 +rebuild_stats_at(VHost, Date) ->
2615 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2616 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
2617 +delete_messages_by_user_at(VHost, Msgs, Date) ->
2618 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2619 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
2620 +delete_all_messages_by_user_at(User, VHost, Date) ->
2621 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2622 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
2623 +delete_messages_at(VHost, Date) ->
2624 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2625 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
2626 +get_vhost_stats(VHost) ->
2627 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2628 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
2629 +get_vhost_stats_at(VHost, Date) ->
2630 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2631 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
2632 +get_user_stats(User, VHost) ->
2633 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2634 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
2635 +get_user_messages_at(User, VHost, Date) ->
2636 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2637 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
2638 +get_dates(VHost) ->
2639 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2640 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
2641 +get_users_settings(VHost) ->
2642 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2643 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
2644 +get_user_settings(User, VHost) ->
2645 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2646 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
2647 +set_user_settings(User, VHost, Set) ->
2648 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2649 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
2651 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2655 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2656 +log_message_int(DBRef, VHost, Msg) ->
2657 + Date = convert_timestamp_brief(Msg#msg.timestamp),
2659 + Table = messages_table(VHost, Date),
2660 + Owner_id = get_user_id(DBRef, VHost, Msg#msg.owner_name),
2661 + Peer_name_id = get_user_id(DBRef, VHost, Msg#msg.peer_name),
2662 + Peer_server_id = get_server_id(DBRef, VHost, Msg#msg.peer_server),
2663 + Peer_resource_id = get_resource_id(DBRef, VHost, Msg#msg.peer_resource),
2665 + Query = ["INSERT INTO ",Table," ",
2668 + "peer_server_id,",
2669 + "peer_resource_id,",
2676 + "('", Owner_id, "',",
2677 + "'", Peer_name_id, "',",
2678 + "'", Peer_server_id, "',",
2679 + "'", Peer_resource_id, "',",
2680 + "'", atom_to_list(Msg#msg.direction), "',",
2681 + "'", Msg#msg.type, "',",
2682 + "'", ejabberd_odbc:escape(Msg#msg.subject), "',",
2683 + "'", ejabberd_odbc:escape(Msg#msg.body), "',",
2684 + "'", Msg#msg.timestamp, "');"],
2686 + case sql_query_internal_silent(DBRef, Query) of
2688 + ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
2689 + Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
2690 + increment_user_stats(DBRef, Msg#msg.owner_name, Owner_id, VHost, Date);
2691 + {error, Reason} ->
2692 + case regexp:match(Reason, "#42S02") of
2693 + % Table doesn't exist
2695 + case create_msg_table(DBRef, VHost, Date) of
2699 + log_message_int(DBRef, VHost, Msg)
2702 + ?ERROR_MSG("Failed to log message: ~p", [Reason]),
2707 +increment_user_stats(DBRef, User_name, User_id, VHost, Date) ->
2708 + SName = stats_table(VHost),
2709 + UQuery = ["UPDATE ",SName," ",
2710 + "SET count=count+1 ",
2711 + "WHERE owner_id=\"",User_id,"\" AND at=\"",Date,"\";"],
2713 + case sql_query_internal(DBRef, UQuery) of
2715 + IQuery = ["INSERT INTO ",SName," ",
2716 + "(owner_id, at, count) ",
2718 + "('",User_id,"', '",Date,"', '1');"],
2719 + case sql_query_internal(DBRef, IQuery) of
2721 + ?MYDEBUG("New stats for ~s@~s at ~s", [User_name, VHost, Date]),
2727 + ?MYDEBUG("Updated stats for ~s@~s at ~s", [User_name, VHost, Date]),
2733 +get_dates_int(DBRef, VHost) ->
2734 + case sql_query_internal(DBRef, ["SHOW TABLES"]) of
2736 + lists:foldl(fun([Table], Dates) ->
2737 + % TODO: check prefix()
2738 + case regexp:match(Table, escape_vhost(VHost)) of
2740 + case regexp:match(Table,"[0-9]+-[0-9]+-[0-9]+") of
2742 + lists:append(Dates, [lists:sublist(Table,S,E)]);
2754 +rebuild_stats_at_int(DBRef, VHost, Date) ->
2755 + Table = messages_table(VHost, Date),
2756 + STable = stats_table(VHost),
2758 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," WRITE, ",
2759 + STable," WRITE;"]),
2762 + DQuery = [ "DELETE FROM ",STable," ",
2763 + "WHERE at='",Date,"';"],
2765 + {updated, _} = sql_query_internal(DBRef, DQuery),
2767 + SQuery = ["INSERT INTO ",STable," ",
2768 + "(owner_id,at,count) ",
2769 + "SELECT owner_id,\"",Date,"\"",",count(*) ",
2770 + "FROM ",Table," GROUP BY owner_id;"],
2772 + case sql_query_internal(DBRef, SQuery) of
2774 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]),
2776 + {updated, _} -> ok;
2777 + {error, _} -> error
2782 + Res = case sql_transaction_internal(DBRef, Fun) of
2784 + ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
2789 + {updated, _} = sql_query_internal(DBRef, ["UNLOCK TABLES;"]),
2793 +delete_nonexistent_stats(DBRef, VHost) ->
2794 + Dates = get_dates_int(DBRef, VHost),
2795 + STable = stats_table(VHost),
2797 + Temp = lists:flatmap(fun(Date) ->
2798 + ["\"",Date,"\"",","]
2801 + Temp1 = case Temp of
2805 + % replace last "," with ");"
2806 + lists:append([lists:sublist(Temp, length(Temp)-1), ");"])
2809 + Query = ["DELETE FROM ",STable," ",
2810 + "WHERE at NOT IN (", Temp1],
2812 + case sql_query_internal(DBRef, Query) of
2819 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2823 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2824 +create_stats_table(DBRef, VHost) ->
2825 + SName = stats_table(VHost),
2826 + Query = ["CREATE TABLE ",SName," (",
2827 + "owner_id MEDIUMINT UNSIGNED, ",
2828 + "at varchar(20), ",
2829 + "count int(11), ",
2830 + "INDEX(owner_id), ",
2832 + ") ENGINE=InnoDB CHARACTER SET utf8;"
2834 + case sql_query_internal_silent(DBRef, Query) of
2836 + ?MYDEBUG("Created stats table for ~p", [VHost]),
2837 + lists:foreach(fun(Date) ->
2838 + rebuild_stats_at_int(DBRef, VHost, Date)
2839 + end, get_dates_int(DBRef, VHost)),
2841 + {error, Reason} ->
2842 + case regexp:match(Reason, "#42S01") of
2844 + ?MYDEBUG("Stats table for ~p already exists", [VHost]),
2847 + ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
2852 +create_settings_table(DBRef, VHost) ->
2853 + SName = settings_table(VHost),
2854 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
2855 + "owner_id MEDIUMINT UNSIGNED PRIMARY KEY, ",
2856 + "dolog_default TINYINT(1) NOT NULL DEFAULT 1, ",
2857 + "dolog_list TEXT, ",
2858 + "donotlog_list TEXT ",
2859 + ") ENGINE=InnoDB CHARACTER SET utf8;"
2861 + case sql_query_internal(DBRef, Query) of
2863 + ?MYDEBUG("Created settings table for ~p", [VHost]),
2869 +create_users_table(DBRef, VHost) ->
2870 + SName = users_table(VHost),
2871 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
2872 + "username TEXT NOT NULL, ",
2873 + "user_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
2874 + "UNIQUE INDEX(username(",?INDEX_SIZE,")) ",
2875 + ") ENGINE=InnoDB CHARACTER SET utf8;"
2877 + case sql_query_internal(DBRef, Query) of
2879 + ?MYDEBUG("Created users table for ~p", [VHost]),
2880 + ets:new(ets_users_table(VHost), [named_table, set, public]),
2881 + %update_users_from_db(DBRef, VHost),
2887 +create_servers_table(DBRef, VHost) ->
2888 + SName = servers_table(VHost),
2889 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
2890 + "server TEXT NOT NULL, ",
2891 + "server_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
2892 + "UNIQUE INDEX(server(",?INDEX_SIZE,")) ",
2893 + ") ENGINE=InnoDB CHARACTER SET utf8;"
2895 + case sql_query_internal(DBRef, Query) of
2897 + ?MYDEBUG("Created servers table for ~p", [VHost]),
2898 + ets:new(ets_servers_table(VHost), [named_table, set, public]),
2899 + update_servers_from_db(DBRef, VHost),
2905 +create_resources_table(DBRef, VHost) ->
2906 + RName = resources_table(VHost),
2907 + Query = ["CREATE TABLE IF NOT EXISTS ",RName," (",
2908 + "resource TEXT NOT NULL, ",
2909 + "resource_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
2910 + "UNIQUE INDEX(resource(",?INDEX_SIZE,")) ",
2911 + ") ENGINE=InnoDB CHARACTER SET utf8;"
2913 + case sql_query_internal(DBRef, Query) of
2915 + ?MYDEBUG("Created resources table for ~p", [VHost]),
2916 + ets:new(ets_resources_table(VHost), [named_table, set, public]),
2922 +create_msg_table(DBRef, VHost, Date) ->
2923 + TName = messages_table(VHost, Date),
2924 + Query = ["CREATE TABLE ",TName," (",
2925 + "owner_id MEDIUMINT UNSIGNED, ",
2926 + "peer_name_id MEDIUMINT UNSIGNED, ",
2927 + "peer_server_id MEDIUMINT UNSIGNED, ",
2928 + "peer_resource_id MEDIUMINT(8) UNSIGNED, ",
2929 + "direction ENUM('to', 'from'), ",
2930 + "type ENUM('chat','error','groupchat','headline','normal') NOT NULL, ",
2933 + "timestamp DOUBLE, ",
2934 + "INDEX owner_i (owner_id), ",
2935 + "INDEX peer_i (peer_name_id, peer_server_id), ",
2936 + "FULLTEXT (body) "
2937 + ") ENGINE=MyISAM CHARACTER SET utf8;"
2939 + case sql_query_internal(DBRef, Query) of
2940 + {updated, _MySQLRes} ->
2941 + ?MYDEBUG("Created msg table for ~p at ~p", [VHost, Date]),
2947 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2949 +% internal ets cache (users, servers, resources)
2951 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2952 +update_servers_from_db(DBRef, VHost) ->
2953 + ?INFO_MSG("Reading servers from db for ~p", [VHost]),
2954 + SQuery = ["SELECT server, server_id FROM ",servers_table(VHost),";"],
2955 + {data, Result} = sql_query_internal(DBRef, SQuery),
2956 + true = ets:delete_all_objects(ets_servers_table(VHost)),
2957 + true = ets:insert(ets_servers_table(VHost), [ {Server, Server_id} || [Server, Server_id] <- Result]).
2959 +%update_users_from_db(DBRef, VHost) ->
2960 +% ?INFO_MSG("Reading users from db for ~p", [VHost]),
2961 +% SQuery = ["SELECT username, user_id FROM ",users_table(VHost),";"],
2962 +% {data, Result} = sql_query_internal(DBRef, SQuery),
2963 +% true = ets:delete_all_objects(ets_users_table(VHost)),
2964 +% true = ets:insert(ets_users_table(VHost), [ {Username, User_id} || [Username, User_id] <- Result]).
2966 +%get_user_name(DBRef, VHost, User_id) ->
2967 +% case ets:match(ets_users_table(VHost), {'$1', User_id}) of
2968 +% [[User]] -> User;
2969 +% % this can be in clustered environment
2971 +% %update_users_from_db(DBRef, VHost),
2972 +% SQuery = ["SELECT username FROM ",users_table(VHost)," ",
2973 +% "WHERE user_id=\"",User_id,"\";"],
2974 +% {data, [[Name]]} = sql_query_internal(DBRef, SQuery),
2975 +% % cache {user, id} pair
2976 +% ets:insert(ets_users_table(VHost), {Name, User_id}),
2980 +%get_server_name(DBRef, VHost, Server_id) ->
2981 +% case ets:match(ets_servers_table(VHost), {'$1', Server_id}) of
2982 +% [[Server]] -> Server;
2983 + % this can be in clustered environment
2985 +% update_servers_from_db(DBRef, VHost),
2986 +% [[Server1]] = ets:match(ets_servers_table(VHost), {'$1', Server_id}),
2990 +get_user_id_from_db(DBRef, VHost, User) ->
2991 + SQuery = ["SELECT user_id FROM ",users_table(VHost)," ",
2992 + "WHERE username=\"",User,"\";"],
2993 + case sql_query_internal(DBRef, SQuery) of
2994 + % no such user in db
2997 + {data, [[DBId]]} ->
2998 + % cache {user, id} pair
2999 + ets:insert(ets_users_table(VHost), {User, DBId}),
3002 +get_user_id(DBRef, VHost, User) ->
3004 + case ets:match(ets_users_table(VHost), {User, '$1'}) of
3007 + case get_user_id_from_db(DBRef, VHost, User) of
3008 + % no such user in db
3010 + IQuery = ["INSERT INTO ",users_table(VHost)," ",
3011 + "SET username=\"",User,"\";"],
3012 + case sql_query_internal_silent(DBRef, IQuery) of
3014 + {ok, NewId} = get_user_id_from_db(DBRef, VHost, User),
3016 + {error, Reason} ->
3017 + % this can be in clustered environment
3018 + {match, _, _} = regexp:match(Reason, "#23000"),
3019 + ?ERROR_MSG("Duplicate key name for ~p", [User]),
3020 + {ok, ClID} = get_user_id_from_db(DBRef, VHost, User),
3026 + [[EtsId]] -> EtsId
3029 +get_server_id(DBRef, VHost, Server) ->
3030 + case ets:match(ets_servers_table(VHost), {Server, '$1'}) of
3032 + IQuery = ["INSERT INTO ",servers_table(VHost)," ",
3033 + "SET server=\"",Server,"\";"],
3034 + case sql_query_internal_silent(DBRef, IQuery) of
3036 + SQuery = ["SELECT server_id FROM ",servers_table(VHost)," ",
3037 + "WHERE server=\"",Server,"\";"],
3038 + {data, [[Id]]} = sql_query_internal(DBRef, SQuery),
3039 + ets:insert(ets_servers_table(VHost), {Server, Id}),
3041 + {error, Reason} ->
3042 + % this can be in clustered environment
3043 + {match, _, _} = regexp:match(Reason, "#23000"),
3044 + ?ERROR_MSG("Duplicate key name for ~p", [Server]),
3045 + update_servers_from_db(DBRef, VHost),
3046 + [[Id1]] = ets:match(ets_servers_table(VHost), {Server, '$1'}),
3052 +get_resource_id_from_db(DBRef, VHost, Resource) ->
3053 + SQuery = ["SELECT resource_id FROM ",resources_table(VHost)," ",
3054 + "WHERE resource=\"",ejabberd_odbc:escape(Resource),"\";"],
3055 + case sql_query_internal(DBRef, SQuery) of
3056 + % no such resource in db
3059 + {data, [[DBId]]} ->
3060 + % cache {resource, id} pair
3061 + ets:insert(ets_resources_table(VHost), {Resource, DBId}),
3064 +get_resource_id(DBRef, VHost, Resource) ->
3066 + case ets:match(ets_resources_table(VHost), {Resource, '$1'}) of
3069 + case get_resource_id_from_db(DBRef, VHost, Resource) of
3070 + % no such resource in db
3072 + IQuery = ["INSERT INTO ",resources_table(VHost)," ",
3073 + "SET resource=\"",ejabberd_odbc:escape(Resource),"\";"],
3074 + case sql_query_internal_silent(DBRef, IQuery) of
3076 + {ok, NewId} = get_resource_id_from_db(DBRef, VHost, Resource),
3078 + {error, Reason} ->
3079 + % this can be in clustered environment
3080 + {match, _, _} = regexp:match(Reason, "#23000"),
3081 + ?ERROR_MSG("Duplicate key name for ~p", [Resource]),
3082 + {ok, ClID} = get_resource_id_from_db(DBRef, VHost, Resource),
3088 + [[EtsId]] -> EtsId
3091 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3095 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3096 +% like do_transaction/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
3097 +sql_transaction_internal(DBRef, Fun) ->
3098 + case sql_query_internal(DBRef, ["START TRANSACTION;"]) of
3100 + case catch Fun() of
3102 + rollback_internal(DBRef, Err);
3103 + {error, _} = Err ->
3104 + rollback_internal(DBRef, Err);
3105 + {'EXIT', _} = Err ->
3106 + rollback_internal(DBRef, Err);
3108 + case sql_query_internal(DBRef, ["COMMIT;"]) of
3109 + {error, _} -> rollback_internal(DBRef, {commit_error});
3112 + {atomic, _} -> Res;
3113 + _ -> {atomic, Res}
3118 + {aborted, {begin_error}}
3121 +% like rollback/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
3122 +rollback_internal(DBRef, Reason) ->
3123 + Res = sql_query_internal(DBRef, ["ROLLBACK;"]),
3124 + {aborted, {Reason, {rollback_result, Res}}}.
3126 +sql_query_internal(DBRef, Query) ->
3127 + case sql_query_internal_silent(DBRef, Query) of
3128 + {error, Reason} ->
3129 + ?ERROR_MSG("~p while ~p", [Reason, lists:append(Query)]),
3134 +sql_query_internal_silent(DBRef, Query) ->
3135 + ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
3136 + get_result(mysql_conn:fetch(DBRef, Query, self(), ?TIMEOUT)).
3138 +get_result({updated, MySQLRes}) ->
3139 + {updated, mysql:get_result_affected_rows(MySQLRes)};
3140 +get_result({data, MySQLRes}) ->
3141 + {data, mysql:get_result_rows(MySQLRes)};
3142 +get_result({error, "query timed out"}) ->
3143 + {error, "query timed out"};
3144 +get_result({error, MySQLRes}) ->
3145 + Reason = mysql:get_result_reason(MySQLRes),
3147 --- src/mod_logdb_mysql5.erl.orig Tue Dec 11 14:23:19 2007
3148 +++ src/mod_logdb_mysql5.erl Tue Dec 11 11:58:33 2007
3150 +%%%----------------------------------------------------------------------
3151 +%%% File : mod_logdb_mysql5.erl
3152 +%%% Author : Oleg Palij (mailto:o.palij@gmail.com xmpp://malik@jabber.te.ua)
3153 +%%% Purpose : MySQL 5 backend for mod_logdb
3154 +%%% Version : trunk
3156 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
3157 +%%%----------------------------------------------------------------------
3159 +-module(mod_logdb_mysql5).
3160 +-author('o.palij@gmail.com').
3161 +-vsn('$Revision$').
3163 +-include("mod_logdb.hrl").
3164 +-include("ejabberd.hrl").
3165 +-include("jlib.hrl").
3167 +-behaviour(gen_logdb).
3168 +-behaviour(gen_server).
3171 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
3173 +-export([start/2, stop/1]).
3175 +-export([log_message/2,
3177 + rebuild_stats_at/2,
3178 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
3179 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
3181 + get_users_settings/1, get_user_settings/2, set_user_settings/3]).
3183 +% gen_server call timeout
3184 +-define(CALL_TIMEOUT, 60000).
3185 +-define(TIMEOUT, 60000).
3186 +-define(INDEX_SIZE, integer_to_list(170)).
3187 +-define(PROCNAME, mod_logdb_mysql5).
3189 +-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
3190 + list_to_string/1, string_to_list/1,
3191 + convert_timestamp_brief/1]).
3193 +-record(state, {dbref, vhost}).
3195 +% replace "." with "_"
3196 +escape_vhost(VHost) -> lists:map(fun(46) -> 95;
3203 + "_" ++ escape_vhost(VHost) ++ "`".
3205 +messages_table(VHost, Date) ->
3206 + prefix() ++ "messages_" ++ Date ++ suffix(VHost).
3208 +% TODO: this needs to be redone to unify view name in stored procedure and in delete_messages_at/2
3209 +view_table(VHost, Date) ->
3210 + Table = messages_table(VHost, Date),
3211 + TablewoQ = lists:sublist(Table, 2, length(Table) - 2),
3212 + lists:append(["`v_", TablewoQ, "`"]).
3214 +stats_table(VHost) ->
3215 + prefix() ++ "stats" ++ suffix(VHost).
3217 +settings_table(VHost) ->
3218 + prefix() ++ "settings" ++ suffix(VHost).
3220 +users_table(VHost) ->
3221 + prefix() ++ "users" ++ suffix(VHost).
3222 +servers_table(VHost) ->
3223 + prefix() ++ "servers" ++ suffix(VHost).
3224 +resources_table(VHost) ->
3225 + prefix() ++ "resources" ++ suffix(VHost).
3227 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3229 +% gen_mod callbacks
3231 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3232 +start(VHost, Opts) ->
3233 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3234 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
3237 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3238 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
3240 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3242 +% gen_server callbacks
3244 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3245 +init([VHost, Opts]) ->
3248 + Server = gen_mod:get_opt(server, Opts, "localhost"),
3249 + Port = gen_mod:get_opt(port, Opts, 3128),
3250 + DB = gen_mod:get_opt(db, Opts, "logdb"),
3251 + User = gen_mod:get_opt(user, Opts, "root"),
3252 + Password = gen_mod:get_opt(password, Opts, ""),
3254 + LogFun = fun(debug, Format, Argument) ->
3255 + ?MYDEBUG(Format, Argument);
3256 + (error, Format, Argument) ->
3257 + ?ERROR_MSG(Format, Argument);
3258 + (Level, Format, Argument) ->
3259 + ?MYDEBUG("MySQL (~p)~n", [Level]),
3260 + ?MYDEBUG(Format, Argument)
3262 + case mysql_conn:start(Server, Port, User, Password, DB, [65536, 131072], LogFun) of
3264 + ok = create_internals(DBRef, VHost),
3265 + ok = create_stats_table(DBRef, VHost),
3266 + ok = create_settings_table(DBRef, VHost),
3267 + ok = create_users_table(DBRef, VHost),
3268 + ok = create_servers_table(DBRef, VHost),
3269 + ok = create_resources_table(DBRef, VHost),
3270 + erlang:monitor(process, DBRef),
3271 + {ok, #state{dbref=DBRef, vhost=VHost}};
3272 + {error, Reason} ->
3273 + ?ERROR_MSG("MySQL connection failed: ~p~n", [Reason]),
3274 + {stop, db_connection_failed}
3277 +handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3278 + Date = convert_timestamp_brief(Msg#msg.timestamp),
3279 + TableName = messages_table(VHost, Date),
3281 + Query = [ "CALL logmessage "
3282 + "('", TableName, "',",
3284 + "'", Msg#msg.owner_name, "',",
3285 + "'", Msg#msg.peer_name, "',",
3286 + "'", Msg#msg.peer_server, "',",
3287 + "'", ejabberd_odbc:escape(Msg#msg.peer_resource), "',",
3288 + "'", atom_to_list(Msg#msg.direction), "',",
3289 + "'", Msg#msg.type, "',",
3290 + "'", ejabberd_odbc:escape(Msg#msg.subject), "',",
3291 + "'", ejabberd_odbc:escape(Msg#msg.body), "',",
3292 + "'", Msg#msg.timestamp, "');"],
3295 + case sql_query_internal(DBRef, Query) of
3297 + ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
3298 + Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
3300 + {error, _Reason} ->
3303 + {reply, Reply, State};
3304 +handle_call({rebuild_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3305 + ok = delete_nonexistent_stats(DBRef, VHost),
3307 + lists:foreach(fun(Date) ->
3308 + catch rebuild_stats_at_int(DBRef, VHost, Date)
3309 + end, get_dates_int(DBRef, VHost)),
3310 + {reply, Reply, State};
3311 +handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3312 + Reply = rebuild_stats_at_int(DBRef, VHost, Date),
3313 + {reply, Reply, State};
3314 +handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
3315 + {reply, error, State};
3316 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3317 + Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
3318 + ["\"",Timestamp,"\"",","]
3321 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
3323 + Query = ["DELETE FROM ",messages_table(VHost, Date)," ",
3324 + "WHERE timestamp IN (", Temp1],
3327 + case sql_query_internal(DBRef, Query) of
3329 + ?MYDEBUG("Aff=~p", [Aff]),
3330 + rebuild_stats_at_int(DBRef, VHost, Date);
3334 + {reply, Reply, State};
3335 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3336 + DQuery = ["DELETE FROM ",messages_table(VHost, Date)," ",
3337 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
3339 + case sql_query_internal(DBRef, DQuery) of
3341 + rebuild_stats_at_int(DBRef, VHost, Date);
3345 + {reply, Reply, State};
3346 +handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3348 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Date),";"]),
3349 + TQuery = ["DELETE FROM ",stats_table(VHost)," "
3350 + "WHERE at=\"",Date,"\";"],
3351 + {updated, _} = sql_query_internal(DBRef, TQuery),
3352 + VQuery = ["DROP VIEW IF EXISTS ",view_table(VHost,Date),";"],
3353 + {updated, _} = sql_query_internal(DBRef, VQuery)
3356 + case sql_transaction_internal(DBRef, Fun) of
3362 + {reply, Reply, State};
3363 +handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3364 + SName = stats_table(VHost),
3365 + Query = ["SELECT at, sum(count) ",
3366 + "FROM ",SName," ",
3368 + "ORDER BY DATE(at) DESC;"
3371 + case sql_query_internal(DBRef, Query) of
3373 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
3374 + {error, Reason} ->
3375 + % TODO: Duplicate error message ?
3378 + {reply, Reply, State};
3379 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3380 + SName = stats_table(VHost),
3381 + Query = ["SELECT username, count ",
3382 + "FROM ",SName," ",
3383 + "JOIN ",users_table(VHost)," ON owner_id=user_id "
3384 + "WHERE at=\"",Date,"\" ",
3385 + "ORDER BY count DESC;"
3388 + case sql_query_internal(DBRef, Query) of
3390 + {ok, [ {User, list_to_integer(Count)} || [User, Count] <- Result ]};
3391 + {error, Reason} ->
3394 + {reply, Reply, State};
3395 +handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3396 + SName = stats_table(VHost),
3397 + UName = users_table(VHost),
3398 + Query = ["SELECT stats.at, stats.count ",
3399 + "FROM ",UName," AS users ",
3400 + "JOIN ",SName," AS stats ON owner_id=user_id "
3401 + "WHERE users.username=\"",User,"\" ",
3402 + "ORDER BY DATE(at) DESC;"
3405 + case sql_query_internal(DBRef, Query) of
3407 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
3408 + {error, Result} ->
3411 + {reply, Reply, State};
3412 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3413 + Query = ["SELECT peer_name,",
3421 + "FROM ",view_table(VHost, Date)," "
3422 + "WHERE owner_name=\"",User,"\";"],
3424 + case sql_query_internal(DBRef, Query) of
3426 + Fun = fun([Peer_name, Peer_server, Peer_resource,
3431 + #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
3432 + direction=list_to_atom(Direction),
3434 + subject=Subject, body=Body,
3435 + timestamp=Timestamp}
3437 + {ok, lists:map(Fun, Result)};
3438 + {error, Reason} ->
3441 + {reply, Reply, State};
3442 +handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3443 + SName = stats_table(VHost),
3444 + Query = ["SELECT at ",
3445 + "FROM ",SName," ",
3447 + "ORDER BY DATE(at) DESC;"
3450 + case sql_query_internal(DBRef, Query) of
3452 + [ Date || [Date] <- Result ];
3453 + {error, Reason} ->
3456 + {reply, Reply, State};
3457 +handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3458 + Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
3459 + "FROM ",settings_table(VHost)," ",
3460 + "JOIN ",users_table(VHost)," ON user_id=owner_id;"],
3462 + case sql_query_internal(DBRef, Query) of
3464 + {ok, lists:map(fun([Owner, DoLogDef, DoLogL, DoNotLogL]) ->
3465 + #user_settings{owner_name=Owner,
3466 + dolog_default=list_to_bool(DoLogDef),
3467 + dolog_list=string_to_list(DoLogL),
3468 + donotlog_list=string_to_list(DoNotLogL)
3474 + {reply, Reply, State};
3475 +handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3476 + Query = ["SELECT dolog_default,dolog_list,donotlog_list FROM ",settings_table(VHost)," ",
3477 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
3479 + case sql_query_internal(DBRef, Query) of
3482 + {data, [[Owner, DoLogDef, DoLogL, DoNotLogL]]} ->
3483 + {ok, #user_settings{owner_name=Owner,
3484 + dolog_default=list_to_bool(DoLogDef),
3485 + dolog_list=string_to_list(DoLogL),
3486 + donotlog_list=string_to_list(DoNotLogL)}};
3490 + {reply, Reply, State};
3491 +handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
3492 + dolog_list=DoLogL,
3493 + donotlog_list=DoNotLogL}},
3494 + _From, #state{dbref=DBRef, vhost=VHost} = State) ->
3495 + User_id = get_user_id(DBRef, VHost, User),
3496 + Query = ["UPDATE ",settings_table(VHost)," ",
3497 + "SET dolog_default=",bool_to_list(DoLogDef),", ",
3498 + "dolog_list='",list_to_string(DoLogL),"', ",
3499 + "donotlog_list='",list_to_string(DoNotLogL),"' ",
3500 + "WHERE owner_id=",User_id,";"],
3503 + case sql_query_internal(DBRef, Query) of
3505 + IQuery = ["INSERT INTO ",settings_table(VHost)," ",
3506 + "(owner_id, dolog_default, dolog_list, donotlog_list) ",
3508 + "(",User_id,",",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
3509 + case sql_query_internal_silent(DBRef, IQuery) of
3511 + ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
3513 + {error, Reason} ->
3514 + case regexp:match(Reason, "#23000") of
3519 + ?ERROR_MSG("Failed setup user ~p@~p: ~p", [User, VHost, Reason]),
3524 + ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
3529 + {reply, Reply, State};
3530 +handle_call({stop}, _From, #state{vhost=VHost}=State) ->
3531 + ?MYDEBUG("Stoping mysql5 backend for ~p", [VHost]),
3532 + {stop, normal, ok, State};
3533 +handle_call(Msg, _From, State) ->
3534 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
3537 +handle_cast(Msg, State) ->
3538 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
3541 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
3542 + {stop, connection_dropped, State};
3543 +handle_info(Info, State) ->
3544 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
3547 +terminate(_Reason, _State) ->
3550 +code_change(_OldVsn, State, _Extra) ->
3553 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3555 +% gen_logdb callbacks
3557 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3558 +log_message(VHost, Msg) ->
3559 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3560 + gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
3561 +rebuild_stats(VHost) ->
3562 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3563 + gen_server:call(Proc, {rebuild_stats}, ?CALL_TIMEOUT).
3564 +rebuild_stats_at(VHost, Date) ->
3565 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3566 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
3567 +delete_messages_by_user_at(VHost, Msgs, Date) ->
3568 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3569 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
3570 +delete_all_messages_by_user_at(User, VHost, Date) ->
3571 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3572 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
3573 +delete_messages_at(VHost, Date) ->
3574 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3575 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
3576 +get_vhost_stats(VHost) ->
3577 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3578 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
3579 +get_vhost_stats_at(VHost, Date) ->
3580 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3581 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
3582 +get_user_stats(User, VHost) ->
3583 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3584 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
3585 +get_user_messages_at(User, VHost, Date) ->
3586 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3587 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
3588 +get_dates(VHost) ->
3589 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3590 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
3591 +get_users_settings(VHost) ->
3592 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3593 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
3594 +get_user_settings(User, VHost) ->
3595 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3596 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
3597 +set_user_settings(User, VHost, Set) ->
3598 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3599 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
3601 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3605 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3606 +get_dates_int(DBRef, VHost) ->
3607 + case sql_query_internal(DBRef, ["SHOW TABLES"]) of
3609 + lists:foldl(fun([Table], Dates) ->
3610 + % TODO: check prefix()
3611 + case regexp:match(Table, escape_vhost(VHost)) of
3613 + case regexp:match(Table,"[0-9]+-[0-9]+-[0-9]+") of
3615 + lists:append(Dates, [lists:sublist(Table,S,E)]);
3627 +rebuild_stats_at_int(DBRef, VHost, Date) ->
3628 + Table = messages_table(VHost, Date),
3629 + STable = stats_table(VHost),
3631 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," WRITE, ",
3632 + STable," WRITE;"]),
3635 + DQuery = [ "DELETE FROM ",STable," ",
3636 + "WHERE at='",Date,"';"],
3638 + {updated, _} = sql_query_internal(DBRef, DQuery),
3640 + SQuery = ["INSERT INTO ",STable," ",
3641 + "(owner_id,at,count) ",
3642 + "SELECT owner_id,\"",Date,"\"",",count(*) ",
3643 + "FROM ",Table," GROUP BY owner_id;"],
3645 + case sql_query_internal(DBRef, SQuery) of
3647 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]),
3649 + {updated, _} -> ok;
3650 + {error, _} -> error
3654 + Res = case sql_transaction_internal(DBRef, Fun) of
3656 + ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
3661 + {updated, _} = sql_query_internal(DBRef, ["UNLOCK TABLES;"]),
3665 +delete_nonexistent_stats(DBRef, VHost) ->
3666 + Dates = get_dates_int(DBRef, VHost),
3667 + STable = stats_table(VHost),
3669 + Temp = lists:flatmap(fun(Date) ->
3670 + ["\"",Date,"\"",","]
3673 + Temp1 = case Temp of
3677 + % replace last "," with ");"
3678 + lists:append([lists:sublist(Temp, length(Temp)-1), ");"])
3681 + Query = ["DELETE FROM ",STable," ",
3682 + "WHERE at NOT IN (", Temp1],
3684 + case sql_query_internal(DBRef, Query) of
3691 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3695 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3696 +create_stats_table(DBRef, VHost) ->
3697 + SName = stats_table(VHost),
3698 + Query = ["CREATE TABLE ",SName," (",
3699 + "owner_id MEDIUMINT UNSIGNED, ",
3700 + "at VARCHAR(11), ",
3701 + "count INT(11), ",
3702 + "INDEX(owner_id), ",
3704 + ") ENGINE=InnoDB CHARACTER SET utf8;"
3706 + case sql_query_internal_silent(DBRef, Query) of
3708 + ?MYDEBUG("Created stats table for ~p", [VHost]),
3709 + lists:foreach(fun(Date) ->
3710 + rebuild_stats_at_int(DBRef, VHost, Date)
3711 + end, get_dates_int(DBRef, VHost)),
3713 + {error, Reason} ->
3714 + case regexp:match(Reason, "#42S01") of
3716 + ?MYDEBUG("Stats table for ~p already exists", [VHost]),
3719 + ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
3724 +create_settings_table(DBRef, VHost) ->
3725 + SName = settings_table(VHost),
3726 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
3727 + "owner_id MEDIUMINT UNSIGNED PRIMARY KEY, ",
3728 + "dolog_default TINYINT(1) NOT NULL DEFAULT 1, ",
3729 + "dolog_list TEXT, ",
3730 + "donotlog_list TEXT ",
3731 + ") ENGINE=InnoDB CHARACTER SET utf8;"
3733 + case sql_query_internal(DBRef, Query) of
3735 + ?MYDEBUG("Created settings table for ~p", [VHost]),
3741 +create_users_table(DBRef, VHost) ->
3742 + SName = users_table(VHost),
3743 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
3744 + "username TEXT NOT NULL, ",
3745 + "user_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
3746 + "UNIQUE INDEX(username(",?INDEX_SIZE,")) ",
3747 + ") ENGINE=InnoDB CHARACTER SET utf8;"
3749 + case sql_query_internal(DBRef, Query) of
3751 + ?MYDEBUG("Created users table for ~p", [VHost]),
3757 +create_servers_table(DBRef, VHost) ->
3758 + SName = servers_table(VHost),
3759 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
3760 + "server TEXT NOT NULL, ",
3761 + "server_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
3762 + "UNIQUE INDEX(server(",?INDEX_SIZE,")) ",
3763 + ") ENGINE=InnoDB CHARACTER SET utf8;"
3765 + case sql_query_internal(DBRef, Query) of
3767 + ?MYDEBUG("Created servers table for ~p", [VHost]),
3773 +create_resources_table(DBRef, VHost) ->
3774 + RName = resources_table(VHost),
3775 + Query = ["CREATE TABLE IF NOT EXISTS ",RName," (",
3776 + "resource TEXT NOT NULL, ",
3777 + "resource_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
3778 + "UNIQUE INDEX(resource(",?INDEX_SIZE,")) ",
3779 + ") ENGINE=InnoDB CHARACTER SET utf8;"
3781 + case sql_query_internal(DBRef, Query) of
3783 + ?MYDEBUG("Created resources table for ~p", [VHost]),
3789 +create_internals(DBRef, VHost) ->
3790 + sql_query_internal(DBRef, ["DROP PROCEDURE IF EXISTS `logmessage`;"]),
3791 + case sql_query_internal(DBRef, [get_logmessage(VHost)]) of
3793 + ?MYDEBUG("Created logmessage for ~p", [VHost]),
3799 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3803 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3804 +% like do_transaction/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
3805 +sql_transaction_internal(DBRef, Fun) ->
3806 + case sql_query_internal(DBRef, ["START TRANSACTION;"]) of
3808 + case catch Fun() of
3810 + rollback_internal(DBRef, Err);
3811 + {error, _} = Err ->
3812 + rollback_internal(DBRef, Err);
3813 + {'EXIT', _} = Err ->
3814 + rollback_internal(DBRef, Err);
3816 + case sql_query_internal(DBRef, ["COMMIT;"]) of
3817 + {error, _} -> rollback_internal(DBRef, {commit_error});
3820 + {atomic, _} -> Res;
3821 + _ -> {atomic, Res}
3826 + {aborted, {begin_error}}
3829 +% like rollback/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
3830 +rollback_internal(DBRef, Reason) ->
3831 + Res = sql_query_internal(DBRef, ["ROLLBACK;"]),
3832 + {aborted, {Reason, {rollback_result, Res}}}.
3834 +sql_query_internal(DBRef, Query) ->
3835 + case sql_query_internal_silent(DBRef, Query) of
3836 + {error, Reason} ->
3837 + ?ERROR_MSG("~p while ~p", [Reason, lists:append(Query)]),
3842 +sql_query_internal_silent(DBRef, Query) ->
3843 + ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
3844 + get_result(mysql_conn:fetch(DBRef, Query, self(), ?TIMEOUT)).
3846 +get_result({updated, MySQLRes}) ->
3847 + {updated, mysql:get_result_affected_rows(MySQLRes)};
3848 +get_result({data, MySQLRes}) ->
3849 + {data, mysql:get_result_rows(MySQLRes)};
3850 +get_result({error, "query timed out"}) ->
3851 + {error, "query timed out"};
3852 +get_result({error, MySQLRes}) ->
3853 + Reason = mysql:get_result_reason(MySQLRes),
3856 +get_user_id(DBRef, VHost, User) ->
3857 + SQuery = ["SELECT user_id FROM ",users_table(VHost)," ",
3858 + "WHERE username=\"",User,"\";"],
3859 + case sql_query_internal(DBRef, SQuery) of
3861 + IQuery = ["INSERT INTO ",users_table(VHost)," ",
3862 + "SET username=\"",User,"\";"],
3863 + case sql_query_internal_silent(DBRef, IQuery) of
3865 + {data, [[DBIdNew]]} = sql_query_internal(DBRef, SQuery),
3867 + {error, Reason} ->
3868 + % this can be in clustered environment
3869 + {match, _, _} = regexp:match(Reason, "#23000"),
3870 + ?ERROR_MSG("Duplicate key name for ~p", [User]),
3871 + {data, [[ClID]]} = sql_query_internal(DBRef, SQuery),
3874 + {data, [[DBId]]} ->
3878 +get_logmessage(VHost) ->
3879 + UName = users_table(VHost),
3880 + SName = servers_table(VHost),
3881 + RName = resources_table(VHost),
3882 + StName = stats_table(VHost),
3884 +CREATE PROCEDURE logmessage(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)
3886 + DECLARE ownerID MEDIUMINT UNSIGNED;
3887 + DECLARE peer_nameID MEDIUMINT UNSIGNED;
3888 + DECLARE peer_serverID MEDIUMINT UNSIGNED;
3889 + DECLARE peer_resourceID MEDIUMINT UNSIGNED;
3890 + DECLARE Vmtype VARCHAR(10);
3891 + DECLARE Vmtimestamp DOUBLE;
3892 + DECLARE Vmdirection VARCHAR(4);
3893 + DECLARE Vmbody TEXT;
3894 + DECLARE Vmsubject TEXT;
3897 + DECLARE viewname TEXT;
3898 + DECLARE notable INT;
3899 + DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' SET @notable = 1;
3902 + SET @ownerID = NULL;
3903 + SET @peer_nameID = NULL;
3904 + SET @peer_serverID = NULL;
3905 + SET @peer_resourceID = NULL;
3907 + SET @Vmtype = mtype;
3908 + SET @Vmtimestamp = mtimestamp;
3909 + SET @Vmdirection = mdirection;
3910 + SET @Vmbody = mbody;
3911 + SET @Vmsubject = msubject;
3913 + SELECT user_id INTO @ownerID FROM ~s WHERE username=owner;
3914 + IF @ownerID IS NULL THEN
3915 + INSERT INTO ~s SET username=owner;
3916 + SET @ownerID = LAST_INSERT_ID();
3919 + SELECT user_id INTO @peer_nameID FROM ~s WHERE username=peer_name;
3920 + IF @peer_nameID IS NULL THEN
3921 + INSERT INTO ~s SET username=peer_name;
3922 + SET @peer_nameID = LAST_INSERT_ID();
3925 + SELECT server_id INTO @peer_serverID FROM ~s WHERE server=peer_server;
3926 + IF @peer_serverID IS NULL THEN
3927 + INSERT INTO ~s SET server=peer_server;
3928 + SET @peer_serverID = LAST_INSERT_ID();
3931 + SELECT resource_id INTO @peer_resourceID FROM ~s WHERE resource=peer_resource;
3932 + IF @peer_resourceID IS NULL THEN
3933 + INSERT INTO ~s SET resource=peer_resource;
3934 + SET @peer_resourceID = LAST_INSERT_ID();
3937 + 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);\");
3938 + PREPARE insertmsg FROM @iq;
3940 + IF @notable = 1 THEN
3941 + SET @cq = CONCAT(\"CREATE TABLE \",tablename,\" (
3942 + owner_id MEDIUMINT UNSIGNED,
3943 + peer_name_id MEDIUMINT UNSIGNED,
3944 + peer_server_id MEDIUMINT UNSIGNED,
3945 + peer_resource_id MEDIUMINT(8) UNSIGNED,
3946 + direction ENUM('to', 'from'),
3947 + type ENUM('chat','error','groupchat','headline','normal') NOT NULL,
3951 + ext INTEGER DEFAULT NULL,
3952 + INDEX owner_i (owner_id),
3953 + INDEX peer_i (peer_name_id, peer_server_id),
3954 + INDEX ext_i (ext),
3956 + ) ENGINE=MyISAM CHARACTER SET utf8;\");
3957 + PREPARE createtable FROM @cq;
3958 + EXECUTE createtable;
3959 + DEALLOCATE PREPARE createtable;
3961 + SET @viewname = CONCAT(\"`v_\", TRIM(BOTH '`' FROM tablename), \"`\");
3962 + SET @cq = CONCAT(\"CREATE OR REPLACE VIEW \",@viewname,\" AS
3963 + SELECT owner.username AS owner_name,
3964 + peer.username AS peer_name,
3965 + servers.server AS peer_server,
3966 + resources.resource AS peer_resource,
3967 + messages.direction,
3971 + messages.timestamp
3977 + \", tablename,\" messages
3979 + owner.user_id=messages.owner_id and
3980 + peer.user_id=messages.peer_name_id and
3981 + servers.server_id=messages.peer_server_id and
3982 + resources.resource_id=messages.peer_resource_id
3983 + ORDER BY messages.timestamp;\");
3984 + PREPARE createview FROM @cq;
3985 + EXECUTE createview;
3986 + DEALLOCATE PREPARE createview;
3989 + PREPARE insertmsg FROM @iq;
3990 + EXECUTE insertmsg;
3991 + ELSEIF @notable = 0 THEN
3992 + EXECUTE insertmsg;
3995 + DEALLOCATE PREPARE insertmsg;
3997 + IF @notable = 0 THEN
3998 + UPDATE ~s SET count=count+1 WHERE owner_id=@ownerID AND at=atdate;
3999 + IF ROW_COUNT() = 0 THEN
4000 + INSERT INTO ~s (owner_id, at, count) VALUES (@ownerID, atdate, 1);
4003 +END;", [UName,UName,UName,UName,SName,SName,RName,RName,UName,UName,SName,RName,StName,StName]).
4004 --- src/mod_logdb_pgsql.erl.orig Tue Dec 11 14:23:19 2007
4005 +++ src/mod_logdb_pgsql.erl Sun Nov 18 20:53:55 2007
4007 +%%%----------------------------------------------------------------------
4008 +%%% File : mod_logdb_pgsql.erl
4009 +%%% Author : Oleg Palij (mailto:o.palij@gmail.com xmpp://malik@jabber.te.ua)
4010 +%%% Purpose : Posgresql backend for mod_logdb
4011 +%%% Version : trunk
4013 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
4014 +%%%----------------------------------------------------------------------
4016 +-module(mod_logdb_pgsql).
4017 +-author('o.palij@gmail.com').
4018 +-vsn('$Revision$').
4020 +-include("mod_logdb.hrl").
4021 +-include("ejabberd.hrl").
4022 +-include("jlib.hrl").
4024 +-behaviour(gen_logdb).
4025 +-behaviour(gen_server).
4028 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
4030 +-export([start/2, stop/1]).
4032 +-export([log_message/2,
4034 + rebuild_stats_at/2,
4035 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
4036 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
4038 + get_users_settings/1, get_user_settings/2, set_user_settings/3]).
4040 +% gen_server call timeout
4041 +-define(CALL_TIMEOUT, 60000).
4042 +-define(TIMEOUT, 60000).
4043 +-define(PROCNAME, mod_logdb_pgsql).
4045 +-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
4046 + list_to_string/1, string_to_list/1,
4047 + convert_timestamp_brief/1]).
4049 +-record(state, {dbref, vhost, schema}).
4051 +% replace "." with "_"
4052 +escape_vhost(VHost) -> lists:map(fun(46) -> 95;
4057 + Schema ++ ".\"" ++ "logdb_".
4060 + "_" ++ escape_vhost(VHost) ++ "\"".
4062 +messages_table(VHost, Schema, Date) ->
4063 + prefix(Schema) ++ "messages_" ++ Date ++ suffix(VHost).
4065 +% TODO: this needs to be redone to unify view name in stored procedure and in delete_messages_at/2
4066 +view_table(VHost, Schema, Date) ->
4067 + Table = messages_table(VHost, Schema, Date),
4068 + TablewoS = lists:sublist(Table, length(Schema) + 3, length(Table) - length(Schema) - 3),
4069 + lists:append([Schema, ".\"v_", TablewoS, "\""]).
4071 +stats_table(VHost, Schema) ->
4072 + prefix(Schema) ++ "stats" ++ suffix(VHost).
4074 +settings_table(VHost, Schema) ->
4075 + prefix(Schema) ++ "settings" ++ suffix(VHost).
4077 +users_table(VHost, Schema) ->
4078 + prefix(Schema) ++ "users" ++ suffix(VHost).
4079 +servers_table(VHost, Schema) ->
4080 + prefix(Schema) ++ "servers" ++ suffix(VHost).
4081 +resources_table(VHost, Schema) ->
4082 + prefix(Schema) ++ "resources" ++ suffix(VHost).
4084 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4086 +% gen_mod callbacks
4088 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4089 +start(VHost, Opts) ->
4090 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4091 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
4094 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4095 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
4097 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4099 +% gen_server callbacks
4101 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4102 +init([VHost, Opts]) ->
4103 + Server = gen_mod:get_opt(server, Opts, "localhost"),
4104 + DB = gen_mod:get_opt(db, Opts, "ejabberd_logdb"),
4105 + User = gen_mod:get_opt(user, Opts, "root"),
4106 + Port = gen_mod:get_opt(port, Opts, 5432),
4107 + Password = gen_mod:get_opt(password, Opts, ""),
4108 + Schema = gen_mod:get_opt(schema, Opts, "public"),
4110 + case catch pgsql:connect(Server, DB, User, Password, Port) of
4112 + {updated, _} = sql_query_internal(DBRef, ["SET SEARCH_PATH TO ",Schema,";"]),
4113 + ok = create_internals(DBRef, VHost, Schema),
4114 + ok = create_stats_table(DBRef, VHost, Schema),
4115 + ok = create_settings_table(DBRef, VHost, Schema),
4116 + ok = create_users_table(DBRef, VHost, Schema),
4117 + ok = create_servers_table(DBRef, VHost, Schema),
4118 + ok = create_resources_table(DBRef, VHost, Schema),
4119 + erlang:monitor(process, DBRef),
4120 + {ok, #state{dbref=DBRef, vhost=VHost, schema=Schema}};
4121 + % this does not work
4122 + {error, Reason} ->
4123 + ?ERROR_MSG("PgSQL connection failed: ~p~n", [Reason]),
4124 + {stop, db_connection_failed};
4125 + % and this too, becouse pgsql_conn do exit() which can not be catched
4127 + ?ERROR_MSG("Rez: ~p~n", [Rez]),
4128 + {stop, db_connection_failed}
4131 +handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4132 + Date = convert_timestamp_brief(Msg#msg.timestamp),
4133 + TableName = messages_table(VHost, Schema, Date),
4135 + Query = [ "SELECT logmessage "
4136 + "('", TableName, "',",
4138 + "'", Msg#msg.owner_name, "',",
4139 + "'", Msg#msg.peer_name, "',",
4140 + "'", Msg#msg.peer_server, "',",
4141 + "'", ejabberd_odbc:escape(Msg#msg.peer_resource), "',",
4142 + "'", atom_to_list(Msg#msg.direction), "',",
4143 + "'", Msg#msg.type, "',",
4144 + "'", ejabberd_odbc:escape(Msg#msg.subject), "',",
4145 + "'", ejabberd_odbc:escape(Msg#msg.body), "',",
4146 + "'", Msg#msg.timestamp, "');"],
4149 + case sql_query_internal_silent(DBRef, Query) of
4150 + % TODO: change this
4151 + {data, [{"0"}]} ->
4152 + ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
4153 + Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
4155 + {error, _Reason} ->
4158 + {reply, Reply, State};
4159 +handle_call({rebuild_stats}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4160 + ok = delete_nonexistent_stats(DBRef, VHost, Schema),
4162 + lists:foreach(fun(Date) ->
4163 + catch rebuild_stats_at_int(DBRef, VHost, Schema, Date)
4164 + end, get_dates_int(DBRef, VHost)),
4165 + {reply, Reply, State};
4166 +handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4167 + Reply = rebuild_stats_at_int(DBRef, VHost, Schema, Date),
4168 + {reply, Reply, State};
4169 +handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
4170 + {reply, error, State};
4171 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4172 + Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
4173 + ["'",Timestamp,"'",","]
4176 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
4178 + Query = ["DELETE FROM ",messages_table(VHost, Schema, Date)," ",
4179 + "WHERE timestamp IN (", Temp1],
4182 + case sql_query_internal(DBRef, Query) of
4184 + rebuild_stats_at_int(DBRef, VHost, Schema, Date);
4188 + {reply, Reply, State};
4189 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4190 + DQuery = ["DELETE FROM ",messages_table(VHost, Schema, Date)," ",
4191 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
4193 + case sql_query_internal(DBRef, DQuery) of
4195 + rebuild_stats_at_int(DBRef, VHost, Schema, Date);
4199 + {reply, Reply, State};
4200 +handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4202 + {updated, _} = sql_query_internal(DBRef, ["DROP VIEW ",view_table(VHost, Schema, Date),";"]),
4204 + case sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Schema, Date),";"]) of
4206 + Query = ["DELETE FROM ",stats_table(VHost, Schema)," "
4207 + "WHERE at='",Date,"';"],
4208 + case sql_query_internal(DBRef, Query) of
4217 + {reply, Reply, State};
4218 +handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4219 + SName = stats_table(VHost, Schema),
4220 + Query = ["SELECT at, sum(count) ",
4221 + "FROM ",SName," ",
4223 + "ORDER BY DATE(at) DESC;"
4226 + case sql_query_internal(DBRef, Query) of
4228 + {ok, [ {Date, list_to_integer(Count)} || {Date, Count} <- Recs]};
4229 + {error, Reason} ->
4230 + % TODO: Duplicate error message ?
4233 + {reply, Reply, State};
4234 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4235 + SName = stats_table(VHost, Schema),
4236 + Query = ["SELECT username, count ",
4237 + "FROM ",SName," ",
4238 + "JOIN ",users_table(VHost, Schema)," ON owner_id=user_id "
4239 + "WHERE at='",Date,"';"
4242 + case sql_query_internal(DBRef, Query) of
4244 + RFun = fun({User, Count}) ->
4245 + {User, list_to_integer(Count)}
4247 + {ok, lists:reverse(lists:keysort(2, lists:map(RFun, Recs)))};
4248 + {error, Reason} ->
4252 + {reply, Reply, State};
4253 +handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4254 + SName = stats_table(VHost, Schema),
4255 + Query = ["SELECT at, count ",
4256 + "FROM ",SName," ",
4257 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"') ",
4258 + "ORDER BY DATE(at) DESC;"
4261 + case sql_query_internal(DBRef, Query) of
4263 + {ok, [ {Date, list_to_integer(Count)} || {Date, Count} <- Recs ]};
4264 + {error, Result} ->
4267 + {reply, Reply, State};
4268 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4269 + Query = ["SELECT peer_name,",
4277 + "FROM ",view_table(VHost, Schema, Date)," "
4278 + "WHERE owner_name='",User,"';"],
4280 + case sql_query_internal(DBRef, Query) of
4282 + Fun = fun({Peer_name, Peer_server, Peer_resource,
4287 + #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
4288 + direction=list_to_atom(Direction),
4290 + subject=Subject, body=Body,
4291 + timestamp=Timestamp}
4293 + {ok, lists:map(Fun, Recs)};
4294 + {error, Reason} ->
4297 + {reply, Reply, State};
4298 +handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4299 + SName = stats_table(VHost, Schema),
4300 + Query = ["SELECT at ",
4301 + "FROM ",SName," ",
4303 + "ORDER BY at DESC;"
4306 + case sql_query_internal(DBRef, Query) of
4308 + [ Date || {Date} <- Result ];
4309 + {error, Reason} ->
4312 + {reply, Reply, State};
4313 +handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4314 + Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
4315 + "FROM ",settings_table(VHost, Schema)," ",
4316 + "JOIN ",users_table(VHost, Schema)," ON user_id=owner_id;"],
4318 + case sql_query_internal(DBRef, Query) of
4320 + {ok, [#user_settings{owner_name=Owner,
4321 + dolog_default=list_to_bool(DoLogDef),
4322 + dolog_list=string_to_list(DoLogL),
4323 + donotlog_list=string_to_list(DoNotLogL)
4324 + } || {Owner, DoLogDef, DoLogL, DoNotLogL} <- Recs]};
4325 + {error, Reason} ->
4328 + {reply, Reply, State};
4329 +handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4330 + Query = ["SELECT dolog_default,dolog_list,donotlog_list ",
4331 + "FROM ",settings_table(VHost, Schema)," ",
4332 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
4334 + case sql_query_internal_silent(DBRef, Query) of
4337 + {data, [{DoLogDef, DoLogL, DoNotLogL}]} ->
4338 + {ok, #user_settings{owner_name=User,
4339 + dolog_default=list_to_bool(DoLogDef),
4340 + dolog_list=string_to_list(DoLogL),
4341 + donotlog_list=string_to_list(DoNotLogL)}};
4342 + {error, Reason} ->
4343 + ?ERROR_MSG("Failed to get_user_settings for ~p@~p: ~p", [User, VHost, Reason]),
4346 + {reply, Reply, State};
4347 +handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
4348 + dolog_list=DoLogL,
4349 + donotlog_list=DoNotLogL}},
4350 + _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4351 + User_id = get_user_id(DBRef, VHost, Schema, User),
4352 + Query = ["UPDATE ",settings_table(VHost, Schema)," ",
4353 + "SET dolog_default=",bool_to_list(DoLogDef),", ",
4354 + "dolog_list='",list_to_string(DoLogL),"', ",
4355 + "donotlog_list='",list_to_string(DoNotLogL),"' ",
4356 + "WHERE owner_id=",User_id,";"],
4359 + case sql_query_internal(DBRef, Query) of
4361 + IQuery = ["INSERT INTO ",settings_table(VHost, Schema)," ",
4362 + "(owner_id, dolog_default, dolog_list, donotlog_list) ",
4364 + "(",User_id,", ",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
4365 + case sql_query_internal(DBRef, IQuery) of
4367 + ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
4373 + ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
4378 + {reply, Reply, State};
4379 +handle_call({stop}, _From, State) ->
4380 + ?MYDEBUG("Stoping pgsql backend for ~p", [State#state.vhost]),
4381 + {stop, normal, ok, State};
4382 +handle_call(Msg, _From, State) ->
4383 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
4386 +handle_cast(Msg, State) ->
4387 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
4390 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
4391 + {stop, connection_dropped, State};
4392 +handle_info(Info, State) ->
4393 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
4396 +terminate(_Reason, _State) ->
4399 +code_change(_OldVsn, State, _Extra) ->
4402 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4404 +% gen_logdb callbacks
4406 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4407 +log_message(VHost, Msg) ->
4408 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4409 + gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
4410 +rebuild_stats(VHost) ->
4411 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4412 + gen_server:call(Proc, {rebuild_stats}, ?CALL_TIMEOUT).
4413 +rebuild_stats_at(VHost, Date) ->
4414 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4415 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
4416 +delete_messages_by_user_at(VHost, Msgs, Date) ->
4417 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4418 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
4419 +delete_all_messages_by_user_at(User, VHost, Date) ->
4420 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4421 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
4422 +delete_messages_at(VHost, Date) ->
4423 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4424 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
4425 +get_vhost_stats(VHost) ->
4426 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4427 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
4428 +get_vhost_stats_at(VHost, Date) ->
4429 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4430 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
4431 +get_user_stats(User, VHost) ->
4432 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4433 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
4434 +get_user_messages_at(User, VHost, Date) ->
4435 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4436 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
4437 +get_dates(VHost) ->
4438 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4439 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
4440 +get_users_settings(VHost) ->
4441 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4442 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
4443 +get_user_settings(User, VHost) ->
4444 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4445 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
4446 +set_user_settings(User, VHost, Set) ->
4447 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4448 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
4450 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4454 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4455 +get_dates_int(DBRef, VHost) ->
4456 + Query = ["SELECT n.nspname as \"Schema\",
4457 + c.relname as \"Name\",
4458 + 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\",
4459 + r.rolname as \"Owner\"
4460 + FROM pg_catalog.pg_class c
4461 + JOIN pg_catalog.pg_roles r ON r.oid = c.relowner
4462 + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
4463 + WHERE c.relkind IN ('r','')
4464 + AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
4465 + AND c.relname ~ '^(.*",escape_vhost(VHost),".*)$'
4466 + AND pg_catalog.pg_table_is_visible(c.oid)
4468 + case sql_query_internal(DBRef, Query) of
4470 + lists:foldl(fun({_Schema, Table, _Type, _Owner}, Dates) ->
4471 + case regexp:match(Table,"[0-9]+-[0-9]+-[0-9]+") of
4473 + lists:append(Dates, [lists:sublist(Table,S,E)]);
4482 +rebuild_stats_at_int(DBRef, VHost, Schema, Date) ->
4483 + Table = messages_table(VHost, Schema, Date),
4484 + STable = stats_table(VHost, Schema),
4488 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," IN ACCESS EXCLUSIVE MODE;"]),
4489 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," IN ACCESS EXCLUSIVE MODE;"]),
4491 + DQuery = [ "DELETE FROM ",STable," ",
4492 + "WHERE at='",Date,"';"],
4494 + {updated, _} = sql_query_internal(DBRef, DQuery),
4496 + SQuery = ["INSERT INTO ",STable," ",
4497 + "(owner_id,at,count) ",
4498 + "SELECT owner_id,'",Date,"'",",count(*) ",
4499 + "FROM ",Table," GROUP BY owner_id;"],
4501 + case sql_query_internal(DBRef, SQuery) of
4503 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]),
4505 + {updated, _} -> ok;
4506 + {error, _} -> error
4510 + case sql_transaction_internal(DBRef, Fun) of
4512 + ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
4518 +delete_nonexistent_stats(DBRef, VHost, Schema) ->
4519 + Dates = get_dates_int(DBRef, VHost),
4520 + STable = stats_table(VHost, Schema),
4522 + Temp = lists:flatmap(fun(Date) ->
4523 + ["'",Date,"'",","]
4526 + Temp1 = case Temp of
4530 + % replace last "," with ");"
4531 + lists:append([lists:sublist(Temp, length(Temp)-1), ");"])
4534 + Query = ["DELETE FROM ",STable," ",
4535 + "WHERE at NOT IN (", Temp1],
4537 + case sql_query_internal(DBRef, Query) of
4544 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4548 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4549 +create_stats_table(DBRef, VHost, Schema) ->
4550 + SName = stats_table(VHost, Schema),
4554 + Query = ["CREATE TABLE ",SName," (",
4555 + "owner_id INTEGER, ",
4556 + "at VARCHAR(20), ",
4560 + case sql_query_internal_silent(DBRef, Query) of
4562 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"s_owner_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (owner_id);"]),
4563 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"s_at_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (at);"]),
4565 + {error, Reason} ->
4566 + case lists:keysearch(code, 1, Reason) of
4567 + {value, {code, "42P07"}} ->
4570 + ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
4575 + case sql_transaction_internal(DBRef, Fun) of
4576 + {atomic, created} ->
4577 + ?MYDEBUG("Created stats table for ~p", [VHost]),
4578 + lists:foreach(fun(Date) ->
4579 + rebuild_stats_at_int(DBRef, VHost, Schema, Date)
4580 + end, get_dates_int(DBRef, VHost));
4581 + {atomic, exists} ->
4582 + ?MYDEBUG("Stats table for ~p already exists", [VHost]),
4584 + {error, _} -> error
4587 +create_settings_table(DBRef, VHost, Schema) ->
4588 + SName = settings_table(VHost, Schema),
4589 + Query = ["CREATE TABLE ",SName," (",
4590 + "owner_id INTEGER PRIMARY KEY, ",
4591 + "dolog_default BOOLEAN, ",
4592 + "dolog_list TEXT DEFAULT '', ",
4593 + "donotlog_list TEXT DEFAULT ''",
4596 + case sql_query_internal_silent(DBRef, Query) of
4598 + ?MYDEBUG("Created settings table for ~p", [VHost]),
4600 + {error, Reason} ->
4601 + case lists:keysearch(code, 1, Reason) of
4602 + {value, {code, "42P07"}} ->
4603 + ?MYDEBUG("Settings table for ~p already exists", [VHost]),
4606 + ?ERROR_MSG("Failed to create settings table for ~p: ~p", [VHost, Reason]),
4611 +create_users_table(DBRef, VHost, Schema) ->
4612 + SName = users_table(VHost, Schema),
4616 + Query = ["CREATE TABLE ",SName," (",
4617 + "username TEXT UNIQUE, ",
4618 + "user_id SERIAL PRIMARY KEY",
4621 + case sql_query_internal_silent(DBRef, Query) of
4623 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"username_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (username);"]),
4625 + {error, Reason} ->
4626 + case lists:keysearch(code, 1, Reason) of
4627 + {value, {code, "42P07"}} ->
4630 + ?ERROR_MSG("Failed to create users table for ~p: ~p", [VHost, Reason]),
4635 + case sql_transaction_internal(DBRef, Fun) of
4636 + {atomic, created} ->
4637 + ?MYDEBUG("Created users table for ~p", [VHost]),
4639 + {atomic, exists} ->
4640 + ?MYDEBUG("Users table for ~p already exists", [VHost]),
4642 + {aborted, _} -> error
4645 +create_servers_table(DBRef, VHost, Schema) ->
4646 + SName = servers_table(VHost, Schema),
4650 + Query = ["CREATE TABLE ",SName," (",
4651 + "server TEXT UNIQUE, ",
4652 + "server_id SERIAL PRIMARY KEY",
4655 + case sql_query_internal_silent(DBRef, Query) of
4657 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"server_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (server);"]),
4659 + {error, Reason} ->
4660 + case lists:keysearch(code, 1, Reason) of
4661 + {value, {code, "42P07"}} ->
4664 + ?ERROR_MSG("Failed to create servers table for ~p: ~p", [VHost, Reason]),
4669 + case sql_transaction_internal(DBRef, Fun) of
4670 + {atomic, created} ->
4671 + ?MYDEBUG("Created servers table for ~p", [VHost]),
4673 + {atomic, exists} ->
4674 + ?MYDEBUG("Servers table for ~p already exists", [VHost]),
4676 + {aborted, _} -> error
4679 +create_resources_table(DBRef, VHost, Schema) ->
4680 + RName = resources_table(VHost, Schema),
4682 + Query = ["CREATE TABLE ",RName," (",
4683 + "resource TEXT UNIQUE, ",
4684 + "resource_id SERIAL PRIMARY KEY",
4687 + case sql_query_internal_silent(DBRef, Query) of
4689 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"resource_i_",Schema,"_",escape_vhost(VHost),"\" ON ",RName," (resource);"]),
4691 + {error, Reason} ->
4692 + case lists:keysearch(code, 1, Reason) of
4693 + {value, {code, "42P07"}} ->
4696 + ?ERROR_MSG("Failed to create users table for ~p: ~p", [VHost, Reason]),
4701 + case sql_transaction_internal(DBRef, Fun) of
4702 + {atomic, created} ->
4703 + ?MYDEBUG("Created resources table for ~p", [VHost]),
4705 + {atomic, exists} ->
4706 + ?MYDEBUG("Resources table for ~p already exists", [VHost]),
4708 + {aborted, _} -> error
4711 +create_internals(DBRef, VHost, Schema) ->
4712 + case sql_query_internal(DBRef, [get_logmessage(VHost, Schema)]) of
4714 + ?MYDEBUG("Created logmessage for ~p", [VHost]),
4720 +get_user_id(DBRef, VHost, Schema, User) ->
4721 + SQuery = ["SELECT user_id FROM ",users_table(VHost, Schema)," ",
4722 + "WHERE username='",User,"';"],
4723 + case sql_query_internal(DBRef, SQuery) of
4725 + IQuery = ["INSERT INTO ",users_table(VHost, Schema)," ",
4726 + "VALUES ('",User,"');"],
4727 + case sql_query_internal_silent(DBRef, IQuery) of
4729 + {data, [{DBIdNew}]} = sql_query_internal(DBRef, SQuery),
4731 + {error, Reason} ->
4732 + % this can be in clustered environment
4733 + {value, {code, "23505"}} = lists:keysearch(code, 1, Reason),
4734 + ?ERROR_MSG("Duplicate key name for ~p", [User]),
4735 + {data, [{ClID}]} = sql_query_internal(DBRef, SQuery),
4738 + {data, [{DBId}]} ->
4742 +get_logmessage(VHost,Schema) ->
4743 + UName = users_table(VHost,Schema),
4744 + SName = servers_table(VHost,Schema),
4745 + RName = resources_table(VHost,Schema),
4746 + StName = stats_table(VHost,Schema),
4747 + io_lib:format("CREATE OR REPLACE FUNCTION \"logmessage\" (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) RETURNS INTEGER AS $$
4750 + peer_nameID INTEGER;
4751 + peer_serverID INTEGER;
4752 + peer_resourceID INTEGER;
4753 + tablename ALIAS for $1;
4754 + atdate ALIAS for $2;
4757 + SELECT INTO ownerID user_id FROM ~s WHERE username = owner;
4759 + INSERT INTO ~s (username) VALUES (owner);
4760 + ownerID := lastval();
4763 + SELECT INTO peer_nameID user_id FROM ~s WHERE username = peer_name;
4765 + INSERT INTO ~s (username) VALUES (peer_name);
4766 + peer_nameID := lastval();
4769 + SELECT INTO peer_serverID server_id FROM ~s WHERE server = peer_server;
4771 + INSERT INTO ~s (server) VALUES (peer_server);
4772 + peer_serverID := lastval();
4775 + SELECT INTO peer_resourceID resource_id FROM ~s WHERE resource = peer_resource;
4777 + INSERT INTO ~s (resource) VALUES (peer_resource);
4778 + peer_resourceID := lastval();
4782 + 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 || ''',''' || msubj || ''',''' || mbody || ''',' || mtimestamp || ')';
4783 + EXCEPTION WHEN undefined_table THEN
4784 + EXECUTE 'CREATE TABLE ' || tablename || ' (' ||
4785 + 'owner_id INTEGER, ' ||
4786 + 'peer_name_id INTEGER, ' ||
4787 + 'peer_server_id INTEGER, ' ||
4788 + 'peer_resource_id INTEGER, ' ||
4789 + 'direction VARCHAR(4) CHECK (direction IN (''to'',''from'')), ' ||
4790 + 'type VARCHAR(9) CHECK (type IN (''chat'',''error'',''groupchat'',''headline'',''normal'')), ' ||
4791 + 'subject TEXT, ' ||
4793 + 'timestamp DOUBLE PRECISION)';
4794 + EXECUTE 'CREATE INDEX \"owner_i_' || '~s' || '_' || atdate || '_' || '~s' || '\"' || ' ON ' || tablename || ' (owner_id)';
4795 + EXECUTE 'CREATE INDEX \"peer_i_' || '~s' || '_' || atdate || '_' || '~s' || '\"' || ' ON ' || tablename || ' (peer_server_id, peer_name_id)';
4797 + viewname := '~s.\"v_' || trim(both '~s.\"' from tablename) || '\"';
4799 + EXECUTE 'CREATE OR REPLACE VIEW ' || viewname || ' AS ' ||
4800 + 'SELECT owner.username AS owner_name, ' ||
4801 + 'peer.username AS peer_name, ' ||
4802 + 'servers.server AS peer_server, ' ||
4803 + 'resources.resource AS peer_resource, ' ||
4804 + 'messages.direction, ' ||
4805 + 'messages.type, ' ||
4806 + 'messages.subject, ' ||
4807 + 'messages.body, ' ||
4808 + 'messages.timestamp ' ||
4813 + '~s resources, ' ||
4814 + tablename || ' messages ' ||
4816 + 'owner.user_id=messages.owner_id and ' ||
4817 + 'peer.user_id=messages.peer_name_id and ' ||
4818 + 'servers.server_id=messages.peer_server_id and ' ||
4819 + 'resources.resource_id=messages.peer_resource_id ' ||
4820 + 'ORDER BY messages.timestamp';
4822 + 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 || ''',''' || msubj || ''',''' || mbody || ''',' || mtimestamp || ')';
4825 + UPDATE ~s SET count=count+1 where at=atdate and owner_id=ownerID;
4827 + INSERT INTO ~s (owner_id, at, count) VALUES (ownerID, atdate, 1);
4831 +$$ LANGUAGE plpgsql;
4832 +", [UName,UName,UName,UName,SName,SName,RName,RName,Schema,escape_vhost(VHost),Schema,escape_vhost(VHost),Schema,Schema,UName,UName,SName,RName,StName,StName]).
4834 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4838 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4839 +% like do_transaction/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
4840 +sql_transaction_internal(DBRef, Fun) ->
4841 + case sql_query_internal(DBRef, ["BEGIN;"]) of
4843 + case catch Fun() of
4845 + rollback_internal(DBRef, Err);
4846 + {error, _} = Err ->
4847 + rollback_internal(DBRef, Err);
4848 + {'EXIT', _} = Err ->
4849 + rollback_internal(DBRef, Err);
4851 + case sql_query_internal(DBRef, ["COMMIT;"]) of
4852 + {error, _} -> rollback_internal(DBRef, {commit_error});
4855 + {atomic, _} -> Res;
4856 + _ -> {atomic, Res}
4861 + {aborted, {begin_error}}
4864 +% like rollback/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
4865 +rollback_internal(DBRef, Reason) ->
4866 + Res = sql_query_internal(DBRef, ["ROLLBACK;"]),
4867 + {aborted, {Reason, {rollback_result, Res}}}.
4869 +sql_query_internal(DBRef, Query) ->
4870 + case sql_query_internal_silent(DBRef, Query) of
4871 + {error, undefined, Rez} ->
4872 + ?ERROR_MSG("Got undefined result: ~p while ~p", [Rez, lists:append(Query)]),
4873 + {error, undefined};
4875 + ?ERROR_MSG("Failed: ~p while ~p", [Error, lists:append(Query)]),
4880 +sql_query_internal_silent(DBRef, Query) ->
4881 + ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
4882 + get_result(pgsql:squery(DBRef, Query)).
4884 +get_result({ok, ["CREATE TABLE"]}) ->
4886 +get_result({ok, ["DROP TABLE"]}) ->
4888 +get_result({ok,["DROP VIEW"]}) ->
4890 +get_result({ok, ["CREATE INDEX"]}) ->
4892 +get_result({ok, ["CREATE FUNCTION"]}) ->
4894 +get_result({ok, [{"SELECT", _Rows, Recs}]}) ->
4895 + {data, [list_to_tuple(Rec) || Rec <- Recs]};
4896 +get_result({ok, ["INSERT " ++ OIDN]}) ->
4897 + [_OID, N] = string:tokens(OIDN, " "),
4898 + {updated, list_to_integer(N)};
4899 +get_result({ok, ["DELETE " ++ N]}) ->
4900 + {updated, list_to_integer(N)};
4901 +get_result({ok, ["UPDATE " ++ N]}) ->
4902 + {updated, list_to_integer(N)};
4903 +get_result({ok, ["BEGIN"]}) ->
4905 +get_result({ok, ["LOCK TABLE"]}) ->
4907 +get_result({ok, ["ROLLBACK"]}) ->
4909 +get_result({ok, ["COMMIT"]}) ->
4911 +get_result({ok, ["SET"]}) ->
4913 +get_result({ok, [{error, Error}]}) ->
4916 + {error, undefined, Rez}.
4918 --- src/mod_logdb_mnesia_old.erl.orig Tue Dec 11 14:23:19 2007
4919 +++ src/mod_logdb_mnesia_old.erl Wed Aug 22 22:58:11 2007
4921 +%%%----------------------------------------------------------------------
4922 +%%% File : mod_logdb_mnesia_old.erl
4923 +%%% Author : Oleg Palij (mailto:o.palij@gmail.com xmpp://malik@jabber.te.ua)
4924 +%%% Purpose : mod_logmnesia backend for mod_logdb (should be used only for copy_tables functionality)
4925 +%%% Version : trunk
4927 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
4928 +%%%----------------------------------------------------------------------
4930 +-module(mod_logdb_mnesia_old).
4931 +-author('o.palij@gmail.com').
4932 +-vsn('$Revision$').
4934 +-include("ejabberd.hrl").
4935 +-include("jlib.hrl").
4937 +-behaviour(gen_logdb).
4939 +-export([start/2, stop/1,
4942 + rebuild_stats_at/2,
4943 + rebuild_stats_at1/2,
4944 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
4945 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
4947 + get_users_settings/1, get_user_settings/2, set_user_settings/3]).
4949 +-record(stats, {user, server, table, count}).
4950 +-record(msg, {to_user, to_server, to_resource, from_user, from_server, from_resource, id, type, subject, body, timestamp}).
4952 +tables_prefix() -> "messages_".
4953 +% stats_table should not start with tables_prefix(VHost) !
4954 +% i.e. lists:prefix(tables_prefix(VHost), atom_to_list(stats_table())) must be /= true
4955 +stats_table() -> list_to_atom("messages-stats").
4956 +% table name as atom from Date
4957 +-define(ATABLE(Date), list_to_atom(tables_prefix() ++ Date)).
4958 +-define(LTABLE(Date), tables_prefix() ++ Date).
4960 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4962 +% gen_logdb callbacks
4964 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4965 +start(_Opts, _VHost) ->
4966 + case mnesia:system_info(is_running) of
4968 + ok = create_stats_table(),
4971 + ?ERROR_MSG("Mnesia not running", []),
4974 + ?ERROR_MSG("Mnesia status: ~p", [Status]),
4981 +log_message(_VHost, _Msg) ->
4984 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4986 +% gen_logdb callbacks (maintaince)
4988 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4989 +rebuild_stats(_VHost) ->
4992 +rebuild_stats_at(VHost, Date) ->
4993 + Table = ?LTABLE(Date),
4994 + {Time, Value}=timer:tc(?MODULE, rebuild_stats_at1, [VHost, Table]),
4995 + ?INFO_MSG("rebuild_stats_at ~p elapsed ~p sec: ~p~n", [Date, Time/1000000, Value]),
4997 +rebuild_stats_at1(VHost, Table) ->
4998 + CFun = fun(Msg, Stats) ->
4999 + To = Msg#msg.to_user ++ "@" ++ Msg#msg.to_server,
5001 + Msg#msg.to_server == VHost ->
5002 + case lists:keysearch(To, 1, Stats) of
5003 + {value, {Who_to, Count_to}} ->
5004 + lists:keyreplace(To, 1, Stats, {Who_to, Count_to + 1});
5006 + lists:append(Stats, [{To, 1}])
5011 + From = Msg#msg.from_user ++ "@" ++ Msg#msg.from_server,
5013 + Msg#msg.from_server == VHost ->
5014 + case lists:keysearch(From, 1, Stats_to) of
5015 + {value, {Who_from, Count_from}} ->
5016 + lists:keyreplace(From, 1, Stats_to, {Who_from, Count_from + 1});
5018 + lists:append(Stats_to, [{From, 1}])
5025 + DFun = fun(#stats{table=STable, server=Server} = Stat, _Acc)
5026 + when STable == Table, Server == VHost ->
5027 + mnesia:delete_object(stats_table(), Stat, write);
5028 + (_Stat, _Acc) -> ok
5030 + case mnesia:transaction(fun() ->
5031 + mnesia:write_lock_table(list_to_atom(Table)),
5032 + mnesia:write_lock_table(stats_table()),
5033 + % Calc stats for VHost at Date
5034 + AStats = mnesia:foldl(CFun, [], list_to_atom(Table)),
5035 + % Delete all stats for VHost at Date
5036 + mnesia:foldl(DFun, [], stats_table()),
5037 + % Write new calc'ed stats
5038 + lists:foreach(fun({Who, Count}) ->
5039 + Jid = jlib:string_to_jid(Who),
5040 + JUser = Jid#jid.user,
5041 + WStat = #stats{user=JUser, server=VHost, table=Table, count=Count},
5042 + mnesia:write(stats_table(), WStat, write)
5045 + {aborted, Reason} ->
5046 + ?ERROR_MSG("Failed to rebuild_stats_at for ~p at ~p: ~p", [VHost, Table, Reason]),
5052 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5054 +% gen_logdb callbacks (delete)
5056 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5057 +delete_messages_by_user_at(_VHost, _Msgs, _Date) ->
5060 +delete_all_messages_by_user_at(_User, _VHost, _Date) ->
5063 +delete_messages_at(VHost, Date) ->
5064 + Table = list_to_atom(tables_prefix() ++ Date),
5066 + DFun = fun(#msg{to_server=To_server, from_server=From_server}=Msg, _Acc)
5067 + when To_server == VHost; From_server == VHost ->
5068 + mnesia:delete_object(Table, Msg, write);
5069 + (_Msg, _Acc) -> ok
5072 + case mnesia:transaction(fun() ->
5073 + mnesia:foldl(DFun, [], Table)
5075 + {aborted, Reason} ->
5076 + ?ERROR_MSG("Failed to delete_messages_at for ~p at ~p: ~p", [VHost, Date, Reason]),
5082 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5084 +% gen_logdb callbacks (get)
5086 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5087 +get_vhost_stats(_VHost) ->
5088 + {error, "does not emplemented"}.
5090 +get_vhost_stats_at(VHost, Date) ->
5092 + Pat = #stats{user='$1', server=VHost, table=tables_prefix()++Date, count = '$2'},
5093 + mnesia:select(stats_table(), [{Pat, [], [['$1', '$2']]}])
5095 + case mnesia:transaction(Fun) of
5096 + {atomic, Result} ->
5097 + RFun = fun([User, Count]) ->
5100 + {ok, lists:reverse(lists:keysort(2, lists:map(RFun, Result)))};
5101 + {aborted, Reason} -> {error, Reason}
5104 +get_user_stats(_User, _VHost) ->
5105 + {error, "does not emplemented"}.
5107 +get_user_messages_at(User, VHost, Date) ->
5108 + Table_name = tables_prefix() ++ Date,
5109 + case mnesia:transaction(fun() ->
5110 + Pat_to = #msg{to_user=User, to_server=VHost, _='_'},
5111 + Pat_from = #msg{from_user=User, from_server=VHost, _='_'},
5112 + mnesia:select(list_to_atom(Table_name),
5113 + [{Pat_to, [], ['$_']},
5114 + {Pat_from, [], ['$_']}])
5116 + {atomic, Result} ->
5117 + Msgs = lists:map(fun(#msg{to_user=To_user, to_server=To_server, to_resource=To_res,
5118 + from_user=From_user, from_server=From_server, from_resource=From_res,
5121 + body=Body, timestamp=Timestamp} = _Msg) ->
5122 + Subject = case Subj of
5126 + {msg, To_user, To_server, To_res, From_user, From_server, From_res, Type, Subject, Body, Timestamp}
5129 + {aborted, Reason} ->
5133 +get_dates(_VHost) ->
5134 + Tables = mnesia:system_info(tables),
5136 + lists:filter(fun(Table) ->
5137 + lists:prefix(tables_prefix(), atom_to_list(Table))
5140 + lists:map(fun(Table) ->
5141 + lists:sublist(atom_to_list(Table),
5142 + length(tables_prefix())+1,
5143 + length(atom_to_list(Table)))
5147 +get_users_settings(_VHost) ->
5149 +get_user_settings(_User, _VHost) ->
5151 +set_user_settings(_User, _VHost, _Set) ->
5154 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5158 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5159 +% called from db_logon/2
5160 +create_stats_table() ->
5161 + SName = stats_table(),
5162 + case mnesia:create_table(SName,
5163 + [{disc_only_copies, [node()]},
5165 + {attributes, record_info(fields, stats)},
5166 + {record_name, stats}
5169 + ?INFO_MSG("Created stats table", []),
5171 + {aborted, {already_exists, _}} ->
5173 + {aborted, Reason} ->
5174 + ?ERROR_MSG("Failed to create stats table: ~p", [Reason]),
5177 --- src/gen_logdb.erl.orig Tue Dec 11 14:23:19 2007
5178 +++ src/gen_logdb.erl Wed Aug 22 22:58:11 2007
5180 +%%%----------------------------------------------------------------------
5181 +%%% File : gen_logdb.erl
5182 +%%% Author : Oleg Palij (mailto:o.palij@gmail.com xmpp://malik@jabber.te.ua)
5183 +%%% Purpose : Describes generic behaviour for mod_logdb backends.
5184 +%%% Version : trunk
5186 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
5187 +%%%----------------------------------------------------------------------
5189 +-module(gen_logdb).
5190 +-author('o.palij@gmail.com').
5191 +-vsn('$Revision$').
5193 +-export([behaviour_info/1]).
5195 +behaviour_info(callbacks) ->
5197 + % called from handle_info(start, _)
5198 + % it should logon database and return reference to started instance
5199 + % start(VHost, Opts) -> {ok, SPid} | error
5200 + % Options - list of options to connect to db
5201 + % Types: Options = list() -> [] |
5202 + % [{user, "logdb"},
5204 + % {db, "logdb"}] | ...
5205 + % VHost = list() -> "jabber.example.org"
5208 + % called from cleanup/1
5209 + % it should logoff database and do cleanup
5211 + % Types: VHost = list() -> "jabber.example.org"
5214 + % called from handle_call({addlog, _}, _, _)
5215 + % it should log messages to database
5216 + % log_message(VHost, Msg) -> ok | error
5218 + % VHost = list() -> "jabber.example.org"
5219 + % Msg = record() -> #msg
5222 + % called from ejabberdctl rebuild_stats
5223 + % it should rebuild stats table (if used) for vhost
5224 + % rebuild_stats(VHost)
5226 + % VHost = list() -> "jabber.example.org"
5227 + {rebuild_stats, 1},
5229 + % it should rebuild stats table (if used) for vhost at Date
5230 + % rebuild_stats_at(VHost, Date)
5232 + % VHost = list() -> "jabber.example.org"
5233 + % Date = list() -> "2007-02-12"
5234 + {rebuild_stats_at, 2},
5236 + % called from user_messages_at_parse_query/5
5237 + % it should delete selected user messages at date
5238 + % delete_messages_by_user_at(VHost, Msgs, Date) -> ok | error
5240 + % VHost = list() -> "jabber.example.org"
5241 + % Msgs = list() -> [ #msg1, msg2, ... ]
5242 + % Date = list() -> "2007-02-12"
5243 + {delete_messages_by_user_at, 3},
5245 + % called from user_messages_parse_query/4 | vhost_messages_at_parse_query/4
5246 + % it should delete all user messages at date
5247 + % delete_all_messages_by_user_at(User, VHost, Date) -> ok | error
5249 + % User = list() -> "admin"
5250 + % VHost = list() -> "jabber.example.org"
5251 + % Date = list() -> "2007-02-12"
5252 + {delete_all_messages_by_user_at, 3},
5254 + % called from vhost_messages_parse_query/3
5255 + % it should delete messages for vhost at date and update stats
5256 + % delete_messages_at(VHost, Date) -> ok | error
5258 + % VHost = list() -> "jabber.example.org"
5259 + % Date = list() -> "2007-02-12"
5260 + {delete_messages_at, 2},
5262 + % called from ejabberd_web_admin:vhost_messages_stats/3
5263 + % it should return sorted list of count of messages by dates for vhost
5264 + % get_vhost_stats(VHost) -> {ok, [{Date1, Msgs_count1}, {Date2, Msgs_count2}, ... ]} |
5267 + % VHost = list() -> "jabber.example.org"
5268 + % DateN = list() -> "2007-02-12"
5269 + % Msgs_countN = number() -> 241
5270 + {get_vhost_stats, 1},
5272 + % called from ejabberd_web_admin:vhost_messages_stats_at/4
5273 + % it should return sorted list of count of messages by users at date for vhost
5274 + % get_vhost_stats_at(VHost, Date) -> {ok, [{User1, Msgs_count1}, {User2, Msgs_count2}, ....]} |
5277 + % VHost = list() -> "jabber.example.org"
5278 + % Date = list() -> "2007-02-12"
5279 + % UserN = list() -> "admin"
5280 + % Msgs_countN = number() -> 241
5281 + {get_vhost_stats_at, 2},
5283 + % called from ejabberd_web_admin:user_messages_stats/4
5284 + % it should return sorted list of count of messages by date for user at vhost
5285 + % get_user_stats(User, VHost) -> {ok, [{Date1, Msgs_count1}, {Date2, Msgs_count2}, ...]} |
5288 + % User = list() -> "admin"
5289 + % VHost = list() -> "jabber.example.org"
5290 + % DateN = list() -> "2007-02-12"
5291 + % Msgs_countN = number() -> 241
5292 + {get_user_stats, 2},
5294 + % called from ejabberd_web_admin:user_messages_stats_at/5
5295 + % it should return all user messages at date
5296 + % get_user_messages_at(User, VHost, Date) -> {ok, Msgs} | {error, Reason}
5298 + % User = list() -> "admin"
5299 + % VHost = list() -> "jabber.example.org"
5300 + % Date = list() -> "2007-02-12"
5301 + % Msgs = list() -> [ #msg1, msg2, ... ]
5302 + {get_user_messages_at, 3},
5304 + % called from many places
5305 + % it should return list of dates for vhost
5306 + % get_dates(VHost) -> [Date1, Date2, ... ]
5308 + % VHost = list() -> "jabber.example.org"
5309 + % DateN = list() -> "2007-02-12"
5312 + % called from start
5313 + % it should return list with users settings for VHost in db
5314 + % get_users_settings(VHost) -> [#user_settings1, #user_settings2, ... ] | error
5316 + % VHost = list() -> "jabber.example.org"
5317 + % User = list() -> "admin"
5318 + {get_users_settings, 1},
5320 + % called from many places
5321 + % it should return User settings at VHost from db
5322 + % get_user_settings(User, VHost) -> error | {ok, #user_settings}
5324 + % User = list() -> "admin"
5325 + % VHost = list() -> "jabber.example.org"
5326 + {get_user_settings, 2},
5328 + % called from web admin
5329 + % it should set User settings at VHost
5330 + % set_user_settings(User, VHost, #user_settings) -> ok | error
5332 + % User = list() -> "admin"
5333 + % VHost = list() -> "jabber.example.org"
5334 + {set_user_settings, 3}
5336 +behaviour_info(_) ->
5338 --- src/web/ejabberd_web_admin-1.1.4.erl Tue Dec 11 13:25:24 2007
5339 +++ src/web/ejabberd_web_admin.erl Fri Jul 27 09:19:48 2007
5341 -include("ejabberd.hrl").
5342 -include("jlib.hrl").
5343 -include("ejabberd_http.hrl").
5344 +-include("mod_logdb.hrl").
5346 -define(X(Name), {xmlelement, Name, [], []}).
5347 -define(XA(Name, Attrs), {xmlelement, Name, Attrs, []}).
5349 ?XA("input", [{"type", Type},
5351 {"value", Value}])).
5352 +-define(INPUTC(Type, Name, Value),
5353 + ?XA("input", [{"type", Type},
5356 + {"checked", "true"}])).
5357 -define(INPUTT(Type, Name, Value), ?INPUT(Type, Name, ?T(Value))).
5358 -define(INPUTS(Type, Name, Value, Size),
5359 ?XA("input", [{"type", Type},
5360 @@ -137,6 +143,12 @@
5361 [?LI([?ACT(Base ++ "shared-roster/", "Shared Roster")])];
5365 + case gen_mod:is_loaded(Host, mod_logdb) of
5367 + [?LI([?ACT(Base ++ "messages/", "Users Messages")])];
5373 @@ -564,6 +576,12 @@
5374 [?LI([?ACT(Base ++ "shared-roster/", "Shared Roster")])];
5378 + case gen_mod:is_loaded(Host, mod_logdb) of
5380 + [?LI([?ACT(Base ++ "messages/", "Users Messages")])];
5386 @@ -925,6 +943,38 @@
5387 make_xhtml(Res, Host, Lang);
5391 + path = ["messages"],
5393 + lang = Lang} = Request) when is_list(Host) ->
5394 + Res = vhost_messages_stats(Host, Query, Lang),
5395 + make_xhtml(Res, Host, Lang);
5397 +process_admin(Host,
5399 + path = ["messages", Date],
5401 + lang = Lang} = Request) when is_list(Host) ->
5402 + Res = vhost_messages_stats_at(Host, Query, Lang, Date),
5403 + make_xhtml(Res, Host, Lang);
5405 +process_admin(Host,
5407 + path = ["user", U, "messages"],
5409 + lang = Lang} = Request) ->
5410 + Res = user_messages_stats(U, Host, Query, Lang),
5411 + make_xhtml(Res, Host, Lang);
5413 +process_admin(Host,
5415 + path = ["user", U, "messages", Date],
5417 + lang = Lang} = Request) ->
5418 + Res = user_messages_stats_at(U, Host, Query, Lang, Date),
5419 + make_xhtml(Res, Host, Lang);
5421 +process_admin(Host,
5423 path = ["user", U, "roster"],
5425 @@ -1442,6 +1492,22 @@
5426 [?XCT("h3", "Password:")] ++ FPassword ++
5427 [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++
5428 [?XE("h3", [?ACT("roster/", "Roster")])] ++
5429 + case gen_mod:is_loaded(Server, mod_logdb) of
5431 + Sett = mod_logdb:get_user_settings(User, Server),
5433 + case Sett#user_settings.dolog_default of
5435 + ?INPUTT("submit", "dolog", "Log Messages");
5437 + ?INPUTT("submit", "donotlog", "Do Not Log Messages");
5440 + [?XE("h3", [?ACT("messages/", "Messages"), ?C(" "), Log])];
5441 + %[?INPUT("test", "test", "test"), ?C(" "), Log];
5445 [?BR, ?INPUTT("submit", "removeuser", "Remove User")])].
5448 @@ -1462,8 +1528,24 @@
5450 ejabberd_auth:remove_user(User, Server),
5455 + case lists:keysearch("dolog", 1, Query) of
5457 + Sett = mod_logdb:get_user_settings(User, Server),
5458 + % TODO: check returned value
5459 + mod_logdb:set_user_settings(User, Server, Sett#user_settings{dolog_default=true}),
5462 + case lists:keysearch("donotlog", 1, Query) of
5464 + Sett = mod_logdb:get_user_settings(User, Server),
5465 + % TODO: check returned value
5466 + mod_logdb:set_user_settings(User, Server, Sett#user_settings{dolog_default=false}),
5475 @@ -1574,6 +1656,14 @@
5476 Res = user_roster_parse_query(User, Server, Items1, Query, Admin),
5477 Items = mnesia:dirty_index_read(roster, US, #roster.us),
5478 SItems = lists:sort(Items),
5480 + Settings = case gen_mod:is_loaded(Server, mod_logdb) of
5482 + mod_logdb:get_user_settings(User, Server);
5490 @@ -1621,7 +1711,33 @@
5493 term_to_id(R#roster.jid),
5496 + case gen_mod:is_loaded(Server, mod_logdb) of
5498 + Peer = jlib:jid_to_string(R#roster.jid),
5499 + A = lists:member(Peer, Settings#user_settings.dolog_list),
5500 + B = lists:member(Peer, Settings#user_settings.donotlog_list),
5504 + {"donotlog", "Do Not Log Messages"};
5506 + {"dolog", "Log Messages"};
5507 + Settings#user_settings.dolog_default == true ->
5508 + {"donotlog", "Do Not Log Messages"};
5509 + Settings#user_settings.dolog_default == false ->
5510 + {"dolog", "Log Messages"}
5513 + ?XAE("td", [{"class", "valign"}],
5514 + [?INPUTT("submit",
5516 + term_to_id(R#roster.jid),
5524 [?XC("h1", ?T("Roster of ") ++ us_to_list(US))] ++
5525 @@ -1637,6 +1753,288 @@
5526 ?INPUTT("submit", "addjid", "Add Jabber ID")
5529 +vhost_messages_stats(Server, Query, Lang) ->
5530 + Res = case catch mod_logdb:vhost_messages_parse_query(Server, Query) of
5531 + {'EXIT', Reason} ->
5532 + ?ERROR_MSG("~p", [Reason]),
5534 + VResult -> VResult
5536 + {Time, Value} = timer:tc(mod_logdb, get_vhost_stats, [Server]),
5537 + ?INFO_MSG("get_vhost_stats(~p) elapsed ~p sec", [Server, Time/1000000]),
5538 + %case mod_logdb:get_vhost_stats(Server) of
5540 + {'EXIT', CReason} ->
5541 + ?ERROR_MSG("Failed to get_vhost_stats: ~p", [CReason]),
5542 + [?XC("h1", ?T("Error occupied while fetching list"))];
5543 + {error, GReason} ->
5544 + ?ERROR_MSG("Failed to get_vhost_stats: ~p", [GReason]),
5545 + [?XC("h1", ?T("Error occupied while fetching list"))];
5547 + [?XC("h1", ?T("No logged messages for ") ++ Server)];
5549 + Fun = fun({Date, Count}) ->
5550 + ID = jlib:encode_base64(binary_to_list(term_to_binary(Server++Date))),
5552 + [?XE("td", [?INPUT("checkbox", "selected", ID)]),
5553 + ?XE("td", [?AC(Date, Date)]),
5554 + ?XC("td", integer_to_list(Count))
5557 + [?XC("h1", ?T("Logged messages for ") ++ Server)] ++
5559 + ok -> [?CT("Submitted"), ?P];
5560 + error -> [?CT("Bad format"), ?P];
5563 + [?XAE("form", [{"action", ""}, {"method", "post"}],
5568 + ?XCT("td", "Date"),
5569 + ?XCT("td", "Count")
5572 + lists:map(Fun, Dates)
5575 + ?INPUTT("submit", "delete", "Delete Selected")
5579 +vhost_messages_stats_at(Server, Query, Lang, Date) ->
5580 + {Time, Value} = timer:tc(mod_logdb, get_vhost_stats_at, [Server, Date]),
5581 + ?INFO_MSG("get_vhost_stats_at(~p,~p) elapsed ~p sec", [Server, Date, Time/1000000]),
5582 + %case mod_logdb:get_vhost_stats_at(Server, Date) of
5584 + {'EXIT', CReason} ->
5585 + ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [CReason]),
5586 + [?XC("h1", ?T("Error occupied while fetching list"))];
5587 + {error, GReason} ->
5588 + ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [GReason]),
5589 + [?XC("h1", ?T("Error occupied while fetching list"))];
5591 + [?XC("h1", ?T("No logged messages for ") ++ Server ++ ?T(" at ") ++ Date)];
5593 + Res = case catch mod_logdb:vhost_messages_at_parse_query(Server, Date, Users, Query) of
5594 + {'EXIT', Reason} ->
5595 + ?ERROR_MSG("~p", [Reason]),
5597 + VResult -> VResult
5599 + Fun = fun({User, Count}) ->
5600 + ID = jlib:encode_base64(binary_to_list(term_to_binary(User++Server))),
5602 + [?XE("td", [?INPUT("checkbox", "selected", ID)]),
5603 + ?XE("td", [?AC("../user/"++User++"/messages/"++Date, User)]),
5604 + ?XC("td", integer_to_list(Count))
5607 + [?XC("h1", ?T("Logged messages for ") ++ Server ++ ?T(" at ") ++ Date)] ++
5609 + ok -> [?CT("Submitted"), ?P];
5610 + error -> [?CT("Bad format"), ?P];
5613 + [?XAE("form", [{"action", ""}, {"method", "post"}],
5618 + ?XCT("td", "User"),
5619 + ?XCT("td", "Count")
5622 + lists:map(Fun, Users)
5625 + ?INPUTT("submit", "delete", "Delete Selected")
5629 +user_messages_stats(User, Server, Query, Lang) ->
5630 + US = {jlib:nodeprep(User), jlib:nameprep(Server)},
5631 + Jid = us_to_list(US),
5633 + Res = case catch mod_logdb:user_messages_parse_query(User, Server, Query) of
5634 + {'EXIT', Reason} ->
5635 + ?ERROR_MSG("~p", [Reason]),
5637 + VResult -> VResult
5640 + {Time, Value} = timer:tc(mod_logdb, get_user_stats, [User, Server]),
5641 + ?INFO_MSG("get_user_stats(~p,~p) elapsed ~p sec", [User, Server, Time/1000000]),
5644 + {'EXIT', CReason} ->
5645 + ?ERROR_MSG("Failed to get_user_stats: ~p", [CReason]),
5646 + [?XC("h1", ?T("Error occupied while fetching days"))];
5647 + {error, GReason} ->
5648 + ?ERROR_MSG("Failed to get_user_stats: ~p", [GReason]),
5649 + [?XC("h1", ?T("Error occupied while fetching days"))];
5651 + [?XC("h1", ?T("No logged messages for ") ++ Jid)];
5653 + Fun = fun({Date, Count}) ->
5654 + ID = jlib:encode_base64(binary_to_list(term_to_binary(User++Date))),
5656 + [?XE("td", [?INPUT("checkbox", "selected", ID)]),
5657 + ?XE("td", [?AC(Date, Date)]),
5658 + ?XC("td", integer_to_list(Count))
5660 + %[?AC(Date, Date ++ " (" ++ integer_to_list(Count) ++ ")"), ?BR]
5662 + [?XC("h1", ?T("Logged messages for ") ++ Jid)] ++
5664 + ok -> [?CT("Submitted"), ?P];
5665 + error -> [?CT("Bad format"), ?P];
5668 + [?XAE("form", [{"action", ""}, {"method", "post"}],
5673 + ?XCT("td", "Date"),
5674 + ?XCT("td", "Count")
5677 + lists:map(Fun, Dates)
5680 + ?INPUTT("submit", "delete", "Delete Selected")
5684 +user_messages_stats_at(User, Server, Query, Lang, Date) ->
5685 + US = {jlib:nodeprep(User), jlib:nameprep(Server)},
5686 + Jid = us_to_list(US),
5688 + {Time, Value} = timer:tc(mod_logdb, get_user_messages_at, [User, Server, Date]),
5689 + ?INFO_MSG("get_user_messages_at(~p,~p,~p) elapsed ~p sec", [User, Server, Date, Time/1000000]),
5691 + {'EXIT', CReason} ->
5692 + ?ERROR_MSG("Failed to get_user_messages_at: ~p", [CReason]),
5693 + [?XC("h1", ?T("Error occupied while fetching messages"))];
5694 + {error, GReason} ->
5695 + ?ERROR_MSG("Failed to get_user_messages_at: ~p", [GReason]),
5696 + [?XC("h1", ?T("Error occupied while fetching messages"))];
5698 + [?XC("h1", ?T("No logged messages for ") ++ Jid ++ ?T(" at ") ++ Date)];
5699 + {ok, User_messages} ->
5700 + Res = case catch mod_logdb:user_messages_at_parse_query(Server,
5704 + {'EXIT', Reason} ->
5705 + ?ERROR_MSG("~p", [Reason]),
5707 + VResult -> VResult
5710 + UniqUsers = lists:foldl(fun(#msg{peer_name=PName, peer_server=PServer}, List) ->
5711 + case lists:member(PName++"@"++PServer, List) of
5713 + false -> lists:append([PName++"@"++PServer], List)
5715 + end, [], User_messages),
5717 + % Users to filter (sublist of UniqUsers)
5718 + CheckedUsers = case lists:keysearch("filter", 1, Query) of
5720 + lists:filter(fun(UFUser) ->
5721 + ID = jlib:encode_base64(binary_to_list(term_to_binary(UFUser))),
5722 + lists:member({"selected", ID}, Query)
5727 + % UniqUsers in html (noone selected -> everyone selected)
5728 + Users = lists:map(fun(UHUser) ->
5729 + ID = jlib:encode_base64(binary_to_list(term_to_binary(UHUser))),
5730 + Input = case lists:member(UHUser, CheckedUsers) of
5731 + true -> [?INPUTC("checkbox", "selected", ID)];
5732 + false when CheckedUsers == [] -> [?INPUTC("checkbox", "selected", ID)];
5733 + false -> [?INPUT("checkbox", "selected", ID)]
5736 + [?XE("td", Input),
5737 + ?XC("td", UHUser)])
5738 + end, lists:sort(UniqUsers)),
5740 + % Messages to show (based on Users)
5741 + User_messages_filtered = case CheckedUsers of
5742 + [] -> User_messages;
5743 + _ -> lists:filter(fun(#msg{peer_name=PName, peer_server=PServer}) ->
5744 + lists:member(PName++"@"++PServer, CheckedUsers)
5745 + end, User_messages)
5748 + Msgs_Fun = fun(#msg{timestamp=Timestamp,
5750 + direction=Direction,
5751 + peer_name=PName, peer_server=PServer, peer_resource=PRes,
5753 + TextRaw = case Subject of
5755 + _ -> [?T("Subject"),": ",Subject,"<br>", Body]
5757 + ID = jlib:encode_base64(binary_to_list(term_to_binary(Timestamp))),
5758 + % replace \n with <br>
5759 + Text = lists:map(fun(10) -> "<br>";
5762 + Resource = case PRes of
5768 + [?XE("td", [?INPUT("checkbox", "selected", ID)]),
5769 + ?XC("td", mod_logdb:convert_timestamp(Timestamp)),
5770 + ?XC("td", atom_to_list(Direction)++": "++PName++"@"++PServer++Resource),
5773 + % Filtered user messages in html
5774 + Msgs = lists:map(Msgs_Fun, lists:sort(User_messages_filtered)),
5776 + [?XC("h1", ?T("Logged messages for ") ++ Jid ++ ?T(" at ") ++ Date)] ++
5778 + ok -> [?CT("Submitted"), ?P];
5779 + error -> [?CT("Bad format"), ?P];
5782 + [?XAE("form", [{"action", ""}, {"method", "post"}],
5786 + ?XCT("td", "User")
5792 + ?INPUTT("submit", "filter", "Filter Selected")
5798 + ?XCT("td", "Date, Time"),
5799 + ?XCT("td", "Direction: Jid"),
5800 + ?XCT("td", "Body")
5805 + ?INPUTT("submit", "delete", "Delete Selected"),
5811 user_roster_parse_query(User, Server, Items, Query, Admin) ->
5812 case lists:keysearch("addjid", 1, Query) of
5814 @@ -1704,10 +2102,41 @@
5822 + case lists:keysearch(
5823 + "donotlog" ++ term_to_id(JID), 1, Query) of
5825 + Peer = jlib:jid_to_string(JID),
5826 + Settings = mod_logdb:get_user_settings(User, Server),
5827 + DNLL = case lists:member(Peer, Settings#user_settings.donotlog_list) of
5828 + false -> lists:append(Settings#user_settings.donotlog_list, [Peer]);
5829 + true -> Settings#user_settings.donotlog_list
5831 + DLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.dolog_list),
5832 + Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
5833 + % TODO: check returned value
5834 + ok = mod_logdb:set_user_settings(User, Server, Sett),
5837 + case lists:keysearch(
5838 + "dolog" ++ term_to_id(JID), 1, Query) of
5840 + Peer = jlib:jid_to_string(JID),
5841 + Settings = mod_logdb:get_user_settings(User, Server),
5842 + DLL = case lists:member(Peer, Settings#user_settings.dolog_list) of
5843 + false -> lists:append(Settings#user_settings.dolog_list, [Peer]);
5844 + true -> Settings#user_settings.dolog_list
5846 + DNLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.donotlog_list),
5847 + Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
5848 + % TODO: check returned value
5849 + ok = mod_logdb:set_user_settings(User, Server, Sett),
5860 --- src/mod_muc/mod_muc_room-1.1.4.erl Tue Dec 11 13:26:10 2007
5861 +++ src/mod_muc/mod_muc_room.erl Tue Dec 11 14:21:59 2007
5862 @@ -652,6 +652,12 @@
5865 {reply, Reply, StateName, StateData};
5866 +handle_sync_event({get_jid_nick, Jid}, _From, StateName, StateData) ->
5867 + R = case ?DICT:find(jlib:jid_tolower(Jid), StateData#state.users) of
5869 + {ok, {user, _, Nick, _, _}} -> Nick
5871 + {reply, R, StateName, StateData};
5872 handle_sync_event(_Event, _From, StateName, StateData) ->
5874 {reply, Reply, StateName, StateData}.
5875 --- src/msgs/uk-1.1.4.msg Tue Dec 11 14:15:44 2007
5876 +++ src/msgs/uk.msg Tue Dec 11 14:23:19 2007
5877 @@ -372,6 +372,32 @@
5878 {"ejabberd virtual hosts", "віртуальні хости ejabberd"}.
5882 +{"Users Messages", "Повідомлення користувачів"}.
5884 +{"Count", "Кількість"}.
5885 +{"Logged messages for ", "Збережені повідомлення для "}.
5887 +{"No logged messages for ", "Відсутні повідомлення для "}.
5888 +{"Date, Time", "Дата, Час"}.
5889 +{"Direction: Jid", "Напрямок: Jid"}.
5890 +{"Subject", "Тема"}.
5892 +{"Messages", "Повідомлення"}.
5893 +{"Filter Selected", "Відфільтрувати виділені"}.
5894 +{"Do Not Log Messages", "Не зберігати повідомлення"}.
5895 +{"Log Messages", "Зберігати повідомлення"}.
5896 +{"Messages logging engine", "Система збереження повідомлень"}.
5897 +{"Default", "За замовчуванням"}.
5898 +{"Set logging preferences", "Вкажіть налагоджування збереження повідомлень"}.
5899 +{"Messages logging engine users", "Користувачі системи збереження повідомлень"}.
5900 +{"Messages logging engine settings", "Налагоджування системи збереження повідомлень"}.
5901 +{"Set run-time settings", "Вкажіть поточні налагоджування"}.
5902 +{"Groupchat messages logging", "Збереження повідомлень типу groupchat"}.
5903 +{"Jids/Domains to ignore", "Ігнорувати наступні jids/домени"}.
5904 +{"Purge messages older than (days)", "Видаляти повідомлення старіші ніж (дні)"}.
5905 +{"Poll users settings (seconds)", "Оновлювати налагоджування користувачів кожні (секунд)"}.
5910 --- src/msgs/ru-1.1.4.msg Tue Dec 11 14:15:51 2007
5911 +++ src/msgs/ru.msg Tue Dec 11 14:23:19 2007
5912 @@ -372,6 +372,32 @@
5913 {"ejabberd virtual hosts", "Виртуальные хосты ejabberd"}.
5917 +{"Users Messages", "Сообщения пользователей"}.
5919 +{"Count", "Количество"}.
5920 +{"Logged messages for ", "Сохранённые cообщения для "}.
5922 +{"No logged messages for ", "Отсутствуют сообщения для "}.
5923 +{"Date, Time", "Дата, Время"}.
5924 +{"Direction: Jid", "Направление: Jid"}.
5925 +{"Subject", "Тема"}.
5927 +{"Messages", "Сообщения"}.
5928 +{"Filter Selected", "Отфильтровать выделенные"}.
5929 +{"Do Not Log Messages", "Не сохранять сообщения"}.
5930 +{"Log Messages", "Сохранять сообщения"}.
5931 +{"Messages logging engine", "Система логирования сообщений"}.
5932 +{"Default", "По умолчанию"}.
5933 +{"Set logging preferences", "Задайте настройки логирования"}.
5934 +{"Messages logging engine users", "Пользователи системы логирования сообщений"}.
5935 +{"Messages logging engine settings", "Настройки системы логирования сообщений"}.
5936 +{"Set run-time settings", "Задайте текущие настройки"}.
5937 +{"Groupchat messages logging", "Логирование сообщений типа groupchat"}.
5938 +{"Jids/Domains to ignore", "Игнорировать следующие jids/домены"}.
5939 +{"Purge messages older than (days)", "Удалять сообщения старее чем (дни)"}.
5940 +{"Poll users settings (seconds)", "Обновлять настройки пользователей через (секунд)"}.
5945 --- src/msgs/nl-1.1.4.msg Tue Dec 11 14:15:58 2007
5946 +++ src/msgs/nl.msg Thu Apr 26 16:04:49 2007
5947 @@ -331,4 +331,15 @@
5948 {"Members:", "Groepsleden:"}.
5949 {"Displayed Groups:", "Weergegeven groepen:"}.
5950 {"Group ", "Groep "}.
5951 +{"Users Messages", "Gebruikersberichten"}.
5953 +{"Count", "Aantal"}.
5954 +{"Logged messages for ", "Gelogde berichten van "}.
5956 +{"No logged messages for ", "Geen gelogde berichten van "}.
5957 +{"Date, Time", "Datum en tijd"}.
5958 +{"Direction: Jid", "Richting: Jabber ID"}.
5959 +{"Subject", "Onderwerp"}.
5960 +{"Body", "Berichtveld"}.
5961 +{"Messages", "Berichten"}.
5963 --- src/msgs/pl-1.1.4.msg Tue Dec 11 14:16:04 2007
5964 +++ src/msgs/pl.msg Thu Sep 6 09:52:55 2007
5965 @@ -423,3 +423,27 @@
5966 % ./mod_muc/mod_muc.erl
5967 {"ejabberd MUC module\nCopyright (c) 2003-2006 Alexey Shchepin", ""}.
5970 +{"Users Messages", "Wiadomości użytkownika"}.
5972 +{"Count", "Liczba"}.
5973 +{"Logged messages for ", "Zapisane wiadomości dla "}.
5975 +{"No logged messages for ", "Brak zapisanych wiadomości dla "}.
5976 +{"Date, Time", "Data, Godzina"}.
5977 +{"Direction: Jid", "Kierunek: Jid"}.
5978 +{"Subject", "Temat"}.
5980 +{"Messages","Wiadomości"}.
5981 +{"Filter Selected", "Odfiltruj zaznaczone"}.
5982 +{"Do Not Log Messages", "Nie zapisuj wiadomości"}.
5983 +{"Log Messages", "Zapisuj wiadomości"}.
5984 +{"Messages logging engine", "System zapisywania historii rozmów"}.
5985 +{"Default", "Domyślne"}.
5986 +{"Set logging preferences", "Ustaw preferencje zapisywania"}.
5987 +{"Messages logging engine settings", "Ustawienia systemu logowania"}.
5988 +{"Set run-time settings", "Zapisz ustawienia systemu logowania"}.
5989 +{"Groupchat messages logging", "Zapisywanie rozmów z konferencji"}.
5990 +{"Jids/Domains to ignore", "JID/Domena która ma być ignorowana"}.
5991 +{"Purge messages older than (days)", "Usuń wiadomości starsze niż (w dniach)"}.
5992 +{"Poll users settings (seconds)", "Czas aktualizacji preferencji użytkowników (sekundy)"}.