1 --- mod_logdb.erl.orig 2009-11-22 13:06:23.000000000 +0200
2 +++ mod_logdb.erl 2009-11-22 13:06:16.000000000 +0200
4 +%%%----------------------------------------------------------------------
5 +%%% File : mod_logdb.erl
6 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
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').
16 +-behaviour(gen_server).
20 +-export([start_link/2]).
22 +-export([start/2,stop/1]).
24 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
26 +-export([send_packet/3, receive_packet/4, offline_packet/3, remove_user/2]).
27 +-export([get_local_identity/5,
28 + get_local_features/5,
30 + adhoc_local_items/4,
31 + adhoc_local_commands/4
36 +% adhoc_sm_commands/4]).
39 +-export([rebuild_stats/3,
40 + copy_messages/1, copy_messages_ctl/3, copy_messages_int_tc/1]).
42 +-export([get_vhost_stats/1, get_vhost_stats_at/2,
43 + get_user_stats/2, get_user_messages_at/3,
46 + convert_timestamp/1, convert_timestamp_brief/1,
47 + get_user_settings/2, set_user_settings/3,
48 + user_messages_at_parse_query/4, user_messages_parse_query/3,
49 + vhost_messages_parse_query/2, vhost_messages_at_parse_query/4,
50 + list_to_bool/1, bool_to_list/1,
51 + list_to_string/1, string_to_list/1,
52 + get_module_settings/1, set_module_settings/2,
53 + purge_old_records/2]).
55 +-export([webadmin_menu/3,
58 + user_parse_query/5]).
60 +-export([vhost_messages_stats/3,
61 + vhost_messages_stats_at/4,
62 + user_messages_stats/4,
63 + user_messages_stats_at/5]).
65 +-include("mod_logdb.hrl").
66 +-include("ejabberd.hrl").
67 +-include("mod_roster.hrl").
68 +-include("jlib.hrl").
69 +-include("ejabberd_ctl.hrl").
70 +-include("adhoc.hrl").
71 +-include("web/ejabberd_web_admin.hrl").
72 +-include("web/ejabberd_http.hrl").
74 +-define(PROCNAME, ejabberd_mod_logdb).
75 +% gen_server call timeout
76 +-define(CALL_TIMEOUT, 10000).
78 +-record(state, {vhost, dbmod, backendPid, monref, purgeRef, pollRef, dbopts, dbs, dolog_default, ignore_jids, groupchat, purge_older_days, poll_users_settings, drop_messages_on_user_removal}).
80 +ets_settings_table(VHost) -> list_to_atom("ets_logdb_settings_" ++ VHost).
82 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
84 +% gen_mod/gen_server callbacks
86 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
87 +% ejabberd starts module
88 +start(VHost, Opts) ->
90 + {gen_mod:get_module_proc(VHost, ?PROCNAME),
91 + {?MODULE, start_link, [VHost, Opts]},
96 + % add child to ejabberd_sup
97 + supervisor:start_child(ejabberd_sup, ChildSpec).
99 +% supervisor starts gen_server
100 +start_link(VHost, Opts) ->
101 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
102 + {ok, Pid} = gen_server:start_link({local, Proc}, ?MODULE, [VHost, Opts], []),
106 +init([VHost, Opts]) ->
107 + ?MYDEBUG("Starting mod_logdb", []),
108 + process_flag(trap_exit, true),
109 + DBs = gen_mod:get_opt(dbs, Opts, [{mnesia, []}]),
110 + VHostDB = gen_mod:get_opt(vhosts, Opts, [{VHost, mnesia}]),
111 + % 10 is default becouse of using in clustered environment
112 + PollUsersSettings = gen_mod:get_opt(poll_users_settings, Opts, 10),
114 + {value,{_, DBName}} = lists:keysearch(VHost, 1, VHostDB),
115 + {value, {DBName, DBOpts}} = lists:keysearch(DBName, 1, DBs),
117 + ?MYDEBUG("Starting mod_logdb for ~p with ~p backend", [VHost, DBName]),
119 + DBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(DBName)),
121 + {ok, #state{vhost=VHost,
124 + % dbs used for convert messages from one backend to other
126 + dolog_default=gen_mod:get_opt(dolog_default, Opts, true),
127 + drop_messages_on_user_removal=gen_mod:get_opt(drop_messages_on_user_removal, Opts, true),
128 + ignore_jids=gen_mod:get_opt(ignore_jids, Opts, []),
129 + groupchat=gen_mod:get_opt(groupchat, Opts, none),
130 + purge_older_days=gen_mod:get_opt(purge_older_days, Opts, never),
131 + poll_users_settings=PollUsersSettings}}.
133 +cleanup(#state{vhost=VHost} = _State) ->
134 + ?MYDEBUG("Stopping ~s for ~p", [?MODULE, VHost]),
136 + %ets:delete(ets_settings_table(VHost)),
138 + ejabberd_hooks:delete(remove_user, VHost, ?MODULE, remove_user, 90),
139 + ejabberd_hooks:delete(user_send_packet, VHost, ?MODULE, send_packet, 90),
140 + ejabberd_hooks:delete(user_receive_packet, VHost, ?MODULE, receive_packet, 90),
141 + ejabberd_hooks:delete(offline_message_hook, VHost, ?MODULE, offline_packet, 10),
142 + %ejabberd_hooks:delete(adhoc_sm_commands, VHost, ?MODULE, adhoc_sm_commands, 110),
143 + %ejabberd_hooks:delete(adhoc_sm_items, VHost, ?MODULE, adhoc_sm_items, 110),
144 + ejabberd_hooks:delete(adhoc_local_commands, VHost, ?MODULE, adhoc_local_commands, 110),
145 + ejabberd_hooks:delete(adhoc_local_items, VHost, ?MODULE, adhoc_local_items, 110),
146 + %ejabberd_hooks:delete(disco_sm_identity, VHost, ?MODULE, get_sm_identity, 110),
147 + %ejabberd_hooks:delete(disco_sm_features, VHost, ?MODULE, get_sm_features, 110),
148 + %ejabberd_hooks:delete(disco_sm_items, VHost, ?MODULE, get_sm_items, 110),
149 + ejabberd_hooks:delete(disco_local_identity, VHost, ?MODULE, get_local_identity, 110),
150 + ejabberd_hooks:delete(disco_local_features, VHost, ?MODULE, get_local_features, 110),
151 + ejabberd_hooks:delete(disco_local_items, VHost, ?MODULE, get_local_items, 110),
153 + ejabberd_hooks:delete(webadmin_menu_host, VHost, ?MODULE, webadmin_menu, 70),
154 + ejabberd_hooks:delete(webadmin_user, VHost, ?MODULE, webadmin_user, 50),
155 + ejabberd_hooks:delete(webadmin_page_host, VHost, ?MODULE, webadmin_page, 50),
156 + ejabberd_hooks:delete(webadmin_user_parse_query, VHost, ?MODULE, user_parse_query, 50),
158 + ?MYDEBUG("Removed hooks for ~p", [VHost]),
160 + %ejabberd_ctl:unregister_commands(VHost, [{"rebuild_stats", "rebuild mod_logdb module stats for vhost"}], ?MODULE, rebuild_stats),
161 + %Supported_backends = lists:flatmap(fun({Backend, _Opts}) ->
162 + % [atom_to_list(Backend), " "]
163 + % end, State#state.dbs),
164 + %ejabberd_ctl:unregister_commands(
166 + % [{"copy_messages backend", "copy messages from backend to current backend. backends could be: " ++ Supported_backends }],
167 + % ?MODULE, copy_messages_ctl),
168 + ?MYDEBUG("Unregistered commands for ~p", [VHost]).
171 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
172 + %gen_server:call(Proc, {cleanup}),
173 + %?MYDEBUG("Cleanup in stop finished!!!!", []),
174 + %timer:sleep(10000),
175 + ok = supervisor:terminate_child(ejabberd_sup, Proc),
176 + ok = supervisor:delete_child(ejabberd_sup, Proc).
178 +handle_call({cleanup}, _From, State) ->
180 + ?MYDEBUG("Cleanup finished!!!!!", []),
181 + {reply, ok, State};
182 +handle_call({get_dates}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
183 + Reply = DBMod:get_dates(VHost),
184 + {reply, Reply, State};
185 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
186 +% ejabberd_web_admin callbacks
187 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
188 +handle_call({delete_messages_by_user_at, PMsgs, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
189 + Reply = DBMod:delete_messages_by_user_at(VHost, PMsgs, Date),
190 + {reply, Reply, State};
191 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
192 + Reply = DBMod:delete_all_messages_by_user_at(User, VHost, Date),
193 + {reply, Reply, State};
194 +handle_call({delete_messages_at, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
195 + Reply = DBMod:delete_messages_at(VHost, Date),
196 + {reply, Reply, State};
197 +handle_call({get_vhost_stats}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
198 + Reply = DBMod:get_vhost_stats(VHost),
199 + {reply, Reply, State};
200 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
201 + Reply = DBMod:get_vhost_stats_at(VHost, Date),
202 + {reply, Reply, State};
203 +handle_call({get_user_stats, User}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
204 + Reply = DBMod:get_user_stats(User, VHost),
205 + {reply, Reply, State};
206 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
207 + Reply = DBMod:get_user_messages_at(User, VHost, Date),
208 + {reply, Reply, State};
209 +handle_call({get_user_settings, User}, _From, #state{dbmod=_DBMod, vhost=VHost}=State) ->
210 + Reply = case ets:match_object(ets_settings_table(VHost),
211 + #user_settings{owner_name=User, _='_'}) of
213 + _ -> #user_settings{owner_name=User,
214 + dolog_default=State#state.dolog_default,
218 + {reply, Reply, State};
219 +% TODO: remove User ??
220 +handle_call({set_user_settings, User, GSet}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
221 + Set = GSet#user_settings{owner_name=User},
223 + case ets:match_object(ets_settings_table(VHost),
224 + #user_settings{owner_name=User, _='_'}) of
226 + ?MYDEBUG("Settings is equal", []),
229 + case DBMod:set_user_settings(User, VHost, Set) of
233 + true = ets:insert(ets_settings_table(VHost), Set),
237 + {reply, Reply, State};
238 +handle_call({get_module_settings}, _From, State) ->
239 + {reply, State, State};
240 +handle_call({set_module_settings, #state{purge_older_days=PurgeDays,
241 + poll_users_settings=PollSec} = Settings},
243 + #state{purgeRef=PurgeRefOld,
244 + pollRef=PollRefOld,
245 + purge_older_days=PurgeDaysOld,
246 + poll_users_settings=PollSecOld} = State) ->
248 + PurgeDays == never, PurgeDaysOld /= never ->
249 + {ok, cancel} = timer:cancel(PurgeRefOld),
251 + is_integer(PurgeDays), PurgeDaysOld == never ->
252 + set_purge_timer(PurgeDays);
258 + PollSec == PollSecOld ->
260 + PollSec == 0, PollSecOld /= 0 ->
261 + {ok, cancel} = timer:cancel(PollRefOld),
263 + is_integer(PollSec), PollSecOld == 0 ->
264 + set_poll_timer(PollSec);
265 + is_integer(PollSec), PollSecOld /= 0 ->
266 + {ok, cancel} = timer:cancel(PollRefOld),
267 + set_poll_timer(PollSec)
270 + NewState = State#state{dolog_default=Settings#state.dolog_default,
271 + ignore_jids=Settings#state.ignore_jids,
272 + groupchat=Settings#state.groupchat,
273 + drop_messages_on_user_removal=Settings#state.drop_messages_on_user_removal,
274 + purge_older_days=PurgeDays,
275 + poll_users_settings=PollSec,
278 + {reply, ok, NewState};
279 +handle_call(Msg, _From, State) ->
280 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
282 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
283 +% end ejabberd_web_admin callbacks
284 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
286 +% ejabberd_hooks call
287 +handle_cast({addlog, Direction, Owner, Peer, Packet}, #state{dbmod=DBMod, vhost=VHost}=State) ->
288 + case filter(Owner, Peer, State) of
290 + case catch packet_parse(Owner, Peer, Packet, Direction, State) of
293 + {'EXIT', Reason} ->
294 + ?ERROR_MSG("Failed to parse: ~p", [Reason]);
296 + DBMod:log_message(VHost, Msg)
302 +handle_cast({remove_user, User}, #state{dbmod=DBMod, vhost=VHost}=State) ->
303 + case State#state.drop_messages_on_user_removal of
305 + DBMod:drop_user(User, VHost),
306 + ?INFO_MSG("Launched ~s@~s removal", [User, VHost]);
308 + ?INFO_MSG("Message removing is disabled. Keeping messages for ~s@~s", [User, VHost])
311 +% ejabberdctl rebuild_stats/3
312 +handle_cast({rebuild_stats}, #state{dbmod=DBMod, vhost=VHost}=State) ->
313 + DBMod:rebuild_stats(VHost),
315 +handle_cast({copy_messages, Backend}, State) ->
316 + spawn(?MODULE, copy_messages, [[State, Backend]]),
318 +handle_cast({copy_messages, Backend, Date}, State) ->
319 + spawn(?MODULE, copy_messages, [[State, Backend, Date]]),
321 +handle_cast(Msg, State) ->
322 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
325 +% return: disabled | timer reference
326 +set_purge_timer(PurgeDays) ->
329 + Days when is_integer(Days) ->
330 + {ok, Ref1} = timer:send_interval(timer:hours(24), scheduled_purging),
334 +% return: disabled | timer reference
335 +set_poll_timer(PollSec) ->
338 + {ok, Ref2} = timer:send_interval(timer:seconds(PollSec), poll_users_settings),
340 + % db polling disabled
344 + {ok, Ref3} = timer:send_interval(timer:seconds(10), poll_users_settings),
348 +% actual starting of logging
349 +% from timer:send_after (in init)
350 +handle_info(start, #state{dbmod=DBMod, vhost=VHost}=State) ->
351 + case DBMod:start(VHost, State#state.dbopts) of
352 + {error,{already_started,_}} ->
353 + ?MYDEBUG("backend module already started - trying to stop it", []),
355 + {stop, already_started, State};
357 + timer:sleep(30000),
358 + ?ERROR_MSG("Failed to start: ~p", [Reason]),
359 + {stop, db_connection_failed, State};
361 + ?INFO_MSG("~p connection established", [DBMod]),
363 + MonRef = erlang:monitor(process, SPid),
365 + ets:new(ets_settings_table(VHost), [named_table,public,set,{keypos, #user_settings.owner_name}]),
366 + {ok, DoLog} = DBMod:get_users_settings(VHost),
367 + ets:insert(ets_settings_table(VHost), DoLog),
369 + TrefPurge = set_purge_timer(State#state.purge_older_days),
370 + TrefPoll = set_poll_timer(State#state.poll_users_settings),
372 + ejabberd_hooks:add(remove_user, VHost, ?MODULE, remove_user, 90),
373 + ejabberd_hooks:add(user_send_packet, VHost, ?MODULE, send_packet, 90),
374 + ejabberd_hooks:add(user_receive_packet, VHost, ?MODULE, receive_packet, 90),
375 + ejabberd_hooks:add(offline_message_hook, VHost, ?MODULE, offline_packet, 10),
377 + ejabberd_hooks:add(disco_local_items, VHost, ?MODULE, get_local_items, 110),
378 + ejabberd_hooks:add(disco_local_features, VHost, ?MODULE, get_local_features, 110),
379 + ejabberd_hooks:add(disco_local_identity, VHost, ?MODULE, get_local_identity, 110),
380 + %ejabberd_hooks:add(disco_sm_items, VHost, ?MODULE, get_sm_items, 110),
381 + %ejabberd_hooks:add(disco_sm_features, VHost, ?MODULE, get_sm_features, 110),
382 + %ejabberd_hooks:add(disco_sm_identity, VHost, ?MODULE, get_sm_identity, 110),
383 + ejabberd_hooks:add(adhoc_local_items, VHost, ?MODULE, adhoc_local_items, 110),
384 + ejabberd_hooks:add(adhoc_local_commands, VHost, ?MODULE, adhoc_local_commands, 110),
385 + %ejabberd_hooks:add(adhoc_sm_items, VHost, ?MODULE, adhoc_sm_items, 110),
386 + %ejabberd_hooks:add(adhoc_sm_commands, VHost, ?MODULE, adhoc_sm_commands, 110),
388 + ejabberd_hooks:add(webadmin_menu_host, VHost, ?MODULE, webadmin_menu, 70),
389 + ejabberd_hooks:add(webadmin_user, VHost, ?MODULE, webadmin_user, 50),
390 + ejabberd_hooks:add(webadmin_page_host, VHost, ?MODULE, webadmin_page, 50),
391 + ejabberd_hooks:add(webadmin_user_parse_query, VHost, ?MODULE, user_parse_query, 50),
393 + ?MYDEBUG("Added hooks for ~p", [VHost]),
395 + %ejabberd_ctl:register_commands(
397 + % [{"rebuild_stats", "rebuild mod_logdb module stats for vhost"}],
398 + % ?MODULE, rebuild_stats),
399 + %Supported_backends = lists:flatmap(fun({Backend, _Opts}) ->
400 + % [atom_to_list(Backend), " "]
401 + % end, State#state.dbs),
402 + %ejabberd_ctl:register_commands(
404 + % [{"copy_messages backend", "copy messages from backend to current backend. backends could be: " ++ Supported_backends }],
405 + % ?MODULE, copy_messages_ctl),
406 + ?MYDEBUG("Registered commands for ~p", [VHost]),
408 + NewState=State#state{monref = MonRef, backendPid=SPid, purgeRef=TrefPurge, pollRef=TrefPoll},
409 + {noreply, NewState};
411 + ?ERROR_MSG("Rez=~p", [Rez]),
412 + timer:sleep(30000),
413 + {stop, db_connection_failed, State}
415 +% from timer:send_interval/2 (in start handle_info)
416 +handle_info(scheduled_purging, #state{vhost=VHost, purge_older_days=Days} = State) ->
417 + ?MYDEBUG("Starting scheduled purging of old records for ~p", [VHost]),
418 + spawn(?MODULE, purge_old_records, [VHost, integer_to_list(Days)]),
420 +% from timer:send_interval/2 (in start handle_info)
421 +handle_info(poll_users_settings, #state{dbmod=DBMod, vhost=VHost}=State) ->
422 + {ok, DoLog} = DBMod:get_users_settings(VHost),
423 + ?MYDEBUG("DoLog=~p", [DoLog]),
424 + true = ets:delete_all_objects(ets_settings_table(VHost)),
425 + ets:insert(ets_settings_table(VHost), DoLog),
427 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
428 + {stop, db_connection_dropped, State};
429 +handle_info({fetch_result, _, _}, State) ->
430 + ?MYDEBUG("Got timed out mysql fetch result", []),
432 +handle_info(Info, State) ->
433 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
436 +terminate(db_connection_failed, _State) ->
438 +terminate(db_connection_dropped, State) ->
439 + ?MYDEBUG("Got terminate with db_connection_dropped", []),
442 +terminate(Reason, #state{monref=undefined} = State) ->
443 + ?MYDEBUG("Got terminate with undefined monref.~nReason: ~p", [Reason]),
446 +terminate(Reason, #state{dbmod=DBMod, vhost=VHost, monref=MonRef, backendPid=Pid} = State) ->
447 + ?INFO_MSG("Reason: ~p", [Reason]),
448 + case erlang:is_process_alive(Pid) of
450 + erlang:demonitor(MonRef, [flush]),
458 +code_change(_OldVsn, State, _Extra) ->
461 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
463 +% ejabberd_hooks callbacks
465 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
466 +% TODO: change to/from to list as sql stores it as list
467 +send_packet(Owner, Peer, P) ->
468 + VHost = Owner#jid.lserver,
469 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
470 + gen_server:cast(Proc, {addlog, to, Owner, Peer, P}).
472 +offline_packet(Peer, Owner, P) ->
473 + VHost = Owner#jid.lserver,
474 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
475 + gen_server:cast(Proc, {addlog, from, Owner, Peer, P}).
477 +receive_packet(_JID, Peer, Owner, P) ->
478 + VHost = Owner#jid.lserver,
479 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
480 + gen_server:cast(Proc, {addlog, from, Owner, Peer, P}).
482 +remove_user(User, Server) ->
483 + LUser = jlib:nodeprep(User),
484 + LServer = jlib:nameprep(Server),
485 + Proc = gen_mod:get_module_proc(LServer, ?PROCNAME),
486 + gen_server:cast(Proc, {remove_user, LUser}).
488 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
492 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
493 +rebuild_stats(_Val, VHost, ["rebuild_stats"]) ->
494 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
495 + gen_server:cast(Proc, {rebuild_stats}),
496 + {stop, ?STATUS_SUCCESS};
497 +rebuild_stats(Val, _VHost, _Args) ->
500 +copy_messages_ctl(_Val, VHost, ["copy_messages", Backend]) ->
501 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
502 + gen_server:cast(Proc, {copy_messages, Backend}),
503 + {stop, ?STATUS_SUCCESS};
504 +copy_messages_ctl(_Val, VHost, ["copy_messages", Backend, Date]) ->
505 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
506 + gen_server:cast(Proc, {copy_messages, Backend, Date}),
507 + {stop, ?STATUS_SUCCESS};
508 +copy_messages_ctl(Val, _VHost, _Args) ->
510 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
514 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
516 +% handle_cast({addlog, E}, _)
517 +% raw packet -> #msg
518 +packet_parse(Owner, Peer, Packet, Direction, State) ->
519 + case xml:get_subtag(Packet, "body") of
524 + case xml:get_tag_attr_s("type", Packet) of
529 + case Message_type of
530 + "groupchat" when State#state.groupchat == send, Direction == to ->
532 + "groupchat" when State#state.groupchat == send, Direction == from ->
534 + "groupchat" when State#state.groupchat == half ->
535 + Rooms = ets:match(muc_online_room, '$1'),
536 + Ni=lists:foldl(fun([{muc_online_room, {GName, GHost}, Pid}], Names) ->
537 + case gen_fsm:sync_send_all_state_event(Pid, {get_jid_nick,Owner}) of
540 + lists:append(Names, [jlib:jid_to_string({GName, GHost, Nick})])
543 + case lists:member(jlib:jid_to_string(Peer), Ni) of
544 + true when Direction == from ->
549 + "groupchat" when State#state.groupchat == none ->
555 + Message_body = xml:get_tag_cdata(Body_xml),
557 + case xml:get_subtag(Packet, "subject") of
561 + xml:get_tag_cdata(Subject_xml)
564 + OwnerName = stringprep:tolower(Owner#jid.user),
565 + PName = stringprep:tolower(Peer#jid.user),
566 + PServer = stringprep:tolower(Peer#jid.server),
567 + PResource = Peer#jid.resource,
569 + #msg{timestamp=get_timestamp(),
570 + owner_name=OwnerName,
572 + peer_server=PServer,
573 + peer_resource=PResource,
574 + direction=Direction,
576 + subject=Message_subject,
580 +% called from handle_cast({addlog, _}, _) -> true (log messages) | false (do not log messages)
581 +filter(Owner, Peer, State) ->
582 + OwnerStr = Owner#jid.luser++"@"++Owner#jid.lserver,
583 + OwnerServ = "@"++Owner#jid.lserver,
584 + PeerStr = Peer#jid.luser++"@"++Peer#jid.lserver,
585 + PeerServ = "@"++Peer#jid.lserver,
587 + LogTo = case ets:match_object(ets_settings_table(State#state.vhost),
588 + #user_settings{owner_name=Owner#jid.luser, _='_'}) of
589 + [#user_settings{dolog_default=Default,
591 + donotlog_list=DNLL}] ->
592 + A = lists:member(PeerStr, DLL),
593 + B = lists:member(PeerStr, DNLL),
597 + Default == true -> true;
598 + Default == false -> false;
599 + true -> State#state.dolog_default
601 + _ -> State#state.dolog_default
604 + lists:all(fun(O) -> O end,
605 + [not lists:member(OwnerStr, State#state.ignore_jids),
606 + not lists:member(PeerStr, State#state.ignore_jids),
607 + not lists:member(OwnerServ, State#state.ignore_jids),
608 + not lists:member(PeerServ, State#state.ignore_jids),
611 +purge_old_records(VHost, Days) ->
612 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
614 + Dates = ?MODULE:get_dates(VHost),
615 + DateNow = calendar:datetime_to_gregorian_seconds({date(), {0,0,1}}),
616 + DateDiff = list_to_integer(Days)*24*60*60,
617 + ?MYDEBUG("Purging tables older than ~s days", [Days]),
618 + lists:foreach(fun(Date) ->
619 + {ok, [Year, Month, Day]} = regexp:split(Date, "[^0-9]+"),
620 + DateInSec = calendar:datetime_to_gregorian_seconds({{list_to_integer(Year), list_to_integer(Month), list_to_integer(Day)}, {0,0,1}}),
622 + (DateNow - DateInSec) > DateDiff ->
623 + gen_server:call(Proc, {delete_messages_at, Date});
625 + ?MYDEBUG("Skipping messages at ~p", [Date])
629 +% called from get_vhost_stats/2, get_user_stats/3
630 +sort_stats(Stats) ->
631 + % Stats = [{"2003-4-15",1}, {"2006-8-18",1}, ... ]
632 + CFun = fun({TableName, Count}) ->
633 + {ok, [Year, Month, Day]} = regexp:split(TableName, "[^0-9]+"),
634 + { calendar:datetime_to_gregorian_seconds({{list_to_integer(Year), list_to_integer(Month), list_to_integer(Day)}, {0,0,1}}), Count }
636 + % convert to [{63364377601,1}, {63360662401,1}, ... ]
637 + CStats = lists:map(CFun, Stats),
639 + SortedStats = lists:reverse(lists:keysort(1, CStats)),
640 + % convert to [{"2007-12-9",1}, {"2007-10-27",1}, ... ] sorted list
641 + [{mod_logdb:convert_timestamp_brief(TableSec), Count} || {TableSec, Count} <- SortedStats].
643 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
645 +% Date/Time operations
647 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
648 +% return float seconds elapsed from "zero hour" as list
650 + {MegaSec, Sec, MicroSec} = now(),
651 + [List] = io_lib:format("~.5f", [MegaSec*1000000 + Sec + MicroSec/1000000]),
654 +% convert float seconds elapsed from "zero hour" to local time "%Y-%m-%d %H:%M:%S" string
655 +convert_timestamp(Seconds) when is_list(Seconds) ->
656 + case string:to_float(Seconds++".0") of
657 + {F,_} when is_float(F) -> convert_timestamp(F);
658 + _ -> erlang:error(badarg, [Seconds])
660 +convert_timestamp(Seconds) when is_float(Seconds) ->
661 + GregSec = trunc(Seconds + 719528*86400),
662 + UnivDT = calendar:gregorian_seconds_to_datetime(GregSec),
663 + {{Year, Month, Day},{Hour, Minute, Sec}} = calendar:universal_time_to_local_time(UnivDT),
664 + integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day) ++ " " ++ integer_to_list(Hour) ++ ":" ++ integer_to_list(Minute) ++ ":" ++ integer_to_list(Sec).
666 +% convert float seconds elapsed from "zero hour" to local time "%Y-%m-%d" string
667 +convert_timestamp_brief(Seconds) when is_list(Seconds) ->
668 + convert_timestamp_brief(list_to_float(Seconds));
669 +convert_timestamp_brief(Seconds) when is_float(Seconds) ->
670 + GregSec = trunc(Seconds + 719528*86400),
671 + UnivDT = calendar:gregorian_seconds_to_datetime(GregSec),
672 + {{Year, Month, Day},{_Hour, _Minute, _Sec}} = calendar:universal_time_to_local_time(UnivDT),
673 + integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day);
674 +convert_timestamp_brief(Seconds) when is_integer(Seconds) ->
675 + {{Year, Month, Day},{_Hour, _Minute, _Sec}} = calendar:gregorian_seconds_to_datetime(Seconds),
676 + integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day).
678 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
680 +% DB operations (get)
682 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
683 +get_vhost_stats(VHost) ->
684 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
685 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
687 +get_vhost_stats_at(VHost, Date) ->
688 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
689 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
691 +get_user_stats(User, VHost) ->
692 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
693 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
695 +get_user_messages_at(User, VHost, Date) ->
696 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
697 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
700 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
701 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
703 +get_user_settings(User, VHost) ->
704 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
705 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
707 +set_user_settings(User, VHost, Set) ->
708 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
709 + gen_server:call(Proc, {set_user_settings, User, Set}).
711 +get_module_settings(VHost) ->
712 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
713 + gen_server:call(Proc, {get_module_settings}).
715 +set_module_settings(VHost, Settings) ->
716 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
717 + gen_server:call(Proc, {set_module_settings, Settings}).
719 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
721 +% Web admin callbacks (delete)
723 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
724 +user_messages_at_parse_query(VHost, Date, Msgs, Query) ->
725 + case lists:keysearch("delete", 1, Query) of
727 + PMsgs = lists:filter(
729 + ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg#msg.timestamp))),
730 + lists:member({"selected", ID}, Query)
732 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
733 + gen_server:call(Proc, {delete_messages_by_user_at, PMsgs, Date}, ?CALL_TIMEOUT);
738 +user_messages_parse_query(User, VHost, Query) ->
739 + case lists:keysearch("delete", 1, Query) of
741 + Dates = get_dates(VHost),
742 + PDates = lists:filter(
744 + ID = jlib:encode_base64(binary_to_list(term_to_binary(User++Date))),
745 + lists:member({"selected", ID}, Query)
747 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
751 + [gen_server:call(Proc,
752 + {delete_all_messages_by_user_at, User, Date},
755 + case lists:member(error, Rez) of
765 +vhost_messages_parse_query(VHost, Query) ->
766 + case lists:keysearch("delete", 1, Query) of
768 + Dates = get_dates(VHost),
769 + PDates = lists:filter(
771 + ID = jlib:encode_base64(binary_to_list(term_to_binary(VHost++Date))),
772 + lists:member({"selected", ID}, Query)
774 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
775 + Rez = lists:foldl(fun(Date, Acc) ->
776 + lists:append(Acc, [gen_server:call(Proc,
777 + {delete_messages_at, Date},
780 + case lists:member(error, Rez) of
790 +vhost_messages_at_parse_query(VHost, Date, Stats, Query) ->
791 + case lists:keysearch("delete", 1, Query) of
793 + PStats = lists:filter(
794 + fun({User, _Count}) ->
795 + ID = jlib:encode_base64(binary_to_list(term_to_binary(User++VHost))),
796 + lists:member({"selected", ID}, Query)
798 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
799 + Rez = lists:foldl(fun({User, _Count}, Acc) ->
800 + lists:append(Acc, [gen_server:call(Proc,
801 + {delete_all_messages_by_user_at,
805 + case lists:member(error, Rez) of
815 +copy_messages([#state{vhost=VHost}=State, From]) ->
816 + ?INFO_MSG("Going to copy messages from ~p for ~p", [From, VHost]),
818 + {FromDBName, FromDBOpts} =
819 + case lists:keysearch(list_to_atom(From), 1, State#state.dbs) of
820 + {value, {FN, FO}} ->
823 + ?ERROR_MSG("Failed to find record for ~p in dbs", [From]),
827 + FromDBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(FromDBName)),
829 + {ok, _FromPid} = FromDBMod:start(VHost, FromDBOpts),
831 + Dates = FromDBMod:get_dates(VHost),
832 + DatesLength = length(Dates),
834 + lists:foldl(fun(Date, Acc) ->
835 + case copy_messages_int([FromDBMod, State#state.dbmod, VHost, Date]) of
837 + ?INFO_MSG("Copied messages at ~p (~p/~p)", [Date, Acc, DatesLength]);
839 + ?ERROR_MSG("Failed to copy messages at ~p (~p/~p): ~p", [Date, Acc, DatesLength, Value]),
840 + FromDBMod:stop(VHost),
845 + ?INFO_MSG("Copied messages from ~p", [From]),
846 + FromDBMod:stop(VHost);
847 +copy_messages([#state{vhost=VHost}=State, From, Date]) ->
848 + {value, {FromDBName, FromDBOpts}} = lists:keysearch(list_to_atom(From), 1, State#state.dbs),
849 + FromDBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(FromDBName)),
850 + {ok, _FromPid} = FromDBMod:start(VHost, FromDBOpts),
851 + case catch copy_messages_int([FromDBMod, State#state.dbmod, VHost, Date]) of
852 + {'exit', Reason} ->
853 + ?ERROR_MSG("Failed to copy messages at ~p: ~p", [Date, Reason]);
855 + ?INFO_MSG("Copied messages at ~p", [Date]);
857 + ?ERROR_MSG("Failed to copy messages at ~p: ~p", [Date, Value])
859 + FromDBMod:stop(VHost).
861 +copy_messages_int([FromDBMod, ToDBMod, VHost, Date]) ->
862 + ets:new(mod_logdb_temp, [named_table, set, public]),
863 + {Time, Value} = timer:tc(?MODULE, copy_messages_int_tc, [[FromDBMod, ToDBMod, VHost, Date]]),
864 + ets:delete_all_objects(mod_logdb_temp),
865 + ets:delete(mod_logdb_temp),
866 + ?INFO_MSG("copy_messages at ~p elapsed ~p sec", [Date, Time/1000000]),
869 +copy_messages_int_tc([FromDBMod, ToDBMod, VHost, Date]) ->
870 + ?INFO_MSG("Going to copy messages from ~p for ~p at ~p", [FromDBMod, VHost, Date]),
872 + ok = FromDBMod:rebuild_stats_at(VHost, Date),
873 + catch mod_logdb:rebuild_stats_at(VHost, Date),
874 + {ok, FromStats} = FromDBMod:get_vhost_stats_at(VHost, Date),
875 + ToStats = case mod_logdb:get_vhost_stats_at(VHost, Date) of
876 + {ok, Stats} -> Stats;
880 + FromStatsS = lists:keysort(1, FromStats),
881 + ToStatsS = lists:keysort(1, ToStats),
883 + StatsLength = length(FromStats),
886 + % destination table is empty
887 + FromDBMod /= mod_logdb_mnesia_old, ToStats == [] ->
888 + fun({User, _Count}, Acc) ->
889 + {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date),
891 + lists:foldl(fun(Msg, MFAcc) ->
892 + ok = ToDBMod:log_message(VHost, Msg),
896 + ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
900 + % destination table is not empty
901 + FromDBMod /= mod_logdb_mnesia_old, ToStats /= [] ->
902 + fun({User, _Count}, Acc) ->
903 + {ok, ToMsgs} = ToDBMod:get_user_messages_at(User, VHost, Date),
904 + lists:foreach(fun(#msg{timestamp=Tst}) when length(Tst) == 16 ->
905 + ets:insert(mod_logdb_temp, {Tst});
906 + % mysql, pgsql removes final zeros after decimal point
907 + (#msg{timestamp=Tst}) when length(Tst) < 16 ->
908 + {F, _} = string:to_float(Tst++".0"),
909 + [T] = io_lib:format("~.5f", [F]),
910 + ets:insert(mod_logdb_temp, {T})
912 + {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date),
914 + lists:foldl(fun(#msg{timestamp=ToTimestamp} = Msg, MFAcc) ->
915 + case ets:member(mod_logdb_temp, ToTimestamp) of
917 + ok = ToDBMod:log_message(VHost, Msg),
918 + ets:insert(mod_logdb_temp, {ToTimestamp}),
925 + ets:delete_all_objects(mod_logdb_temp),
926 + ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
930 + % copying from mod_logmnesia
932 + fun({User, _Count}, Acc) ->
934 + case ToDBMod:get_user_messages_at(User, VHost, Date) of
938 + lists:foreach(fun(#msg{timestamp=Tst}) when length(Tst) == 16 ->
939 + ets:insert(mod_logdb_temp, {Tst});
940 + % mysql, pgsql removes final zeros after decimal point
941 + (#msg{timestamp=Tst}) when length(Tst) < 15 ->
942 + {F, _} = string:to_float(Tst++".0"),
943 + [T] = io_lib:format("~.5f", [F]),
944 + ets:insert(mod_logdb_temp, {T})
949 + {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date),
953 + fun({msg, TU, TS, TR, FU, FS, FR, Type, Subj, Body, Timest},
955 + [Timestamp] = if is_float(Timest) == true ->
956 + io_lib:format("~.5f", [Timest]);
957 + % early versions of mod_logmnesia
958 + is_integer(Timest) == true ->
959 + io_lib:format("~.5f", [Timest-719528*86400.0]);
961 + ?ERROR_MSG("Incorrect timestamp ~p", [Timest]),
964 + case ets:member(mod_logdb_temp, Timestamp) of
969 + TMsg = #msg{timestamp=Timestamp,
971 + peer_name=FU, peer_server=FS, peer_resource=FR,
974 + subject=Subj, body=Body},
975 + ok = ToDBMod:log_message(VHost, TMsg);
981 + FMsg = #msg{timestamp=Timestamp,
983 + peer_name=TU, peer_server=TS, peer_resource=TR,
986 + subject=Subj, body=Body},
987 + ok = ToDBMod:log_message(VHost, FMsg);
990 + ets:insert(mod_logdb_temp, {Timestamp}),
992 + true -> % not ets:member
995 + end, 0, Msgs), % foldl
997 + ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
1001 + end, % if FromDBMod /= mod_logdb_mnesia_old
1004 + FromStats == [] ->
1005 + ?INFO_MSG("No messages were found at ~p", [Date]);
1006 + FromStatsS == ToStatsS ->
1007 + ?INFO_MSG("Stats are equal at ~p", [Date]);
1008 + FromStatsS /= ToStatsS ->
1009 + lists:foldl(CopyFun, 0, FromStats),
1010 + ok = ToDBMod:rebuild_stats_at(VHost, Date)
1011 + %timer:sleep(1000)
1016 +list_to_bool(Num) ->
1017 + case lists:member(Num, ["t", "true", "y", "yes", "1"]) of
1021 + case lists:member(Num, ["f", "false", "n", "no", "0"]) of
1029 +bool_to_list(true) ->
1031 +bool_to_list(false) ->
1034 +list_to_string([]) ->
1036 +list_to_string(List) when is_list(List) ->
1037 + Str = lists:flatmap(fun(Elm) -> Elm ++ "\n" end, List),
1038 + lists:sublist(Str, length(Str)-1).
1040 +string_to_list(null) ->
1042 +string_to_list([]) ->
1044 +string_to_list(String) ->
1045 + {ok, List} = regexp:split(String, "\n"),
1048 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1050 +% ad-hoc (copy/pasted from mod_configure.erl)
1052 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1053 +-define(ITEMS_RESULT(Allow, LNode, Fallback),
1058 + case get_local_items(LServer, LNode,
1059 + jlib:jid_to_string(To), Lang) of
1067 +get_local_items(Acc, From, #jid{lserver = LServer} = To, "", Lang) ->
1068 + case gen_mod:is_loaded(LServer, mod_adhoc) of
1072 + Items = case Acc of
1073 + {result, Its} -> Its;
1076 + AllowUser = acl:match_rule(LServer, mod_logdb, From),
1077 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1079 + AllowUser == allow; AllowAdmin == allow ->
1080 + case get_local_items(LServer, [],
1081 + jlib:jid_to_string(To), Lang) of
1083 + {result, Items ++ Res};
1084 + {error, _Error} ->
1091 +get_local_items(Acc, From, #jid{lserver = LServer} = To, Node, Lang) ->
1092 + case gen_mod:is_loaded(LServer, mod_adhoc) of
1096 + LNode = string:tokens(Node, "/"),
1097 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1100 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1101 + ["mod_logdb_users"] ->
1102 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1103 + ["mod_logdb_users", [$@ | _]] ->
1104 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1105 + ["mod_logdb_users", _User] ->
1106 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1107 + ["mod_logdb_settings"] ->
1108 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1114 +-define(NODE(Name, Node),
1115 + {xmlelement, "item",
1117 + {"name", translate:translate(Lang, Name)},
1118 + {"node", Node}], []}).
1120 +get_local_items(_Host, [], Server, Lang) ->
1122 + [?NODE("Messages logging engine", "mod_logdb")]
1124 +get_local_items(_Host, ["mod_logdb"], Server, Lang) ->
1126 + [?NODE("Messages logging engine users", "mod_logdb_users"),
1127 + ?NODE("Messages logging engine settings", "mod_logdb_settings")]
1129 +get_local_items(Host, ["mod_logdb_users"], Server, Lang) ->
1130 + {result, get_all_vh_users(Host, Server, Lang)};
1131 +get_local_items(_Host, ["mod_logdb_users", [$@ | Diap]], Server, Lang) ->
1132 + case catch ejabberd_auth:dirty_get_registered_users() of
1133 + {'EXIT', _Reason} ->
1134 + ?ERR_INTERNAL_SERVER_ERROR;
1136 + SUsers = lists:sort([{S, U} || {U, S} <- Users]),
1138 + {ok, [S1, S2]} = regexp:split(Diap, "-"),
1139 + N1 = list_to_integer(S1),
1140 + N2 = list_to_integer(S2),
1141 + Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
1142 + lists:map(fun({S, U}) ->
1143 + ?NODE(U ++ "@" ++ S, "mod_logdb_users/" ++ U ++ "@" ++ S)
1146 + {'EXIT', _Reason} ->
1147 + ?ERR_NOT_ACCEPTABLE;
1152 +get_local_items(_Host, ["mod_logdb_users", _User], _Server, _Lang) ->
1154 +get_local_items(_Host, ["mod_logdb_settings"], _Server, _Lang) ->
1156 +get_local_items(_Host, Item, _Server, _Lang) ->
1157 + ?MYDEBUG("asked for items in ~p", [Item]),
1158 + {error, ?ERR_ITEM_NOT_FOUND}.
1160 +-define(INFO_RESULT(Allow, Feats),
1163 + {error, ?ERR_FORBIDDEN};
1168 +get_local_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
1169 + case gen_mod:is_loaded(LServer, mod_adhoc) of
1173 + LNode = string:tokens(Node, "/"),
1174 + AllowUser = acl:match_rule(LServer, mod_logdb, From),
1175 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1177 + ["mod_logdb"] when AllowUser == allow; AllowAdmin == allow ->
1178 + ?INFO_RESULT(allow, [?NS_COMMANDS]);
1180 + ?INFO_RESULT(deny, [?NS_COMMANDS]);
1181 + ["mod_logdb_users"] ->
1182 + ?INFO_RESULT(AllowAdmin, []);
1183 + ["mod_logdb_users", [$@ | _]] ->
1184 + ?INFO_RESULT(AllowAdmin, []);
1185 + ["mod_logdb_users", _User] ->
1186 + ?INFO_RESULT(AllowAdmin, [?NS_COMMANDS]);
1187 + ["mod_logdb_settings"] ->
1188 + ?INFO_RESULT(AllowAdmin, [?NS_COMMANDS]);
1192 + %?MYDEBUG("asked for ~p features: ~p", [LNode, Allow]),
1197 +-define(INFO_IDENTITY(Category, Type, Name, Lang),
1198 + [{xmlelement, "identity",
1199 + [{"category", Category},
1201 + {"name", translate:translate(Lang, Name)}], []}]).
1203 +-define(INFO_COMMAND(Name, Lang),
1204 + ?INFO_IDENTITY("automation", "command-node", Name, Lang)).
1206 +get_local_identity(Acc, _From, _To, Node, Lang) ->
1207 + LNode = string:tokens(Node, "/"),
1210 + ?INFO_COMMAND("Messages logging engine", Lang);
1211 + ["mod_logdb_users"] ->
1212 + ?INFO_COMMAND("Messages logging engine users", Lang);
1213 + ["mod_logdb_users", [$@ | _]] ->
1215 + ["mod_logdb_users", User] ->
1216 + ?INFO_COMMAND(User, Lang);
1217 + ["mod_logdb_settings"] ->
1218 + ?INFO_COMMAND("Messages logging engine settings", Lang);
1225 +%get_sm_items(Acc, From, To, Node, Lang) ->
1226 +% ?MYDEBUG("get_sm_items Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
1229 +%get_sm_features(Acc, From, To, Node, Lang) ->
1230 +% ?MYDEBUG("get_sm_features Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
1233 +%get_sm_identity(Acc, From, To, Node, Lang) ->
1234 +% ?MYDEBUG("get_sm_identity Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
1237 +adhoc_local_items(Acc, From, #jid{lserver = LServer, server = Server} = To,
1239 + Items = case Acc of
1240 + {result, Its} -> Its;
1243 + Nodes = recursively_get_local_items(LServer, "", Server, Lang),
1244 + Nodes1 = lists:filter(
1246 + Nd = xml:get_tag_attr_s("node", N),
1247 + F = get_local_features([], From, To, Nd, Lang),
1249 + {result, [?NS_COMMANDS]} ->
1255 + {result, Items ++ Nodes1}.
1257 +recursively_get_local_items(_LServer, "mod_logdb_users", _Server, _Lang) ->
1259 +recursively_get_local_items(LServer, Node, Server, Lang) ->
1260 + LNode = string:tokens(Node, "/"),
1261 + Items = case get_local_items(LServer, LNode, Server, Lang) of
1264 + {error, _Error} ->
1267 + Nodes = lists:flatten(
1270 + S = xml:get_tag_attr_s("jid", N),
1271 + Nd = xml:get_tag_attr_s("node", N),
1272 + if (S /= Server) or (Nd == "") ->
1275 + [N, recursively_get_local_items(
1276 + LServer, Nd, Server, Lang)]
1281 +-define(COMMANDS_RESULT(Allow, From, To, Request),
1284 + {error, ?ERR_FORBIDDEN};
1286 + adhoc_local_commands(From, To, Request)
1289 +adhoc_local_commands(Acc, From, #jid{lserver = LServer} = To,
1290 + #adhoc_request{node = Node} = Request) ->
1291 + LNode = string:tokens(Node, "/"),
1292 + AllowUser = acl:match_rule(LServer, mod_logdb, From),
1293 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1295 + ["mod_logdb"] when AllowUser == allow; AllowAdmin == allow ->
1296 + ?COMMANDS_RESULT(allow, From, To, Request);
1297 + ["mod_logdb_users", _User] when AllowAdmin == allow ->
1298 + ?COMMANDS_RESULT(allow, From, To, Request);
1299 + ["mod_logdb_settings"] when AllowAdmin == allow ->
1300 + ?COMMANDS_RESULT(allow, From, To, Request);
1305 +adhoc_local_commands(From, #jid{lserver = LServer} = _To,
1306 + #adhoc_request{lang = Lang,
1308 + sessionid = SessionID,
1310 + xdata = XData} = Request) ->
1311 + LNode = string:tokens(Node, "/"),
1312 + %% If the "action" attribute is not present, it is
1313 + %% understood as "execute". If there was no <actions/>
1314 + %% element in the first response (which there isn't in our
1315 + %% case), "execute" and "complete" are equivalent.
1316 + ActionIsExecute = lists:member(Action,
1317 + ["", "execute", "complete"]),
1318 + if Action == "cancel" ->
1319 + %% User cancels request
1320 + adhoc:produce_response(
1322 + #adhoc_response{status = canceled});
1323 + XData == false, ActionIsExecute ->
1324 + %% User requests form
1325 + case get_form(LServer, LNode, From, Lang) of
1327 + adhoc:produce_response(
1329 + #adhoc_response{status = executing,
1330 + elements = Form});
1334 + XData /= false, ActionIsExecute ->
1335 + %% User returns form.
1336 + case jlib:parse_xdata_submit(XData) of
1338 + {error, ?ERR_BAD_REQUEST};
1340 + case set_form(From, LServer, LNode, Lang, Fields) of
1342 + adhoc:produce_response(
1343 + #adhoc_response{lang = Lang,
1345 + sessionid = SessionID,
1346 + status = completed});
1352 + {error, ?ERR_BAD_REQUEST}
1355 +-define(LISTLINE(Label, Value),
1356 + {xmlelement, "option", [{"label", Label}],
1357 + [{xmlelement, "value", [], [{xmlcdata, Value}]}]}).
1358 +-define(DEFVAL(Value), {xmlelement, "value", [], [{xmlcdata, Value}]}).
1360 +get_user_form(LUser, LServer, Lang) ->
1361 + %From = jlib:jid_to_string(jlib:jid_remove_resource(Jid)),
1362 + #user_settings{dolog_default=DLD,
1364 + donotlog_list=DNLL} = get_user_settings(LUser, LServer),
1365 + {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
1366 + [{xmlelement, "title", [],
1368 + translate:translate(
1369 + Lang, "Messages logging engine settings")}]},
1370 + {xmlelement, "instructions", [],
1372 + translate:translate(
1373 + Lang, "Set logging preferences")++ ": " ++ LUser ++ "@" ++ LServer}]},
1375 + {xmlelement, "field", [{"type", "list-single"},
1377 + translate:translate(Lang, "Default")},
1378 + {"var", "dolog_default"}],
1379 + [?DEFVAL(atom_to_list(DLD)),
1380 + ?LISTLINE(translate:translate(Lang, "Log Messages"), "true"),
1381 + ?LISTLINE(translate:translate(Lang, "Do Not Log Messages"), "false")
1384 + {xmlelement, "field", [{"type", "text-multi"},
1386 + translate:translate(
1387 + Lang, "Log Messages")},
1388 + {"var", "dolog_list"}],
1389 + [{xmlelement, "value", [], [{xmlcdata, list_to_string(DLL)}]}]},
1391 + {xmlelement, "field", [{"type", "text-multi"},
1393 + translate:translate(
1394 + Lang, "Do Not Log Messages")},
1395 + {"var", "donotlog_list"}],
1396 + [{xmlelement, "value", [], [{xmlcdata, list_to_string(DNLL)}]}]}
1399 +get_settings_form(Host, Lang) ->
1400 + #state{dbmod=DBMod,
1402 + dolog_default=DLD,
1403 + ignore_jids=IgnoreJids,
1404 + groupchat=GroupChat,
1405 + purge_older_days=PurgeDaysT,
1406 + drop_messages_on_user_removal=MRemoval,
1407 + poll_users_settings=PollTime} = mod_logdb:get_module_settings(Host),
1409 + Backends = lists:map(fun({Backend, _Opts}) ->
1410 + ?LISTLINE(atom_to_list(Backend), atom_to_list(Backend))
1412 + DB = lists:sublist(atom_to_list(DBMod), length(atom_to_list(?MODULE)) + 2, length(atom_to_list(DBMod))),
1413 + DBsL = lists:append([?DEFVAL(DB)], Backends),
1416 + case PurgeDaysT of
1418 + Num when is_integer(Num) -> integer_to_list(Num);
1421 + {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
1422 + [{xmlelement, "title", [],
1424 + translate:translate(
1425 + Lang, "Messages logging engine settings") ++ " (run-time)"}]},
1426 + {xmlelement, "instructions", [],
1428 + translate:translate(
1429 + Lang, "Set run-time settings")}]},
1431 + {xmlelement, "field", [{"type", "list-single"},
1433 + translate:translate(Lang, "Backend")},
1434 + {"var", "backend"}],
1437 + {xmlelement, "field", [{"type", "text-multi"},
1439 + translate:translate(
1442 + [{xmlelement, "value", [], [{xmlcdata, lists:flatten(io_lib:format("~p.",[DBs]))}]}]},
1444 + {xmlelement, "field", [{"type", "list-single"},
1446 + translate:translate(Lang, "Default")},
1447 + {"var", "dolog_default"}],
1448 + [?DEFVAL(atom_to_list(DLD)),
1449 + ?LISTLINE(translate:translate(Lang, "Log Messages"), "true"),
1450 + ?LISTLINE(translate:translate(Lang, "Do Not Log Messages"), "false")
1452 + % drop_messages_on_user_removal
1453 + {xmlelement, "field", [{"type", "list-single"},
1455 + translate:translate(Lang, "Drop messages on user removal")},
1456 + {"var", "drop_messages_on_user_removal"}],
1457 + [?DEFVAL(atom_to_list(MRemoval)),
1458 + ?LISTLINE(translate:translate(Lang, "Drop"), "true"),
1459 + ?LISTLINE(translate:translate(Lang, "Do not drop"), "false")
1462 + {xmlelement, "field", [{"type", "list-single"},
1464 + translate:translate(Lang, "Groupchat messages logging")},
1465 + {"var", "groupchat"}],
1466 + [?DEFVAL(atom_to_list(GroupChat)),
1467 + ?LISTLINE("all", "all"),
1468 + ?LISTLINE("none", "none"),
1469 + ?LISTLINE("send", "send"),
1470 + ?LISTLINE("half", "half")
1473 + {xmlelement, "field", [{"type", "text-multi"},
1475 + translate:translate(
1476 + Lang, "Jids/Domains to ignore")},
1477 + {"var", "ignore_list"}],
1478 + [{xmlelement, "value", [], [{xmlcdata, list_to_string(IgnoreJids)}]}]},
1479 + % purge older days
1480 + {xmlelement, "field", [{"type", "text-single"},
1482 + translate:translate(
1483 + Lang, "Purge messages older than (days)")},
1484 + {"var", "purge_older_days"}],
1485 + [{xmlelement, "value", [], [{xmlcdata, PurgeDays}]}]},
1486 + % poll users settings
1487 + {xmlelement, "field", [{"type", "text-single"},
1489 + translate:translate(
1490 + Lang, "Poll users settings (seconds)")},
1491 + {"var", "poll_users_settings"}],
1492 + [{xmlelement, "value", [], [{xmlcdata, integer_to_list(PollTime)}]}]}
1495 +get_form(_Host, ["mod_logdb"], #jid{luser = LUser, lserver = LServer} = _Jid, Lang) ->
1496 + get_user_form(LUser, LServer, Lang);
1497 +get_form(_Host, ["mod_logdb_users", User], _JidFrom, Lang) ->
1498 + #jid{luser=LUser, lserver=LServer} = jlib:string_to_jid(User),
1499 + get_user_form(LUser, LServer, Lang);
1500 +get_form(Host, ["mod_logdb_settings"], _JidFrom, Lang) ->
1501 + get_settings_form(Host, Lang);
1502 +get_form(_Host, Command, _, _Lang) ->
1503 + ?MYDEBUG("asked for form ~p", [Command]),
1504 + {error, ?ERR_SERVICE_UNAVAILABLE}.
1506 +check_log_list([Head | Tail]) ->
1507 + case lists:member($@, Head) of
1509 + false -> throw(error)
1511 + % this check for Head to be valid jid
1512 + case jlib:string_to_jid(Head) of
1516 + check_log_list(Tail)
1518 +check_log_list([]) ->
1521 +check_ignore_list([Head | Tail]) ->
1522 + case lists:member($@, Head) of
1524 + false -> throw(error)
1526 + % this check for Head to be valid jid
1527 + case jlib:string_to_jid(Head) of
1529 + % this check for Head to be valid domain "@domain.org"
1530 + case lists:nth(1, Head) of
1532 + % TODO: this allows spaces and special characters in Head. May be change to nodeprep?
1533 + case jlib:nameprep(lists:delete($@, Head)) of
1534 + error -> throw(error);
1535 + _ -> check_log_list(Tail)
1540 + check_ignore_list(Tail)
1542 +check_ignore_list([]) ->
1545 +parse_users_settings(XData) ->
1546 + DLD = case lists:keysearch("dolog_default", 1, XData) of
1547 + {value, {_, [String]}} when String == "true"; String == "false" ->
1548 + list_to_bool(String);
1550 + throw(bad_request)
1552 + DLL = case lists:keysearch("dolog_list", 1, XData) of
1554 + throw(bad_request);
1555 + {value, {_, [[]]}} ->
1557 + {value, {_, List1}} ->
1558 + case catch check_log_list(List1) of
1560 + throw(bad_request);
1565 + DNLL = case lists:keysearch("donotlog_list", 1, XData) of
1567 + throw(bad_request);
1568 + {value, {_, [[]]}} ->
1570 + {value, {_, List2}} ->
1571 + case catch check_log_list(List2) of
1573 + throw(bad_request);
1578 + #user_settings{dolog_default=DLD,
1580 + donotlog_list=DNLL}.
1582 +parse_module_settings(XData) ->
1583 + DLD = case lists:keysearch("dolog_default", 1, XData) of
1584 + {value, {_, [Str1]}} when Str1 == "true"; Str1 == "false" ->
1585 + list_to_bool(Str1);
1587 + throw(bad_request)
1589 + MRemoval = case lists:keysearch("drop_messages_on_user_removal", 1, XData) of
1590 + {value, {_, [Str5]}} when Str5 == "true"; Str5 == "false" ->
1591 + list_to_bool(Str5);
1593 + throw(bad_request)
1595 + GroupChat = case lists:keysearch("groupchat", 1, XData) of
1596 + {value, {_, [Str2]}} when Str2 == "none";
1600 + list_to_atom(Str2);
1602 + throw(bad_request)
1604 + Ignore = case lists:keysearch("ignore_list", 1, XData) of
1605 + {value, {_, List}} ->
1606 + case catch check_ignore_list(List) of
1610 + throw(bad_request)
1613 + throw(bad_request)
1615 + Purge = case lists:keysearch("purge_older_days", 1, XData) of
1616 + {value, {_, ["never"]}} ->
1618 + {value, {_, [Str3]}} ->
1619 + case catch list_to_integer(Str3) of
1620 + {'EXIT', {badarg, _}} -> throw(bad_request);
1624 + throw(bad_request)
1626 + Poll = case lists:keysearch("poll_users_settings", 1, XData) of
1627 + {value, {_, [Str4]}} ->
1628 + case catch list_to_integer(Str4) of
1629 + {'EXIT', {badarg, _}} -> throw(bad_request);
1633 + throw(bad_request)
1635 + #state{dolog_default=DLD,
1636 + groupchat=GroupChat,
1637 + ignore_jids=Ignore,
1638 + purge_older_days=Purge,
1639 + drop_messages_on_user_removal=MRemoval,
1640 + poll_users_settings=Poll}.
1642 +set_form(From, _Host, ["mod_logdb"], _Lang, XData) ->
1643 + #jid{luser=LUser, lserver=LServer} = From,
1644 + case catch parse_users_settings(XData) of
1646 + {error, ?ERR_BAD_REQUEST};
1648 + case mod_logdb:set_user_settings(LUser, LServer, UserSettings) of
1652 + {error, ?ERR_INTERNAL_SERVER_ERROR}
1655 +set_form(_From, _Host, ["mod_logdb_users", User], _Lang, XData) ->
1656 + #jid{luser=LUser, lserver=LServer} = jlib:string_to_jid(User),
1657 + case catch parse_users_settings(XData) of
1658 + bad_request -> {error, ?ERR_BAD_REQUEST};
1660 + case mod_logdb:set_user_settings(LUser, LServer, UserSettings) of
1664 + {error, ?ERR_INTERNAL_SERVER_ERROR}
1667 +set_form(_From, Host, ["mod_logdb_settings"], _Lang, XData) ->
1668 + case catch parse_module_settings(XData) of
1669 + bad_request -> {error, ?ERR_BAD_REQUEST};
1671 + case mod_logdb:set_module_settings(Host, Settings) of
1675 + {error, ?ERR_INTERNAL_SERVER_ERROR}
1678 +set_form(From, _Host, Node, _Lang, XData) ->
1679 + User = jlib:jid_to_string(jlib:jid_remove_resource(From)),
1680 + ?MYDEBUG("set form for ~p at ~p XData=~p", [User, Node, XData]),
1681 + {error, ?ERR_SERVICE_UNAVAILABLE}.
1683 +%adhoc_sm_items(Acc, From, To, Request) ->
1684 +% ?MYDEBUG("adhoc_sm_items Acc=~p From=~p To=~p Request=~p", [Acc, From, To, Request]),
1687 +%adhoc_sm_commands(Acc, From, To, Request) ->
1688 +% ?MYDEBUG("adhoc_sm_commands Acc=~p From=~p To=~p Request=~p", [Acc, From, To, Request]),
1691 +get_all_vh_users(Host, Server, Lang) ->
1692 + case catch ejabberd_auth:get_vh_registered_users(Host) of
1693 + {'EXIT', _Reason} ->
1696 + SUsers = lists:sort([{S, U} || {U, S} <- Users]),
1697 + case length(SUsers) of
1698 + N when N =< 100 ->
1699 + lists:map(fun({S, U}) ->
1700 + ?NODE(U ++ "@" ++ S, "mod_logdb_users/" ++ U ++ "@" ++ S)
1703 + NParts = trunc(math:sqrt(N * 0.618)) + 1,
1704 + M = trunc(N / NParts) + 1,
1705 + lists:map(fun(K) ->
1708 + "@" ++ integer_to_list(K) ++
1709 + "-" ++ integer_to_list(L),
1710 + {FS, FU} = lists:nth(K, SUsers),
1712 + if L < N -> lists:nth(L, SUsers);
1713 + true -> lists:last(SUsers)
1716 + FU ++ "@" ++ FS ++
1719 + ?NODE(Name, "mod_logdb_users/" ++ Node)
1720 + end, lists:seq(1, N, M))
1724 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1728 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1729 +webadmin_menu(Acc, _Host, Lang) ->
1730 + [{"messages", ?T("Users Messages")} | Acc].
1732 +webadmin_user(Acc, User, Server, Lang) ->
1733 + Sett = get_user_settings(User, Server),
1735 + case Sett#user_settings.dolog_default of
1737 + ?INPUTT("submit", "dolog", "Log Messages");
1739 + ?INPUTT("submit", "donotlog", "Do Not Log Messages");
1742 + Acc ++ [?XE("h3", [?ACT("messages/", "Messages"), ?C(" "), Log])].
1744 +webadmin_page(_, Host,
1745 + #request{path = ["messages"],
1747 + lang = Lang}) when is_list(Host) ->
1748 + Res = vhost_messages_stats(Host, Query, Lang),
1750 +webadmin_page(_, Host,
1751 + #request{path = ["messages", Date],
1753 + lang = Lang}) when is_list(Host) ->
1754 + Res = vhost_messages_stats_at(Host, Query, Lang, Date),
1756 +webadmin_page(_, Host,
1757 + #request{path = ["user", U, "messages"],
1760 + Res = user_messages_stats(U, Host, Query, Lang),
1762 +webadmin_page(_, Host,
1763 + #request{path = ["user", U, "messages", Date],
1766 + Res = mod_logdb:user_messages_stats_at(U, Host, Query, Lang, Date),
1768 +webadmin_page(Acc, _, _) -> Acc.
1770 +user_parse_query(_, "dolog", User, Server, _Query) ->
1771 + Sett = get_user_settings(User, Server),
1772 + % TODO: check returned value
1773 + set_user_settings(User, Server, Sett#user_settings{dolog_default=true}),
1775 +user_parse_query(_, "donotlog", User, Server, _Query) ->
1776 + Sett = get_user_settings(User, Server),
1777 + % TODO: check returned value
1778 + set_user_settings(User, Server, Sett#user_settings{dolog_default=false}),
1780 +user_parse_query(Acc, _Action, _User, _Server, _Query) ->
1783 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1787 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1788 +vhost_messages_stats(Server, Query, Lang) ->
1789 + Res = case catch vhost_messages_parse_query(Server, Query) of
1790 + {'EXIT', Reason} ->
1791 + ?ERROR_MSG("~p", [Reason]),
1793 + VResult -> VResult
1795 + {Time, Value} = timer:tc(mod_logdb, get_vhost_stats, [Server]),
1796 + ?INFO_MSG("get_vhost_stats(~p) elapsed ~p sec", [Server, Time/1000000]),
1797 + %case get_vhost_stats(Server) of
1799 + {'EXIT', CReason} ->
1800 + ?ERROR_MSG("Failed to get_vhost_stats: ~p", [CReason]),
1801 + [?XC("h1", ?T("Error occupied while fetching list"))];
1802 + {error, GReason} ->
1803 + ?ERROR_MSG("Failed to get_vhost_stats: ~p", [GReason]),
1804 + [?XC("h1", ?T("Error occupied while fetching list"))];
1806 + [?XC("h1", ?T("No logged messages for ") ++ Server)];
1808 + Fun = fun({Date, Count}) ->
1809 + ID = jlib:encode_base64(binary_to_list(term_to_binary(Server++Date))),
1811 + [?XE("td", [?INPUT("checkbox", "selected", ID)]),
1812 + ?XE("td", [?AC(Date, Date)]),
1813 + ?XC("td", integer_to_list(Count))
1816 + [?XC("h1", ?T("Logged messages for ") ++ Server)] ++
1818 + ok -> [?CT("Submitted"), ?P];
1819 + error -> [?CT("Bad format"), ?P];
1822 + [?XAE("form", [{"action", ""}, {"method", "post"}],
1827 + ?XCT("td", "Date"),
1828 + ?XCT("td", "Count")
1831 + lists:map(Fun, Dates)
1834 + ?INPUTT("submit", "delete", "Delete Selected")
1838 +vhost_messages_stats_at(Server, Query, Lang, Date) ->
1839 + {Time, Value} = timer:tc(mod_logdb, get_vhost_stats_at, [Server, Date]),
1840 + ?INFO_MSG("get_vhost_stats_at(~p,~p) elapsed ~p sec", [Server, Date, Time/1000000]),
1841 + %case get_vhost_stats_at(Server, Date) of
1843 + {'EXIT', CReason} ->
1844 + ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [CReason]),
1845 + [?XC("h1", ?T("Error occupied while fetching list"))];
1846 + {error, GReason} ->
1847 + ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [GReason]),
1848 + [?XC("h1", ?T("Error occupied while fetching list"))];
1850 + [?XC("h1", ?T("No logged messages for ") ++ Server ++ ?T(" at ") ++ Date)];
1852 + Res = case catch vhost_messages_at_parse_query(Server, Date, Users, Query) of
1853 + {'EXIT', Reason} ->
1854 + ?ERROR_MSG("~p", [Reason]),
1856 + VResult -> VResult
1858 + Fun = fun({User, Count}) ->
1859 + ID = jlib:encode_base64(binary_to_list(term_to_binary(User++Server))),
1861 + [?XE("td", [?INPUT("checkbox", "selected", ID)]),
1862 + ?XE("td", [?AC("../user/"++User++"/messages/"++Date, User)]),
1863 + ?XC("td", integer_to_list(Count))
1866 + [?XC("h1", ?T("Logged messages for ") ++ Server ++ ?T(" at ") ++ Date)] ++
1868 + ok -> [?CT("Submitted"), ?P];
1869 + error -> [?CT("Bad format"), ?P];
1872 + [?XAE("form", [{"action", ""}, {"method", "post"}],
1877 + ?XCT("td", "User"),
1878 + ?XCT("td", "Count")
1881 + lists:map(Fun, Users)
1884 + ?INPUTT("submit", "delete", "Delete Selected")
1888 +user_messages_stats(User, Server, Query, Lang) ->
1889 + Jid = jlib:jid_to_string({User, Server, ""}),
1891 + Res = case catch user_messages_parse_query(User, Server, Query) of
1892 + {'EXIT', Reason} ->
1893 + ?ERROR_MSG("~p", [Reason]),
1895 + VResult -> VResult
1898 + {Time, Value} = timer:tc(mod_logdb, get_user_stats, [User, Server]),
1899 + ?INFO_MSG("get_user_stats(~p,~p) elapsed ~p sec", [User, Server, Time/1000000]),
1902 + {'EXIT', CReason} ->
1903 + ?ERROR_MSG("Failed to get_user_stats: ~p", [CReason]),
1904 + [?XC("h1", ?T("Error occupied while fetching days"))];
1905 + {error, GReason} ->
1906 + ?ERROR_MSG("Failed to get_user_stats: ~p", [GReason]),
1907 + [?XC("h1", ?T("Error occupied while fetching days"))];
1909 + [?XC("h1", ?T("No logged messages for ") ++ Jid)];
1911 + Fun = fun({Date, Count}) ->
1912 + ID = jlib:encode_base64(binary_to_list(term_to_binary(User++Date))),
1914 + [?XE("td", [?INPUT("checkbox", "selected", ID)]),
1915 + ?XE("td", [?AC(Date, Date)]),
1916 + ?XC("td", integer_to_list(Count))
1918 + %[?AC(Date, Date ++ " (" ++ integer_to_list(Count) ++ ")"), ?BR]
1920 + [?XC("h1", ?T("Logged messages for ") ++ Jid)] ++
1922 + ok -> [?CT("Submitted"), ?P];
1923 + error -> [?CT("Bad format"), ?P];
1926 + [?XAE("form", [{"action", ""}, {"method", "post"}],
1931 + ?XCT("td", "Date"),
1932 + ?XCT("td", "Count")
1935 + lists:map(Fun, Dates)
1938 + ?INPUTT("submit", "delete", "Delete Selected")
1942 +search_user_nick(User, List) ->
1943 + case lists:keysearch(User, 1, List) of
1944 + {value,{User, []}} ->
1946 + {value,{User, Nick}} ->
1952 +user_messages_stats_at(User, Server, Query, Lang, Date) ->
1953 + Jid = jlib:jid_to_string({User, Server, ""}),
1955 + {Time, Value} = timer:tc(mod_logdb, get_user_messages_at, [User, Server, Date]),
1956 + ?INFO_MSG("get_user_messages_at(~p,~p,~p) elapsed ~p sec", [User, Server, Date, Time/1000000]),
1958 + {'EXIT', CReason} ->
1959 + ?ERROR_MSG("Failed to get_user_messages_at: ~p", [CReason]),
1960 + [?XC("h1", ?T("Error occupied while fetching messages"))];
1961 + {error, GReason} ->
1962 + ?ERROR_MSG("Failed to get_user_messages_at: ~p", [GReason]),
1963 + [?XC("h1", ?T("Error occupied while fetching messages"))];
1965 + [?XC("h1", ?T("No logged messages for ") ++ Jid ++ ?T(" at ") ++ Date)];
1966 + {ok, User_messages} ->
1967 + Res = case catch user_messages_at_parse_query(Server,
1971 + {'EXIT', Reason} ->
1972 + ?ERROR_MSG("~p", [Reason]),
1974 + VResult -> VResult
1977 + UR = ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]),
1979 + lists:map(fun(Item) ->
1980 + {jlib:jid_to_string(Item#roster.jid), Item#roster.name}
1983 + UniqUsers = lists:foldl(fun(#msg{peer_name=PName, peer_server=PServer}, List) ->
1984 + ToAdd = PName++"@"++PServer,
1985 + case lists:member(ToAdd, List) of
1987 + false -> lists:append([ToAdd], List)
1989 + end, [], User_messages),
1991 + % Users to filter (sublist of UniqUsers)
1992 + CheckedUsers = case lists:keysearch("filter", 1, Query) of
1994 + lists:filter(fun(UFUser) ->
1995 + ID = jlib:encode_base64(binary_to_list(term_to_binary(UFUser))),
1996 + lists:member({"selected", ID}, Query)
2001 + % UniqUsers in html (noone selected -> everyone selected)
2002 + Users = lists:map(fun(UHUser) ->
2003 + ID = jlib:encode_base64(binary_to_list(term_to_binary(UHUser))),
2004 + Input = case lists:member(UHUser, CheckedUsers) of
2005 + true -> [?INPUTC("checkbox", "selected", ID)];
2006 + false when CheckedUsers == [] -> [?INPUTC("checkbox", "selected", ID)];
2007 + false -> [?INPUT("checkbox", "selected", ID)]
2010 + case search_user_nick(UHUser, UserRoster) of
2012 + N -> " ("++ N ++")"
2015 + [?XE("td", Input),
2016 + ?XC("td", UHUser++Nick)])
2017 + end, lists:sort(UniqUsers)),
2018 + % Messages to show (based on Users)
2019 + User_messages_filtered = case CheckedUsers of
2020 + [] -> User_messages;
2021 + _ -> lists:filter(fun(#msg{peer_name=PName, peer_server=PServer}) ->
2022 + lists:member(PName++"@"++PServer, CheckedUsers)
2023 + end, User_messages)
2026 + Msgs_Fun = fun(#msg{timestamp=Timestamp,
2028 + direction=Direction,
2029 + peer_name=PName, peer_server=PServer, peer_resource=PRes,
2032 + TextRaw = case Subject of
2034 + _ -> [?T("Subject"),": ",Subject,"<br>", Body]
2036 + ID = jlib:encode_base64(binary_to_list(term_to_binary(Timestamp))),
2037 + % replace \n with <br>
2038 + Text = lists:map(fun(10) -> "<br>";
2041 + Resource = case PRes of
2047 + case search_user_nick(PName++"@"++PServer, UserRoster) of
2048 + nothing when PServer == Server ->
2050 + nothing when Type == "groupchat", Direction == from ->
2051 + PName++"@"++PServer++Resource;
2053 + PName++"@"++PServer;
2057 + [?XE("td", [?INPUT("checkbox", "selected", ID)]),
2058 + ?XC("td", convert_timestamp(Timestamp)),
2059 + ?XC("td", atom_to_list(Direction)++": "++UserNick),
2062 + % Filtered user messages in html
2063 + Msgs = lists:map(Msgs_Fun, lists:sort(User_messages_filtered)),
2065 + [?XC("h1", ?T("Logged messages for ") ++ Jid ++ ?T(" at ") ++ Date)] ++
2067 + ok -> [?CT("Submitted"), ?P];
2068 + error -> [?CT("Bad format"), ?P];
2071 + [?XAE("form", [{"action", ""}, {"method", "post"}],
2075 + ?XCT("td", "User")
2081 + ?INPUTT("submit", "filter", "Filter Selected")
2087 + ?XCT("td", "Date, Time"),
2088 + ?XCT("td", "Direction: Jid"),
2089 + ?XCT("td", "Body")
2094 + ?INPUTT("submit", "delete", "Delete Selected"),
2099 --- mod_logdb.hrl.orig 2009-11-22 13:06:23.000000000 +0200
2100 +++ mod_logdb.hrl 2009-02-05 20:12:58.000000000 +0200
2102 +%%%----------------------------------------------------------------------
2103 +%%% File : mod_logdb.hrl
2104 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
2106 +%%% Version : trunk
2108 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
2109 +%%%----------------------------------------------------------------------
2111 +-define(logdb_debug, true).
2113 +-ifdef(logdb_debug).
2114 +-define(MYDEBUG(Format, Args), io:format("D(~p:~p:~p) : "++Format++"~n",
2115 + [calendar:local_time(),?MODULE,?LINE]++Args)).
2117 +-define(MYDEBUG(_F,_A),[]).
2120 +-record(msg, {timestamp,
2122 + peer_name, peer_server, peer_resource,
2127 +-record(user_settings, {owner_name,
2130 + donotlog_list=[]}).
2132 +-define(INPUTC(Type, Name, Value),
2133 + ?XA("input", [{"type", Type},
2136 + {"checked", "true"}])).
2137 --- mod_logdb_mnesia.erl.orig 2009-11-22 13:06:23.000000000 +0200
2138 +++ mod_logdb_mnesia.erl 2009-02-05 20:12:58.000000000 +0200
2140 +%%%----------------------------------------------------------------------
2141 +%%% File : mod_logdb_mnesia.erl
2142 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
2143 +%%% Purpose : mnesia backend for mod_logdb
2144 +%%% Version : trunk
2146 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
2147 +%%%----------------------------------------------------------------------
2149 +-module(mod_logdb_mnesia).
2150 +-author('o.palij@gmail.com').
2152 +-include("mod_logdb.hrl").
2153 +-include("ejabberd.hrl").
2154 +-include("jlib.hrl").
2156 +-behaviour(gen_logdb).
2157 +-behaviour(gen_server).
2160 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
2162 +-export([start/2, stop/1]).
2164 +-export([log_message/2,
2166 + rebuild_stats_at/2,
2167 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
2168 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
2170 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
2173 +-define(PROCNAME, mod_logdb_mnesia).
2174 +-define(CALL_TIMEOUT, 10000).
2176 +-record(state, {vhost}).
2178 +-record(stats, {user, at, count}).
2186 +stats_table(VHost) ->
2187 + list_to_atom(prefix() ++ "stats" ++ suffix(VHost)).
2189 +table_name(VHost, Date) ->
2190 + list_to_atom(prefix() ++ "messages_" ++ Date ++ suffix(VHost)).
2192 +settings_table(VHost) ->
2193 + list_to_atom(prefix() ++ "settings" ++ suffix(VHost)).
2195 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2197 +% gen_mod callbacks
2199 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2200 +start(VHost, Opts) ->
2201 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2202 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
2205 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2206 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
2208 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2210 +% gen_server callbacks
2212 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2213 +init([VHost, _Opts]) ->
2214 + case mnesia:system_info(is_running) of
2216 + ok = create_stats_table(VHost),
2217 + ok = create_settings_table(VHost),
2218 + {ok, #state{vhost=VHost}};
2220 + ?ERROR_MSG("Mnesia not running", []),
2221 + {stop, db_connection_failed};
2223 + ?ERROR_MSG("Mnesia status: ~p", [Status]),
2224 + {stop, db_connection_failed}
2227 +handle_call({log_message, Msg}, _From, #state{vhost=VHost}=State) ->
2228 + {reply, log_message_int(VHost, Msg), State};
2229 +handle_call({rebuild_stats}, _From, #state{vhost=VHost}=State) ->
2230 + {atomic, ok} = delete_nonexistent_stats(VHost),
2232 + lists:foreach(fun(Date) ->
2233 + rebuild_stats_at_int(VHost, Date)
2234 + end, get_dates_int(VHost)),
2235 + {reply, Reply, State};
2236 +handle_call({rebuild_stats_at, Date}, _From, #state{vhost=VHost}=State) ->
2237 + Reply = rebuild_stats_at_int(VHost, Date),
2238 + {reply, Reply, State};
2239 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{vhost=VHost}=State) ->
2240 + Table = table_name(VHost, Date),
2244 + mnesia:write_lock_table(stats_table(VHost)),
2245 + mnesia:write_lock_table(Table),
2246 + mnesia:delete_object(Table, Msg, write)
2249 + DRez = case mnesia:transaction(Fun) of
2250 + {aborted, Reason} ->
2251 + ?ERROR_MSG("Failed to delete_messages_by_user_at at ~p for ~p: ~p", [Date, VHost, Reason]),
2257 + case rebuild_stats_at_int(VHost, Date) of
2263 + {reply, Reply, State};
2264 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{vhost=VHost}=State) ->
2265 + {reply, delete_all_messages_by_user_at_int(User, VHost, Date), State};
2266 +handle_call({delete_messages_at, Date}, _From, #state{vhost=VHost}=State) ->
2268 + case mnesia:delete_table(table_name(VHost, Date)) of
2270 + delete_stats_by_vhost_at_int(VHost, Date);
2271 + {aborted, Reason} ->
2272 + ?ERROR_MSG("Failed to delete_messages_at for ~p at ~p", [VHost, Date, Reason]),
2275 + {reply, Reply, State};
2276 +handle_call({get_vhost_stats}, _From, #state{vhost=VHost}=State) ->
2277 + Fun = fun(#stats{at=Date, count=Count}, Stats) ->
2278 + case lists:keysearch(Date, 1, Stats) of
2280 + lists:append(Stats, [{Date, Count}]);
2281 + {value, {_, TempCount}} ->
2282 + lists:keyreplace(Date, 1, Stats, {Date, TempCount+Count})
2286 + case mnesia:transaction(fun() ->
2287 + mnesia:foldl(Fun, [], stats_table(VHost))
2289 + {atomic, Result} -> {ok, mod_logdb:sort_stats(Result)};
2290 + {aborted, Reason} -> {error, Reason}
2292 + {reply, Reply, State};
2293 +handle_call({get_vhost_stats_at, Date}, _From, #state{vhost=VHost}=State) ->
2295 + Pat = #stats{user='$1', at=Date, count='$2'},
2296 + mnesia:select(stats_table(VHost), [{Pat, [], [['$1', '$2']]}])
2299 + case mnesia:transaction(Fun) of
2300 + {atomic, Result} ->
2301 + {ok, lists:reverse(lists:keysort(2, [{User, Count} || [User, Count] <- Result]))};
2302 + {aborted, Reason} ->
2305 + {reply, Reply, State};
2306 +handle_call({get_user_stats, User}, _From, #state{vhost=VHost}=State) ->
2307 + {reply, get_user_stats_int(User, VHost), State};
2308 +handle_call({get_user_messages_at, User, Date}, _From, #state{vhost=VHost}=State) ->
2310 + case mnesia:transaction(fun() ->
2311 + Pat = #msg{owner_name=User, _='_'},
2312 + mnesia:select(table_name(VHost, Date),
2313 + [{Pat, [], ['$_']}])
2315 + {atomic, Result} -> {ok, Result};
2316 + {aborted, Reason} ->
2319 + {reply, Reply, State};
2320 +handle_call({get_dates}, _From, #state{vhost=VHost}=State) ->
2321 + {reply, get_dates_int(VHost), State};
2322 +handle_call({get_users_settings}, _From, #state{vhost=VHost}=State) ->
2323 + Reply = mnesia:dirty_match_object(settings_table(VHost), #user_settings{_='_'}),
2324 + {reply, {ok, Reply}, State};
2325 +handle_call({get_user_settings, User}, _From, #state{vhost=VHost}=State) ->
2327 + case mnesia:dirty_match_object(settings_table(VHost), #user_settings{owner_name=User, _='_'}) of
2332 + {reply, Reply, State};
2333 +handle_call({set_user_settings, _User, Set}, _From, #state{vhost=VHost}=State) ->
2334 + ?MYDEBUG("~p~n~p", [settings_table(VHost), Set]),
2335 + Reply = mnesia:dirty_write(settings_table(VHost), Set),
2336 + ?MYDEBUG("~p", [Reply]),
2337 + {reply, Reply, State};
2338 +handle_call({drop_user, User}, _From, #state{vhost=VHost}=State) ->
2339 + {ok, Dates} = get_user_stats_int(User, VHost),
2340 + MDResult = lists:map(fun({Date, _}) ->
2341 + delete_all_messages_by_user_at_int(User, VHost, Date)
2343 + SDResult = delete_user_settings_int(User, VHost),
2345 + case lists:all(fun(Result) when Result == ok ->
2347 + (Result) when Result == error ->
2349 + end, lists:append(MDResult, [SDResult])) of
2355 + {reply, Reply, State};
2356 +handle_call({stop}, _From, State) ->
2357 + {stop, normal, ok, State};
2358 +handle_call(Msg, _From, State) ->
2359 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
2362 +handle_cast(Msg, State) ->
2363 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
2366 +handle_info(Info, State) ->
2367 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
2370 +terminate(_Reason, _State) ->
2373 +code_change(_OldVsn, State, _Extra) ->
2376 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2378 +% gen_logdb callbacks
2380 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2381 +log_message(VHost, Msg) ->
2382 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2383 + gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
2384 +rebuild_stats(VHost) ->
2385 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2386 + gen_server:call(Proc, {rebuild_stats}, ?CALL_TIMEOUT).
2387 +rebuild_stats_at(VHost, Date) ->
2388 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2389 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
2390 +delete_messages_by_user_at(VHost, Msgs, Date) ->
2391 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2392 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
2393 +delete_all_messages_by_user_at(User, VHost, Date) ->
2394 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2395 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
2396 +delete_messages_at(VHost, Date) ->
2397 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2398 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
2399 +get_vhost_stats(VHost) ->
2400 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2401 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
2402 +get_vhost_stats_at(VHost, Date) ->
2403 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2404 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
2405 +get_user_stats(User, VHost) ->
2406 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2407 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
2408 +get_user_messages_at(User, VHost, Date) ->
2409 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2410 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
2411 +get_dates(VHost) ->
2412 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2413 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
2414 +get_user_settings(User, VHost) ->
2415 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2416 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
2417 +get_users_settings(VHost) ->
2418 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2419 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
2420 +set_user_settings(User, VHost, Set) ->
2421 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2422 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
2423 +drop_user(User, VHost) ->
2424 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2425 + gen_server:call(Proc, {drop_user, User}, ?CALL_TIMEOUT).
2427 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2431 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2432 +log_message_int(VHost, #msg{timestamp=Timestamp}=Msg) ->
2433 + Date = mod_logdb:convert_timestamp_brief(Timestamp),
2435 + ATable = table_name(VHost, Date),
2437 + mnesia:write_lock_table(ATable),
2438 + mnesia:write(ATable, Msg, write)
2440 + % log message, increment stats for both users
2441 + case mnesia:transaction(Fun) of
2442 + % if table does not exists - create it and try to log message again
2443 + {aborted,{no_exists, _Table}} ->
2444 + case create_msg_table(VHost, Date) of
2445 + {aborted, CReason} ->
2446 + ?ERROR_MSG("Failed to log message: ~p", [CReason]),
2449 + ?MYDEBUG("Created msg table for ~p at ~p", [VHost, Date]),
2450 + log_message_int(VHost, Msg)
2452 + {aborted, TReason} ->
2453 + ?ERROR_MSG("Failed to log message: ~p", [TReason]),
2456 + ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
2457 + Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
2458 + increment_user_stats(Msg#msg.owner_name, VHost, Date)
2461 +increment_user_stats(Owner, VHost, Date) ->
2463 + Pat = #stats{user=Owner, at=Date, count='$1'},
2464 + mnesia:write_lock_table(stats_table(VHost)),
2465 + case mnesia:select(stats_table(VHost), [{Pat, [], ['$_']}]) of
2467 + mnesia:write(stats_table(VHost),
2468 + #stats{user=Owner,
2473 + mnesia:delete_object(stats_table(VHost),
2474 + #stats{user=Owner,
2476 + count=Stats#stats.count},
2478 + New = Stats#stats{count = Stats#stats.count+1},
2480 + New#stats.count > 0 -> mnesia:write(stats_table(VHost),
2487 + case mnesia:transaction(Fun) of
2488 + {aborted, Reason} ->
2489 + ?ERROR_MSG("Failed to update stats for ~s@~s: ~p", [Owner, VHost, Reason]),
2492 + ?MYDEBUG("Updated stats for ~s@~s", [Owner, VHost]),
2496 +get_dates_int(VHost) ->
2497 + Tables = mnesia:system_info(tables),
2498 + lists:foldl(fun(ATable, Dates) ->
2499 + Table = atom_to_list(ATable),
2500 + case regexp:match(Table, VHost++"$") of
2502 + case regexp:match(Table,"_[0-9]+-[0-9]+-[0-9]+_") of
2504 + lists:append(Dates, [lists:sublist(Table,S+1,E-2)]);
2513 +rebuild_stats_at_int(VHost, Date) ->
2514 + Table = table_name(VHost, Date),
2515 + STable = stats_table(VHost),
2516 + CFun = fun(Msg, Stats) ->
2517 + Owner = Msg#msg.owner_name,
2518 + case lists:keysearch(Owner, 1, Stats) of
2519 + {value, {_, Count}} ->
2520 + lists:keyreplace(Owner, 1, Stats, {Owner, Count + 1});
2522 + lists:append(Stats, [{Owner, 1}])
2525 + DFun = fun(#stats{at=SDate} = Stat, _Acc)
2526 + when SDate == Date ->
2527 + mnesia:delete_object(stats_table(VHost), Stat, write);
2528 + (_Stat, _Acc) -> ok
2530 + % TODO: Maybe unregister hooks ?
2531 + case mnesia:transaction(fun() ->
2532 + mnesia:write_lock_table(Table),
2533 + mnesia:write_lock_table(STable),
2534 + % Calc stats for VHost at Date
2535 + case mnesia:foldl(CFun, [], Table) of
2538 + % Delete all stats for VHost at Date
2539 + mnesia:foldl(DFun, [], STable),
2540 + % Write new calc'ed stats
2541 + lists:foreach(fun({Owner, Count}) ->
2542 + WStat = #stats{user=Owner, at=Date, count=Count},
2543 + mnesia:write(stats_table(VHost), WStat, write)
2548 + {aborted, Reason} ->
2549 + ?ERROR_MSG("Failed to rebuild_stats_at for ~p at ~p: ~p", [VHost, Date, Reason]),
2553 + {atomic, empty} ->
2554 + {atomic,ok} = mnesia:delete_table(Table),
2555 + ?MYDEBUG("Dropped table at ~p", [Date]),
2559 +delete_nonexistent_stats(VHost) ->
2560 + Dates = get_dates_int(VHost),
2561 + mnesia:transaction(fun() ->
2562 + mnesia:foldl(fun(#stats{at=Date} = Stat, _Acc) ->
2563 + case lists:member(Date, Dates) of
2564 + false -> mnesia:delete_object(Stat);
2567 + end, ok, stats_table(VHost))
2570 +delete_stats_by_vhost_at_int(VHost, Date) ->
2571 + StatsDelete = fun(#stats{at=SDate} = Stat, _Acc)
2572 + when SDate == Date ->
2573 + mnesia:delete_object(stats_table(VHost), Stat, write),
2575 + (_Msg, _Acc) -> ok
2577 + case mnesia:transaction(fun() ->
2578 + mnesia:write_lock_table(stats_table(VHost)),
2579 + mnesia:foldl(StatsDelete, ok, stats_table(VHost))
2581 + {aborted, Reason} ->
2582 + ?ERROR_MSG("Failed to update stats at ~p for ~p: ~p", [Date, VHost, Reason]),
2583 + rebuild_stats_at_int(VHost, Date);
2585 + ?INFO_MSG("Updated stats at ~p for ~p", [Date, VHost]),
2589 +get_user_stats_int(User, VHost) ->
2590 + case mnesia:transaction(fun() ->
2591 + Pat = #stats{user=User, at='$1', count='$2'},
2592 + mnesia:select(stats_table(VHost), [{Pat, [], [['$1', '$2']]}])
2594 + {atomic, Result} ->
2595 + {ok, mod_logdb:sort_stats([{Date, Count} || [Date, Count] <- Result])};
2596 + {aborted, Reason} ->
2600 +delete_all_messages_by_user_at_int(User, VHost, Date) ->
2601 + Table = table_name(VHost, Date),
2602 + MsgDelete = fun(#msg{owner_name=Owner} = Msg, _Acc)
2603 + when Owner == User ->
2604 + mnesia:delete_object(Table, Msg, write),
2606 + (_Msg, _Acc) -> ok
2608 + DRez = case mnesia:transaction(fun() ->
2609 + mnesia:foldl(MsgDelete, ok, Table)
2611 + {aborted, Reason} ->
2612 + ?ERROR_MSG("Failed to delete_all_messages_by_user_at for ~p@~p at ~p: ~p", [User, VHost, Date, Reason]),
2617 + case rebuild_stats_at_int(VHost, Date) of
2624 +delete_user_settings_int(User, VHost) ->
2625 + STable = settings_table(VHost),
2626 + case mnesia:dirty_match_object(STable, #user_settings{owner_name=User, _='_'}) of
2630 + mnesia:dirty_delete_object(STable, UserSettings)
2633 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2637 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2638 +create_stats_table(VHost) ->
2639 + SName = stats_table(VHost),
2640 + case mnesia:create_table(SName,
2641 + [{disc_only_copies, [node()]},
2643 + {attributes, record_info(fields, stats)},
2644 + {record_name, stats}
2647 + ?MYDEBUG("Created stats table for ~p", [VHost]),
2648 + lists:foreach(fun(Date) ->
2649 + rebuild_stats_at_int(VHost, Date)
2650 + end, get_dates_int(VHost)),
2652 + {aborted, {already_exists, _}} ->
2653 + ?MYDEBUG("Stats table for ~p already exists", [VHost]),
2655 + {aborted, Reason} ->
2656 + ?ERROR_MSG("Failed to create stats table: ~p", [Reason]),
2660 +create_settings_table(VHost) ->
2661 + SName = settings_table(VHost),
2662 + case mnesia:create_table(SName,
2663 + [{disc_copies, [node()]},
2665 + {attributes, record_info(fields, user_settings)},
2666 + {record_name, user_settings}
2669 + ?MYDEBUG("Created settings table for ~p", [VHost]),
2671 + {aborted, {already_exists, _}} ->
2672 + ?MYDEBUG("Settings table for ~p already exists", [VHost]),
2674 + {aborted, Reason} ->
2675 + ?ERROR_MSG("Failed to create settings table: ~p", [Reason]),
2679 +create_msg_table(VHost, Date) ->
2680 + mnesia:create_table(
2681 + table_name(VHost, Date),
2682 + [{disc_only_copies, [node()]},
2684 + {attributes, record_info(fields, msg)},
2685 + {record_name, msg}]).
2686 --- mod_logdb_mysql.erl.orig 2009-11-22 13:06:23.000000000 +0200
2687 +++ mod_logdb_mysql.erl 2009-07-30 09:00:14.000000000 +0300
2689 +%%%----------------------------------------------------------------------
2690 +%%% File : mod_logdb_mysql.erl
2691 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
2692 +%%% Purpose : MySQL backend for mod_logdb
2693 +%%% Version : trunk
2695 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
2696 +%%%----------------------------------------------------------------------
2698 +-module(mod_logdb_mysql).
2699 +-author('o.palij@gmail.com').
2701 +-include("mod_logdb.hrl").
2702 +-include("ejabberd.hrl").
2703 +-include("jlib.hrl").
2705 +-behaviour(gen_logdb).
2706 +-behaviour(gen_server).
2709 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
2711 +-export([start/2, stop/1]).
2713 +-export([log_message/2,
2715 + rebuild_stats_at/2,
2716 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
2717 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
2719 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
2722 +% gen_server call timeout
2723 +-define(CALL_TIMEOUT, 30000).
2724 +-define(MYSQL_TIMEOUT, 60000).
2725 +-define(INDEX_SIZE, integer_to_list(170)).
2726 +-define(PROCNAME, mod_logdb_mysql).
2728 +-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
2729 + list_to_string/1, string_to_list/1,
2730 + convert_timestamp_brief/1]).
2732 +-record(state, {dbref, vhost, server, port, db, user, password}).
2734 +% replace "." with "_"
2735 +escape_vhost(VHost) -> lists:map(fun(46) -> 95;
2742 + "_" ++ escape_vhost(VHost) ++ "`".
2744 +messages_table(VHost, Date) ->
2745 + prefix() ++ "messages_" ++ Date ++ suffix(VHost).
2747 +stats_table(VHost) ->
2748 + prefix() ++ "stats" ++ suffix(VHost).
2750 +temp_table(VHost) ->
2751 + prefix() ++ "temp" ++ suffix(VHost).
2753 +settings_table(VHost) ->
2754 + prefix() ++ "settings" ++ suffix(VHost).
2756 +users_table(VHost) ->
2757 + prefix() ++ "users" ++ suffix(VHost).
2758 +servers_table(VHost) ->
2759 + prefix() ++ "servers" ++ suffix(VHost).
2760 +resources_table(VHost) ->
2761 + prefix() ++ "resources" ++ suffix(VHost).
2763 +ets_users_table(VHost) -> list_to_atom("logdb_users_" ++ VHost).
2764 +ets_servers_table(VHost) -> list_to_atom("logdb_servers_" ++ VHost).
2765 +ets_resources_table(VHost) -> list_to_atom("logdb_resources_" ++ VHost).
2767 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2769 +% gen_mod callbacks
2771 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2772 +start(VHost, Opts) ->
2773 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2774 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
2777 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2778 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
2780 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2782 +% gen_server callbacks
2784 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2785 +init([VHost, Opts]) ->
2788 + Server = gen_mod:get_opt(server, Opts, "localhost"),
2789 + Port = gen_mod:get_opt(port, Opts, 3306),
2790 + DB = gen_mod:get_opt(db, Opts, "logdb"),
2791 + User = gen_mod:get_opt(user, Opts, "root"),
2792 + Password = gen_mod:get_opt(password, Opts, ""),
2794 + St = #state{vhost=VHost,
2795 + server=Server, port=Port, db=DB,
2796 + user=User, password=Password},
2798 + case open_mysql_connection(St) of
2800 + State = St#state{dbref=DBRef},
2801 + ok = create_stats_table(State),
2802 + ok = create_settings_table(State),
2803 + ok = create_users_table(State),
2804 + % clear ets cache every ...
2805 + timer:send_interval(timer:hours(12), clear_ets_tables),
2806 + ok = create_servers_table(State),
2807 + ok = create_resources_table(State),
2808 + erlang:monitor(process, DBRef),
2810 + {error, Reason} ->
2811 + ?ERROR_MSG("MySQL connection failed: ~p~n", [Reason]),
2812 + {stop, db_connection_failed}
2815 +open_mysql_connection(#state{server=Server, port=Port, db=DB,
2816 + user=DBUser, password=Password} = _State) ->
2817 + LogFun = fun(debug, _Format, _Argument) ->
2818 + %?MYDEBUG(Format, Argument);
2820 + (error, Format, Argument) ->
2821 + ?ERROR_MSG(Format, Argument);
2822 + (Level, Format, Argument) ->
2823 + ?MYDEBUG("MySQL (~p)~n", [Level]),
2824 + ?MYDEBUG(Format, Argument)
2826 + ?INFO_MSG("Opening mysql connection ~s@~s:~p/~s", [DBUser, Server, Port, DB]),
2827 + mysql_conn:start(Server, Port, DBUser, Password, DB, LogFun).
2829 +close_mysql_connection(DBRef) ->
2830 + ?MYDEBUG("Closing ~p mysql connection", [DBRef]),
2831 + mysql_conn:stop(DBRef).
2833 +handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2834 + Date = convert_timestamp_brief(Msg#msg.timestamp),
2836 + Table = messages_table(VHost, Date),
2837 + Owner_id = get_user_id(DBRef, VHost, Msg#msg.owner_name),
2838 + Peer_name_id = get_user_id(DBRef, VHost, Msg#msg.peer_name),
2839 + Peer_server_id = get_server_id(DBRef, VHost, Msg#msg.peer_server),
2840 + Peer_resource_id = get_resource_id(DBRef, VHost, Msg#msg.peer_resource),
2842 + Query = ["INSERT INTO ",Table," ",
2845 + "peer_server_id,",
2846 + "peer_resource_id,",
2853 + "('", Owner_id, "',",
2854 + "'", Peer_name_id, "',",
2855 + "'", Peer_server_id, "',",
2856 + "'", Peer_resource_id, "',",
2857 + "'", atom_to_list(Msg#msg.direction), "',",
2858 + "'", Msg#msg.type, "',",
2859 + "'", ejabberd_odbc:escape(Msg#msg.subject), "',",
2860 + "'", ejabberd_odbc:escape(Msg#msg.body), "',",
2861 + "'", Msg#msg.timestamp, "');"],
2864 + case sql_query_internal_silent(DBRef, Query) of
2866 + ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
2867 + Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
2868 + increment_user_stats(DBRef, Msg#msg.owner_name, Owner_id, VHost, Peer_name_id, Peer_server_id, Date);
2869 + {error, Reason} ->
2870 + case regexp:match(Reason, "#42S02") of
2871 + % Table doesn't exist
2873 + case create_msg_table(DBRef, VHost, Date) of
2877 + {updated, _} = sql_query_internal(DBRef, Query),
2878 + increment_user_stats(DBRef, Msg#msg.owner_name, Owner_id, VHost, Peer_name_id, Peer_server_id, Date)
2881 + ?ERROR_MSG("Failed to log message: ~p", [Reason]),
2885 + {reply, Reply, State};
2886 +handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2887 + Reply = rebuild_stats_at_int(DBRef, VHost, Date),
2888 + {reply, Reply, State};
2889 +handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
2890 + {reply, error, State};
2891 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2892 + Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
2893 + ["\"",Timestamp,"\"",","]
2896 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
2898 + Query = ["DELETE FROM ",messages_table(VHost, Date)," ",
2899 + "WHERE timestamp IN (", Temp1],
2902 + case sql_query_internal(DBRef, Query) of
2904 + ?MYDEBUG("Aff=~p", [Aff]),
2905 + rebuild_stats_at_int(DBRef, VHost, Date);
2909 + {reply, Reply, State};
2910 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2911 + ok = delete_all_messages_by_user_at_int(DBRef, User, VHost, Date),
2912 + ok = delete_stats_by_user_at_int(DBRef, User, VHost, Date),
2913 + {reply, ok, State};
2914 +handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2916 + case sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Date),";"]) of
2918 + Query = ["DELETE FROM ",stats_table(VHost)," "
2919 + "WHERE at=\"",Date,"\";"],
2920 + case sql_query_internal(DBRef, Query) of
2929 + {reply, Reply, State};
2930 +handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2931 + SName = stats_table(VHost),
2932 + Query = ["SELECT at, sum(count) ",
2933 + "FROM ",SName," ",
2935 + "ORDER BY DATE(at) DESC;"
2938 + case sql_query_internal(DBRef, Query) of
2940 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
2941 + {error, Reason} ->
2942 + % TODO: Duplicate error message ?
2945 + {reply, Reply, State};
2946 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2947 + SName = stats_table(VHost),
2948 + Query = ["SELECT username, sum(count) AS allcount ",
2949 + "FROM ",SName," ",
2950 + "JOIN ",users_table(VHost)," ON owner_id=user_id "
2951 + "WHERE at=\"",Date,"\" "
2952 + "GROUP BY username ",
2953 + "ORDER BY allcount DESC;"
2956 + case sql_query_internal(DBRef, Query) of
2958 + {ok, lists:reverse(
2960 + [ {User, list_to_integer(Count)} || [User, Count] <- Result]))};
2961 + {error, Reason} ->
2965 + {reply, Reply, State};
2966 +handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2967 + {reply, get_user_stats_int(DBRef, User, VHost), State};
2968 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2969 + TName = messages_table(VHost, Date),
2970 + UName = users_table(VHost),
2971 + SName = servers_table(VHost),
2972 + RName = resources_table(VHost),
2973 + Query = ["SELECT users.username,",
2974 + "servers.server,",
2975 + "resources.resource,",
2976 + "messages.direction,"
2978 + "messages.subject,"
2980 + "messages.timestamp "
2981 + "FROM ",TName," AS messages "
2982 + "JOIN ",UName," AS users ON peer_name_id=user_id ",
2983 + "JOIN ",SName," AS servers ON peer_server_id=server_id ",
2984 + "JOIN ",RName," AS resources ON peer_resource_id=resource_id ",
2985 + "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\" ",
2986 + "ORDER BY timestamp ASC;"],
2988 + case sql_query_internal(DBRef, Query) of
2990 + Fun = fun([Peer_name, Peer_server, Peer_resource,
2995 + #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
2996 + direction=list_to_atom(Direction),
2998 + subject=Subject, body=Body,
2999 + timestamp=Timestamp}
3001 + {ok, lists:map(Fun, Result)};
3002 + {error, Reason} ->
3005 + {reply, Reply, State};
3006 +handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3007 + SName = stats_table(VHost),
3008 + Query = ["SELECT at ",
3009 + "FROM ",SName," ",
3011 + "ORDER BY DATE(at) DESC;"
3014 + case sql_query_internal(DBRef, Query) of
3016 + [ Date || [Date] <- Result ];
3017 + {error, Reason} ->
3020 + {reply, Reply, State};
3021 +handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3022 + Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
3023 + "FROM ",settings_table(VHost)," ",
3024 + "JOIN ",users_table(VHost)," ON user_id=owner_id;"],
3026 + case sql_query_internal(DBRef, Query) of
3028 + {ok, lists:map(fun([Owner, DoLogDef, DoLogL, DoNotLogL]) ->
3029 + #user_settings{owner_name=Owner,
3030 + dolog_default=list_to_bool(DoLogDef),
3031 + dolog_list=string_to_list(DoLogL),
3032 + donotlog_list=string_to_list(DoNotLogL)
3038 + {reply, Reply, State};
3039 +handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3040 + Query = ["SELECT dolog_default,dolog_list,donotlog_list FROM ",settings_table(VHost)," ",
3041 + "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\";"],
3043 + case sql_query_internal(DBRef, Query) of
3046 + {data, [[Owner, DoLogDef, DoLogL, DoNotLogL]]} ->
3047 + {ok, #user_settings{owner_name=Owner,
3048 + dolog_default=list_to_bool(DoLogDef),
3049 + dolog_list=string_to_list(DoLogL),
3050 + donotlog_list=string_to_list(DoNotLogL)}};
3054 + {reply, Reply, State};
3055 +handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
3056 + dolog_list=DoLogL,
3057 + donotlog_list=DoNotLogL}},
3058 + _From, #state{dbref=DBRef, vhost=VHost} = State) ->
3059 + User_id = get_user_id(DBRef, VHost, User),
3061 + Query = ["UPDATE ",settings_table(VHost)," ",
3062 + "SET dolog_default=",bool_to_list(DoLogDef),", ",
3063 + "dolog_list='",list_to_string(DoLogL),"', ",
3064 + "donotlog_list='",list_to_string(DoNotLogL),"' ",
3065 + "WHERE owner_id=\"",User_id,"\";"],
3068 + case sql_query_internal(DBRef, Query) of
3070 + IQuery = ["INSERT INTO ",settings_table(VHost)," ",
3071 + "(owner_id, dolog_default, dolog_list, donotlog_list) ",
3073 + "('",User_id,"', ",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
3074 + case sql_query_internal_silent(DBRef, IQuery) of
3076 + ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
3078 + {error, Reason} ->
3079 + case regexp:match(Reason, "#23000") of
3084 + ?ERROR_MSG("Failed setup user ~p@~p: ~p", [User, VHost, Reason]),
3089 + ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
3094 + {reply, Reply, State};
3095 +handle_call({stop}, _From, #state{vhost=VHost}=State) ->
3096 + ets:delete(ets_users_table(VHost)),
3097 + ets:delete(ets_servers_table(VHost)),
3098 + ?MYDEBUG("Stoping mysql backend for ~p", [VHost]),
3099 + {stop, normal, ok, State};
3100 +handle_call(Msg, _From, State) ->
3101 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
3104 +handle_cast({rebuild_stats}, State) ->
3105 + rebuild_all_stats_int(State),
3107 +handle_cast({drop_user, User}, #state{vhost=VHost} = State) ->
3109 + {ok, DBRef} = open_mysql_connection(State),
3110 + {ok, Dates} = get_user_stats_int(DBRef, User, VHost),
3111 + MDResult = lists:map(fun({Date, _}) ->
3112 + delete_all_messages_by_user_at_int(DBRef, User, VHost, Date)
3114 + StDResult = delete_all_stats_by_user_int(DBRef, User, VHost),
3115 + SDResult = delete_user_settings_int(DBRef, User, VHost),
3116 + case lists:all(fun(Result) when Result == ok ->
3118 + (Result) when Result == error ->
3120 + end, lists:append([MDResult, [StDResult], [SDResult]])) of
3122 + ?INFO_MSG("Removed ~s@~s", [User, VHost]);
3124 + ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost])
3126 + close_mysql_connection(DBRef)
3130 +handle_cast(Msg, State) ->
3131 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
3134 +handle_info(clear_ets_tables, State) ->
3135 + ets:delete_all_objects(ets_users_table(State#state.vhost)),
3136 + ets:delete_all_objects(ets_resources_table(State#state.vhost)),
3138 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
3139 + {stop, connection_dropped, State};
3140 +handle_info(Info, State) ->
3141 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
3144 +terminate(_Reason, #state{dbref=DBRef}=_State) ->
3145 + close_mysql_connection(DBRef),
3148 +code_change(_OldVsn, State, _Extra) ->
3151 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3153 +% gen_logdb callbacks
3155 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3156 +log_message(VHost, Msg) ->
3157 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3158 + gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
3159 +rebuild_stats(VHost) ->
3160 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3161 + gen_server:cast(Proc, {rebuild_stats}).
3162 +rebuild_stats_at(VHost, Date) ->
3163 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3164 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
3165 +delete_messages_by_user_at(VHost, Msgs, Date) ->
3166 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3167 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
3168 +delete_all_messages_by_user_at(User, VHost, Date) ->
3169 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3170 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
3171 +delete_messages_at(VHost, Date) ->
3172 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3173 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
3174 +get_vhost_stats(VHost) ->
3175 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3176 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
3177 +get_vhost_stats_at(VHost, Date) ->
3178 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3179 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
3180 +get_user_stats(User, VHost) ->
3181 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3182 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
3183 +get_user_messages_at(User, VHost, Date) ->
3184 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3185 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
3186 +get_dates(VHost) ->
3187 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3188 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
3189 +get_users_settings(VHost) ->
3190 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3191 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
3192 +get_user_settings(User, VHost) ->
3193 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3194 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
3195 +set_user_settings(User, VHost, Set) ->
3196 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3197 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
3198 +drop_user(User, VHost) ->
3199 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3200 + gen_server:cast(Proc, {drop_user, User}).
3202 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3206 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3207 +increment_user_stats(DBRef, User_name, User_id, VHost, PNameID, PServerID, Date) ->
3208 + SName = stats_table(VHost),
3209 + UQuery = ["UPDATE ",SName," ",
3210 + "SET count=count+1 ",
3211 + "WHERE owner_id=\"",User_id,"\" AND peer_name_id=\"",PNameID,"\" AND peer_server_id=\"",PServerID,"\" AND at=\"",Date,"\";"],
3213 + case sql_query_internal(DBRef, UQuery) of
3215 + IQuery = ["INSERT INTO ",SName," ",
3216 + "(owner_id, peer_name_id, peer_server_id, at, count) ",
3218 + "('",User_id,"', '",PNameID,"', '",PServerID,"', '",Date,"', '1');"],
3219 + case sql_query_internal(DBRef, IQuery) of
3221 + ?MYDEBUG("New stats for ~s@~s at ~s", [User_name, VHost, Date]),
3227 + ?MYDEBUG("Updated stats for ~s@~s at ~s", [User_name, VHost, Date]),
3233 +get_dates_int(DBRef, VHost) ->
3234 + case sql_query_internal(DBRef, ["SHOW TABLES"]) of
3236 + lists:foldl(fun([Table], Dates) ->
3237 + Reg = lists:sublist(prefix(),2,length(prefix())) ++ ".*" ++ escape_vhost(VHost),
3238 + case regexp:match(Table, Reg) of
3240 + ?MYDEBUG("matched ~p against ~p", [Table, Reg]),
3241 + case regexp:match(Table,"[0-9]+-[0-9]+-[0-9]+") of
3243 + lists:append(Dates, [lists:sublist(Table,S,E)]);
3256 +rebuild_all_stats_int(#state{vhost=VHost}=State) ->
3258 + {ok, DBRef} = open_mysql_connection(State),
3259 + ok = delete_nonexistent_stats(DBRef, VHost),
3260 + case lists:filter(fun(Date) ->
3261 + case catch rebuild_stats_at_int(DBRef, VHost, Date) of
3264 + {'EXIT', _} -> true
3266 + end, get_dates_int(DBRef, VHost)) of
3269 + ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]),
3272 + close_mysql_connection(DBRef)
3276 +rebuild_stats_at_int(DBRef, VHost, Date) ->
3277 + TempTable = temp_table(VHost),
3279 + Table = messages_table(VHost, Date),
3280 + STable = stats_table(VHost),
3282 + DQuery = [ "DELETE FROM ",STable," ",
3283 + "WHERE at='",Date,"';"],
3285 + ok = create_temp_table(DBRef, TempTable),
3286 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," WRITE, ",TempTable," WRITE;"]),
3287 + SQuery = ["INSERT INTO ",TempTable," ",
3288 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
3289 + "SELECT owner_id,peer_name_id,peer_server_id,\"",Date,"\",count(*) ",
3290 + "FROM ",Table," GROUP BY owner_id,peer_name_id,peer_server_id;"],
3291 + case sql_query_internal(DBRef, SQuery) of
3293 + Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]),
3295 + {data, [["0"]]} ->
3296 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]),
3297 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE;"]),
3298 + {updated, _} = sql_query_internal(DBRef, DQuery),
3301 + ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]),
3305 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE, ",TempTable," WRITE;"]),
3306 + {updated, _} = sql_query_internal(DBRef, DQuery),
3307 + SQuery1 = ["INSERT INTO ",STable," ",
3308 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
3309 + "SELECT owner_id,peer_name_id,peer_server_id,at,count ",
3310 + "FROM ",TempTable,";"],
3311 + case sql_query_internal(DBRef, SQuery1) of
3312 + {updated, _} -> ok;
3313 + {error, _} -> error
3315 + {error, _} -> error
3319 + case catch apply(Fun, []) of
3321 + ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
3325 + {'EXIT', Reason} ->
3326 + ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]),
3329 + sql_query_internal(DBRef, ["UNLOCK TABLES;"]),
3330 + sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]),
3334 +delete_nonexistent_stats(DBRef, VHost) ->
3335 + Dates = get_dates_int(DBRef, VHost),
3336 + STable = stats_table(VHost),
3338 + Temp = lists:flatmap(fun(Date) ->
3339 + ["\"",Date,"\"",","]
3346 + % replace last "," with ");"
3347 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
3348 + Query = ["DELETE FROM ",STable," ",
3349 + "WHERE at NOT IN (", Temp1],
3350 + case sql_query_internal(DBRef, Query) of
3358 +get_user_stats_int(DBRef, User, VHost) ->
3359 + SName = stats_table(VHost),
3360 + Query = ["SELECT at, sum(count) as allcount ",
3361 + "FROM ",SName," ",
3362 + "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\" ",
3364 + "ORDER BY DATE(at) DESC;"
3366 + case sql_query_internal(DBRef, Query) of
3368 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result]};
3369 + {error, Result} ->
3373 +delete_all_messages_by_user_at_int(DBRef, User, VHost, Date) ->
3374 + DQuery = ["DELETE FROM ",messages_table(VHost, Date)," ",
3375 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
3376 + case sql_query_internal(DBRef, DQuery) of
3378 + ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]),
3384 +delete_all_stats_by_user_int(DBRef, User, VHost) ->
3385 + SQuery = ["DELETE FROM ",stats_table(VHost)," ",
3386 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
3387 + case sql_query_internal(DBRef, SQuery) of
3389 + ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]),
3391 + {error, _} -> error
3394 +delete_stats_by_user_at_int(DBRef, User, VHost, Date) ->
3395 + SQuery = ["DELETE FROM ",stats_table(VHost)," ",
3396 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\") ",
3397 + "AND at=\"",Date,"\";"],
3398 + case sql_query_internal(DBRef, SQuery) of
3400 + ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]),
3402 + {error, _} -> error
3405 +delete_user_settings_int(DBRef, User, VHost) ->
3406 + Query = ["DELETE FROM ",settings_table(VHost)," ",
3407 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
3408 + case sql_query_internal(DBRef, Query) of
3410 + ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]),
3412 + {error, Reason} ->
3413 + ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]),
3417 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3421 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3422 +create_temp_table(DBRef, Name) ->
3423 + Query = ["CREATE TABLE ",Name," (",
3424 + "owner_id MEDIUMINT UNSIGNED, ",
3425 + "peer_name_id MEDIUMINT UNSIGNED, ",
3426 + "peer_server_id MEDIUMINT UNSIGNED, ",
3427 + "at VARCHAR(11), ",
3429 + ") ENGINE=MyISAM CHARACTER SET utf8;"
3431 + case sql_query_internal(DBRef, Query) of
3432 + {updated, _} -> ok;
3433 + {error, _Reason} -> error
3436 +create_stats_table(#state{dbref=DBRef, vhost=VHost}=State) ->
3437 + SName = stats_table(VHost),
3438 + Query = ["CREATE TABLE ",SName," (",
3439 + "owner_id MEDIUMINT UNSIGNED, ",
3440 + "peer_name_id MEDIUMINT UNSIGNED, ",
3441 + "peer_server_id MEDIUMINT UNSIGNED, ",
3442 + "at varchar(20), ",
3443 + "count int(11), ",
3444 + "INDEX(owner_id, peer_name_id, peer_server_id), ",
3446 + ") ENGINE=InnoDB CHARACTER SET utf8;"
3448 + case sql_query_internal_silent(DBRef, Query) of
3450 + ?INFO_MSG("Created stats table for ~p", [VHost]),
3451 + rebuild_all_stats_int(State),
3453 + {error, Reason} ->
3454 + case regexp:match(Reason, "#42S01") of
3456 + ?MYDEBUG("Stats table for ~p already exists", [VHost]),
3457 + CheckQuery = ["SHOW COLUMNS FROM ",SName," LIKE 'peer_%_id';"],
3458 + case sql_query_internal(DBRef, CheckQuery) of
3459 + {data, Elems} when length(Elems) == 2 ->
3460 + ?MYDEBUG("Stats table structure is ok", []),
3463 + ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []),
3464 + case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of
3466 + ?INFO_MSG("Successfully dropped ~p", [SName]);
3468 + ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName])
3473 + ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
3478 +create_settings_table(#state{dbref=DBRef, vhost=VHost}) ->
3479 + SName = settings_table(VHost),
3480 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
3481 + "owner_id MEDIUMINT UNSIGNED PRIMARY KEY, ",
3482 + "dolog_default TINYINT(1) NOT NULL DEFAULT 1, ",
3483 + "dolog_list TEXT, ",
3484 + "donotlog_list TEXT ",
3485 + ") ENGINE=InnoDB CHARACTER SET utf8;"
3487 + case sql_query_internal(DBRef, Query) of
3489 + ?MYDEBUG("Created settings table for ~p", [VHost]),
3495 +create_users_table(#state{dbref=DBRef, vhost=VHost}) ->
3496 + SName = users_table(VHost),
3497 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
3498 + "username TEXT NOT NULL, ",
3499 + "user_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
3500 + "UNIQUE INDEX(username(",?INDEX_SIZE,")) ",
3501 + ") ENGINE=InnoDB CHARACTER SET utf8;"
3503 + case sql_query_internal(DBRef, Query) of
3505 + ?MYDEBUG("Created users table for ~p", [VHost]),
3506 + ets:new(ets_users_table(VHost), [named_table, set, public]),
3507 + %update_users_from_db(DBRef, VHost),
3513 +create_servers_table(#state{dbref=DBRef, vhost=VHost}) ->
3514 + SName = servers_table(VHost),
3515 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
3516 + "server TEXT NOT NULL, ",
3517 + "server_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
3518 + "UNIQUE INDEX(server(",?INDEX_SIZE,")) ",
3519 + ") ENGINE=InnoDB CHARACTER SET utf8;"
3521 + case sql_query_internal(DBRef, Query) of
3523 + ?MYDEBUG("Created servers table for ~p", [VHost]),
3524 + ets:new(ets_servers_table(VHost), [named_table, set, public]),
3525 + update_servers_from_db(DBRef, VHost),
3531 +create_resources_table(#state{dbref=DBRef, vhost=VHost}) ->
3532 + RName = resources_table(VHost),
3533 + Query = ["CREATE TABLE IF NOT EXISTS ",RName," (",
3534 + "resource TEXT NOT NULL, ",
3535 + "resource_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
3536 + "UNIQUE INDEX(resource(",?INDEX_SIZE,")) ",
3537 + ") ENGINE=InnoDB CHARACTER SET utf8;"
3539 + case sql_query_internal(DBRef, Query) of
3541 + ?MYDEBUG("Created resources table for ~p", [VHost]),
3542 + ets:new(ets_resources_table(VHost), [named_table, set, public]),
3548 +create_msg_table(DBRef, VHost, Date) ->
3549 + TName = messages_table(VHost, Date),
3550 + Query = ["CREATE TABLE ",TName," (",
3551 + "owner_id MEDIUMINT UNSIGNED, ",
3552 + "peer_name_id MEDIUMINT UNSIGNED, ",
3553 + "peer_server_id MEDIUMINT UNSIGNED, ",
3554 + "peer_resource_id MEDIUMINT(8) UNSIGNED, ",
3555 + "direction ENUM('to', 'from'), ",
3556 + "type ENUM('chat','error','groupchat','headline','normal') NOT NULL, ",
3559 + "timestamp DOUBLE, ",
3560 + "INDEX search_i (owner_id, peer_name_id, peer_server_id, peer_resource_id), ",
3561 + "FULLTEXT (body) "
3562 + ") ENGINE=MyISAM CHARACTER SET utf8;"
3564 + case sql_query_internal(DBRef, Query) of
3565 + {updated, _MySQLRes} ->
3566 + ?MYDEBUG("Created msg table for ~p at ~p", [VHost, Date]),
3572 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3574 +% internal ets cache (users, servers, resources)
3576 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3577 +update_servers_from_db(DBRef, VHost) ->
3578 + ?INFO_MSG("Reading servers from db for ~p", [VHost]),
3579 + SQuery = ["SELECT server, server_id FROM ",servers_table(VHost),";"],
3580 + {data, Result} = sql_query_internal(DBRef, SQuery),
3581 + true = ets:delete_all_objects(ets_servers_table(VHost)),
3582 + true = ets:insert(ets_servers_table(VHost), [ {Server, Server_id} || [Server, Server_id] <- Result]).
3584 +%update_users_from_db(DBRef, VHost) ->
3585 +% ?INFO_MSG("Reading users from db for ~p", [VHost]),
3586 +% SQuery = ["SELECT username, user_id FROM ",users_table(VHost),";"],
3587 +% {data, Result} = sql_query_internal(DBRef, SQuery),
3588 +% true = ets:delete_all_objects(ets_users_table(VHost)),
3589 +% true = ets:insert(ets_users_table(VHost), [ {Username, User_id} || [Username, User_id] <- Result]).
3591 +%get_user_name(DBRef, VHost, User_id) ->
3592 +% case ets:match(ets_users_table(VHost), {'$1', User_id}) of
3593 +% [[User]] -> User;
3594 +% % this can be in clustered environment
3596 +% %update_users_from_db(DBRef, VHost),
3597 +% SQuery = ["SELECT username FROM ",users_table(VHost)," ",
3598 +% "WHERE user_id=\"",User_id,"\";"],
3599 +% {data, [[Name]]} = sql_query_internal(DBRef, SQuery),
3600 +% % cache {user, id} pair
3601 +% ets:insert(ets_users_table(VHost), {Name, User_id}),
3605 +%get_server_name(DBRef, VHost, Server_id) ->
3606 +% case ets:match(ets_servers_table(VHost), {'$1', Server_id}) of
3607 +% [[Server]] -> Server;
3608 + % this can be in clustered environment
3610 +% update_servers_from_db(DBRef, VHost),
3611 +% [[Server1]] = ets:match(ets_servers_table(VHost), {'$1', Server_id}),
3615 +get_user_id_from_db(DBRef, VHost, User) ->
3616 + SQuery = ["SELECT user_id FROM ",users_table(VHost)," ",
3617 + "WHERE username=\"",User,"\";"],
3618 + case sql_query_internal(DBRef, SQuery) of
3619 + % no such user in db
3622 + {data, [[DBId]]} ->
3623 + % cache {user, id} pair
3624 + ets:insert(ets_users_table(VHost), {User, DBId}),
3627 +get_user_id(DBRef, VHost, User) ->
3629 + case ets:match(ets_users_table(VHost), {User, '$1'}) of
3632 + case get_user_id_from_db(DBRef, VHost, User) of
3633 + % no such user in db
3635 + IQuery = ["INSERT INTO ",users_table(VHost)," ",
3636 + "SET username=\"",User,"\";"],
3637 + case sql_query_internal_silent(DBRef, IQuery) of
3639 + {ok, NewId} = get_user_id_from_db(DBRef, VHost, User),
3641 + {error, Reason} ->
3642 + % this can be in clustered environment
3643 + {match, _, _} = regexp:match(Reason, "#23000"),
3644 + ?ERROR_MSG("Duplicate key name for ~p", [User]),
3645 + {ok, ClID} = get_user_id_from_db(DBRef, VHost, User),
3651 + [[EtsId]] -> EtsId
3654 +get_server_id(DBRef, VHost, Server) ->
3655 + case ets:match(ets_servers_table(VHost), {Server, '$1'}) of
3657 + IQuery = ["INSERT INTO ",servers_table(VHost)," ",
3658 + "SET server=\"",Server,"\";"],
3659 + case sql_query_internal_silent(DBRef, IQuery) of
3661 + SQuery = ["SELECT server_id FROM ",servers_table(VHost)," ",
3662 + "WHERE server=\"",Server,"\";"],
3663 + {data, [[Id]]} = sql_query_internal(DBRef, SQuery),
3664 + ets:insert(ets_servers_table(VHost), {Server, Id}),
3666 + {error, Reason} ->
3667 + % this can be in clustered environment
3668 + {match, _, _} = regexp:match(Reason, "#23000"),
3669 + ?ERROR_MSG("Duplicate key name for ~p", [Server]),
3670 + update_servers_from_db(DBRef, VHost),
3671 + [[Id1]] = ets:match(ets_servers_table(VHost), {Server, '$1'}),
3677 +get_resource_id_from_db(DBRef, VHost, Resource) ->
3678 + SQuery = ["SELECT resource_id FROM ",resources_table(VHost)," ",
3679 + "WHERE resource=\"",ejabberd_odbc:escape(Resource),"\";"],
3680 + case sql_query_internal(DBRef, SQuery) of
3681 + % no such resource in db
3684 + {data, [[DBId]]} ->
3685 + % cache {resource, id} pair
3686 + ets:insert(ets_resources_table(VHost), {Resource, DBId}),
3689 +get_resource_id(DBRef, VHost, Resource) ->
3691 + case ets:match(ets_resources_table(VHost), {Resource, '$1'}) of
3694 + case get_resource_id_from_db(DBRef, VHost, Resource) of
3695 + % no such resource in db
3697 + IQuery = ["INSERT INTO ",resources_table(VHost)," ",
3698 + "SET resource=\"",ejabberd_odbc:escape(Resource),"\";"],
3699 + case sql_query_internal_silent(DBRef, IQuery) of
3701 + {ok, NewId} = get_resource_id_from_db(DBRef, VHost, Resource),
3703 + {error, Reason} ->
3704 + % this can be in clustered environment
3705 + {match, _, _} = regexp:match(Reason, "#23000"),
3706 + ?ERROR_MSG("Duplicate key name for ~p", [Resource]),
3707 + {ok, ClID} = get_resource_id_from_db(DBRef, VHost, Resource),
3713 + [[EtsId]] -> EtsId
3716 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3720 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3721 +sql_query_internal(DBRef, Query) ->
3722 + case sql_query_internal_silent(DBRef, Query) of
3723 + {error, Reason} ->
3724 + ?ERROR_MSG("~p while ~p", [Reason, lists:append(Query)]),
3729 +sql_query_internal_silent(DBRef, Query) ->
3730 + ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
3731 + get_result(mysql_conn:fetch(DBRef, Query, self(), ?MYSQL_TIMEOUT)).
3733 +get_result({updated, MySQLRes}) ->
3734 + {updated, mysql:get_result_affected_rows(MySQLRes)};
3735 +get_result({data, MySQLRes}) ->
3736 + {data, mysql:get_result_rows(MySQLRes)};
3737 +get_result({error, "query timed out"}) ->
3738 + {error, "query timed out"};
3739 +get_result({error, MySQLRes}) ->
3740 + Reason = mysql:get_result_reason(MySQLRes),
3742 --- mod_logdb_mysql5.erl.orig 2009-11-22 13:06:23.000000000 +0200
3743 +++ mod_logdb_mysql5.erl 2009-07-30 09:00:14.000000000 +0300
3745 +%%%----------------------------------------------------------------------
3746 +%%% File : mod_logdb_mysql5.erl
3747 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
3748 +%%% Purpose : MySQL 5 backend for mod_logdb
3749 +%%% Version : trunk
3751 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
3752 +%%%----------------------------------------------------------------------
3754 +-module(mod_logdb_mysql5).
3755 +-author('o.palij@gmail.com').
3757 +-include("mod_logdb.hrl").
3758 +-include("ejabberd.hrl").
3759 +-include("jlib.hrl").
3761 +-behaviour(gen_logdb).
3762 +-behaviour(gen_server).
3765 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
3767 +-export([start/2, stop/1]).
3769 +-export([log_message/2,
3771 + rebuild_stats_at/2,
3772 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
3773 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
3775 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
3778 +% gen_server call timeout
3779 +-define(CALL_TIMEOUT, 30000).
3780 +-define(MYSQL_TIMEOUT, 60000).
3781 +-define(INDEX_SIZE, integer_to_list(170)).
3782 +-define(PROCNAME, mod_logdb_mysql5).
3784 +-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
3785 + list_to_string/1, string_to_list/1,
3786 + convert_timestamp_brief/1]).
3788 +-record(state, {dbref, vhost, server, port, db, user, password}).
3790 +% replace "." with "_"
3791 +escape_vhost(VHost) -> lists:map(fun(46) -> 95;
3798 + "_" ++ escape_vhost(VHost) ++ "`".
3800 +messages_table(VHost, Date) ->
3801 + prefix() ++ "messages_" ++ Date ++ suffix(VHost).
3803 +% TODO: this needs to be redone to unify view name in stored procedure and in delete_messages_at/2
3804 +view_table(VHost, Date) ->
3805 + Table = messages_table(VHost, Date),
3806 + TablewoQ = lists:sublist(Table, 2, length(Table) - 2),
3807 + lists:append(["`v_", TablewoQ, "`"]).
3809 +stats_table(VHost) ->
3810 + prefix() ++ "stats" ++ suffix(VHost).
3812 +temp_table(VHost) ->
3813 + prefix() ++ "temp" ++ suffix(VHost).
3815 +settings_table(VHost) ->
3816 + prefix() ++ "settings" ++ suffix(VHost).
3818 +users_table(VHost) ->
3819 + prefix() ++ "users" ++ suffix(VHost).
3820 +servers_table(VHost) ->
3821 + prefix() ++ "servers" ++ suffix(VHost).
3822 +resources_table(VHost) ->
3823 + prefix() ++ "resources" ++ suffix(VHost).
3825 +logmessage_name(VHost) ->
3826 + prefix() ++ "logmessage" ++ suffix(VHost).
3828 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3830 +% gen_mod callbacks
3832 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3833 +start(VHost, Opts) ->
3834 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3835 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
3838 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3839 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
3841 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3843 +% gen_server callbacks
3845 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3846 +init([VHost, Opts]) ->
3849 + Server = gen_mod:get_opt(server, Opts, "localhost"),
3850 + Port = gen_mod:get_opt(port, Opts, 3306),
3851 + DB = gen_mod:get_opt(db, Opts, "logdb"),
3852 + User = gen_mod:get_opt(user, Opts, "root"),
3853 + Password = gen_mod:get_opt(password, Opts, ""),
3855 + St = #state{vhost=VHost,
3856 + server=Server, port=Port, db=DB,
3857 + user=User, password=Password},
3859 + case open_mysql_connection(St) of
3861 + State = St#state{dbref=DBRef},
3862 + ok = create_internals(State),
3863 + ok = create_stats_table(State),
3864 + ok = create_settings_table(State),
3865 + ok = create_users_table(State),
3866 + ok = create_servers_table(State),
3867 + ok = create_resources_table(State),
3868 + erlang:monitor(process, DBRef),
3870 + {error, Reason} ->
3871 + ?ERROR_MSG("MySQL connection failed: ~p~n", [Reason]),
3872 + {stop, db_connection_failed}
3875 +open_mysql_connection(#state{server=Server, port=Port, db=DB,
3876 + user=DBUser, password=Password} = _State) ->
3877 + LogFun = fun(debug, _Format, _Argument) ->
3878 + %?MYDEBUG(Format, Argument);
3880 + (error, Format, Argument) ->
3881 + ?ERROR_MSG(Format, Argument);
3882 + (Level, Format, Argument) ->
3883 + ?MYDEBUG("MySQL (~p)~n", [Level]),
3884 + ?MYDEBUG(Format, Argument)
3886 + ?INFO_MSG("Opening mysql connection ~s@~s:~p/~s", [DBUser, Server, Port, DB]),
3887 + mysql_conn:start(Server, Port, DBUser, Password, DB, [65536, 131072], LogFun).
3889 +close_mysql_connection(DBRef) ->
3890 + ?MYDEBUG("Closing ~p mysql connection", [DBRef]),
3891 + mysql_conn:stop(DBRef).
3893 +handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3894 + Reply = rebuild_stats_at_int(DBRef, VHost, Date),
3895 + {reply, Reply, State};
3896 +handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
3897 + {reply, error, State};
3898 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3899 + Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
3900 + ["\"",Timestamp,"\"",","]
3903 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
3905 + Query = ["DELETE FROM ",messages_table(VHost, Date)," ",
3906 + "WHERE timestamp IN (", Temp1],
3909 + case sql_query_internal(DBRef, Query) of
3911 + ?MYDEBUG("Aff=~p", [Aff]),
3912 + rebuild_stats_at_int(DBRef, VHost, Date);
3916 + {reply, Reply, State};
3917 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3918 + ok = delete_all_messages_by_user_at_int(DBRef, User, VHost, Date),
3919 + ok = delete_stats_by_user_at_int(DBRef, User, VHost, Date),
3920 + {reply, ok, State};
3921 +handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3923 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Date),";"]),
3924 + TQuery = ["DELETE FROM ",stats_table(VHost)," "
3925 + "WHERE at=\"",Date,"\";"],
3926 + {updated, _} = sql_query_internal(DBRef, TQuery),
3927 + VQuery = ["DROP VIEW IF EXISTS ",view_table(VHost,Date),";"],
3928 + {updated, _} = sql_query_internal(DBRef, VQuery),
3932 + case catch apply(Fun, []) of
3938 + {reply, Reply, State};
3939 +handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3940 + SName = stats_table(VHost),
3941 + Query = ["SELECT at, sum(count) ",
3942 + "FROM ",SName," ",
3944 + "ORDER BY DATE(at) DESC;"
3947 + case sql_query_internal(DBRef, Query) of
3949 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
3950 + {error, Reason} ->
3951 + % TODO: Duplicate error message ?
3954 + {reply, Reply, State};
3955 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3956 + SName = stats_table(VHost),
3957 + Query = ["SELECT username, sum(count) as allcount ",
3958 + "FROM ",SName," ",
3959 + "JOIN ",users_table(VHost)," ON owner_id=user_id "
3960 + "WHERE at=\"",Date,"\" ",
3961 + "GROUP BY username ",
3962 + "ORDER BY allcount DESC;"
3965 + case sql_query_internal(DBRef, Query) of
3967 + {ok, [ {User, list_to_integer(Count)} || [User, Count] <- Result ]};
3968 + {error, Reason} ->
3971 + {reply, Reply, State};
3972 +handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3973 + {reply, get_user_stats_int(DBRef, User, VHost), State};
3974 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3975 + Query = ["SELECT peer_name,",
3983 + "FROM ",view_table(VHost, Date)," "
3984 + "WHERE owner_name=\"",User,"\";"],
3986 + case sql_query_internal(DBRef, Query) of
3988 + Fun = fun([Peer_name, Peer_server, Peer_resource,
3993 + #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
3994 + direction=list_to_atom(Direction),
3996 + subject=Subject, body=Body,
3997 + timestamp=Timestamp}
3999 + {ok, lists:map(Fun, Result)};
4000 + {error, Reason} ->
4003 + {reply, Reply, State};
4004 +handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4005 + SName = stats_table(VHost),
4006 + Query = ["SELECT at ",
4007 + "FROM ",SName," ",
4009 + "ORDER BY DATE(at) DESC;"
4012 + case sql_query_internal(DBRef, Query) of
4014 + [ Date || [Date] <- Result ];
4015 + {error, Reason} ->
4018 + {reply, Reply, State};
4019 +handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4020 + Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
4021 + "FROM ",settings_table(VHost)," ",
4022 + "JOIN ",users_table(VHost)," ON user_id=owner_id;"],
4024 + case sql_query_internal(DBRef, Query) of
4026 + {ok, lists:map(fun([Owner, DoLogDef, DoLogL, DoNotLogL]) ->
4027 + #user_settings{owner_name=Owner,
4028 + dolog_default=list_to_bool(DoLogDef),
4029 + dolog_list=string_to_list(DoLogL),
4030 + donotlog_list=string_to_list(DoNotLogL)
4036 + {reply, Reply, State};
4037 +handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4038 + Query = ["SELECT dolog_default,dolog_list,donotlog_list FROM ",settings_table(VHost)," ",
4039 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4041 + case sql_query_internal(DBRef, Query) of
4044 + {data, [[Owner, DoLogDef, DoLogL, DoNotLogL]]} ->
4045 + {ok, #user_settings{owner_name=Owner,
4046 + dolog_default=list_to_bool(DoLogDef),
4047 + dolog_list=string_to_list(DoLogL),
4048 + donotlog_list=string_to_list(DoNotLogL)}};
4052 + {reply, Reply, State};
4053 +handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
4054 + dolog_list=DoLogL,
4055 + donotlog_list=DoNotLogL}},
4056 + _From, #state{dbref=DBRef, vhost=VHost} = State) ->
4057 + User_id = get_user_id(DBRef, VHost, User),
4058 + Query = ["UPDATE ",settings_table(VHost)," ",
4059 + "SET dolog_default=",bool_to_list(DoLogDef),", ",
4060 + "dolog_list='",list_to_string(DoLogL),"', ",
4061 + "donotlog_list='",list_to_string(DoNotLogL),"' ",
4062 + "WHERE owner_id=",User_id,";"],
4065 + case sql_query_internal(DBRef, Query) of
4067 + IQuery = ["INSERT INTO ",settings_table(VHost)," ",
4068 + "(owner_id, dolog_default, dolog_list, donotlog_list) ",
4070 + "(",User_id,",",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
4071 + case sql_query_internal_silent(DBRef, IQuery) of
4073 + ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
4075 + {error, Reason} ->
4076 + case regexp:match(Reason, "#23000") of
4081 + ?ERROR_MSG("Failed setup user ~p@~p: ~p", [User, VHost, Reason]),
4086 + ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
4091 + {reply, Reply, State};
4092 +handle_call({stop}, _From, #state{vhost=VHost}=State) ->
4093 + ?MYDEBUG("Stoping mysql5 backend for ~p", [VHost]),
4094 + {stop, normal, ok, State};
4095 +handle_call(Msg, _From, State) ->
4096 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
4099 +handle_cast({log_message, Msg}, #state{dbref=DBRef, vhost=VHost}=State) ->
4101 + Date = convert_timestamp_brief(Msg#msg.timestamp),
4102 + TableName = messages_table(VHost, Date),
4104 + Query = [ "CALL ",logmessage_name(VHost)," "
4105 + "('", TableName, "',",
4107 + "'", Msg#msg.owner_name, "',",
4108 + "'", Msg#msg.peer_name, "',",
4109 + "'", Msg#msg.peer_server, "',",
4110 + "'", ejabberd_odbc:escape(Msg#msg.peer_resource), "',",
4111 + "'", atom_to_list(Msg#msg.direction), "',",
4112 + "'", Msg#msg.type, "',",
4113 + "'", ejabberd_odbc:escape(Msg#msg.subject), "',",
4114 + "'", ejabberd_odbc:escape(Msg#msg.body), "',",
4115 + "'", Msg#msg.timestamp, "');"],
4117 + case sql_query_internal(DBRef, Query) of
4119 + ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
4120 + Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
4122 + {error, _Reason} ->
4128 +handle_cast({rebuild_stats}, State) ->
4129 + rebuild_all_stats_int(State),
4131 +handle_cast({drop_user, User}, #state{vhost=VHost} = State) ->
4133 + {ok, DBRef} = open_mysql_connection(State),
4134 + {ok, Dates} = get_user_stats_int(DBRef, User, VHost),
4135 + MDResult = lists:map(fun({Date, _}) ->
4136 + delete_all_messages_by_user_at_int(DBRef, User, VHost, Date)
4138 + StDResult = delete_all_stats_by_user_int(DBRef, User, VHost),
4139 + SDResult = delete_user_settings_int(DBRef, User, VHost),
4140 + case lists:all(fun(Result) when Result == ok ->
4142 + (Result) when Result == error ->
4144 + end, lists:append([MDResult, [StDResult], [SDResult]])) of
4146 + ?INFO_MSG("Removed ~s@~s", [User, VHost]);
4148 + ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost])
4150 + close_mysql_connection(DBRef)
4154 +handle_cast(Msg, State) ->
4155 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
4158 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
4159 + {stop, connection_dropped, State};
4160 +handle_info(Info, State) ->
4161 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
4164 +terminate(_Reason, #state{dbref=DBRef}=_State) ->
4165 + close_mysql_connection(DBRef),
4168 +code_change(_OldVsn, State, _Extra) ->
4171 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4173 +% gen_logdb callbacks
4175 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4176 +log_message(VHost, Msg) ->
4177 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4178 + gen_server:cast(Proc, {log_message, Msg}).
4179 +rebuild_stats(VHost) ->
4180 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4181 + gen_server:cast(Proc, {rebuild_stats}).
4182 +rebuild_stats_at(VHost, Date) ->
4183 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4184 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
4185 +delete_messages_by_user_at(VHost, Msgs, Date) ->
4186 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4187 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
4188 +delete_all_messages_by_user_at(User, VHost, Date) ->
4189 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4190 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
4191 +delete_messages_at(VHost, Date) ->
4192 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4193 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
4194 +get_vhost_stats(VHost) ->
4195 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4196 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
4197 +get_vhost_stats_at(VHost, Date) ->
4198 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4199 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
4200 +get_user_stats(User, VHost) ->
4201 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4202 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
4203 +get_user_messages_at(User, VHost, Date) ->
4204 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4205 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
4206 +get_dates(VHost) ->
4207 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4208 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
4209 +get_users_settings(VHost) ->
4210 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4211 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
4212 +get_user_settings(User, VHost) ->
4213 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4214 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
4215 +set_user_settings(User, VHost, Set) ->
4216 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4217 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
4218 +drop_user(User, VHost) ->
4219 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4220 + gen_server:cast(Proc, {drop_user, User}).
4222 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4226 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4227 +get_dates_int(DBRef, VHost) ->
4228 + case sql_query_internal(DBRef, ["SHOW TABLES"]) of
4230 + lists:foldl(fun([Table], Dates) ->
4231 + Reg = lists:sublist(prefix(),2,length(prefix())) ++ ".*" ++ escape_vhost(VHost),
4232 + case regexp:match(Table, Reg) of
4234 + case regexp:match(Table,"[0-9]+-[0-9]+-[0-9]+") of
4236 + lists:append(Dates, [lists:sublist(Table,S,E)]);
4248 +rebuild_all_stats_int(#state{vhost=VHost}=State) ->
4250 + {ok, DBRef} = open_mysql_connection(State),
4251 + ok = delete_nonexistent_stats(DBRef, VHost),
4252 + case lists:filter(fun(Date) ->
4253 + case catch rebuild_stats_at_int(DBRef, VHost, Date) of
4256 + {'EXIT', _} -> true
4258 + end, get_dates_int(DBRef, VHost)) of
4261 + ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]),
4264 + close_mysql_connection(DBRef)
4268 +rebuild_stats_at_int(DBRef, VHost, Date) ->
4269 + TempTable = temp_table(VHost),
4271 + Table = messages_table(VHost, Date),
4272 + STable = stats_table(VHost),
4274 + DQuery = [ "DELETE FROM ",STable," ",
4275 + "WHERE at='",Date,"';"],
4277 + ok = create_temp_table(DBRef, TempTable),
4278 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," WRITE, ",TempTable," WRITE;"]),
4279 + SQuery = ["INSERT INTO ",TempTable," ",
4280 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
4281 + "SELECT owner_id,peer_name_id,peer_server_id,\"",Date,"\",count(*) ",
4282 + "FROM ",Table," WHERE ext is NULL GROUP BY owner_id,peer_name_id,peer_server_id;"],
4283 + case sql_query_internal(DBRef, SQuery) of
4285 + Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]),
4287 + {data, [["0"]]} ->
4288 + {updated, _} = sql_query_internal(DBRef, ["DROP VIEW IF EXISTS ",view_table(VHost,Date),";"]),
4289 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]),
4290 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE;"]),
4291 + {updated, _} = sql_query_internal(DBRef, DQuery),
4294 + ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]),
4298 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE, ",TempTable," WRITE;"]),
4299 + {updated, _} = sql_query_internal(DBRef, DQuery),
4300 + SQuery1 = ["INSERT INTO ",STable," ",
4301 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
4302 + "SELECT owner_id,peer_name_id,peer_server_id,at,count ",
4303 + "FROM ",TempTable,";"],
4304 + case sql_query_internal(DBRef, SQuery1) of
4305 + {updated, _} -> ok;
4306 + {error, _} -> error
4308 + {error, _} -> error
4312 + case catch apply(Fun, []) of
4314 + ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
4318 + {'EXIT', Reason} ->
4319 + ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]),
4322 + sql_query_internal(DBRef, ["UNLOCK TABLES;"]),
4323 + sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]),
4326 +delete_nonexistent_stats(DBRef, VHost) ->
4327 + Dates = get_dates_int(DBRef, VHost),
4328 + STable = stats_table(VHost),
4330 + Temp = lists:flatmap(fun(Date) ->
4331 + ["\"",Date,"\"",","]
4337 + % replace last "," with ");"
4338 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
4339 + Query = ["DELETE FROM ",STable," ",
4340 + "WHERE at NOT IN (", Temp1],
4341 + case sql_query_internal(DBRef, Query) of
4349 +get_user_stats_int(DBRef, User, VHost) ->
4350 + SName = stats_table(VHost),
4351 + UName = users_table(VHost),
4352 + Query = ["SELECT stats.at, sum(stats.count) ",
4353 + "FROM ",UName," AS users ",
4354 + "JOIN ",SName," AS stats ON owner_id=user_id "
4355 + "WHERE users.username=\"",User,"\" ",
4356 + "GROUP BY stats.at "
4357 + "ORDER BY DATE(stats.at) DESC;"
4359 + case sql_query_internal(DBRef, Query) of
4361 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
4362 + {error, Result} ->
4366 +delete_all_messages_by_user_at_int(DBRef, User, VHost, Date) ->
4367 + DQuery = ["DELETE FROM ",messages_table(VHost, Date)," ",
4368 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4369 + case sql_query_internal(DBRef, DQuery) of
4371 + ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]),
4377 +delete_all_stats_by_user_int(DBRef, User, VHost) ->
4378 + SQuery = ["DELETE FROM ",stats_table(VHost)," ",
4379 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4380 + case sql_query_internal(DBRef, SQuery) of
4382 + ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]),
4384 + {error, _} -> error
4387 +delete_stats_by_user_at_int(DBRef, User, VHost, Date) ->
4388 + SQuery = ["DELETE FROM ",stats_table(VHost)," ",
4389 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\") ",
4390 + "AND at=\"",Date,"\";"],
4391 + case sql_query_internal(DBRef, SQuery) of
4393 + ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]),
4395 + {error, _} -> error
4398 +delete_user_settings_int(DBRef, User, VHost) ->
4399 + Query = ["DELETE FROM ",settings_table(VHost)," ",
4400 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4401 + case sql_query_internal(DBRef, Query) of
4403 + ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]),
4405 + {error, Reason} ->
4406 + ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]),
4410 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4414 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4415 +create_temp_table(DBRef, Name) ->
4416 + Query = ["CREATE TABLE ",Name," (",
4417 + "owner_id MEDIUMINT UNSIGNED, ",
4418 + "peer_name_id MEDIUMINT UNSIGNED, ",
4419 + "peer_server_id MEDIUMINT UNSIGNED, ",
4420 + "at VARCHAR(11), ",
4422 + ") ENGINE=MyISAM CHARACTER SET utf8;"
4424 + case sql_query_internal(DBRef, Query) of
4425 + {updated, _} -> ok;
4426 + {error, _Reason} -> error
4429 +create_stats_table(#state{dbref=DBRef, vhost=VHost}=State) ->
4430 + SName = stats_table(VHost),
4431 + Query = ["CREATE TABLE ",SName," (",
4432 + "owner_id MEDIUMINT UNSIGNED, ",
4433 + "peer_name_id MEDIUMINT UNSIGNED, ",
4434 + "peer_server_id MEDIUMINT UNSIGNED, ",
4435 + "at VARCHAR(11), ",
4436 + "count INT(11), ",
4437 + "ext INTEGER DEFAULT NULL, "
4438 + "INDEX ext_i (ext), "
4439 + "INDEX(owner_id,peer_name_id,peer_server_id), ",
4441 + ") ENGINE=MyISAM CHARACTER SET utf8;"
4443 + case sql_query_internal_silent(DBRef, Query) of
4445 + ?MYDEBUG("Created stats table for ~p", [VHost]),
4446 + rebuild_all_stats_int(State),
4448 + {error, Reason} ->
4449 + case regexp:match(Reason, "#42S01") of
4451 + ?MYDEBUG("Stats table for ~p already exists", [VHost]),
4452 + CheckQuery = ["SHOW COLUMNS FROM ",SName," LIKE 'peer_%_id';"],
4453 + case sql_query_internal(DBRef, CheckQuery) of
4454 + {data, Elems} when length(Elems) == 2 ->
4455 + ?MYDEBUG("Stats table structure is ok", []),
4458 + ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []),
4459 + case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of
4461 + ?INFO_MSG("Successfully dropped ~p", [SName]);
4463 + ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName])
4468 + ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
4473 +create_settings_table(#state{dbref=DBRef, vhost=VHost}) ->
4474 + SName = settings_table(VHost),
4475 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
4476 + "owner_id MEDIUMINT UNSIGNED PRIMARY KEY, ",
4477 + "dolog_default TINYINT(1) NOT NULL DEFAULT 1, ",
4478 + "dolog_list TEXT, ",
4479 + "donotlog_list TEXT ",
4480 + ") ENGINE=InnoDB CHARACTER SET utf8;"
4482 + case sql_query_internal(DBRef, Query) of
4484 + ?MYDEBUG("Created settings table for ~p", [VHost]),
4490 +create_users_table(#state{dbref=DBRef, vhost=VHost}) ->
4491 + SName = users_table(VHost),
4492 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
4493 + "username TEXT NOT NULL, ",
4494 + "user_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
4495 + "UNIQUE INDEX(username(",?INDEX_SIZE,")) ",
4496 + ") ENGINE=InnoDB CHARACTER SET utf8;"
4498 + case sql_query_internal(DBRef, Query) of
4500 + ?MYDEBUG("Created users table for ~p", [VHost]),
4506 +create_servers_table(#state{dbref=DBRef, vhost=VHost}) ->
4507 + SName = servers_table(VHost),
4508 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
4509 + "server TEXT NOT NULL, ",
4510 + "server_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
4511 + "UNIQUE INDEX(server(",?INDEX_SIZE,")) ",
4512 + ") ENGINE=InnoDB CHARACTER SET utf8;"
4514 + case sql_query_internal(DBRef, Query) of
4516 + ?MYDEBUG("Created servers table for ~p", [VHost]),
4522 +create_resources_table(#state{dbref=DBRef, vhost=VHost}) ->
4523 + RName = resources_table(VHost),
4524 + Query = ["CREATE TABLE IF NOT EXISTS ",RName," (",
4525 + "resource TEXT NOT NULL, ",
4526 + "resource_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
4527 + "UNIQUE INDEX(resource(",?INDEX_SIZE,")) ",
4528 + ") ENGINE=InnoDB CHARACTER SET utf8;"
4530 + case sql_query_internal(DBRef, Query) of
4532 + ?MYDEBUG("Created resources table for ~p", [VHost]),
4538 +create_internals(#state{dbref=DBRef, vhost=VHost}) ->
4539 + sql_query_internal(DBRef, ["DROP PROCEDURE IF EXISTS ",logmessage_name(VHost),";"]),
4540 + case sql_query_internal(DBRef, [get_logmessage(VHost)]) of
4542 + ?MYDEBUG("Created logmessage for ~p", [VHost]),
4548 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4552 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4553 +sql_query_internal(DBRef, Query) ->
4554 + case sql_query_internal_silent(DBRef, Query) of
4555 + {error, Reason} ->
4556 + ?ERROR_MSG("~p while ~p", [Reason, lists:append(Query)]),
4561 +sql_query_internal_silent(DBRef, Query) ->
4562 + ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
4563 + get_result(mysql_conn:fetch(DBRef, Query, self(), ?MYSQL_TIMEOUT)).
4565 +get_result({updated, MySQLRes}) ->
4566 + {updated, mysql:get_result_affected_rows(MySQLRes)};
4567 +get_result({data, MySQLRes}) ->
4568 + {data, mysql:get_result_rows(MySQLRes)};
4569 +get_result({error, "query timed out"}) ->
4570 + {error, "query timed out"};
4571 +get_result({error, MySQLRes}) ->
4572 + Reason = mysql:get_result_reason(MySQLRes),
4575 +get_user_id(DBRef, VHost, User) ->
4576 + SQuery = ["SELECT user_id FROM ",users_table(VHost)," ",
4577 + "WHERE username=\"",User,"\";"],
4578 + case sql_query_internal(DBRef, SQuery) of
4580 + IQuery = ["INSERT INTO ",users_table(VHost)," ",
4581 + "SET username=\"",User,"\";"],
4582 + case sql_query_internal_silent(DBRef, IQuery) of
4584 + {data, [[DBIdNew]]} = sql_query_internal(DBRef, SQuery),
4586 + {error, Reason} ->
4587 + % this can be in clustered environment
4588 + {match, _, _} = regexp:match(Reason, "#23000"),
4589 + ?ERROR_MSG("Duplicate key name for ~p", [User]),
4590 + {data, [[ClID]]} = sql_query_internal(DBRef, SQuery),
4593 + {data, [[DBId]]} ->
4597 +get_logmessage(VHost) ->
4598 + UName = users_table(VHost),
4599 + SName = servers_table(VHost),
4600 + RName = resources_table(VHost),
4601 + StName = stats_table(VHost),
4603 +CREATE PROCEDURE ~s(tablename TEXT, atdate TEXT, owner TEXT, peer_name TEXT, peer_server TEXT, peer_resource TEXT, mdirection VARCHAR(4), mtype VARCHAR(10), msubject TEXT, mbody TEXT, mtimestamp DOUBLE)
4605 + DECLARE ownerID MEDIUMINT UNSIGNED;
4606 + DECLARE peer_nameID MEDIUMINT UNSIGNED;
4607 + DECLARE peer_serverID MEDIUMINT UNSIGNED;
4608 + DECLARE peer_resourceID MEDIUMINT UNSIGNED;
4609 + DECLARE Vmtype VARCHAR(10);
4610 + DECLARE Vmtimestamp DOUBLE;
4611 + DECLARE Vmdirection VARCHAR(4);
4612 + DECLARE Vmbody TEXT;
4613 + DECLARE Vmsubject TEXT;
4616 + DECLARE viewname TEXT;
4617 + DECLARE notable INT;
4618 + DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' SET @notable = 1;
4621 + SET @ownerID = NULL;
4622 + SET @peer_nameID = NULL;
4623 + SET @peer_serverID = NULL;
4624 + SET @peer_resourceID = NULL;
4626 + SET @Vmtype = mtype;
4627 + SET @Vmtimestamp = mtimestamp;
4628 + SET @Vmdirection = mdirection;
4629 + SET @Vmbody = mbody;
4630 + SET @Vmsubject = msubject;
4632 + SELECT user_id INTO @ownerID FROM ~s WHERE username=owner;
4633 + IF @ownerID IS NULL THEN
4634 + INSERT INTO ~s SET username=owner;
4635 + SET @ownerID = LAST_INSERT_ID();
4638 + SELECT user_id INTO @peer_nameID FROM ~s WHERE username=peer_name;
4639 + IF @peer_nameID IS NULL THEN
4640 + INSERT INTO ~s SET username=peer_name;
4641 + SET @peer_nameID = LAST_INSERT_ID();
4644 + SELECT server_id INTO @peer_serverID FROM ~s WHERE server=peer_server;
4645 + IF @peer_serverID IS NULL THEN
4646 + INSERT INTO ~s SET server=peer_server;
4647 + SET @peer_serverID = LAST_INSERT_ID();
4650 + SELECT resource_id INTO @peer_resourceID FROM ~s WHERE resource=peer_resource;
4651 + IF @peer_resourceID IS NULL THEN
4652 + INSERT INTO ~s SET resource=peer_resource;
4653 + SET @peer_resourceID = LAST_INSERT_ID();
4656 + 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);\");
4657 + PREPARE insertmsg FROM @iq;
4659 + IF @notable = 1 THEN
4660 + SET @cq = CONCAT(\"CREATE TABLE \",tablename,\" (
4661 + owner_id MEDIUMINT UNSIGNED NOT NULL,
4662 + peer_name_id MEDIUMINT UNSIGNED NOT NULL,
4663 + peer_server_id MEDIUMINT UNSIGNED NOT NULL,
4664 + peer_resource_id MEDIUMINT(8) UNSIGNED NOT NULL,
4665 + direction ENUM('to', 'from') NOT NULL,
4666 + type ENUM('chat','error','groupchat','headline','normal') NOT NULL,
4669 + timestamp DOUBLE NOT NULL,
4670 + ext INTEGER DEFAULT NULL,
4671 + INDEX search_i (owner_id, peer_name_id, peer_server_id, peer_resource_id),
4672 + INDEX ext_i (ext),
4676 + CHARACTER SET utf8;\");
4677 + PREPARE createtable FROM @cq;
4678 + EXECUTE createtable;
4679 + DEALLOCATE PREPARE createtable;
4681 + SET @viewname = CONCAT(\"`v_\", TRIM(BOTH '`' FROM tablename), \"`\");
4682 + SET @cq = CONCAT(\"CREATE OR REPLACE VIEW \",@viewname,\" AS
4683 + SELECT owner.username AS owner_name,
4684 + peer.username AS peer_name,
4685 + servers.server AS peer_server,
4686 + resources.resource AS peer_resource,
4687 + messages.direction,
4691 + messages.timestamp
4697 + \", tablename,\" messages
4699 + owner.user_id=messages.owner_id and
4700 + peer.user_id=messages.peer_name_id and
4701 + servers.server_id=messages.peer_server_id and
4702 + resources.resource_id=messages.peer_resource_id
4703 + ORDER BY messages.timestamp;\");
4704 + PREPARE createview FROM @cq;
4705 + EXECUTE createview;
4706 + DEALLOCATE PREPARE createview;
4709 + PREPARE insertmsg FROM @iq;
4710 + EXECUTE insertmsg;
4711 + ELSEIF @notable = 0 THEN
4712 + EXECUTE insertmsg;
4715 + DEALLOCATE PREPARE insertmsg;
4717 + IF @notable = 0 THEN
4718 + UPDATE ~s SET count=count+1 WHERE owner_id=@ownerID AND peer_name_id=@peer_nameID AND peer_server_id=@peer_serverID AND at=atdate;
4719 + IF ROW_COUNT() = 0 THEN
4720 + INSERT INTO ~s (owner_id, peer_name_id, peer_server_id, at, count) VALUES (@ownerID, @peer_nameID, @peer_serverID, atdate, 1);
4723 +END;", [logmessage_name(VHost),UName,UName,UName,UName,SName,SName,RName,RName,UName,UName,SName,RName,StName,StName]).
4724 --- mod_logdb_pgsql.erl.orig 2009-11-22 13:06:23.000000000 +0200
4725 +++ mod_logdb_pgsql.erl 2009-07-30 09:49:10.000000000 +0300
4727 +%%%----------------------------------------------------------------------
4728 +%%% File : mod_logdb_pgsql.erl
4729 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
4730 +%%% Purpose : Posgresql backend for mod_logdb
4731 +%%% Version : trunk
4733 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
4734 +%%%----------------------------------------------------------------------
4736 +-module(mod_logdb_pgsql).
4737 +-author('o.palij@gmail.com').
4739 +-include("mod_logdb.hrl").
4740 +-include("ejabberd.hrl").
4741 +-include("jlib.hrl").
4743 +-behaviour(gen_logdb).
4744 +-behaviour(gen_server).
4747 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
4749 +-export([start/2, stop/1]).
4751 +-export([log_message/2,
4753 + rebuild_stats_at/2,
4754 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
4755 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
4757 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
4760 +-export([view_table/3]).
4762 +% gen_server call timeout
4763 +-define(CALL_TIMEOUT, 30000).
4764 +-define(PGSQL_TIMEOUT, 60000).
4765 +-define(PROCNAME, mod_logdb_pgsql).
4767 +-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
4768 + list_to_string/1, string_to_list/1,
4769 + convert_timestamp_brief/1]).
4771 +-record(state, {dbref, vhost, server, port, db, user, password, schema}).
4773 +% replace "." with "_"
4774 +escape_vhost(VHost) -> lists:map(fun(46) -> 95;
4779 + Schema ++ ".\"" ++ "logdb_".
4782 + "_" ++ escape_vhost(VHost) ++ "\"".
4784 +messages_table(VHost, Schema, Date) ->
4785 + prefix(Schema) ++ "messages_" ++ Date ++ suffix(VHost).
4787 +view_table(VHost, Schema, Date) ->
4788 + Table = messages_table(VHost, Schema, Date),
4789 + TablewoS = lists:sublist(Table, length(Schema) + 3, length(Table) - length(Schema) - 3),
4790 + lists:append([Schema, ".\"v_", TablewoS, "\""]).
4792 +stats_table(VHost, Schema) ->
4793 + prefix(Schema) ++ "stats" ++ suffix(VHost).
4795 +temp_table(VHost, Schema) ->
4796 + prefix(Schema) ++ "temp" ++ suffix(VHost).
4798 +settings_table(VHost, Schema) ->
4799 + prefix(Schema) ++ "settings" ++ suffix(VHost).
4801 +users_table(VHost, Schema) ->
4802 + prefix(Schema) ++ "users" ++ suffix(VHost).
4803 +servers_table(VHost, Schema) ->
4804 + prefix(Schema) ++ "servers" ++ suffix(VHost).
4805 +resources_table(VHost, Schema) ->
4806 + prefix(Schema) ++ "resources" ++ suffix(VHost).
4808 +logmessage_name(VHost, Schema) ->
4809 + prefix(Schema) ++ "logmessage" ++ suffix(VHost).
4811 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4813 +% gen_mod callbacks
4815 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4816 +start(VHost, Opts) ->
4817 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4818 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
4821 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4822 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
4824 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4826 +% gen_server callbacks
4828 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4829 +init([VHost, Opts]) ->
4830 + Server = gen_mod:get_opt(server, Opts, "localhost"),
4831 + DB = gen_mod:get_opt(db, Opts, "ejabberd_logdb"),
4832 + User = gen_mod:get_opt(user, Opts, "root"),
4833 + Port = gen_mod:get_opt(port, Opts, 5432),
4834 + Password = gen_mod:get_opt(password, Opts, ""),
4835 + Schema = gen_mod:get_opt(schema, Opts, "public"),
4837 + ?MYDEBUG("Starting pgsql backend for ~p", [VHost]),
4839 + St = #state{vhost=VHost,
4840 + server=Server, port=Port, db=DB,
4841 + user=User, password=Password,
4844 + case open_pgsql_connection(St) of
4846 + State = St#state{dbref=DBRef},
4847 + ok = create_internals(State),
4848 + ok = create_stats_table(State),
4849 + ok = create_settings_table(State),
4850 + ok = create_users_table(State),
4851 + ok = create_servers_table(State),
4852 + ok = create_resources_table(State),
4853 + erlang:monitor(process, DBRef),
4855 + % this does not work
4856 + {error, Reason} ->
4857 + ?ERROR_MSG("PgSQL connection failed: ~p~n", [Reason]),
4858 + {stop, db_connection_failed};
4859 + % and this too, becouse pgsql_conn do exit() which can not be catched
4861 + ?ERROR_MSG("Rez: ~p~n", [Rez]),
4862 + {stop, db_connection_failed}
4865 +open_pgsql_connection(#state{server=Server, port=Port, db=DB, schema=Schema,
4866 + user=User, password=Password} = _State) ->
4867 + ?INFO_MSG("Opening pgsql connection ~s@~s:~p/~s", [User, Server, Port, DB]),
4868 + {ok, DBRef} = pgsql:connect(Server, DB, User, Password, Port),
4869 + {updated, _} = sql_query_internal(DBRef, ["SET SEARCH_PATH TO ",Schema,";"]),
4872 +close_pgsql_connection(DBRef) ->
4873 + ?MYDEBUG("Closing ~p pgsql connection", [DBRef]),
4874 + pgsql:terminate(DBRef).
4876 +handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4877 + Date = convert_timestamp_brief(Msg#msg.timestamp),
4878 + TableName = messages_table(VHost, Schema, Date),
4879 + ViewName = view_table(VHost, Schema, Date),
4881 + Query = [ "SELECT ", logmessage_name(VHost, Schema)," "
4882 + "('", TableName, "',",
4883 + "'", ViewName, "',",
4885 + "'", Msg#msg.owner_name, "',",
4886 + "'", Msg#msg.peer_name, "',",
4887 + "'", Msg#msg.peer_server, "',",
4888 + "'", ejabberd_odbc:escape(Msg#msg.peer_resource), "',",
4889 + "'", atom_to_list(Msg#msg.direction), "',",
4890 + "'", Msg#msg.type, "',",
4891 + "'", ejabberd_odbc:escape(Msg#msg.subject), "',",
4892 + "'", ejabberd_odbc:escape(Msg#msg.body), "',",
4893 + "'", Msg#msg.timestamp, "');"],
4895 + case sql_query_internal_silent(DBRef, Query) of
4896 + % TODO: change this
4897 + {data, [{"0"}]} ->
4898 + ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
4899 + Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
4901 + {error, _Reason} ->
4904 + {reply, ok, State};
4905 +handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4906 + Reply = rebuild_stats_at_int(DBRef, VHost, Schema, Date),
4907 + {reply, Reply, State};
4908 +handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
4909 + {reply, error, State};
4910 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4911 + Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
4912 + ["'",Timestamp,"'",","]
4915 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
4917 + Query = ["DELETE FROM ",messages_table(VHost, Schema, Date)," ",
4918 + "WHERE timestamp IN (", Temp1],
4921 + case sql_query_internal(DBRef, Query) of
4923 + rebuild_stats_at_int(DBRef, VHost, Schema, Date);
4927 + {reply, Reply, State};
4928 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4929 + ok = delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date),
4930 + ok = delete_stats_by_user_at_int(DBRef, Schema, User, VHost, Date),
4931 + {reply, ok, State};
4932 +handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4933 + {updated, _} = sql_query_internal(DBRef, ["DROP VIEW ",view_table(VHost, Schema, Date),";"]),
4935 + case sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Schema, Date)," CASCADE;"]) of
4937 + Query = ["DELETE FROM ",stats_table(VHost, Schema)," "
4938 + "WHERE at='",Date,"';"],
4939 + case sql_query_internal(DBRef, Query) of
4948 + {reply, Reply, State};
4949 +handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4950 + SName = stats_table(VHost, Schema),
4951 + Query = ["SELECT at, sum(count) ",
4952 + "FROM ",SName," ",
4954 + "ORDER BY DATE(at) DESC;"
4957 + case sql_query_internal(DBRef, Query) of
4959 + {ok, [ {Date, list_to_integer(Count)} || {Date, Count} <- Recs]};
4960 + {error, Reason} ->
4961 + % TODO: Duplicate error message ?
4964 + {reply, Reply, State};
4965 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4966 + SName = stats_table(VHost, Schema),
4967 + Query = ["SELECT username, sum(count) AS allcount ",
4968 + "FROM ",SName," ",
4969 + "JOIN ",users_table(VHost, Schema)," ON owner_id=user_id ",
4970 + "WHERE at='",Date,"' ",
4971 + "GROUP BY username ",
4972 + "ORDER BY allcount DESC;"
4975 + case sql_query_internal(DBRef, Query) of
4977 + RFun = fun({User, Count}) ->
4978 + {User, list_to_integer(Count)}
4980 + {ok, lists:reverse(lists:keysort(2, lists:map(RFun, Recs)))};
4981 + {error, Reason} ->
4985 + {reply, Reply, State};
4986 +handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4987 + {reply, get_user_stats_int(DBRef, Schema, User, VHost), State};
4988 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4989 + Query = ["SELECT peer_name,",
4997 + "FROM ",view_table(VHost, Schema, Date)," "
4998 + "WHERE owner_name='",User,"';"],
5000 + case sql_query_internal(DBRef, Query) of
5002 + Fun = fun({Peer_name, Peer_server, Peer_resource,
5007 + #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
5008 + direction=list_to_atom(Direction),
5010 + subject=Subject, body=Body,
5011 + timestamp=Timestamp}
5013 + {ok, lists:map(Fun, Recs)};
5014 + {error, Reason} ->
5017 + {reply, Reply, State};
5018 +handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5019 + SName = stats_table(VHost, Schema),
5020 + Query = ["SELECT at ",
5021 + "FROM ",SName," ",
5023 + "ORDER BY at DESC;"
5026 + case sql_query_internal(DBRef, Query) of
5028 + [ Date || {Date} <- Result ];
5029 + {error, Reason} ->
5032 + {reply, Reply, State};
5033 +handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5034 + Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
5035 + "FROM ",settings_table(VHost, Schema)," ",
5036 + "JOIN ",users_table(VHost, Schema)," ON user_id=owner_id;"],
5038 + case sql_query_internal(DBRef, Query) of
5040 + {ok, [#user_settings{owner_name=Owner,
5041 + dolog_default=list_to_bool(DoLogDef),
5042 + dolog_list=string_to_list(DoLogL),
5043 + donotlog_list=string_to_list(DoNotLogL)
5044 + } || {Owner, DoLogDef, DoLogL, DoNotLogL} <- Recs]};
5045 + {error, Reason} ->
5048 + {reply, Reply, State};
5049 +handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5050 + Query = ["SELECT dolog_default,dolog_list,donotlog_list ",
5051 + "FROM ",settings_table(VHost, Schema)," ",
5052 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
5054 + case sql_query_internal_silent(DBRef, Query) of
5057 + {data, [{DoLogDef, DoLogL, DoNotLogL}]} ->
5058 + {ok, #user_settings{owner_name=User,
5059 + dolog_default=list_to_bool(DoLogDef),
5060 + dolog_list=string_to_list(DoLogL),
5061 + donotlog_list=string_to_list(DoNotLogL)}};
5062 + {error, Reason} ->
5063 + ?ERROR_MSG("Failed to get_user_settings for ~p@~p: ~p", [User, VHost, Reason]),
5066 + {reply, Reply, State};
5067 +handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
5068 + dolog_list=DoLogL,
5069 + donotlog_list=DoNotLogL}},
5070 + _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5071 + User_id = get_user_id(DBRef, VHost, Schema, User),
5072 + Query = ["UPDATE ",settings_table(VHost, Schema)," ",
5073 + "SET dolog_default=",bool_to_list(DoLogDef),", ",
5074 + "dolog_list='",list_to_string(DoLogL),"', ",
5075 + "donotlog_list='",list_to_string(DoNotLogL),"' ",
5076 + "WHERE owner_id=",User_id,";"],
5079 + case sql_query_internal(DBRef, Query) of
5081 + IQuery = ["INSERT INTO ",settings_table(VHost, Schema)," ",
5082 + "(owner_id, dolog_default, dolog_list, donotlog_list) ",
5084 + "(",User_id,", ",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
5085 + case sql_query_internal(DBRef, IQuery) of
5087 + ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
5093 + ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
5098 + {reply, Reply, State};
5099 +handle_call({stop}, _From, State) ->
5100 + ?MYDEBUG("Stoping pgsql backend for ~p", [State#state.vhost]),
5101 + {stop, normal, ok, State};
5102 +handle_call(Msg, _From, State) ->
5103 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
5107 +handle_cast({rebuild_stats}, State) ->
5108 + rebuild_all_stats_int(State),
5110 +handle_cast({drop_user, User}, #state{vhost=VHost, schema=Schema}=State) ->
5112 + {ok, DBRef} = open_pgsql_connection(State),
5113 + {ok, Dates} = get_user_stats_int(DBRef, Schema, User, VHost),
5114 + MDResult = lists:map(fun({Date, _}) ->
5115 + delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date)
5117 + StDResult = delete_all_stats_by_user_int(DBRef, Schema, User, VHost),
5118 + SDResult = delete_user_settings_int(DBRef, Schema, User, VHost),
5119 + case lists:all(fun(Result) when Result == ok ->
5121 + (Result) when Result == error ->
5123 + end, lists:append([MDResult, [StDResult], [SDResult]])) of
5125 + ?INFO_MSG("Removed ~s@~s", [User, VHost]);
5127 + ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost])
5129 + close_pgsql_connection(DBRef)
5133 +handle_cast(Msg, State) ->
5134 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
5137 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
5138 + {stop, connection_dropped, State};
5139 +handle_info(Info, State) ->
5140 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
5143 +terminate(_Reason, #state{dbref=DBRef}=_State) ->
5144 + close_pgsql_connection(DBRef),
5147 +code_change(_OldVsn, State, _Extra) ->
5150 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5152 +% gen_logdb callbacks
5154 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5155 +log_message(VHost, Msg) ->
5156 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5157 + gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
5158 +rebuild_stats(VHost) ->
5159 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5160 + gen_server:cast(Proc, {rebuild_stats}).
5161 +rebuild_stats_at(VHost, Date) ->
5162 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5163 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
5164 +delete_messages_by_user_at(VHost, Msgs, Date) ->
5165 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5166 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
5167 +delete_all_messages_by_user_at(User, VHost, Date) ->
5168 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5169 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
5170 +delete_messages_at(VHost, Date) ->
5171 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5172 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
5173 +get_vhost_stats(VHost) ->
5174 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5175 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
5176 +get_vhost_stats_at(VHost, Date) ->
5177 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5178 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
5179 +get_user_stats(User, VHost) ->
5180 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5181 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
5182 +get_user_messages_at(User, VHost, Date) ->
5183 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5184 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
5185 +get_dates(VHost) ->
5186 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5187 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
5188 +get_users_settings(VHost) ->
5189 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5190 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
5191 +get_user_settings(User, VHost) ->
5192 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5193 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
5194 +set_user_settings(User, VHost, Set) ->
5195 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5196 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
5197 +drop_user(User, VHost) ->
5198 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5199 + gen_server:cast(Proc, {drop_user, User}).
5201 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5205 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5206 +get_dates_int(DBRef, VHost) ->
5207 + Query = ["SELECT n.nspname as \"Schema\",
5208 + c.relname as \"Name\",
5209 + 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\",
5210 + r.rolname as \"Owner\"
5211 + FROM pg_catalog.pg_class c
5212 + JOIN pg_catalog.pg_roles r ON r.oid = c.relowner
5213 + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
5214 + WHERE c.relkind IN ('r','')
5215 + AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
5216 + AND c.relname ~ '^(.*",escape_vhost(VHost),".*)$'
5217 + AND pg_catalog.pg_table_is_visible(c.oid)
5219 + case sql_query_internal(DBRef, Query) of
5221 + lists:foldl(fun({_Schema, Table, _Type, _Owner}, Dates) ->
5222 + case regexp:match(Table,"[0-9]+-[0-9]+-[0-9]+") of
5224 + lists:append(Dates, [lists:sublist(Table,S,E)]);
5233 +rebuild_all_stats_int(#state{vhost=VHost, schema=Schema}=State) ->
5235 + {ok, DBRef} = open_pgsql_connection(State),
5236 + ok = delete_nonexistent_stats(DBRef, Schema, VHost),
5237 + case lists:filter(fun(Date) ->
5238 + case catch rebuild_stats_at_int(DBRef, VHost, Schema, Date) of
5241 + {'EXIT', _} -> true
5243 + end, get_dates_int(DBRef, VHost)) of
5246 + ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]),
5249 + close_pgsql_connection(DBRef)
5253 +rebuild_stats_at_int(DBRef, VHost, Schema, Date) ->
5254 + TempTable = temp_table(VHost, Schema),
5257 + Table = messages_table(VHost, Schema, Date),
5258 + STable = stats_table(VHost, Schema),
5260 + DQuery = [ "DELETE FROM ",STable," ",
5261 + "WHERE at='",Date,"';"],
5263 + ok = create_temp_table(DBRef, VHost, Schema),
5264 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," IN ACCESS EXCLUSIVE MODE;"]),
5265 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",TempTable," IN ACCESS EXCLUSIVE MODE;"]),
5266 + SQuery = ["INSERT INTO ",TempTable," ",
5267 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
5268 + "SELECT owner_id,peer_name_id,peer_server_id,'",Date,"'",",count(*) ",
5269 + "FROM ",Table," GROUP BY owner_id,peer_name_id,peer_server_id;"],
5270 + case sql_query_internal(DBRef, SQuery) of
5272 + Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]),
5274 + {data, [{"0"}]} ->
5275 + {updated, _} = sql_query_internal(DBRef, ["DROP VIEW ",view_table(VHost, Schema, Date),";"]),
5276 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table," CASCADE;"]),
5277 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," IN ACCESS EXCLUSIVE MODE;"]),
5278 + {updated, _} = sql_query_internal(DBRef, DQuery),
5281 + ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]),
5285 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," IN ACCESS EXCLUSIVE MODE;"]),
5286 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",TempTable," IN ACCESS EXCLUSIVE MODE;"]),
5287 + {updated, _} = sql_query_internal(DBRef, DQuery),
5288 + SQuery1 = ["INSERT INTO ",STable," ",
5289 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
5290 + "SELECT owner_id,peer_name_id,peer_server_id,at,count ",
5291 + "FROM ",TempTable,";"],
5292 + case sql_query_internal(DBRef, SQuery1) of
5293 + {updated, _} -> ok;
5294 + {error, _} -> error
5296 + {error, _} -> error
5300 + case sql_transaction_internal(DBRef, Fun) of
5302 + ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
5304 + {aborted, Reason} ->
5305 + ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]),
5308 + sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]),
5311 +delete_nonexistent_stats(DBRef, Schema, VHost) ->
5312 + Dates = get_dates_int(DBRef, VHost),
5313 + STable = stats_table(VHost, Schema),
5315 + Temp = lists:flatmap(fun(Date) ->
5316 + ["'",Date,"'",","]
5323 + % replace last "," with ");"
5324 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
5325 + Query = ["DELETE FROM ",STable," ",
5326 + "WHERE at NOT IN (", Temp1],
5327 + case sql_query_internal(DBRef, Query) of
5335 +get_user_stats_int(DBRef, Schema, User, VHost) ->
5336 + SName = stats_table(VHost, Schema),
5337 + UName = users_table(VHost, Schema),
5338 + Query = ["SELECT stats.at, sum(stats.count) ",
5339 + "FROM ",UName," AS users ",
5340 + "JOIN ",SName," AS stats ON owner_id=user_id "
5341 + "WHERE users.username='",User,"' ",
5342 + "GROUP BY stats.at "
5343 + "ORDER BY DATE(at) DESC;"
5345 + case sql_query_internal(DBRef, Query) of
5347 + {ok, [ {Date, list_to_integer(Count)} || {Date, Count} <- Recs ]};
5348 + {error, Result} ->
5352 +delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date) ->
5353 + DQuery = ["DELETE FROM ",messages_table(VHost, Schema, Date)," ",
5354 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
5355 + case sql_query_internal(DBRef, DQuery) of
5357 + ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]),
5363 +delete_all_stats_by_user_int(DBRef, Schema, User, VHost) ->
5364 + SQuery = ["DELETE FROM ",stats_table(VHost, Schema)," ",
5365 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
5366 + case sql_query_internal(DBRef, SQuery) of
5368 + ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]),
5370 + {error, _} -> error
5373 +delete_stats_by_user_at_int(DBRef, Schema, User, VHost, Date) ->
5374 + SQuery = ["DELETE FROM ",stats_table(VHost, Schema)," ",
5375 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"') ",
5376 + "AND at='",Date,"';"],
5377 + case sql_query_internal(DBRef, SQuery) of
5379 + ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]),
5381 + {error, _} -> error
5384 +delete_user_settings_int(DBRef, Schema, User, VHost) ->
5385 + Query = ["DELETE FROM ",settings_table(VHost, Schema)," ",
5386 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
5387 + case sql_query_internal(DBRef, Query) of
5389 + ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]),
5391 + {error, Reason} ->
5392 + ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]),
5396 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5400 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5401 +create_temp_table(DBRef, VHost, Schema) ->
5402 + TName = temp_table(VHost, Schema),
5403 + Query = ["CREATE TABLE ",TName," (",
5404 + "owner_id INTEGER, ",
5405 + "peer_name_id INTEGER, ",
5406 + "peer_server_id INTEGER, ",
5407 + "at VARCHAR(20), ",
5411 + case sql_query_internal(DBRef, Query) of
5412 + {updated, _} -> ok;
5413 + {error, _Reason} -> error
5416 +create_stats_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5417 + SName = stats_table(VHost, Schema),
5421 + Query = ["CREATE TABLE ",SName," (",
5422 + "owner_id INTEGER, ",
5423 + "peer_name_id INTEGER, ",
5424 + "peer_server_id INTEGER, ",
5425 + "at VARCHAR(20), ",
5429 + case sql_query_internal_silent(DBRef, Query) of
5431 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"s_search_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (owner_id, peer_name_id, peer_server_id);"]),
5432 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"s_at_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (at);"]),
5434 + {error, Reason} ->
5435 + case lists:keysearch(code, 1, Reason) of
5436 + {value, {code, "42P07"}} ->
5439 + ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
5444 + case sql_transaction_internal(DBRef, Fun) of
5445 + {atomic, created} ->
5446 + ?MYDEBUG("Created stats table for ~p", [VHost]),
5447 + rebuild_all_stats_int(State),
5449 + {atomic, exists} ->
5450 + ?MYDEBUG("Stats table for ~p already exists", [VHost]),
5451 + {match, F, L} = regexp:match(SName, "\".*\""),
5452 + QTable = lists:sublist(SName, F+1, L-2),
5453 + OIDQuery = ["SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname='",QTable,"' AND pg_catalog.pg_table_is_visible(c.oid);"],
5454 + {data,[{OID}]} = sql_query_internal(DBRef, OIDQuery),
5455 + CheckQuery = ["SELECT a.attname FROM pg_catalog.pg_attribute a WHERE a.attrelid = '",OID,"' AND a.attnum > 0 AND NOT a.attisdropped AND a.attname ~ '^peer_.*_id$';"],
5456 + case sql_query_internal(DBRef, CheckQuery) of
5457 + {data, Elems} when length(Elems) == 2 ->
5458 + ?MYDEBUG("Stats table structure is ok", []),
5461 + ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []),
5462 + case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of
5464 + ?INFO_MSG("Successfully dropped ~p", [SName]);
5466 + ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName])
5470 + {error, _} -> error
5473 +create_settings_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
5474 + SName = settings_table(VHost, Schema),
5475 + Query = ["CREATE TABLE ",SName," (",
5476 + "owner_id INTEGER PRIMARY KEY, ",
5477 + "dolog_default BOOLEAN, ",
5478 + "dolog_list TEXT DEFAULT '', ",
5479 + "donotlog_list TEXT DEFAULT ''",
5482 + case sql_query_internal_silent(DBRef, Query) of
5484 + ?MYDEBUG("Created settings table for ~p", [VHost]),
5486 + {error, Reason} ->
5487 + case lists:keysearch(code, 1, Reason) of
5488 + {value, {code, "42P07"}} ->
5489 + ?MYDEBUG("Settings table for ~p already exists", [VHost]),
5492 + ?ERROR_MSG("Failed to create settings table for ~p: ~p", [VHost, Reason]),
5497 +create_users_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
5498 + SName = users_table(VHost, Schema),
5502 + Query = ["CREATE TABLE ",SName," (",
5503 + "username TEXT UNIQUE, ",
5504 + "user_id SERIAL PRIMARY KEY",
5507 + case sql_query_internal_silent(DBRef, Query) of
5509 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"username_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (username);"]),
5511 + {error, Reason} ->
5512 + case lists:keysearch(code, 1, Reason) of
5513 + {value, {code, "42P07"}} ->
5516 + ?ERROR_MSG("Failed to create users table for ~p: ~p", [VHost, Reason]),
5521 + case sql_transaction_internal(DBRef, Fun) of
5522 + {atomic, created} ->
5523 + ?MYDEBUG("Created users table for ~p", [VHost]),
5525 + {atomic, exists} ->
5526 + ?MYDEBUG("Users table for ~p already exists", [VHost]),
5528 + {aborted, _} -> error
5531 +create_servers_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
5532 + SName = servers_table(VHost, Schema),
5535 + Query = ["CREATE TABLE ",SName," (",
5536 + "server TEXT UNIQUE, ",
5537 + "server_id SERIAL PRIMARY KEY",
5540 + case sql_query_internal_silent(DBRef, Query) of
5542 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"server_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (server);"]),
5544 + {error, Reason} ->
5545 + case lists:keysearch(code, 1, Reason) of
5546 + {value, {code, "42P07"}} ->
5549 + ?ERROR_MSG("Failed to create servers table for ~p: ~p", [VHost, Reason]),
5554 + case sql_transaction_internal(DBRef, Fun) of
5555 + {atomic, created} ->
5556 + ?MYDEBUG("Created servers table for ~p", [VHost]),
5558 + {atomic, exists} ->
5559 + ?MYDEBUG("Servers table for ~p already exists", [VHost]),
5561 + {aborted, _} -> error
5564 +create_resources_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
5565 + RName = resources_table(VHost, Schema),
5567 + Query = ["CREATE TABLE ",RName," (",
5568 + "resource TEXT UNIQUE, ",
5569 + "resource_id SERIAL PRIMARY KEY",
5572 + case sql_query_internal_silent(DBRef, Query) of
5574 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"resource_i_",Schema,"_",escape_vhost(VHost),"\" ON ",RName," (resource);"]),
5576 + {error, Reason} ->
5577 + case lists:keysearch(code, 1, Reason) of
5578 + {value, {code, "42P07"}} ->
5581 + ?ERROR_MSG("Failed to create users table for ~p: ~p", [VHost, Reason]),
5586 + case sql_transaction_internal(DBRef, Fun) of
5587 + {atomic, created} ->
5588 + ?MYDEBUG("Created resources table for ~p", [VHost]),
5590 + {atomic, exists} ->
5591 + ?MYDEBUG("Resources table for ~p already exists", [VHost]),
5593 + {aborted, _} -> error
5596 +create_internals(#state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5597 + sql_query_internal(DBRef, ["DROP FUNCTION IF EXISTS ",logmessage_name(VHost,Schema)," (tbname TEXT, atdt TEXT, owner TEXT, peer_name TEXT, peer_server TEXT, peer_resource TEXT, mdirection VARCHAR(4), mtype VARCHAR(9), msubj TEXT, mbody TEXT, mtimestamp DOUBLE PRECISION);"]),
5598 + case sql_query_internal(DBRef, [get_logmessage(VHost, Schema)]) of
5600 + ?MYDEBUG("Created logmessage for ~p", [VHost]),
5602 + {error, Reason} ->
5603 + case lists:keysearch(code, 1, Reason) of
5604 + {value, {code, "42704"}} ->
5605 + ?ERROR_MSG("plpgsql language must be installed into database '~s'. Use CREATE LANGUAGE...", [State#state.db]),
5612 +get_user_id(DBRef, VHost, Schema, User) ->
5613 + SQuery = ["SELECT user_id FROM ",users_table(VHost, Schema)," ",
5614 + "WHERE username='",User,"';"],
5615 + case sql_query_internal(DBRef, SQuery) of
5617 + IQuery = ["INSERT INTO ",users_table(VHost, Schema)," ",
5618 + "VALUES ('",User,"');"],
5619 + case sql_query_internal_silent(DBRef, IQuery) of
5621 + {data, [{DBIdNew}]} = sql_query_internal(DBRef, SQuery),
5623 + {error, Reason} ->
5624 + % this can be in clustered environment
5625 + {value, {code, "23505"}} = lists:keysearch(code, 1, Reason),
5626 + ?ERROR_MSG("Duplicate key name for ~p", [User]),
5627 + {data, [{ClID}]} = sql_query_internal(DBRef, SQuery),
5630 + {data, [{DBId}]} ->
5634 +get_logmessage(VHost,Schema) ->
5635 + UName = users_table(VHost,Schema),
5636 + SName = servers_table(VHost,Schema),
5637 + RName = resources_table(VHost,Schema),
5638 + StName = stats_table(VHost,Schema),
5639 + io_lib:format("CREATE OR REPLACE FUNCTION ~s (tbname TEXT, vname TEXT, atdt TEXT, owner TEXT, peer_name TEXT, peer_server TEXT, peer_resource TEXT, mdirection VARCHAR(4), mtype VARCHAR(9), msubj TEXT, mbody TEXT, mtimestamp DOUBLE PRECISION) RETURNS INTEGER AS $$
5642 + peer_nameID INTEGER;
5643 + peer_serverID INTEGER;
5644 + peer_resourceID INTEGER;
5645 + tablename ALIAS for $1;
5646 + viewname ALIAS for $2;
5647 + atdate ALIAS for $3;
5649 + SELECT INTO ownerID user_id FROM ~s WHERE username = owner;
5651 + INSERT INTO ~s (username) VALUES (owner);
5652 + ownerID := lastval();
5655 + SELECT INTO peer_nameID user_id FROM ~s WHERE username = peer_name;
5657 + INSERT INTO ~s (username) VALUES (peer_name);
5658 + peer_nameID := lastval();
5661 + SELECT INTO peer_serverID server_id FROM ~s WHERE server = peer_server;
5663 + INSERT INTO ~s (server) VALUES (peer_server);
5664 + peer_serverID := lastval();
5667 + SELECT INTO peer_resourceID resource_id FROM ~s WHERE resource = peer_resource;
5669 + INSERT INTO ~s (resource) VALUES (peer_resource);
5670 + peer_resourceID := lastval();
5674 + EXECUTE 'INSERT INTO ' || tablename || ' (owner_id, peer_name_id, peer_server_id, peer_resource_id, direction, type, subject, body, timestamp) VALUES (' || ownerID || ',' || peer_nameID || ',' || peer_serverID || ',' || peer_resourceID || ',''' || mdirection || ''',''' || mtype || ''',' || quote_literal(msubj) || ',' || quote_literal(mbody) || ',' || mtimestamp || ')';
5675 + EXCEPTION WHEN undefined_table THEN
5676 + EXECUTE 'CREATE TABLE ' || tablename || ' (' ||
5677 + 'owner_id INTEGER, ' ||
5678 + 'peer_name_id INTEGER, ' ||
5679 + 'peer_server_id INTEGER, ' ||
5680 + 'peer_resource_id INTEGER, ' ||
5681 + 'direction VARCHAR(4) CHECK (direction IN (''to'',''from'')), ' ||
5682 + 'type VARCHAR(9) CHECK (type IN (''chat'',''error'',''groupchat'',''headline'',''normal'')), ' ||
5683 + 'subject TEXT, ' ||
5685 + 'timestamp DOUBLE PRECISION)';
5686 + EXECUTE 'CREATE INDEX \"search_i_' || '~s' || '_' || atdate || '_' || '~s' || '\"' || ' ON ' || tablename || ' (owner_id, peer_name_id, peer_server_id, peer_resource_id)';
5688 + EXECUTE 'CREATE OR REPLACE VIEW ' || viewname || ' AS ' ||
5689 + 'SELECT owner.username AS owner_name, ' ||
5690 + 'peer.username AS peer_name, ' ||
5691 + 'servers.server AS peer_server, ' ||
5692 + 'resources.resource AS peer_resource, ' ||
5693 + 'messages.direction, ' ||
5694 + 'messages.type, ' ||
5695 + 'messages.subject, ' ||
5696 + 'messages.body, ' ||
5697 + 'messages.timestamp ' ||
5702 + '~s resources, ' ||
5703 + tablename || ' messages ' ||
5705 + 'owner.user_id=messages.owner_id and ' ||
5706 + 'peer.user_id=messages.peer_name_id and ' ||
5707 + 'servers.server_id=messages.peer_server_id and ' ||
5708 + 'resources.resource_id=messages.peer_resource_id ' ||
5709 + 'ORDER BY messages.timestamp';
5711 + EXECUTE 'INSERT INTO ' || tablename || ' (owner_id, peer_name_id, peer_server_id, peer_resource_id, direction, type, subject, body, timestamp) VALUES (' || ownerID || ',' || peer_nameID || ',' || peer_serverID || ',' || peer_resourceID || ',''' || mdirection || ''',''' || mtype || ''',' || quote_literal(msubj) || ',' || quote_literal(mbody) || ',' || mtimestamp || ')';
5714 + UPDATE ~s SET count=count+1 where at=atdate and owner_id=ownerID and peer_name_id=peer_nameID and peer_server_id=peer_serverID;
5716 + INSERT INTO ~s (owner_id, peer_name_id, peer_server_id, at, count) VALUES (ownerID, peer_nameID, peer_serverID, atdate, 1);
5720 +$$ LANGUAGE plpgsql;
5721 +", [logmessage_name(VHost,Schema),UName,UName,UName,UName,SName,SName,RName,RName,Schema,escape_vhost(VHost),UName,UName,SName,RName,StName,StName]).
5723 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5727 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5728 +% like do_transaction/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
5729 +sql_transaction_internal(DBRef, Fun) ->
5730 + case sql_query_internal(DBRef, ["BEGIN;"]) of
5732 + case catch Fun() of
5734 + rollback_internal(DBRef, Err);
5735 + {error, _} = Err ->
5736 + rollback_internal(DBRef, Err);
5737 + {'EXIT', _} = Err ->
5738 + rollback_internal(DBRef, Err);
5740 + case sql_query_internal(DBRef, ["COMMIT;"]) of
5741 + {error, _} -> rollback_internal(DBRef, {commit_error});
5744 + {atomic, _} -> Res;
5745 + _ -> {atomic, Res}
5750 + {aborted, {begin_error}}
5753 +% like rollback/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
5754 +rollback_internal(DBRef, Reason) ->
5755 + Res = sql_query_internal(DBRef, ["ROLLBACK;"]),
5756 + {aborted, {Reason, {rollback_result, Res}}}.
5758 +sql_query_internal(DBRef, Query) ->
5759 + case sql_query_internal_silent(DBRef, Query) of
5760 + {error, undefined, Rez} ->
5761 + ?ERROR_MSG("Got undefined result: ~p while ~p", [Rez, lists:append(Query)]),
5762 + {error, undefined};
5764 + ?ERROR_MSG("Failed: ~p while ~p", [Error, lists:append(Query)]),
5769 +sql_query_internal_silent(DBRef, Query) ->
5770 + ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
5771 + % TODO: use pquery?
5772 + get_result(pgsql:squery(DBRef, Query)).
5774 +get_result({ok, ["CREATE TABLE"]}) ->
5776 +get_result({ok, ["DROP TABLE"]}) ->
5778 +get_result({ok, ["ALTER TABLE"]}) ->
5780 +get_result({ok,["DROP VIEW"]}) ->
5782 +get_result({ok,["DROP FUNCTION"]}) ->
5784 +get_result({ok, ["CREATE INDEX"]}) ->
5786 +get_result({ok, ["CREATE FUNCTION"]}) ->
5788 +get_result({ok, [{"SELECT", _Rows, Recs}]}) ->
5791 + lists:map(fun(Elem) when is_binary(Elem) ->
5792 + binary_to_list(Elem);
5793 + (Elem) when is_list(Elem) ->
5795 + (Elem) when is_integer(Elem) ->
5796 + integer_to_list(Elem);
5797 + (Elem) when is_float(Elem) ->
5798 + float_to_list(Elem);
5799 + (Elem) when is_boolean(Elem) ->
5800 + atom_to_list(Elem);
5802 + ?ERROR_MSG("Unknown element type ~p", [Elem]),
5806 + Res = lists:map(Fun, Recs),
5807 + %{data, [list_to_tuple(Rec) || Rec <- Recs]};
5809 +get_result({ok, ["INSERT " ++ OIDN]}) ->
5810 + [_OID, N] = string:tokens(OIDN, " "),
5811 + {updated, list_to_integer(N)};
5812 +get_result({ok, ["DELETE " ++ N]}) ->
5813 + {updated, list_to_integer(N)};
5814 +get_result({ok, ["UPDATE " ++ N]}) ->
5815 + {updated, list_to_integer(N)};
5816 +get_result({ok, ["BEGIN"]}) ->
5818 +get_result({ok, ["LOCK TABLE"]}) ->
5820 +get_result({ok, ["ROLLBACK"]}) ->
5822 +get_result({ok, ["COMMIT"]}) ->
5824 +get_result({ok, ["SET"]}) ->
5826 +get_result({ok, [{error, Error}]}) ->
5829 + {error, undefined, Rez}.
5831 --- mod_logdb_mnesia_old.erl.orig 2009-11-22 13:06:23.000000000 +0200
5832 +++ mod_logdb_mnesia_old.erl 2009-02-05 20:12:58.000000000 +0200
5834 +%%%----------------------------------------------------------------------
5835 +%%% File : mod_logdb_mnesia_old.erl
5836 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
5837 +%%% Purpose : mod_logmnesia backend for mod_logdb (should be used only for copy_tables functionality)
5838 +%%% Version : trunk
5840 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
5841 +%%%----------------------------------------------------------------------
5843 +-module(mod_logdb_mnesia_old).
5844 +-author('o.palij@gmail.com').
5846 +-include("ejabberd.hrl").
5847 +-include("jlib.hrl").
5849 +-behaviour(gen_logdb).
5851 +-export([start/2, stop/1,
5854 + rebuild_stats_at/2,
5855 + rebuild_stats_at1/2,
5856 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
5857 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
5859 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
5862 +-record(stats, {user, server, table, count}).
5863 +-record(msg, {to_user, to_server, to_resource, from_user, from_server, from_resource, id, type, subject, body, timestamp}).
5865 +tables_prefix() -> "messages_".
5866 +% stats_table should not start with tables_prefix(VHost) !
5867 +% i.e. lists:prefix(tables_prefix(VHost), atom_to_list(stats_table())) must be /= true
5868 +stats_table() -> list_to_atom("messages-stats").
5869 +% table name as atom from Date
5870 +-define(ATABLE(Date), list_to_atom(tables_prefix() ++ Date)).
5871 +-define(LTABLE(Date), tables_prefix() ++ Date).
5873 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5875 +% gen_logdb callbacks
5877 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5878 +start(_Opts, _VHost) ->
5879 + case mnesia:system_info(is_running) of
5881 + ok = create_stats_table(),
5884 + ?ERROR_MSG("Mnesia not running", []),
5887 + ?ERROR_MSG("Mnesia status: ~p", [Status]),
5894 +log_message(_VHost, _Msg) ->
5897 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5899 +% gen_logdb callbacks (maintaince)
5901 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5902 +rebuild_stats(_VHost) ->
5905 +rebuild_stats_at(VHost, Date) ->
5906 + Table = ?LTABLE(Date),
5907 + {Time, Value}=timer:tc(?MODULE, rebuild_stats_at1, [VHost, Table]),
5908 + ?INFO_MSG("rebuild_stats_at ~p elapsed ~p sec: ~p~n", [Date, Time/1000000, Value]),
5910 +rebuild_stats_at1(VHost, Table) ->
5911 + CFun = fun(Msg, Stats) ->
5912 + To = Msg#msg.to_user ++ "@" ++ Msg#msg.to_server,
5914 + Msg#msg.to_server == VHost ->
5915 + case lists:keysearch(To, 1, Stats) of
5916 + {value, {Who_to, Count_to}} ->
5917 + lists:keyreplace(To, 1, Stats, {Who_to, Count_to + 1});
5919 + lists:append(Stats, [{To, 1}])
5924 + From = Msg#msg.from_user ++ "@" ++ Msg#msg.from_server,
5926 + Msg#msg.from_server == VHost ->
5927 + case lists:keysearch(From, 1, Stats_to) of
5928 + {value, {Who_from, Count_from}} ->
5929 + lists:keyreplace(From, 1, Stats_to, {Who_from, Count_from + 1});
5931 + lists:append(Stats_to, [{From, 1}])
5938 + DFun = fun(#stats{table=STable, server=Server} = Stat, _Acc)
5939 + when STable == Table, Server == VHost ->
5940 + mnesia:delete_object(stats_table(), Stat, write);
5941 + (_Stat, _Acc) -> ok
5943 + case mnesia:transaction(fun() ->
5944 + mnesia:write_lock_table(list_to_atom(Table)),
5945 + mnesia:write_lock_table(stats_table()),
5946 + % Calc stats for VHost at Date
5947 + AStats = mnesia:foldl(CFun, [], list_to_atom(Table)),
5948 + % Delete all stats for VHost at Date
5949 + mnesia:foldl(DFun, [], stats_table()),
5950 + % Write new calc'ed stats
5951 + lists:foreach(fun({Who, Count}) ->
5952 + Jid = jlib:string_to_jid(Who),
5953 + JUser = Jid#jid.user,
5954 + WStat = #stats{user=JUser, server=VHost, table=Table, count=Count},
5955 + mnesia:write(stats_table(), WStat, write)
5958 + {aborted, Reason} ->
5959 + ?ERROR_MSG("Failed to rebuild_stats_at for ~p at ~p: ~p", [VHost, Table, Reason]),
5965 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5967 +% gen_logdb callbacks (delete)
5969 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5970 +delete_messages_by_user_at(_VHost, _Msgs, _Date) ->
5973 +delete_all_messages_by_user_at(_User, _VHost, _Date) ->
5976 +delete_messages_at(VHost, Date) ->
5977 + Table = list_to_atom(tables_prefix() ++ Date),
5979 + DFun = fun(#msg{to_server=To_server, from_server=From_server}=Msg, _Acc)
5980 + when To_server == VHost; From_server == VHost ->
5981 + mnesia:delete_object(Table, Msg, write);
5982 + (_Msg, _Acc) -> ok
5985 + case mnesia:transaction(fun() ->
5986 + mnesia:foldl(DFun, [], Table)
5988 + {aborted, Reason} ->
5989 + ?ERROR_MSG("Failed to delete_messages_at for ~p at ~p: ~p", [VHost, Date, Reason]),
5995 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5997 +% gen_logdb callbacks (get)
5999 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6000 +get_vhost_stats(_VHost) ->
6001 + {error, "does not emplemented"}.
6003 +get_vhost_stats_at(VHost, Date) ->
6005 + Pat = #stats{user='$1', server=VHost, table=tables_prefix()++Date, count = '$2'},
6006 + mnesia:select(stats_table(), [{Pat, [], [['$1', '$2']]}])
6008 + case mnesia:transaction(Fun) of
6009 + {atomic, Result} ->
6010 + RFun = fun([User, Count]) ->
6013 + {ok, lists:reverse(lists:keysort(2, lists:map(RFun, Result)))};
6014 + {aborted, Reason} -> {error, Reason}
6017 +get_user_stats(_User, _VHost) ->
6018 + {error, "does not emplemented"}.
6020 +get_user_messages_at(User, VHost, Date) ->
6021 + Table_name = tables_prefix() ++ Date,
6022 + case mnesia:transaction(fun() ->
6023 + Pat_to = #msg{to_user=User, to_server=VHost, _='_'},
6024 + Pat_from = #msg{from_user=User, from_server=VHost, _='_'},
6025 + mnesia:select(list_to_atom(Table_name),
6026 + [{Pat_to, [], ['$_']},
6027 + {Pat_from, [], ['$_']}])
6029 + {atomic, Result} ->
6030 + Msgs = lists:map(fun(#msg{to_user=To_user, to_server=To_server, to_resource=To_res,
6031 + from_user=From_user, from_server=From_server, from_resource=From_res,
6034 + body=Body, timestamp=Timestamp} = _Msg) ->
6035 + Subject = case Subj of
6039 + {msg, To_user, To_server, To_res, From_user, From_server, From_res, Type, Subject, Body, Timestamp}
6042 + {aborted, Reason} ->
6046 +get_dates(_VHost) ->
6047 + Tables = mnesia:system_info(tables),
6049 + lists:filter(fun(Table) ->
6050 + lists:prefix(tables_prefix(), atom_to_list(Table))
6053 + lists:map(fun(Table) ->
6054 + lists:sublist(atom_to_list(Table),
6055 + length(tables_prefix())+1,
6056 + length(atom_to_list(Table)))
6060 +get_users_settings(_VHost) ->
6062 +get_user_settings(_User, _VHost) ->
6064 +set_user_settings(_User, _VHost, _Set) ->
6066 +drop_user(_User, _VHost) ->
6069 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6073 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6074 +% called from db_logon/2
6075 +create_stats_table() ->
6076 + SName = stats_table(),
6077 + case mnesia:create_table(SName,
6078 + [{disc_only_copies, [node()]},
6080 + {attributes, record_info(fields, stats)},
6081 + {record_name, stats}
6084 + ?INFO_MSG("Created stats table", []),
6086 + {aborted, {already_exists, _}} ->
6088 + {aborted, Reason} ->
6089 + ?ERROR_MSG("Failed to create stats table: ~p", [Reason]),
6092 --- gen_logdb.erl.orig 2009-11-22 13:06:23.000000000 +0200
6093 +++ gen_logdb.erl 2009-07-22 16:43:26.000000000 +0300
6095 +%%%----------------------------------------------------------------------
6096 +%%% File : gen_logdb.erl
6097 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
6098 +%%% Purpose : Describes generic behaviour for mod_logdb backends.
6099 +%%% Version : trunk
6101 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
6102 +%%%----------------------------------------------------------------------
6104 +-module(gen_logdb).
6105 +-author('o.palij@gmail.com').
6107 +-export([behaviour_info/1]).
6109 +behaviour_info(callbacks) ->
6111 + % called from handle_info(start, _)
6112 + % it should logon database and return reference to started instance
6113 + % start(VHost, Opts) -> {ok, SPid} | error
6114 + % Options - list of options to connect to db
6115 + % Types: Options = list() -> [] |
6116 + % [{user, "logdb"},
6118 + % {db, "logdb"}] | ...
6119 + % VHost = list() -> "jabber.example.org"
6122 + % called from cleanup/1
6123 + % it should logoff database and do cleanup
6125 + % Types: VHost = list() -> "jabber.example.org"
6128 + % called from handle_call({addlog, _}, _, _)
6129 + % it should log messages to database
6130 + % log_message(VHost, Msg) -> ok | error
6132 + % VHost = list() -> "jabber.example.org"
6133 + % Msg = record() -> #msg
6136 + % called from ejabberdctl rebuild_stats
6137 + % it should rebuild stats table (if used) for vhost
6138 + % rebuild_stats(VHost)
6140 + % VHost = list() -> "jabber.example.org"
6141 + {rebuild_stats, 1},
6143 + % it should rebuild stats table (if used) for vhost at Date
6144 + % rebuild_stats_at(VHost, Date)
6146 + % VHost = list() -> "jabber.example.org"
6147 + % Date = list() -> "2007-02-12"
6148 + {rebuild_stats_at, 2},
6150 + % called from user_messages_at_parse_query/5
6151 + % it should delete selected user messages at date
6152 + % delete_messages_by_user_at(VHost, Msgs, Date) -> ok | error
6154 + % VHost = list() -> "jabber.example.org"
6155 + % Msgs = list() -> [ #msg1, msg2, ... ]
6156 + % Date = list() -> "2007-02-12"
6157 + {delete_messages_by_user_at, 3},
6159 + % called from user_messages_parse_query/4 | vhost_messages_at_parse_query/4
6160 + % it should delete all user messages at date
6161 + % delete_all_messages_by_user_at(User, VHost, Date) -> ok | error
6163 + % User = list() -> "admin"
6164 + % VHost = list() -> "jabber.example.org"
6165 + % Date = list() -> "2007-02-12"
6166 + {delete_all_messages_by_user_at, 3},
6168 + % called from vhost_messages_parse_query/3
6169 + % it should delete messages for vhost at date and update stats
6170 + % delete_messages_at(VHost, Date) -> ok | error
6172 + % VHost = list() -> "jabber.example.org"
6173 + % Date = list() -> "2007-02-12"
6174 + {delete_messages_at, 2},
6176 + % called from ejabberd_web_admin:vhost_messages_stats/3
6177 + % it should return sorted list of count of messages by dates for vhost
6178 + % get_vhost_stats(VHost) -> {ok, [{Date1, Msgs_count1}, {Date2, Msgs_count2}, ... ]} |
6181 + % VHost = list() -> "jabber.example.org"
6182 + % DateN = list() -> "2007-02-12"
6183 + % Msgs_countN = number() -> 241
6184 + {get_vhost_stats, 1},
6186 + % called from ejabberd_web_admin:vhost_messages_stats_at/4
6187 + % it should return sorted list of count of messages by users at date for vhost
6188 + % get_vhost_stats_at(VHost, Date) -> {ok, [{User1, Msgs_count1}, {User2, Msgs_count2}, ....]} |
6191 + % VHost = list() -> "jabber.example.org"
6192 + % Date = list() -> "2007-02-12"
6193 + % UserN = list() -> "admin"
6194 + % Msgs_countN = number() -> 241
6195 + {get_vhost_stats_at, 2},
6197 + % called from ejabberd_web_admin:user_messages_stats/4
6198 + % it should return sorted list of count of messages by date for user at vhost
6199 + % get_user_stats(User, VHost) -> {ok, [{Date1, Msgs_count1}, {Date2, Msgs_count2}, ...]} |
6202 + % User = list() -> "admin"
6203 + % VHost = list() -> "jabber.example.org"
6204 + % DateN = list() -> "2007-02-12"
6205 + % Msgs_countN = number() -> 241
6206 + {get_user_stats, 2},
6208 + % called from ejabberd_web_admin:user_messages_stats_at/5
6209 + % it should return all user messages at date
6210 + % get_user_messages_at(User, VHost, Date) -> {ok, Msgs} | {error, Reason}
6212 + % User = list() -> "admin"
6213 + % VHost = list() -> "jabber.example.org"
6214 + % Date = list() -> "2007-02-12"
6215 + % Msgs = list() -> [ #msg1, msg2, ... ]
6216 + {get_user_messages_at, 3},
6218 + % called from many places
6219 + % it should return list of dates for vhost
6220 + % get_dates(VHost) -> [Date1, Date2, ... ]
6222 + % VHost = list() -> "jabber.example.org"
6223 + % DateN = list() -> "2007-02-12"
6226 + % called from start
6227 + % it should return list with users settings for VHost in db
6228 + % get_users_settings(VHost) -> [#user_settings1, #user_settings2, ... ] | error
6230 + % VHost = list() -> "jabber.example.org"
6231 + {get_users_settings, 1},
6233 + % called from many places
6234 + % it should return User settings at VHost from db
6235 + % get_user_settings(User, VHost) -> error | {ok, #user_settings}
6237 + % User = list() -> "admin"
6238 + % VHost = list() -> "jabber.example.org"
6239 + {get_user_settings, 2},
6241 + % called from web admin
6242 + % it should set User settings at VHost
6243 + % set_user_settings(User, VHost, #user_settings) -> ok | error
6245 + % User = list() -> "admin"
6246 + % VHost = list() -> "jabber.example.org"
6247 + {set_user_settings, 3},
6249 + % called from remove_user (ejabberd hook)
6250 + % it should remove user messages and settings at VHost
6251 + % drop_user(User, VHost) -> ok | error
6253 + % User = list() -> "admin"
6254 + % VHost = list() -> "jabber.example.org"
6257 +behaviour_info(_) ->
6259 --- mod_muc/mod_muc_room-2.1.0.erl 2009-11-22 12:30:40.000000000 +0200
6260 +++ mod_muc/mod_muc_room.erl 2009-11-22 12:33:43.000000000 +0200
6261 @@ -625,6 +625,12 @@
6262 {reply, {ok, NSD#state.config}, StateName, NSD};
6263 handle_sync_event({change_state, NewStateData}, _From, StateName, _StateData) ->
6264 {reply, {ok, NewStateData}, StateName, NewStateData};
6265 +handle_sync_event({get_jid_nick, Jid}, _From, StateName, StateData) ->
6266 + R = case ?DICT:find(jlib:jid_tolower(Jid), StateData#state.users) of
6268 + {ok, {user, _, Nick, _, _}} -> Nick
6270 + {reply, R, StateName, StateData};
6271 handle_sync_event(_Event, _From, StateName, StateData) ->
6273 {reply, Reply, StateName, StateData}.
6274 --- msgs/uk-2.1.0.msg 2009-11-22 12:30:40.000000000 +0200
6275 +++ msgs/uk.msg 2009-11-22 12:36:12.000000000 +0200
6276 @@ -369,3 +369,31 @@
6277 {"You need an x:data capable client to search","Для пошуку необхідний x:data-придатний клієнт"}.
6278 {"Your contact offline message queue is full. The message has been discarded.","Черга повідомлень, що не були доставлені, переповнена. Повідомлення не було збережено."}.
6279 {"Your messages to ~s are being blocked. To unblock them, visit ~s","Ваші повідомлення до ~s блокуються. Для розблокування відвідайте ~s"}.
6281 +{"Users Messages", "Повідомлення користувачів"}.
6283 +{"Count", "Кількість"}.
6284 +{"Logged messages for ", "Збережені повідомлення для "}.
6286 +{"No logged messages for ", "Відсутні повідомлення для "}.
6287 +{"Date, Time", "Дата, Час"}.
6288 +{"Direction: Jid", "Напрямок: Jid"}.
6289 +{"Subject", "Тема"}.
6291 +{"Messages", "Повідомлення"}.
6292 +{"Filter Selected", "Відфільтрувати виділені"}.
6293 +{"Do Not Log Messages", "Не зберігати повідомлення"}.
6294 +{"Log Messages", "Зберігати повідомлення"}.
6295 +{"Messages logging engine", "Система збереження повідомлень"}.
6296 +{"Default", "За замовчуванням"}.
6297 +{"Set logging preferences", "Вкажіть налагоджування збереження повідомлень"}.
6298 +{"Messages logging engine users", "Користувачі системи збереження повідомлень"}.
6299 +{"Messages logging engine settings", "Налагоджування системи збереження повідомлень"}.
6300 +{"Set run-time settings", "Вкажіть поточні налагоджування"}.
6301 +{"Groupchat messages logging", "Збереження повідомлень типу groupchat"}.
6302 +{"Jids/Domains to ignore", "Ігнорувати наступні jids/домени"}.
6303 +{"Purge messages older than (days)", "Видаляти повідомлення старіші ніж (дні)"}.
6304 +{"Poll users settings (seconds)", "Оновлювати налагоджування користувачів кожні (секунд)"}.
6305 +{"Drop", "Видаляти"}.
6306 +{"Do not drop", "Не видаляти"}.
6307 +{"Drop messages on user removal", "Видаляти повідомлення під час видалення користувача"}.
6308 --- msgs/ru-2.1.0.msg 2009-11-22 12:30:40.000000000 +0200
6309 +++ msgs/ru.msg 2009-11-22 12:35:52.000000000 +0200
6310 @@ -369,3 +369,31 @@
6311 {"You need an x:data capable client to search","Чтобы воспользоваться поиском, требуется x:data-совместимый клиент"}.
6312 {"Your contact offline message queue is full. The message has been discarded.","Очередь недоставленных сообщений Вашего адресата переполнена. Сообщение не было сохранено."}.
6313 {"Your messages to ~s are being blocked. To unblock them, visit ~s","Ваши сообщения к ~s блокируются. Для снятия блокировки перейдите по ссылке ~s"}.
6315 +{"Users Messages", "Сообщения пользователей"}.
6317 +{"Count", "Количество"}.
6318 +{"Logged messages for ", "Сохранённые cообщения для "}.
6320 +{"No logged messages for ", "Отсутствуют сообщения для "}.
6321 +{"Date, Time", "Дата, Время"}.
6322 +{"Direction: Jid", "Направление: Jid"}.
6323 +{"Subject", "Тема"}.
6325 +{"Messages", "Сообщения"}.
6326 +{"Filter Selected", "Отфильтровать выделенные"}.
6327 +{"Do Not Log Messages", "Не сохранять сообщения"}.
6328 +{"Log Messages", "Сохранять сообщения"}.
6329 +{"Messages logging engine", "Система логирования сообщений"}.
6330 +{"Default", "По умолчанию"}.
6331 +{"Set logging preferences", "Задайте настройки логирования"}.
6332 +{"Messages logging engine users", "Пользователи системы логирования сообщений"}.
6333 +{"Messages logging engine settings", "Настройки системы логирования сообщений"}.
6334 +{"Set run-time settings", "Задайте текущие настройки"}.
6335 +{"Groupchat messages logging", "Логирование сообщений типа groupchat"}.
6336 +{"Jids/Domains to ignore", "Игнорировать следующие jids/домены"}.
6337 +{"Purge messages older than (days)", "Удалять сообщения старее чем (дни)"}.
6338 +{"Poll users settings (seconds)", "Обновлять настройки пользователей через (секунд)"}.
6339 +{"Drop", "Удалять"}.
6340 +{"Do not drop", "Не удалять"}.
6341 +{"Drop messages on user removal", "Удалять сообщения при удалении пользователя"}.
6342 --- msgs/pl-2.1.0.msg 2009-11-22 12:30:40.000000000 +0200
6343 +++ msgs/pl.msg 2009-11-22 12:35:07.000000000 +0200
6344 @@ -369,3 +369,27 @@
6345 {"You need an x:data capable client to search","Potrzebujesz klienta obsługującego x:data aby wyszukiwać"}.
6346 {"Your contact offline message queue is full. The message has been discarded.","Kolejka wiadomości offline adresata jest pełna. Wiadomość została odrzucona."}.
6347 {"Your messages to ~s are being blocked. To unblock them, visit ~s","Twoje wiadomości do użytkownika ~s są blokowane. Aby je odblokować, odwiedź ~s"}.
6349 +{"Users Messages", "Wiadomości użytkownika"}.
6351 +{"Count", "Liczba"}.
6352 +{"Logged messages for ", "Zapisane wiadomości dla "}.
6354 +{"No logged messages for ", "Brak zapisanych wiadomości dla "}.
6355 +{"Date, Time", "Data, Godzina"}.
6356 +{"Direction: Jid", "Kierunek: Jid"}.
6357 +{"Subject", "Temat"}.
6359 +{"Messages","Wiadomości"}.
6360 +{"Filter Selected", "Odfiltruj zaznaczone"}.
6361 +{"Do Not Log Messages", "Nie zapisuj wiadomości"}.
6362 +{"Log Messages", "Zapisuj wiadomości"}.
6363 +{"Messages logging engine", "System zapisywania historii rozmów"}.
6364 +{"Default", "Domyślne"}.
6365 +{"Set logging preferences", "Ustaw preferencje zapisywania"}.
6366 +{"Messages logging engine settings", "Ustawienia systemu logowania"}.
6367 +{"Set run-time settings", "Zapisz ustawienia systemu logowania"}.
6368 +{"Groupchat messages logging", "Zapisywanie rozmów z konferencji"}.
6369 +{"Jids/Domains to ignore", "JID/Domena która ma być ignorowana"}.
6370 +{"Purge messages older than (days)", "Usuń wiadomości starsze niż (w dniach)"}.
6371 +{"Poll users settings (seconds)", "Czas aktualizacji preferencji użytkowników (sekundy)"}.
6372 --- msgs/nl-2.1.0.msg 2009-11-22 12:30:40.000000000 +0200
6373 +++ msgs/nl.msg 2009-11-22 12:34:41.000000000 +0200
6374 @@ -369,3 +369,15 @@
6375 {"You need an x:data capable client to search","U hebt een client nodig die x:data ondersteunt om te zoeken"}.
6376 {"Your contact offline message queue is full. The message has been discarded.","Te veel offline berichten voor dit contactpersoon. Het bericht is niet opgeslagen."}.
6377 {"Your messages to ~s are being blocked. To unblock them, visit ~s","Uw berichten aan ~s worden geblokkeerd. Om ze te deblokkeren, ga naar ~s"}.
6379 +{"Users Messages", "Gebruikersberichten"}.
6381 +{"Count", "Aantal"}.
6382 +{"Logged messages for ", "Gelogde berichten van "}.
6384 +{"No logged messages for ", "Geen gelogde berichten van "}.
6385 +{"Date, Time", "Datum en tijd"}.
6386 +{"Direction: Jid", "Richting: Jabber ID"}.
6387 +{"Subject", "Onderwerp"}.
6388 +{"Body", "Berichtveld"}.
6389 +{"Messages", "Berichten"}.
6390 --- ./mod_roster-2.1.0.erl 2009-11-22 12:30:40.000000000 +0200
6391 +++ mod_roster.erl 2009-11-22 12:40:40.000000000 +0200
6393 -include("mod_roster.hrl").
6394 -include("web/ejabberd_http.hrl").
6395 -include("web/ejabberd_web_admin.hrl").
6397 +-include("mod_logdb.hrl").
6399 start(Host, Opts) ->
6400 IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
6401 @@ -937,6 +937,14 @@
6402 Res = user_roster_parse_query(User, Server, Items1, Query),
6403 Items = mnesia:dirty_index_read(roster, US, #roster.us),
6404 SItems = lists:sort(Items),
6406 + Settings = case gen_mod:is_loaded(Server, mod_logdb) of
6408 + mod_logdb:get_user_settings(User, Server);
6416 @@ -984,7 +992,33 @@
6419 ejabberd_web_admin:term_to_id(R#roster.jid),
6422 + case gen_mod:is_loaded(Server, mod_logdb) of
6424 + Peer = jlib:jid_to_string(R#roster.jid),
6425 + A = lists:member(Peer, Settings#user_settings.dolog_list),
6426 + B = lists:member(Peer, Settings#user_settings.donotlog_list),
6430 + {"donotlog", "Do Not Log Messages"};
6432 + {"dolog", "Log Messages"};
6433 + Settings#user_settings.dolog_default == true ->
6434 + {"donotlog", "Do Not Log Messages"};
6435 + Settings#user_settings.dolog_default == false ->
6436 + {"dolog", "Log Messages"}
6439 + ?XAE("td", [{"class", "valign"}],
6440 + [?INPUTT("submit",
6442 + ejabberd_web_admin:term_to_id(R#roster.jid),
6450 [?XC("h1", ?T("Roster of ") ++ us_to_list(US))] ++
6451 @@ -1084,11 +1118,42 @@
6452 {"subscription", "remove"}],
6461 + case lists:keysearch(
6462 + "donotlog" ++ ejabberd_web_admin:term_to_id(JID), 1, Query) of
6464 + Peer = jlib:jid_to_string(JID),
6465 + Settings = mod_logdb:get_user_settings(User, Server),
6466 + DNLL = case lists:member(Peer, Settings#user_settings.donotlog_list) of
6467 + false -> lists:append(Settings#user_settings.donotlog_list, [Peer]);
6468 + true -> Settings#user_settings.donotlog_list
6470 + DLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.dolog_list),
6471 + Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
6472 + % TODO: check returned value
6473 + ok = mod_logdb:set_user_settings(User, Server, Sett),
6476 + case lists:keysearch(
6477 + "dolog" ++ ejabberd_web_admin:term_to_id(JID), 1, Query) of
6479 + Peer = jlib:jid_to_string(JID),
6480 + Settings = mod_logdb:get_user_settings(User, Server),
6481 + DLL = case lists:member(Peer, Settings#user_settings.dolog_list) of
6482 + false -> lists:append(Settings#user_settings.dolog_list, [Peer]);
6483 + true -> Settings#user_settings.dolog_list
6485 + DNLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.donotlog_list),
6486 + Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
6487 + % TODO: check returned value
6488 + ok = mod_logdb:set_user_settings(User, Server, Sett),
6499 --- ./mod_roster_odbc-2.1.0.erl 2009-11-22 12:30:40.000000000 +0200
6500 +++ mod_roster_odbc.erl 2009-11-22 12:42:38.000000000 +0200
6502 -include("mod_roster.hrl").
6503 -include("web/ejabberd_http.hrl").
6504 -include("web/ejabberd_web_admin.hrl").
6506 +-include("mod_logdb.hrl").
6508 start(Host, Opts) ->
6509 IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
6510 @@ -1038,6 +1038,14 @@
6511 Res = user_roster_parse_query(User, Server, Items1, Query),
6512 Items = get_roster(LUser, LServer),
6513 SItems = lists:sort(Items),
6515 + Settings = case gen_mod:is_loaded(Server, mod_logdb) of
6517 + mod_logdb:get_user_settings(User, Server);
6525 @@ -1085,7 +1093,33 @@
6528 ejabberd_web_admin:term_to_id(R#roster.jid),
6531 + case gen_mod:is_loaded(Server, mod_logdb) of
6533 + Peer = jlib:jid_to_string(R#roster.jid),
6534 + A = lists:member(Peer, Settings#user_settings.dolog_list),
6535 + B = lists:member(Peer, Settings#user_settings.donotlog_list),
6539 + {"donotlog", "Do Not Log Messages"};
6541 + {"dolog", "Log Messages"};
6542 + Settings#user_settings.dolog_default == true ->
6543 + {"donotlog", "Do Not Log Messages"};
6544 + Settings#user_settings.dolog_default == false ->
6545 + {"dolog", "Log Messages"}
6548 + ?XAE("td", [{"class", "valign"}],
6549 + [?INPUTT("submit",
6551 + ejabberd_web_admin:term_to_id(R#roster.jid),
6559 [?XC("h1", ?T("Roster of ") ++ us_to_list(US))] ++
6560 @@ -1185,11 +1219,42 @@
6561 {"subscription", "remove"}],
6570 + case lists:keysearch(
6571 + "donotlog" ++ ejabberd_web_admin:term_to_id(JID), 1, Query) of
6573 + Peer = jlib:jid_to_string(JID),
6574 + Settings = mod_logdb:get_user_settings(User, Server),
6575 + DNLL = case lists:member(Peer, Settings#user_settings.donotlog_list) of
6576 + false -> lists:append(Settings#user_settings.donotlog_list, [Peer]);
6577 + true -> Settings#user_settings.donotlog_list
6579 + DLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.dolog_list),
6580 + Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
6581 + % TODO: check returned value
6582 + ok = mod_logdb:set_user_settings(User, Server, Sett),
6585 + case lists:keysearch(
6586 + "dolog" ++ ejabberd_web_admin:term_to_id(JID), 1, Query) of
6588 + Peer = jlib:jid_to_string(JID),
6589 + Settings = mod_logdb:get_user_settings(User, Server),
6590 + DLL = case lists:member(Peer, Settings#user_settings.dolog_list) of
6591 + false -> lists:append(Settings#user_settings.dolog_list, [Peer]);
6592 + true -> Settings#user_settings.dolog_list
6594 + DNLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.donotlog_list),
6595 + Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
6596 + % TODO: check returned value
6597 + ok = mod_logdb:set_user_settings(User, Server, Sett),