1 --- src/mod_logdb.erl.orig 2009-02-05 19:21:29.000000000 +0200
2 +++ src/mod_logdb.erl 2009-02-05 19:19:51.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 + process_flag(trap_exit, true),
108 + DBs = gen_mod:get_opt(dbs, Opts, [{mnesia, []}]),
109 + VHostDB = gen_mod:get_opt(vhosts, Opts, [{VHost, mnesia}]),
110 + % 10 is default becouse of using in clustered environment
111 + PollUsersSettings = gen_mod:get_opt(poll_users_settings, Opts, 10),
113 + {value,{_, DBName}} = lists:keysearch(VHost, 1, VHostDB),
114 + {value, {DBName, DBOpts}} = lists:keysearch(DBName, 1, DBs),
116 + ?MYDEBUG("Starting mod_logdb for ~p with ~p backend", [VHost, DBName]),
118 + DBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(DBName)),
120 + {ok, #state{vhost=VHost,
123 + % dbs used for convert messages from one backend to other
125 + dolog_default=gen_mod:get_opt(dolog_default, Opts, true),
126 + drop_messages_on_user_removal=gen_mod:get_opt(drop_messages_on_user_removal, Opts, true),
127 + ignore_jids=gen_mod:get_opt(ignore_jids, Opts, []),
128 + groupchat=gen_mod:get_opt(groupchat, Opts, none),
129 + purge_older_days=gen_mod:get_opt(purge_older_days, Opts, never),
130 + poll_users_settings=PollUsersSettings}}.
132 +cleanup(#state{vhost=VHost} = State) ->
133 + ?MYDEBUG("Stopping ~s for ~p", [?MODULE, VHost]),
135 + %ets:delete(ets_settings_table(VHost)),
137 + ejabberd_hooks:delete(remove_user, VHost, ?MODULE, remove_user, 90),
138 + ejabberd_hooks:delete(user_send_packet, VHost, ?MODULE, send_packet, 90),
139 + ejabberd_hooks:delete(user_receive_packet, VHost, ?MODULE, receive_packet, 90),
140 + ejabberd_hooks:delete(offline_message_hook, VHost, ?MODULE, offline_packet, 10),
141 + %ejabberd_hooks:delete(adhoc_sm_commands, VHost, ?MODULE, adhoc_sm_commands, 110),
142 + %ejabberd_hooks:delete(adhoc_sm_items, VHost, ?MODULE, adhoc_sm_items, 110),
143 + ejabberd_hooks:delete(adhoc_local_commands, VHost, ?MODULE, adhoc_local_commands, 110),
144 + ejabberd_hooks:delete(adhoc_local_items, VHost, ?MODULE, adhoc_local_items, 110),
145 + %ejabberd_hooks:delete(disco_sm_identity, VHost, ?MODULE, get_sm_identity, 110),
146 + %ejabberd_hooks:delete(disco_sm_features, VHost, ?MODULE, get_sm_features, 110),
147 + %ejabberd_hooks:delete(disco_sm_items, VHost, ?MODULE, get_sm_items, 110),
148 + ejabberd_hooks:delete(disco_local_identity, VHost, ?MODULE, get_local_identity, 110),
149 + ejabberd_hooks:delete(disco_local_features, VHost, ?MODULE, get_local_features, 110),
150 + ejabberd_hooks:delete(disco_local_items, VHost, ?MODULE, get_local_items, 110),
152 + ejabberd_hooks:delete(webadmin_menu_host, VHost, ?MODULE, webadmin_menu, 70),
153 + ejabberd_hooks:delete(webadmin_user, VHost, ?MODULE, webadmin_user, 50),
154 + ejabberd_hooks:delete(webadmin_page_host, VHost, ?MODULE, webadmin_page, 50),
155 + ejabberd_hooks:delete(webadmin_user_parse_query, VHost, ?MODULE, user_parse_query, 50),
157 + ?MYDEBUG("Removed hooks for ~p", [VHost]),
159 + ejabberd_ctl:unregister_commands(VHost, [{"rebuild_stats", "rebuild mod_logdb module stats for vhost"}], ?MODULE, rebuild_stats),
160 + Supported_backends = lists:flatmap(fun({Backend, _Opts}) ->
161 + [atom_to_list(Backend), " "]
162 + end, State#state.dbs),
163 + ejabberd_ctl:unregister_commands(
165 + [{"copy_messages backend", "copy messages from backend to current backend. backends could be: " ++ Supported_backends }],
166 + ?MODULE, copy_messages_ctl),
167 + ?MYDEBUG("Unregistered commands for ~p", [VHost]).
170 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
171 + %gen_server:call(Proc, {cleanup}),
172 + %?MYDEBUG("Cleanup in stop finished!!!!", []),
173 + %timer:sleep(10000),
174 + ok = supervisor:terminate_child(ejabberd_sup, Proc),
175 + ok = supervisor:delete_child(ejabberd_sup, Proc).
177 +handle_call({cleanup}, _From, State) ->
179 + ?MYDEBUG("Cleanup finished!!!!!", []),
180 + {reply, ok, State};
181 +handle_call({get_dates}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
182 + Reply = DBMod:get_dates(VHost),
183 + {reply, Reply, State};
184 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
185 +% ejabberd_web_admin callbacks
186 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
187 +handle_call({delete_messages_by_user_at, PMsgs, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
188 + Reply = DBMod:delete_messages_by_user_at(VHost, PMsgs, Date),
189 + {reply, Reply, State};
190 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
191 + Reply = DBMod:delete_all_messages_by_user_at(User, VHost, Date),
192 + {reply, Reply, State};
193 +handle_call({delete_messages_at, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
194 + Reply = DBMod:delete_messages_at(VHost, Date),
195 + {reply, Reply, State};
196 +handle_call({get_vhost_stats}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
197 + Reply = DBMod:get_vhost_stats(VHost),
198 + {reply, Reply, State};
199 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
200 + Reply = DBMod:get_vhost_stats_at(VHost, Date),
201 + {reply, Reply, State};
202 +handle_call({get_user_stats, User}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
203 + Reply = DBMod:get_user_stats(User, VHost),
204 + {reply, Reply, State};
205 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
206 + Reply = DBMod:get_user_messages_at(User, VHost, Date),
207 + {reply, Reply, State};
208 +handle_call({get_user_settings, User}, _From, #state{dbmod=_DBMod, vhost=VHost}=State) ->
209 + Reply = case ets:match_object(ets_settings_table(VHost),
210 + #user_settings{owner_name=User, _='_'}) of
212 + _ -> #user_settings{owner_name=User,
213 + dolog_default=State#state.dolog_default,
217 + {reply, Reply, State};
218 +% TODO: remove User ??
219 +handle_call({set_user_settings, User, GSet}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
220 + Set = GSet#user_settings{owner_name=User},
222 + case ets:match_object(ets_settings_table(VHost),
223 + #user_settings{owner_name=User, _='_'}) of
225 + ?MYDEBUG("Settings is equal", []),
228 + case DBMod:set_user_settings(User, VHost, Set) of
232 + true = ets:insert(ets_settings_table(VHost), Set),
236 + {reply, Reply, State};
237 +handle_call({get_module_settings}, _From, State) ->
238 + {reply, State, State};
239 +handle_call({set_module_settings, #state{purge_older_days=PurgeDays,
240 + poll_users_settings=PollSec} = Settings},
242 + #state{purgeRef=PurgeRefOld,
243 + pollRef=PollRefOld,
244 + purge_older_days=PurgeDaysOld,
245 + poll_users_settings=PollSecOld} = State) ->
247 + PurgeDays == never, PurgeDaysOld /= never ->
248 + {ok, cancel} = timer:cancel(PurgeRefOld),
250 + is_integer(PurgeDays), PurgeDaysOld == never ->
251 + set_purge_timer(PurgeDays);
257 + PollSec == PollSecOld ->
259 + PollSec == 0, PollSecOld /= 0 ->
260 + {ok, cancel} = timer:cancel(PollRefOld),
262 + is_integer(PollSec), PollSecOld == 0 ->
263 + set_poll_timer(PollSec);
264 + is_integer(PollSec), PollSecOld /= 0 ->
265 + {ok, cancel} = timer:cancel(PollRefOld),
266 + set_poll_timer(PollSec)
269 + NewState = State#state{dolog_default=Settings#state.dolog_default,
270 + ignore_jids=Settings#state.ignore_jids,
271 + groupchat=Settings#state.groupchat,
272 + drop_messages_on_user_removal=Settings#state.drop_messages_on_user_removal,
273 + purge_older_days=PurgeDays,
274 + poll_users_settings=PollSec,
277 + {reply, ok, NewState};
278 +handle_call(Msg, _From, State) ->
279 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
281 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
282 +% end ejabberd_web_admin callbacks
283 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
285 +% ejabberd_hooks call
286 +handle_cast({addlog, Direction, Owner, Peer, Packet}, #state{dbmod=DBMod, vhost=VHost}=State) ->
287 + case filter(Owner, Peer, State) of
289 + case catch packet_parse(Owner, Peer, Packet, Direction, State) of
292 + {'EXIT', Reason} ->
293 + ?ERROR_MSG("Failed to parse: ~p", [Reason]);
295 + DBMod:log_message(VHost, Msg)
301 +handle_cast({remove_user, User}, #state{dbmod=DBMod, vhost=VHost}=State) ->
302 + case State#state.drop_messages_on_user_removal of
304 + DBMod:drop_user(User, VHost),
305 + ?INFO_MSG("Launched ~s@~s removal", [User, VHost]);
307 + ?INFO_MSG("Message removing is disabled. Keeping messages for ~s@~s", [User, VHost])
310 +% ejabberdctl rebuild_stats/3
311 +handle_cast({rebuild_stats}, #state{dbmod=DBMod, vhost=VHost}=State) ->
312 + DBMod:rebuild_stats(VHost),
314 +handle_cast({copy_messages, Backend}, State) ->
315 + spawn(?MODULE, copy_messages, [[State, Backend]]),
317 +handle_cast({copy_messages, Backend, Date}, State) ->
318 + spawn(?MODULE, copy_messages, [[State, Backend, Date]]),
320 +handle_cast(Msg, State) ->
321 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
324 +% return: disabled | timer reference
325 +set_purge_timer(PurgeDays) ->
328 + Days when is_integer(Days) ->
329 + {ok, Ref1} = timer:send_interval(timer:hours(24), scheduled_purging),
333 +% return: disabled | timer reference
334 +set_poll_timer(PollSec) ->
337 + {ok, Ref2} = timer:send_interval(timer:seconds(PollSec), poll_users_settings),
339 + % db polling disabled
343 + {ok, Ref3} = timer:send_interval(timer:seconds(10), poll_users_settings),
347 +% actual starting of logging
348 +% from timer:send_after (in init)
349 +handle_info(start, #state{dbmod=DBMod, vhost=VHost}=State) ->
350 + case DBMod:start(VHost, State#state.dbopts) of
351 + {error,{already_started,_}} ->
352 + ?MYDEBUG("backend module already started - trying to stop it", []),
354 + {stop, already_started, State};
356 + timer:sleep(30000),
357 + ?ERROR_MSG("Failed to start: ~p", [Reason]),
358 + {stop, db_connection_failed, State};
360 + ?INFO_MSG("~p connection established", [DBMod]),
362 + MonRef = erlang:monitor(process, SPid),
364 + ets:new(ets_settings_table(VHost), [named_table,public,set,{keypos, #user_settings.owner_name}]),
365 + {ok, DoLog} = DBMod:get_users_settings(VHost),
366 + ets:insert(ets_settings_table(VHost), DoLog),
368 + TrefPurge = set_purge_timer(State#state.purge_older_days),
369 + TrefPoll = set_poll_timer(State#state.poll_users_settings),
371 + ejabberd_hooks:add(remove_user, VHost, ?MODULE, remove_user, 90),
372 + ejabberd_hooks:add(user_send_packet, VHost, ?MODULE, send_packet, 90),
373 + ejabberd_hooks:add(user_receive_packet, VHost, ?MODULE, receive_packet, 90),
374 + ejabberd_hooks:add(offline_message_hook, VHost, ?MODULE, offline_packet, 10),
376 + ejabberd_hooks:add(disco_local_items, VHost, ?MODULE, get_local_items, 110),
377 + ejabberd_hooks:add(disco_local_features, VHost, ?MODULE, get_local_features, 110),
378 + ejabberd_hooks:add(disco_local_identity, VHost, ?MODULE, get_local_identity, 110),
379 + %ejabberd_hooks:add(disco_sm_items, VHost, ?MODULE, get_sm_items, 110),
380 + %ejabberd_hooks:add(disco_sm_features, VHost, ?MODULE, get_sm_features, 110),
381 + %ejabberd_hooks:add(disco_sm_identity, VHost, ?MODULE, get_sm_identity, 110),
382 + ejabberd_hooks:add(adhoc_local_items, VHost, ?MODULE, adhoc_local_items, 110),
383 + ejabberd_hooks:add(adhoc_local_commands, VHost, ?MODULE, adhoc_local_commands, 110),
384 + %ejabberd_hooks:add(adhoc_sm_items, VHost, ?MODULE, adhoc_sm_items, 110),
385 + %ejabberd_hooks:add(adhoc_sm_commands, VHost, ?MODULE, adhoc_sm_commands, 110),
387 + ejabberd_hooks:add(webadmin_menu_host, VHost, ?MODULE, webadmin_menu, 70),
388 + ejabberd_hooks:add(webadmin_user, VHost, ?MODULE, webadmin_user, 50),
389 + ejabberd_hooks:add(webadmin_page_host, VHost, ?MODULE, webadmin_page, 50),
390 + ejabberd_hooks:add(webadmin_user_parse_query, VHost, ?MODULE, user_parse_query, 50),
392 + ?MYDEBUG("Added hooks for ~p", [VHost]),
394 + ejabberd_ctl:register_commands(
396 + [{"rebuild_stats", "rebuild mod_logdb module stats for vhost"}],
397 + ?MODULE, rebuild_stats),
398 + Supported_backends = lists:flatmap(fun({Backend, _Opts}) ->
399 + [atom_to_list(Backend), " "]
400 + end, State#state.dbs),
401 + ejabberd_ctl:register_commands(
403 + [{"copy_messages backend", "copy messages from backend to current backend. backends could be: " ++ Supported_backends }],
404 + ?MODULE, copy_messages_ctl),
405 + ?MYDEBUG("Registered commands for ~p", [VHost]),
407 + NewState=State#state{monref = MonRef, backendPid=SPid, purgeRef=TrefPurge, pollRef=TrefPoll},
408 + {noreply, NewState};
410 + ?ERROR_MSG("Rez=~p", [Rez]),
411 + timer:sleep(30000),
412 + {stop, db_connection_failed, State}
414 +% from timer:send_interval/2 (in start handle_info)
415 +handle_info(scheduled_purging, #state{vhost=VHost, purge_older_days=Days} = State) ->
416 + ?MYDEBUG("Starting scheduled purging of old records for ~p", [VHost]),
417 + spawn(?MODULE, purge_old_records, [VHost, integer_to_list(Days)]),
419 +% from timer:send_interval/2 (in start handle_info)
420 +handle_info(poll_users_settings, #state{dbmod=DBMod, vhost=VHost}=State) ->
421 + {ok, DoLog} = DBMod:get_users_settings(VHost),
422 + ?MYDEBUG("DoLog=~p", [DoLog]),
423 + true = ets:delete_all_objects(ets_settings_table(VHost)),
424 + ets:insert(ets_settings_table(VHost), DoLog),
426 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
427 + {stop, db_connection_dropped, State};
428 +handle_info({fetch_result, _, _}, State) ->
429 + ?MYDEBUG("Got timed out mysql fetch result", []),
431 +handle_info(Info, State) ->
432 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
435 +terminate(db_connection_failed, _State) ->
437 +terminate(db_connection_dropped, State) ->
438 + ?MYDEBUG("Got terminate with db_connection_dropped", []),
441 +terminate(Reason, #state{monref=undefined} = State) ->
442 + ?MYDEBUG("Got terminate with undefined monref.~nReason: ~p", [Reason]),
445 +terminate(Reason, #state{dbmod=DBMod, vhost=VHost, monref=MonRef, backendPid=Pid} = State) ->
446 + ?INFO_MSG("Reason: ~p", [Reason]),
447 + case erlang:is_process_alive(Pid) of
449 + erlang:demonitor(MonRef, [flush]),
457 +code_change(_OldVsn, State, _Extra) ->
460 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
462 +% ejabberd_hooks callbacks
464 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
465 +% TODO: change to/from to list as sql stores it as list
466 +send_packet(Owner, Peer, P) ->
467 + VHost = Owner#jid.lserver,
468 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
469 + gen_server:cast(Proc, {addlog, to, Owner, Peer, P}).
471 +offline_packet(Peer, Owner, P) ->
472 + VHost = Owner#jid.lserver,
473 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
474 + gen_server:cast(Proc, {addlog, from, Owner, Peer, P}).
476 +receive_packet(_JID, Peer, Owner, P) ->
477 + VHost = Owner#jid.lserver,
478 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
479 + gen_server:cast(Proc, {addlog, from, Owner, Peer, P}).
481 +remove_user(User, Server) ->
482 + LUser = jlib:nodeprep(User),
483 + LServer = jlib:nameprep(Server),
484 + Proc = gen_mod:get_module_proc(LServer, ?PROCNAME),
485 + gen_server:cast(Proc, {remove_user, LUser}).
487 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
491 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
492 +rebuild_stats(_Val, VHost, ["rebuild_stats"]) ->
493 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
494 + gen_server:cast(Proc, {rebuild_stats}),
495 + {stop, ?STATUS_SUCCESS};
496 +rebuild_stats(Val, _VHost, _Args) ->
499 +copy_messages_ctl(_Val, VHost, ["copy_messages", Backend]) ->
500 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
501 + gen_server:cast(Proc, {copy_messages, Backend}),
502 + {stop, ?STATUS_SUCCESS};
503 +copy_messages_ctl(_Val, VHost, ["copy_messages", Backend, Date]) ->
504 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
505 + gen_server:cast(Proc, {copy_messages, Backend, Date}),
506 + {stop, ?STATUS_SUCCESS};
507 +copy_messages_ctl(Val, _VHost, _Args) ->
509 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
513 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
515 +% handle_cast({addlog, E}, _)
516 +% raw packet -> #msg
517 +packet_parse(Owner, Peer, Packet, Direction, State) ->
518 + case xml:get_subtag(Packet, "body") of
523 + case xml:get_tag_attr_s("type", Packet) of
528 + case Message_type of
529 + "groupchat" when State#state.groupchat == send, Direction == to ->
531 + "groupchat" when State#state.groupchat == send, Direction == from ->
533 + "groupchat" when State#state.groupchat == half ->
534 + Rooms = ets:match(muc_online_room, '$1'),
535 + Ni=lists:foldl(fun([{muc_online_room, {GName, GHost}, Pid}], Names) ->
536 + case gen_fsm:sync_send_all_state_event(Pid, {get_jid_nick,Owner}) of
539 + lists:append(Names, [jlib:jid_to_string({GName, GHost, Nick})])
542 + case lists:member(jlib:jid_to_string(Peer), Ni) of
543 + true when Direction == from ->
548 + "groupchat" when State#state.groupchat == none ->
554 + Message_body = xml:get_tag_cdata(Body_xml),
556 + case xml:get_subtag(Packet, "subject") of
560 + xml:get_tag_cdata(Subject_xml)
563 + OwnerName = stringprep:tolower(Owner#jid.user),
564 + PName = stringprep:tolower(Peer#jid.user),
565 + PServer = stringprep:tolower(Peer#jid.server),
566 + PResource = Peer#jid.resource,
568 + #msg{timestamp=get_timestamp(),
569 + owner_name=OwnerName,
571 + peer_server=PServer,
572 + peer_resource=PResource,
573 + direction=Direction,
575 + subject=Message_subject,
579 +% called from handle_cast({addlog, _}, _) -> true (log messages) | false (do not log messages)
580 +filter(Owner, Peer, State) ->
581 + OwnerStr = Owner#jid.luser++"@"++Owner#jid.lserver,
582 + OwnerServ = "@"++Owner#jid.lserver,
583 + PeerStr = Peer#jid.luser++"@"++Peer#jid.lserver,
584 + PeerServ = "@"++Peer#jid.lserver,
586 + LogTo = case ets:match_object(ets_settings_table(State#state.vhost),
587 + #user_settings{owner_name=Owner#jid.luser, _='_'}) of
588 + [#user_settings{dolog_default=Default,
590 + donotlog_list=DNLL}] ->
591 + A = lists:member(PeerStr, DLL),
592 + B = lists:member(PeerStr, DNLL),
596 + Default == true -> true;
597 + Default == false -> false;
598 + true -> State#state.dolog_default
600 + _ -> State#state.dolog_default
603 + lists:all(fun(O) -> O end,
604 + [not lists:member(OwnerStr, State#state.ignore_jids),
605 + not lists:member(PeerStr, State#state.ignore_jids),
606 + not lists:member(OwnerServ, State#state.ignore_jids),
607 + not lists:member(PeerServ, State#state.ignore_jids),
610 +purge_old_records(VHost, Days) ->
611 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
613 + Dates = ?MODULE:get_dates(VHost),
614 + DateNow = calendar:datetime_to_gregorian_seconds({date(), {0,0,1}}),
615 + DateDiff = list_to_integer(Days)*24*60*60,
616 + ?MYDEBUG("Purging tables older than ~s days", [Days]),
617 + lists:foreach(fun(Date) ->
618 + {ok, [Year, Month, Day]} = regexp:split(Date, "[^0-9]+"),
619 + DateInSec = calendar:datetime_to_gregorian_seconds({{list_to_integer(Year), list_to_integer(Month), list_to_integer(Day)}, {0,0,1}}),
621 + (DateNow - DateInSec) > DateDiff ->
622 + gen_server:call(Proc, {delete_messages_at, Date});
624 + ?MYDEBUG("Skipping messages at ~p", [Date])
628 +% called from get_vhost_stats/2, get_user_stats/3
629 +sort_stats(Stats) ->
630 + % Stats = [{"2003-4-15",1}, {"2006-8-18",1}, ... ]
631 + CFun = fun({TableName, Count}) ->
632 + {ok, [Year, Month, Day]} = regexp:split(TableName, "[^0-9]+"),
633 + { calendar:datetime_to_gregorian_seconds({{list_to_integer(Year), list_to_integer(Month), list_to_integer(Day)}, {0,0,1}}), Count }
635 + % convert to [{63364377601,1}, {63360662401,1}, ... ]
636 + CStats = lists:map(CFun, Stats),
638 + SortedStats = lists:reverse(lists:keysort(1, CStats)),
639 + % convert to [{"2007-12-9",1}, {"2007-10-27",1}, ... ] sorted list
640 + [{mod_logdb:convert_timestamp_brief(TableSec), Count} || {TableSec, Count} <- SortedStats].
642 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
644 +% Date/Time operations
646 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
647 +% return float seconds elapsed from "zero hour" as list
649 + {MegaSec, Sec, MicroSec} = now(),
650 + [List] = io_lib:format("~.5f", [MegaSec*1000000 + Sec + MicroSec/1000000]),
653 +% convert float seconds elapsed from "zero hour" to local time "%Y-%m-%d %H:%M:%S" string
654 +convert_timestamp(Seconds) when is_list(Seconds) ->
655 + case string:to_float(Seconds++".0") of
656 + {F,_} when is_float(F) -> convert_timestamp(F);
657 + _ -> erlang:error(badarg, [Seconds])
659 +convert_timestamp(Seconds) when is_float(Seconds) ->
660 + GregSec = trunc(Seconds + 719528*86400),
661 + UnivDT = calendar:gregorian_seconds_to_datetime(GregSec),
662 + {{Year, Month, Day},{Hour, Minute, Sec}} = calendar:universal_time_to_local_time(UnivDT),
663 + integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day) ++ " " ++ integer_to_list(Hour) ++ ":" ++ integer_to_list(Minute) ++ ":" ++ integer_to_list(Sec).
665 +% convert float seconds elapsed from "zero hour" to local time "%Y-%m-%d" string
666 +convert_timestamp_brief(Seconds) when is_list(Seconds) ->
667 + convert_timestamp_brief(list_to_float(Seconds));
668 +convert_timestamp_brief(Seconds) when is_float(Seconds) ->
669 + GregSec = trunc(Seconds + 719528*86400),
670 + UnivDT = calendar:gregorian_seconds_to_datetime(GregSec),
671 + {{Year, Month, Day},{_Hour, _Minute, _Sec}} = calendar:universal_time_to_local_time(UnivDT),
672 + integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day);
673 +convert_timestamp_brief(Seconds) when is_integer(Seconds) ->
674 + {{Year, Month, Day},{_Hour, _Minute, _Sec}} = calendar:gregorian_seconds_to_datetime(Seconds),
675 + integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day).
677 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
679 +% DB operations (get)
681 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
682 +get_vhost_stats(VHost) ->
683 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
684 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
686 +get_vhost_stats_at(VHost, Date) ->
687 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
688 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
690 +get_user_stats(User, VHost) ->
691 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
692 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
694 +get_user_messages_at(User, VHost, Date) ->
695 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
696 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
699 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
700 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
702 +get_user_settings(User, VHost) ->
703 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
704 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
706 +set_user_settings(User, VHost, Set) ->
707 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
708 + gen_server:call(Proc, {set_user_settings, User, Set}).
710 +get_module_settings(VHost) ->
711 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
712 + gen_server:call(Proc, {get_module_settings}).
714 +set_module_settings(VHost, Settings) ->
715 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
716 + gen_server:call(Proc, {set_module_settings, Settings}).
718 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
720 +% Web admin callbacks (delete)
722 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
723 +user_messages_at_parse_query(VHost, Date, Msgs, Query) ->
724 + case lists:keysearch("delete", 1, Query) of
726 + PMsgs = lists:filter(
728 + ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg#msg.timestamp))),
729 + lists:member({"selected", ID}, Query)
731 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
732 + gen_server:call(Proc, {delete_messages_by_user_at, PMsgs, Date}, ?CALL_TIMEOUT);
737 +user_messages_parse_query(User, VHost, Query) ->
738 + case lists:keysearch("delete", 1, Query) of
740 + Dates = get_dates(VHost),
741 + PDates = lists:filter(
743 + ID = jlib:encode_base64(binary_to_list(term_to_binary(User++Date))),
744 + lists:member({"selected", ID}, Query)
746 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
750 + [gen_server:call(Proc,
751 + {delete_all_messages_by_user_at, User, Date},
754 + case lists:member(error, Rez) of
764 +vhost_messages_parse_query(VHost, Query) ->
765 + case lists:keysearch("delete", 1, Query) of
767 + Dates = get_dates(VHost),
768 + PDates = lists:filter(
770 + ID = jlib:encode_base64(binary_to_list(term_to_binary(VHost++Date))),
771 + lists:member({"selected", ID}, Query)
773 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
774 + Rez = lists:foldl(fun(Date, Acc) ->
775 + lists:append(Acc, [gen_server:call(Proc,
776 + {delete_messages_at, Date},
779 + case lists:member(error, Rez) of
789 +vhost_messages_at_parse_query(VHost, Date, Stats, Query) ->
790 + case lists:keysearch("delete", 1, Query) of
792 + PStats = lists:filter(
793 + fun({User, _Count}) ->
794 + ID = jlib:encode_base64(binary_to_list(term_to_binary(User++VHost))),
795 + lists:member({"selected", ID}, Query)
797 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
798 + Rez = lists:foldl(fun({User, _Count}, Acc) ->
799 + lists:append(Acc, [gen_server:call(Proc,
800 + {delete_all_messages_by_user_at,
804 + case lists:member(error, Rez) of
814 +copy_messages([#state{vhost=VHost}=State, From]) ->
815 + ?INFO_MSG("Going to copy messages from ~p for ~p", [From, VHost]),
817 + {FromDBName, FromDBOpts} =
818 + case lists:keysearch(list_to_atom(From), 1, State#state.dbs) of
819 + {value, {FN, FO}} ->
822 + ?ERROR_MSG("Failed to find record for ~p in dbs", [From]),
826 + FromDBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(FromDBName)),
828 + {ok, _FromPid} = FromDBMod:start(VHost, FromDBOpts),
830 + Dates = FromDBMod:get_dates(VHost),
831 + DatesLength = length(Dates),
833 + lists:foldl(fun(Date, Acc) ->
834 + case copy_messages_int([FromDBMod, State#state.dbmod, VHost, Date]) of
836 + ?INFO_MSG("Copied messages at ~p (~p/~p)", [Date, Acc, DatesLength]);
838 + ?ERROR_MSG("Failed to copy messages at ~p (~p/~p): ~p", [Date, Acc, DatesLength, Value]),
839 + FromDBMod:stop(VHost),
844 + ?INFO_MSG("Copied messages from ~p", [From]),
845 + FromDBMod:stop(VHost);
846 +copy_messages([#state{vhost=VHost}=State, From, Date]) ->
847 + {value, {FromDBName, FromDBOpts}} = lists:keysearch(list_to_atom(From), 1, State#state.dbs),
848 + FromDBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(FromDBName)),
849 + {ok, _FromPid} = FromDBMod:start(VHost, FromDBOpts),
850 + case catch copy_messages_int([FromDBMod, State#state.dbmod, VHost, Date]) of
851 + {'exit', Reason} ->
852 + ?ERROR_MSG("Failed to copy messages at ~p: ~p", [Date, Reason]);
854 + ?INFO_MSG("Copied messages at ~p", [Date]);
856 + ?ERROR_MSG("Failed to copy messages at ~p: ~p", [Date, Value])
858 + FromDBMod:stop(VHost).
860 +copy_messages_int([FromDBMod, ToDBMod, VHost, Date]) ->
861 + ets:new(mod_logdb_temp, [named_table, set, public]),
862 + {Time, Value} = timer:tc(?MODULE, copy_messages_int_tc, [[FromDBMod, ToDBMod, VHost, Date]]),
863 + ets:delete_all_objects(mod_logdb_temp),
864 + ets:delete(mod_logdb_temp),
865 + ?INFO_MSG("copy_messages at ~p elapsed ~p sec", [Date, Time/1000000]),
868 +copy_messages_int_tc([FromDBMod, ToDBMod, VHost, Date]) ->
869 + ?INFO_MSG("Going to copy messages from ~p for ~p at ~p", [FromDBMod, VHost, Date]),
871 + ok = FromDBMod:rebuild_stats_at(VHost, Date),
872 + catch mod_logdb:rebuild_stats_at(VHost, Date),
873 + {ok, FromStats} = FromDBMod:get_vhost_stats_at(VHost, Date),
874 + ToStats = case mod_logdb:get_vhost_stats_at(VHost, Date) of
875 + {ok, Stats} -> Stats;
879 + FromStatsS = lists:keysort(1, FromStats),
880 + ToStatsS = lists:keysort(1, ToStats),
882 + StatsLength = length(FromStats),
885 + % destination table is empty
886 + FromDBMod /= mod_logdb_mnesia_old, ToStats == [] ->
887 + fun({User, _Count}, Acc) ->
888 + {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date),
890 + lists:foldl(fun(Msg, MFAcc) ->
891 + ok = ToDBMod:log_message(VHost, Msg),
895 + ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
899 + % destination table is not empty
900 + FromDBMod /= mod_logdb_mnesia_old, ToStats /= [] ->
901 + fun({User, _Count}, Acc) ->
902 + {ok, ToMsgs} = ToDBMod:get_user_messages_at(User, VHost, Date),
903 + lists:foreach(fun(#msg{timestamp=Tst}) when length(Tst) == 16 ->
904 + ets:insert(mod_logdb_temp, {Tst});
905 + % mysql, pgsql removes final zeros after decimal point
906 + (#msg{timestamp=Tst}) when length(Tst) < 16 ->
907 + {F, _} = string:to_float(Tst++".0"),
908 + [T] = io_lib:format("~.5f", [F]),
909 + ets:insert(mod_logdb_temp, {T})
911 + {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date),
913 + lists:foldl(fun(#msg{timestamp=ToTimestamp} = Msg, MFAcc) ->
914 + case ets:member(mod_logdb_temp, ToTimestamp) of
916 + ok = ToDBMod:log_message(VHost, Msg),
917 + ets:insert(mod_logdb_temp, {ToTimestamp}),
924 + ets:delete_all_objects(mod_logdb_temp),
925 + ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
929 + % copying from mod_logmnesia
931 + fun({User, _Count}, Acc) ->
933 + case ToDBMod:get_user_messages_at(User, VHost, Date) of
937 + lists:foreach(fun(#msg{timestamp=Tst}) when length(Tst) == 16 ->
938 + ets:insert(mod_logdb_temp, {Tst});
939 + % mysql, pgsql removes final zeros after decimal point
940 + (#msg{timestamp=Tst}) when length(Tst) < 15 ->
941 + {F, _} = string:to_float(Tst++".0"),
942 + [T] = io_lib:format("~.5f", [F]),
943 + ets:insert(mod_logdb_temp, {T})
948 + {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date),
952 + fun({msg, TU, TS, TR, FU, FS, FR, Type, Subj, Body, Timest},
954 + [Timestamp] = if is_float(Timest) == true ->
955 + io_lib:format("~.5f", [Timest]);
956 + % early versions of mod_logmnesia
957 + is_integer(Timest) == true ->
958 + io_lib:format("~.5f", [Timest-719528*86400.0]);
960 + ?ERROR_MSG("Incorrect timestamp ~p", [Timest]),
963 + case ets:member(mod_logdb_temp, Timestamp) of
968 + TMsg = #msg{timestamp=Timestamp,
970 + peer_name=FU, peer_server=FS, peer_resource=FR,
973 + subject=Subj, body=Body},
974 + ok = ToDBMod:log_message(VHost, TMsg);
980 + FMsg = #msg{timestamp=Timestamp,
982 + peer_name=TU, peer_server=TS, peer_resource=TR,
985 + subject=Subj, body=Body},
986 + ok = ToDBMod:log_message(VHost, FMsg);
989 + ets:insert(mod_logdb_temp, {Timestamp}),
991 + true -> % not ets:member
994 + end, 0, Msgs), % foldl
996 + ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
1000 + end, % if FromDBMod /= mod_logdb_mnesia_old
1003 + FromStats == [] ->
1004 + ?INFO_MSG("No messages were found at ~p", [Date]);
1005 + FromStatsS == ToStatsS ->
1006 + ?INFO_MSG("Stats are equal at ~p", [Date]);
1007 + FromStatsS /= ToStatsS ->
1008 + lists:foldl(CopyFun, 0, FromStats),
1009 + ok = ToDBMod:rebuild_stats_at(VHost, Date)
1010 + %timer:sleep(1000)
1015 +list_to_bool(Num) ->
1016 + case lists:member(Num, ["t", "true", "y", "yes", "1"]) of
1020 + case lists:member(Num, ["f", "false", "n", "no", "0"]) of
1028 +bool_to_list(true) ->
1030 +bool_to_list(false) ->
1033 +list_to_string([]) ->
1035 +list_to_string(List) when is_list(List) ->
1036 + Str = lists:flatmap(fun(Elm) -> Elm ++ "\n" end, List),
1037 + lists:sublist(Str, length(Str)-1).
1039 +string_to_list(null) ->
1041 +string_to_list([]) ->
1043 +string_to_list(String) ->
1044 + {ok, List} = regexp:split(String, "\n"),
1047 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1049 +% ad-hoc (copy/pasted from mod_configure.erl)
1051 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1052 +-define(ITEMS_RESULT(Allow, LNode, Fallback),
1057 + case get_local_items(LServer, LNode,
1058 + jlib:jid_to_string(To), Lang) of
1066 +get_local_items(Acc, From, #jid{lserver = LServer} = To, "", Lang) ->
1067 + case gen_mod:is_loaded(LServer, mod_adhoc) of
1071 + Items = case Acc of
1072 + {result, Its} -> Its;
1075 + AllowUser = acl:match_rule(LServer, mod_logdb, From),
1076 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1078 + AllowUser == allow; AllowAdmin == allow ->
1079 + case get_local_items(LServer, [],
1080 + jlib:jid_to_string(To), Lang) of
1082 + {result, Items ++ Res};
1083 + {error, _Error} ->
1090 +get_local_items(Acc, From, #jid{lserver = LServer} = To, Node, Lang) ->
1091 + case gen_mod:is_loaded(LServer, mod_adhoc) of
1095 + LNode = string:tokens(Node, "/"),
1096 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1099 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1100 + ["mod_logdb_users"] ->
1101 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1102 + ["mod_logdb_users", [$@ | _]] ->
1103 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1104 + ["mod_logdb_users", _User] ->
1105 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1106 + ["mod_logdb_settings"] ->
1107 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1113 +-define(NODE(Name, Node),
1114 + {xmlelement, "item",
1116 + {"name", translate:translate(Lang, Name)},
1117 + {"node", Node}], []}).
1119 +get_local_items(_Host, [], Server, Lang) ->
1121 + [?NODE("Messages logging engine", "mod_logdb")]
1123 +get_local_items(_Host, ["mod_logdb"], Server, Lang) ->
1125 + [?NODE("Messages logging engine users", "mod_logdb_users"),
1126 + ?NODE("Messages logging engine settings", "mod_logdb_settings")]
1128 +get_local_items(Host, ["mod_logdb_users"], Server, Lang) ->
1129 + {result, get_all_vh_users(Host, Server, Lang)};
1130 +get_local_items(_Host, ["mod_logdb_users", [$@ | Diap]], Server, Lang) ->
1131 + case catch ejabberd_auth:dirty_get_registered_users() of
1132 + {'EXIT', _Reason} ->
1133 + ?ERR_INTERNAL_SERVER_ERROR;
1135 + SUsers = lists:sort([{S, U} || {U, S} <- Users]),
1137 + {ok, [S1, S2]} = regexp:split(Diap, "-"),
1138 + N1 = list_to_integer(S1),
1139 + N2 = list_to_integer(S2),
1140 + Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
1141 + lists:map(fun({S, U}) ->
1142 + ?NODE(U ++ "@" ++ S, "mod_logdb_users/" ++ U ++ "@" ++ S)
1145 + {'EXIT', _Reason} ->
1146 + ?ERR_NOT_ACCEPTABLE;
1151 +get_local_items(_Host, ["mod_logdb_users", _User], _Server, _Lang) ->
1153 +get_local_items(_Host, ["mod_logdb_settings"], _Server, _Lang) ->
1155 +get_local_items(_Host, Item, _Server, _Lang) ->
1156 + ?MYDEBUG("asked for items in ~p", [Item]),
1157 + {error, ?ERR_ITEM_NOT_FOUND}.
1159 +-define(INFO_RESULT(Allow, Feats),
1162 + {error, ?ERR_FORBIDDEN};
1167 +get_local_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
1168 + case gen_mod:is_loaded(LServer, mod_adhoc) of
1172 + LNode = string:tokens(Node, "/"),
1173 + AllowUser = acl:match_rule(LServer, mod_logdb, From),
1174 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1176 + ["mod_logdb"] when AllowUser == allow; AllowAdmin == allow ->
1177 + ?INFO_RESULT(allow, [?NS_COMMANDS]);
1179 + ?INFO_RESULT(deny, [?NS_COMMANDS]);
1180 + ["mod_logdb_users"] ->
1181 + ?INFO_RESULT(AllowAdmin, []);
1182 + ["mod_logdb_users", [$@ | _]] ->
1183 + ?INFO_RESULT(AllowAdmin, []);
1184 + ["mod_logdb_users", _User] ->
1185 + ?INFO_RESULT(AllowAdmin, [?NS_COMMANDS]);
1186 + ["mod_logdb_settings"] ->
1187 + ?INFO_RESULT(AllowAdmin, [?NS_COMMANDS]);
1191 + %?MYDEBUG("asked for ~p features: ~p", [LNode, Allow]),
1196 +-define(INFO_IDENTITY(Category, Type, Name, Lang),
1197 + [{xmlelement, "identity",
1198 + [{"category", Category},
1200 + {"name", translate:translate(Lang, Name)}], []}]).
1202 +-define(INFO_COMMAND(Name, Lang),
1203 + ?INFO_IDENTITY("automation", "command-node", Name, Lang)).
1205 +get_local_identity(Acc, _From, _To, Node, Lang) ->
1206 + LNode = string:tokens(Node, "/"),
1209 + ?INFO_COMMAND("Messages logging engine", Lang);
1210 + ["mod_logdb_users"] ->
1211 + ?INFO_COMMAND("Messages logging engine users", Lang);
1212 + ["mod_logdb_users", [$@ | _]] ->
1214 + ["mod_logdb_users", User] ->
1215 + ?INFO_COMMAND(User, Lang);
1216 + ["mod_logdb_settings"] ->
1217 + ?INFO_COMMAND("Messages logging engine settings", Lang);
1224 +%get_sm_items(Acc, From, To, Node, Lang) ->
1225 +% ?MYDEBUG("get_sm_items Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
1228 +%get_sm_features(Acc, From, To, Node, Lang) ->
1229 +% ?MYDEBUG("get_sm_features Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
1232 +%get_sm_identity(Acc, From, To, Node, Lang) ->
1233 +% ?MYDEBUG("get_sm_identity Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
1236 +adhoc_local_items(Acc, From, #jid{lserver = LServer, server = Server} = To,
1238 + Items = case Acc of
1239 + {result, Its} -> Its;
1242 + Nodes = recursively_get_local_items(LServer, "", Server, Lang),
1243 + Nodes1 = lists:filter(
1245 + Nd = xml:get_tag_attr_s("node", N),
1246 + F = get_local_features([], From, To, Nd, Lang),
1248 + {result, [?NS_COMMANDS]} ->
1254 + {result, Items ++ Nodes1}.
1256 +recursively_get_local_items(_LServer, "mod_logdb_users", _Server, _Lang) ->
1258 +recursively_get_local_items(LServer, Node, Server, Lang) ->
1259 + LNode = string:tokens(Node, "/"),
1260 + Items = case get_local_items(LServer, LNode, Server, Lang) of
1263 + {error, _Error} ->
1266 + Nodes = lists:flatten(
1269 + S = xml:get_tag_attr_s("jid", N),
1270 + Nd = xml:get_tag_attr_s("node", N),
1271 + if (S /= Server) or (Nd == "") ->
1274 + [N, recursively_get_local_items(
1275 + LServer, Nd, Server, Lang)]
1280 +-define(COMMANDS_RESULT(Allow, From, To, Request),
1283 + {error, ?ERR_FORBIDDEN};
1285 + adhoc_local_commands(From, To, Request)
1288 +adhoc_local_commands(Acc, From, #jid{lserver = LServer} = To,
1289 + #adhoc_request{node = Node} = Request) ->
1290 + LNode = string:tokens(Node, "/"),
1291 + AllowUser = acl:match_rule(LServer, mod_logdb, From),
1292 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1294 + ["mod_logdb"] when AllowUser == allow; AllowAdmin == allow ->
1295 + ?COMMANDS_RESULT(allow, From, To, Request);
1296 + ["mod_logdb_users", _User] when AllowAdmin == allow ->
1297 + ?COMMANDS_RESULT(allow, From, To, Request);
1298 + ["mod_logdb_settings"] when AllowAdmin == allow ->
1299 + ?COMMANDS_RESULT(allow, From, To, Request);
1304 +adhoc_local_commands(From, #jid{lserver = LServer} = _To,
1305 + #adhoc_request{lang = Lang,
1307 + sessionid = SessionID,
1309 + xdata = XData} = Request) ->
1310 + LNode = string:tokens(Node, "/"),
1311 + %% If the "action" attribute is not present, it is
1312 + %% understood as "execute". If there was no <actions/>
1313 + %% element in the first response (which there isn't in our
1314 + %% case), "execute" and "complete" are equivalent.
1315 + ActionIsExecute = lists:member(Action,
1316 + ["", "execute", "complete"]),
1317 + if Action == "cancel" ->
1318 + %% User cancels request
1319 + adhoc:produce_response(
1321 + #adhoc_response{status = canceled});
1322 + XData == false, ActionIsExecute ->
1323 + %% User requests form
1324 + case get_form(LServer, LNode, From, Lang) of
1326 + adhoc:produce_response(
1328 + #adhoc_response{status = executing,
1329 + elements = Form});
1333 + XData /= false, ActionIsExecute ->
1334 + %% User returns form.
1335 + case jlib:parse_xdata_submit(XData) of
1337 + {error, ?ERR_BAD_REQUEST};
1339 + case set_form(From, LServer, LNode, Lang, Fields) of
1341 + adhoc:produce_response(
1342 + #adhoc_response{lang = Lang,
1344 + sessionid = SessionID,
1345 + status = completed});
1351 + {error, ?ERR_BAD_REQUEST}
1354 +-define(LISTLINE(Label, Value),
1355 + {xmlelement, "option", [{"label", Label}],
1356 + [{xmlelement, "value", [], [{xmlcdata, Value}]}]}).
1357 +-define(DEFVAL(Value), {xmlelement, "value", [], [{xmlcdata, Value}]}).
1359 +get_user_form(LUser, LServer, Lang) ->
1360 + %From = jlib:jid_to_string(jlib:jid_remove_resource(Jid)),
1361 + #user_settings{dolog_default=DLD,
1363 + donotlog_list=DNLL} = get_user_settings(LUser, LServer),
1364 + {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
1365 + [{xmlelement, "title", [],
1367 + translate:translate(
1368 + Lang, "Messages logging engine settings")}]},
1369 + {xmlelement, "instructions", [],
1371 + translate:translate(
1372 + Lang, "Set logging preferences")++ ": " ++ LUser ++ "@" ++ LServer}]},
1374 + {xmlelement, "field", [{"type", "list-single"},
1376 + translate:translate(Lang, "Default")},
1377 + {"var", "dolog_default"}],
1378 + [?DEFVAL(atom_to_list(DLD)),
1379 + ?LISTLINE(translate:translate(Lang, "Log Messages"), "true"),
1380 + ?LISTLINE(translate:translate(Lang, "Do Not Log Messages"), "false")
1383 + {xmlelement, "field", [{"type", "text-multi"},
1385 + translate:translate(
1386 + Lang, "Log Messages")},
1387 + {"var", "dolog_list"}],
1388 + [{xmlelement, "value", [], [{xmlcdata, list_to_string(DLL)}]}]},
1390 + {xmlelement, "field", [{"type", "text-multi"},
1392 + translate:translate(
1393 + Lang, "Do Not Log Messages")},
1394 + {"var", "donotlog_list"}],
1395 + [{xmlelement, "value", [], [{xmlcdata, list_to_string(DNLL)}]}]}
1398 +get_settings_form(Host, Lang) ->
1399 + #state{dbmod=DBMod,
1401 + dolog_default=DLD,
1402 + ignore_jids=IgnoreJids,
1403 + groupchat=GroupChat,
1404 + purge_older_days=PurgeDaysT,
1405 + drop_messages_on_user_removal=MRemoval,
1406 + poll_users_settings=PollTime} = mod_logdb:get_module_settings(Host),
1408 + Backends = lists:map(fun({Backend, _Opts}) ->
1409 + ?LISTLINE(atom_to_list(Backend), atom_to_list(Backend))
1411 + DB = lists:sublist(atom_to_list(DBMod), length(atom_to_list(?MODULE)) + 2, length(atom_to_list(DBMod))),
1412 + DBsL = lists:append([?DEFVAL(DB)], Backends),
1415 + case PurgeDaysT of
1417 + Num when is_integer(Num) -> integer_to_list(Num);
1420 + {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
1421 + [{xmlelement, "title", [],
1423 + translate:translate(
1424 + Lang, "Messages logging engine settings") ++ " (run-time)"}]},
1425 + {xmlelement, "instructions", [],
1427 + translate:translate(
1428 + Lang, "Set run-time settings")}]},
1430 + {xmlelement, "field", [{"type", "list-single"},
1432 + translate:translate(Lang, "Backend")},
1433 + {"var", "backend"}],
1436 + {xmlelement, "field", [{"type", "text-multi"},
1438 + translate:translate(
1441 + [{xmlelement, "value", [], [{xmlcdata, lists:flatten(io_lib:format("~p.",[DBs]))}]}]},
1443 + {xmlelement, "field", [{"type", "list-single"},
1445 + translate:translate(Lang, "Default")},
1446 + {"var", "dolog_default"}],
1447 + [?DEFVAL(atom_to_list(DLD)),
1448 + ?LISTLINE(translate:translate(Lang, "Log Messages"), "true"),
1449 + ?LISTLINE(translate:translate(Lang, "Do Not Log Messages"), "false")
1451 + % drop_messages_on_user_removal
1452 + {xmlelement, "field", [{"type", "list-single"},
1454 + translate:translate(Lang, "Drop messages on user removal")},
1455 + {"var", "drop_messages_on_user_removal"}],
1456 + [?DEFVAL(atom_to_list(MRemoval)),
1457 + ?LISTLINE(translate:translate(Lang, "Drop"), "true"),
1458 + ?LISTLINE(translate:translate(Lang, "Do not drop"), "false")
1461 + {xmlelement, "field", [{"type", "list-single"},
1463 + translate:translate(Lang, "Groupchat messages logging")},
1464 + {"var", "groupchat"}],
1465 + [?DEFVAL(atom_to_list(GroupChat)),
1466 + ?LISTLINE("all", "all"),
1467 + ?LISTLINE("none", "none"),
1468 + ?LISTLINE("send", "send"),
1469 + ?LISTLINE("half", "half")
1472 + {xmlelement, "field", [{"type", "text-multi"},
1474 + translate:translate(
1475 + Lang, "Jids/Domains to ignore")},
1476 + {"var", "ignore_list"}],
1477 + [{xmlelement, "value", [], [{xmlcdata, list_to_string(IgnoreJids)}]}]},
1478 + % purge older days
1479 + {xmlelement, "field", [{"type", "text-single"},
1481 + translate:translate(
1482 + Lang, "Purge messages older than (days)")},
1483 + {"var", "purge_older_days"}],
1484 + [{xmlelement, "value", [], [{xmlcdata, PurgeDays}]}]},
1485 + % poll users settings
1486 + {xmlelement, "field", [{"type", "text-single"},
1488 + translate:translate(
1489 + Lang, "Poll users settings (seconds)")},
1490 + {"var", "poll_users_settings"}],
1491 + [{xmlelement, "value", [], [{xmlcdata, integer_to_list(PollTime)}]}]}
1494 +get_form(_Host, ["mod_logdb"], #jid{luser = LUser, lserver = LServer} = _Jid, Lang) ->
1495 + get_user_form(LUser, LServer, Lang);
1496 +get_form(_Host, ["mod_logdb_users", User], _JidFrom, Lang) ->
1497 + #jid{luser=LUser, lserver=LServer} = jlib:string_to_jid(User),
1498 + get_user_form(LUser, LServer, Lang);
1499 +get_form(Host, ["mod_logdb_settings"], _JidFrom, Lang) ->
1500 + get_settings_form(Host, Lang);
1501 +get_form(_Host, Command, _, _Lang) ->
1502 + ?MYDEBUG("asked for form ~p", [Command]),
1503 + {error, ?ERR_SERVICE_UNAVAILABLE}.
1505 +check_log_list([Head | Tail]) ->
1506 + case lists:member($@, Head) of
1508 + false -> throw(error)
1510 + % this check for Head to be valid jid
1511 + case jlib:string_to_jid(Head) of
1515 + check_log_list(Tail)
1517 +check_log_list([]) ->
1520 +check_ignore_list([Head | Tail]) ->
1521 + case lists:member($@, Head) of
1523 + false -> throw(error)
1525 + % this check for Head to be valid jid
1526 + case jlib:string_to_jid(Head) of
1528 + % this check for Head to be valid domain "@domain.org"
1529 + case lists:nth(1, Head) of
1531 + % TODO: this allows spaces and special characters in Head. May be change to nodeprep?
1532 + case jlib:nameprep(lists:delete($@, Head)) of
1533 + error -> throw(error);
1534 + _ -> check_log_list(Tail)
1539 + check_ignore_list(Tail)
1541 +check_ignore_list([]) ->
1544 +parse_users_settings(XData) ->
1545 + DLD = case lists:keysearch("dolog_default", 1, XData) of
1546 + {value, {_, [String]}} when String == "true"; String == "false" ->
1547 + list_to_bool(String);
1549 + throw(bad_request)
1551 + DLL = case lists:keysearch("dolog_list", 1, XData) of
1553 + throw(bad_request);
1554 + {value, {_, [[]]}} ->
1556 + {value, {_, List1}} ->
1557 + case catch check_log_list(List1) of
1559 + throw(bad_request);
1564 + DNLL = case lists:keysearch("donotlog_list", 1, XData) of
1566 + throw(bad_request);
1567 + {value, {_, [[]]}} ->
1569 + {value, {_, List2}} ->
1570 + case catch check_log_list(List2) of
1572 + throw(bad_request);
1577 + #user_settings{dolog_default=DLD,
1579 + donotlog_list=DNLL}.
1581 +parse_module_settings(XData) ->
1582 + DLD = case lists:keysearch("dolog_default", 1, XData) of
1583 + {value, {_, [Str1]}} when Str1 == "true"; Str1 == "false" ->
1584 + list_to_bool(Str1);
1586 + throw(bad_request)
1588 + MRemoval = case lists:keysearch("drop_messages_on_user_removal", 1, XData) of
1589 + {value, {_, [Str5]}} when Str5 == "true"; Str5 == "false" ->
1590 + list_to_bool(Str5);
1592 + throw(bad_request)
1594 + GroupChat = case lists:keysearch("groupchat", 1, XData) of
1595 + {value, {_, [Str2]}} when Str2 == "none";
1599 + list_to_atom(Str2);
1601 + throw(bad_request)
1603 + Ignore = case lists:keysearch("ignore_list", 1, XData) of
1604 + {value, {_, List}} ->
1605 + case catch check_ignore_list(List) of
1609 + throw(bad_request)
1612 + throw(bad_request)
1614 + Purge = case lists:keysearch("purge_older_days", 1, XData) of
1615 + {value, {_, ["never"]}} ->
1617 + {value, {_, [Str3]}} ->
1618 + case catch list_to_integer(Str3) of
1619 + {'EXIT', {badarg, _}} -> throw(bad_request);
1623 + throw(bad_request)
1625 + Poll = case lists:keysearch("poll_users_settings", 1, XData) of
1626 + {value, {_, [Str4]}} ->
1627 + case catch list_to_integer(Str4) of
1628 + {'EXIT', {badarg, _}} -> throw(bad_request);
1632 + throw(bad_request)
1634 + #state{dolog_default=DLD,
1635 + groupchat=GroupChat,
1636 + ignore_jids=Ignore,
1637 + purge_older_days=Purge,
1638 + drop_messages_on_user_removal=MRemoval,
1639 + poll_users_settings=Poll}.
1641 +set_form(From, _Host, ["mod_logdb"], _Lang, XData) ->
1642 + #jid{luser=LUser, lserver=LServer} = From,
1643 + case catch parse_users_settings(XData) of
1645 + {error, ?ERR_BAD_REQUEST};
1647 + case mod_logdb:set_user_settings(LUser, LServer, UserSettings) of
1651 + {error, ?ERR_INTERNAL_SERVER_ERROR}
1654 +set_form(_From, _Host, ["mod_logdb_users", User], _Lang, XData) ->
1655 + #jid{luser=LUser, lserver=LServer} = jlib:string_to_jid(User),
1656 + case catch parse_users_settings(XData) of
1657 + bad_request -> {error, ?ERR_BAD_REQUEST};
1659 + case mod_logdb:set_user_settings(LUser, LServer, UserSettings) of
1663 + {error, ?ERR_INTERNAL_SERVER_ERROR}
1666 +set_form(_From, Host, ["mod_logdb_settings"], _Lang, XData) ->
1667 + case catch parse_module_settings(XData) of
1668 + bad_request -> {error, ?ERR_BAD_REQUEST};
1670 + case mod_logdb:set_module_settings(Host, Settings) of
1674 + {error, ?ERR_INTERNAL_SERVER_ERROR}
1677 +set_form(From, _Host, Node, _Lang, XData) ->
1678 + User = jlib:jid_to_string(jlib:jid_remove_resource(From)),
1679 + ?MYDEBUG("set form for ~p at ~p XData=~p", [User, Node, XData]),
1680 + {error, ?ERR_SERVICE_UNAVAILABLE}.
1682 +%adhoc_sm_items(Acc, From, To, Request) ->
1683 +% ?MYDEBUG("adhoc_sm_items Acc=~p From=~p To=~p Request=~p", [Acc, From, To, Request]),
1686 +%adhoc_sm_commands(Acc, From, To, Request) ->
1687 +% ?MYDEBUG("adhoc_sm_commands Acc=~p From=~p To=~p Request=~p", [Acc, From, To, Request]),
1690 +get_all_vh_users(Host, Server, Lang) ->
1691 + case catch ejabberd_auth:get_vh_registered_users(Host) of
1692 + {'EXIT', _Reason} ->
1695 + SUsers = lists:sort([{S, U} || {U, S} <- Users]),
1696 + case length(SUsers) of
1697 + N when N =< 100 ->
1698 + lists:map(fun({S, U}) ->
1699 + ?NODE(U ++ "@" ++ S, "mod_logdb_users/" ++ U ++ "@" ++ S)
1702 + NParts = trunc(math:sqrt(N * 0.618)) + 1,
1703 + M = trunc(N / NParts) + 1,
1704 + lists:map(fun(K) ->
1707 + "@" ++ integer_to_list(K) ++
1708 + "-" ++ integer_to_list(L),
1709 + {FS, FU} = lists:nth(K, SUsers),
1711 + if L < N -> lists:nth(L, SUsers);
1712 + true -> lists:last(SUsers)
1715 + FU ++ "@" ++ FS ++
1718 + ?NODE(Name, "mod_logdb_users/" ++ Node)
1719 + end, lists:seq(1, N, M))
1723 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1727 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1728 +webadmin_menu(Acc, _Host, Lang) ->
1729 + [{"messages", ?T("Users Messages")} | Acc].
1731 +webadmin_user(Acc, User, Server, Lang) ->
1732 + Sett = get_user_settings(User, Server),
1734 + case Sett#user_settings.dolog_default of
1736 + ?INPUTT("submit", "dolog", "Log Messages");
1738 + ?INPUTT("submit", "donotlog", "Do Not Log Messages");
1741 + Acc ++ [?XE("h3", [?ACT("messages/", "Messages"), ?C(" "), Log])].
1743 +webadmin_page(_, Host,
1744 + #request{path = ["messages"],
1746 + lang = Lang}) when is_list(Host) ->
1747 + Res = vhost_messages_stats(Host, Query, Lang),
1749 +webadmin_page(_, Host,
1750 + #request{path = ["messages", Date],
1752 + lang = Lang}) when is_list(Host) ->
1753 + Res = vhost_messages_stats_at(Host, Query, Lang, Date),
1755 +webadmin_page(_, Host,
1756 + #request{path = ["user", U, "messages"],
1759 + Res = user_messages_stats(U, Host, Query, Lang),
1761 +webadmin_page(_, Host,
1762 + #request{path = ["user", U, "messages", Date],
1765 + Res = mod_logdb:user_messages_stats_at(U, Host, Query, Lang, Date),
1767 +webadmin_page(Acc, _, _) -> Acc.
1769 +user_parse_query(_, "dolog", User, Server, _Query) ->
1770 + Sett = get_user_settings(User, Server),
1771 + % TODO: check returned value
1772 + set_user_settings(User, Server, Sett#user_settings{dolog_default=true}),
1774 +user_parse_query(_, "donotlog", User, Server, _Query) ->
1775 + Sett = get_user_settings(User, Server),
1776 + % TODO: check returned value
1777 + set_user_settings(User, Server, Sett#user_settings{dolog_default=false}),
1779 +user_parse_query(Acc, _Action, _User, _Server, _Query) ->
1782 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1786 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1787 +vhost_messages_stats(Server, Query, Lang) ->
1788 + Res = case catch vhost_messages_parse_query(Server, Query) of
1789 + {'EXIT', Reason} ->
1790 + ?ERROR_MSG("~p", [Reason]),
1792 + VResult -> VResult
1794 + {Time, Value} = timer:tc(mod_logdb, get_vhost_stats, [Server]),
1795 + ?INFO_MSG("get_vhost_stats(~p) elapsed ~p sec", [Server, Time/1000000]),
1796 + %case get_vhost_stats(Server) of
1798 + {'EXIT', CReason} ->
1799 + ?ERROR_MSG("Failed to get_vhost_stats: ~p", [CReason]),
1800 + [?XC("h1", ?T("Error occupied while fetching list"))];
1801 + {error, GReason} ->
1802 + ?ERROR_MSG("Failed to get_vhost_stats: ~p", [GReason]),
1803 + [?XC("h1", ?T("Error occupied while fetching list"))];
1805 + [?XC("h1", ?T("No logged messages for ") ++ Server)];
1807 + Fun = fun({Date, Count}) ->
1808 + ID = jlib:encode_base64(binary_to_list(term_to_binary(Server++Date))),
1810 + [?XE("td", [?INPUT("checkbox", "selected", ID)]),
1811 + ?XE("td", [?AC(Date, Date)]),
1812 + ?XC("td", integer_to_list(Count))
1815 + [?XC("h1", ?T("Logged messages for ") ++ Server)] ++
1817 + ok -> [?CT("Submitted"), ?P];
1818 + error -> [?CT("Bad format"), ?P];
1821 + [?XAE("form", [{"action", ""}, {"method", "post"}],
1826 + ?XCT("td", "Date"),
1827 + ?XCT("td", "Count")
1830 + lists:map(Fun, Dates)
1833 + ?INPUTT("submit", "delete", "Delete Selected")
1837 +vhost_messages_stats_at(Server, Query, Lang, Date) ->
1838 + {Time, Value} = timer:tc(mod_logdb, get_vhost_stats_at, [Server, Date]),
1839 + ?INFO_MSG("get_vhost_stats_at(~p,~p) elapsed ~p sec", [Server, Date, Time/1000000]),
1840 + %case get_vhost_stats_at(Server, Date) of
1842 + {'EXIT', CReason} ->
1843 + ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [CReason]),
1844 + [?XC("h1", ?T("Error occupied while fetching list"))];
1845 + {error, GReason} ->
1846 + ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [GReason]),
1847 + [?XC("h1", ?T("Error occupied while fetching list"))];
1849 + [?XC("h1", ?T("No logged messages for ") ++ Server ++ ?T(" at ") ++ Date)];
1851 + Res = case catch vhost_messages_at_parse_query(Server, Date, Users, Query) of
1852 + {'EXIT', Reason} ->
1853 + ?ERROR_MSG("~p", [Reason]),
1855 + VResult -> VResult
1857 + Fun = fun({User, Count}) ->
1858 + ID = jlib:encode_base64(binary_to_list(term_to_binary(User++Server))),
1860 + [?XE("td", [?INPUT("checkbox", "selected", ID)]),
1861 + ?XE("td", [?AC("../user/"++User++"/messages/"++Date, User)]),
1862 + ?XC("td", integer_to_list(Count))
1865 + [?XC("h1", ?T("Logged messages for ") ++ Server ++ ?T(" at ") ++ Date)] ++
1867 + ok -> [?CT("Submitted"), ?P];
1868 + error -> [?CT("Bad format"), ?P];
1871 + [?XAE("form", [{"action", ""}, {"method", "post"}],
1876 + ?XCT("td", "User"),
1877 + ?XCT("td", "Count")
1880 + lists:map(Fun, Users)
1883 + ?INPUTT("submit", "delete", "Delete Selected")
1887 +user_messages_stats(User, Server, Query, Lang) ->
1888 + Jid = jlib:jid_to_string({User, Server, ""}),
1890 + Res = case catch user_messages_parse_query(User, Server, Query) of
1891 + {'EXIT', Reason} ->
1892 + ?ERROR_MSG("~p", [Reason]),
1894 + VResult -> VResult
1897 + {Time, Value} = timer:tc(mod_logdb, get_user_stats, [User, Server]),
1898 + ?INFO_MSG("get_user_stats(~p,~p) elapsed ~p sec", [User, Server, Time/1000000]),
1901 + {'EXIT', CReason} ->
1902 + ?ERROR_MSG("Failed to get_user_stats: ~p", [CReason]),
1903 + [?XC("h1", ?T("Error occupied while fetching days"))];
1904 + {error, GReason} ->
1905 + ?ERROR_MSG("Failed to get_user_stats: ~p", [GReason]),
1906 + [?XC("h1", ?T("Error occupied while fetching days"))];
1908 + [?XC("h1", ?T("No logged messages for ") ++ Jid)];
1910 + Fun = fun({Date, Count}) ->
1911 + ID = jlib:encode_base64(binary_to_list(term_to_binary(User++Date))),
1913 + [?XE("td", [?INPUT("checkbox", "selected", ID)]),
1914 + ?XE("td", [?AC(Date, Date)]),
1915 + ?XC("td", integer_to_list(Count))
1917 + %[?AC(Date, Date ++ " (" ++ integer_to_list(Count) ++ ")"), ?BR]
1919 + [?XC("h1", ?T("Logged messages for ") ++ Jid)] ++
1921 + ok -> [?CT("Submitted"), ?P];
1922 + error -> [?CT("Bad format"), ?P];
1925 + [?XAE("form", [{"action", ""}, {"method", "post"}],
1930 + ?XCT("td", "Date"),
1931 + ?XCT("td", "Count")
1934 + lists:map(Fun, Dates)
1937 + ?INPUTT("submit", "delete", "Delete Selected")
1941 +search_user_nick(User, List) ->
1942 + case lists:keysearch(User, 1, List) of
1943 + {value,{User, []}} ->
1945 + {value,{User, Nick}} ->
1951 +user_messages_stats_at(User, Server, Query, Lang, Date) ->
1952 + Jid = jlib:jid_to_string({User, Server, ""}),
1954 + {Time, Value} = timer:tc(mod_logdb, get_user_messages_at, [User, Server, Date]),
1955 + ?INFO_MSG("get_user_messages_at(~p,~p,~p) elapsed ~p sec", [User, Server, Date, Time/1000000]),
1957 + {'EXIT', CReason} ->
1958 + ?ERROR_MSG("Failed to get_user_messages_at: ~p", [CReason]),
1959 + [?XC("h1", ?T("Error occupied while fetching messages"))];
1960 + {error, GReason} ->
1961 + ?ERROR_MSG("Failed to get_user_messages_at: ~p", [GReason]),
1962 + [?XC("h1", ?T("Error occupied while fetching messages"))];
1964 + [?XC("h1", ?T("No logged messages for ") ++ Jid ++ ?T(" at ") ++ Date)];
1965 + {ok, User_messages} ->
1966 + Res = case catch user_messages_at_parse_query(Server,
1970 + {'EXIT', Reason} ->
1971 + ?ERROR_MSG("~p", [Reason]),
1973 + VResult -> VResult
1976 + UR = ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]),
1978 + lists:map(fun(Item) ->
1979 + {jlib:jid_to_string(Item#roster.jid), Item#roster.name}
1982 + UniqUsers = lists:foldl(fun(#msg{peer_name=PName, peer_server=PServer}, List) ->
1983 + ToAdd = PName++"@"++PServer,
1984 + case lists:member(ToAdd, List) of
1986 + false -> lists:append([ToAdd], List)
1988 + end, [], User_messages),
1990 + % Users to filter (sublist of UniqUsers)
1991 + CheckedUsers = case lists:keysearch("filter", 1, Query) of
1993 + lists:filter(fun(UFUser) ->
1994 + ID = jlib:encode_base64(binary_to_list(term_to_binary(UFUser))),
1995 + lists:member({"selected", ID}, Query)
2000 + % UniqUsers in html (noone selected -> everyone selected)
2001 + Users = lists:map(fun(UHUser) ->
2002 + ID = jlib:encode_base64(binary_to_list(term_to_binary(UHUser))),
2003 + Input = case lists:member(UHUser, CheckedUsers) of
2004 + true -> [?INPUTC("checkbox", "selected", ID)];
2005 + false when CheckedUsers == [] -> [?INPUTC("checkbox", "selected", ID)];
2006 + false -> [?INPUT("checkbox", "selected", ID)]
2009 + case search_user_nick(UHUser, UserRoster) of
2011 + N -> " ("++ N ++")"
2014 + [?XE("td", Input),
2015 + ?XC("td", UHUser++Nick)])
2016 + end, lists:sort(UniqUsers)),
2017 + % Messages to show (based on Users)
2018 + User_messages_filtered = case CheckedUsers of
2019 + [] -> User_messages;
2020 + _ -> lists:filter(fun(#msg{peer_name=PName, peer_server=PServer}) ->
2021 + lists:member(PName++"@"++PServer, CheckedUsers)
2022 + end, User_messages)
2025 + Msgs_Fun = fun(#msg{timestamp=Timestamp,
2027 + direction=Direction,
2028 + peer_name=PName, peer_server=PServer, peer_resource=PRes,
2031 + TextRaw = case Subject of
2033 + _ -> [?T("Subject"),": ",Subject,"<br>", Body]
2035 + ID = jlib:encode_base64(binary_to_list(term_to_binary(Timestamp))),
2036 + % replace \n with <br>
2037 + Text = lists:map(fun(10) -> "<br>";
2040 + Resource = case PRes of
2046 + case search_user_nick(PName++"@"++PServer, UserRoster) of
2047 + nothing when PServer == Server ->
2049 + nothing when Type == "groupchat", Direction == from ->
2050 + PName++"@"++PServer++Resource;
2052 + PName++"@"++PServer;
2056 + [?XE("td", [?INPUT("checkbox", "selected", ID)]),
2057 + ?XC("td", convert_timestamp(Timestamp)),
2058 + ?XC("td", atom_to_list(Direction)++": "++UserNick),
2061 + % Filtered user messages in html
2062 + Msgs = lists:map(Msgs_Fun, lists:sort(User_messages_filtered)),
2064 + [?XC("h1", ?T("Logged messages for ") ++ Jid ++ ?T(" at ") ++ Date)] ++
2066 + ok -> [?CT("Submitted"), ?P];
2067 + error -> [?CT("Bad format"), ?P];
2070 + [?XAE("form", [{"action", ""}, {"method", "post"}],
2074 + ?XCT("td", "User")
2080 + ?INPUTT("submit", "filter", "Filter Selected")
2086 + ?XCT("td", "Date, Time"),
2087 + ?XCT("td", "Direction: Jid"),
2088 + ?XCT("td", "Body")
2093 + ?INPUTT("submit", "delete", "Delete Selected"),
2098 --- src/mod_logdb.hrl.orig 2009-02-05 19:21:29.000000000 +0200
2099 +++ src/mod_logdb.hrl 2009-02-05 19:21:02.000000000 +0200
2101 +%%%----------------------------------------------------------------------
2102 +%%% File : mod_logdb.hrl
2103 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
2105 +%%% Version : trunk
2107 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
2108 +%%%----------------------------------------------------------------------
2110 +-define(logdb_debug, true).
2112 +-ifdef(logdb_debug).
2113 +-define(MYDEBUG(Format, Args), io:format("D(~p:~p:~p) : "++Format++"~n",
2114 + [calendar:local_time(),?MODULE,?LINE]++Args)).
2116 +-define(MYDEBUG(_F,_A),[]).
2119 +-record(msg, {timestamp,
2121 + peer_name, peer_server, peer_resource,
2126 +-record(user_settings, {owner_name,
2129 + donotlog_list=[]}).
2131 +-define(INPUTC(Type, Name, Value),
2132 + ?XA("input", [{"type", Type},
2135 + {"checked", "true"}])).
2136 --- src/mod_logdb_mnesia.erl.orig 2009-02-05 19:21:29.000000000 +0200
2137 +++ src/mod_logdb_mnesia.erl 2009-02-05 19:19:59.000000000 +0200
2139 +%%%----------------------------------------------------------------------
2140 +%%% File : mod_logdb_mnesia.erl
2141 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
2142 +%%% Purpose : mnesia backend for mod_logdb
2143 +%%% Version : trunk
2145 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
2146 +%%%----------------------------------------------------------------------
2148 +-module(mod_logdb_mnesia).
2149 +-author('o.palij@gmail.com').
2151 +-include("mod_logdb.hrl").
2152 +-include("ejabberd.hrl").
2153 +-include("jlib.hrl").
2155 +-behaviour(gen_logdb).
2156 +-behaviour(gen_server).
2159 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
2161 +-export([start/2, stop/1]).
2163 +-export([log_message/2,
2165 + rebuild_stats_at/2,
2166 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
2167 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
2169 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
2172 +-define(PROCNAME, mod_logdb_mnesia).
2173 +-define(CALL_TIMEOUT, 10000).
2175 +-record(state, {vhost}).
2177 +-record(stats, {user, at, count}).
2185 +stats_table(VHost) ->
2186 + list_to_atom(prefix() ++ "stats" ++ suffix(VHost)).
2188 +table_name(VHost, Date) ->
2189 + list_to_atom(prefix() ++ "messages_" ++ Date ++ suffix(VHost)).
2191 +settings_table(VHost) ->
2192 + list_to_atom(prefix() ++ "settings" ++ suffix(VHost)).
2194 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2196 +% gen_mod callbacks
2198 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2199 +start(VHost, Opts) ->
2200 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2201 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
2204 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2205 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
2207 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2209 +% gen_server callbacks
2211 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2212 +init([VHost, _Opts]) ->
2213 + case mnesia:system_info(is_running) of
2215 + ok = create_stats_table(VHost),
2216 + ok = create_settings_table(VHost),
2217 + {ok, #state{vhost=VHost}};
2219 + ?ERROR_MSG("Mnesia not running", []),
2220 + {stop, db_connection_failed};
2222 + ?ERROR_MSG("Mnesia status: ~p", [Status]),
2223 + {stop, db_connection_failed}
2226 +handle_call({log_message, Msg}, _From, #state{vhost=VHost}=State) ->
2227 + {reply, log_message_int(VHost, Msg), State};
2228 +handle_call({rebuild_stats}, _From, #state{vhost=VHost}=State) ->
2229 + {atomic, ok} = delete_nonexistent_stats(VHost),
2231 + lists:foreach(fun(Date) ->
2232 + rebuild_stats_at_int(VHost, Date)
2233 + end, get_dates_int(VHost)),
2234 + {reply, Reply, State};
2235 +handle_call({rebuild_stats_at, Date}, _From, #state{vhost=VHost}=State) ->
2236 + Reply = rebuild_stats_at_int(VHost, Date),
2237 + {reply, Reply, State};
2238 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{vhost=VHost}=State) ->
2239 + Table = table_name(VHost, Date),
2243 + mnesia:write_lock_table(stats_table(VHost)),
2244 + mnesia:write_lock_table(Table),
2245 + mnesia:delete_object(Table, Msg, write)
2248 + DRez = case mnesia:transaction(Fun) of
2249 + {aborted, Reason} ->
2250 + ?ERROR_MSG("Failed to delete_messages_by_user_at at ~p for ~p: ~p", [Date, VHost, Reason]),
2256 + case rebuild_stats_at_int(VHost, Date) of
2262 + {reply, Reply, State};
2263 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{vhost=VHost}=State) ->
2264 + {reply, delete_all_messages_by_user_at_int(User, VHost, Date), State};
2265 +handle_call({delete_messages_at, Date}, _From, #state{vhost=VHost}=State) ->
2267 + case mnesia:delete_table(table_name(VHost, Date)) of
2269 + delete_stats_by_vhost_at_int(VHost, Date);
2270 + {aborted, Reason} ->
2271 + ?ERROR_MSG("Failed to delete_messages_at for ~p at ~p", [VHost, Date, Reason]),
2274 + {reply, Reply, State};
2275 +handle_call({get_vhost_stats}, _From, #state{vhost=VHost}=State) ->
2276 + Fun = fun(#stats{at=Date, count=Count}, Stats) ->
2277 + case lists:keysearch(Date, 1, Stats) of
2279 + lists:append(Stats, [{Date, Count}]);
2280 + {value, {_, TempCount}} ->
2281 + lists:keyreplace(Date, 1, Stats, {Date, TempCount+Count})
2285 + case mnesia:transaction(fun() ->
2286 + mnesia:foldl(Fun, [], stats_table(VHost))
2288 + {atomic, Result} -> {ok, mod_logdb:sort_stats(Result)};
2289 + {aborted, Reason} -> {error, Reason}
2291 + {reply, Reply, State};
2292 +handle_call({get_vhost_stats_at, Date}, _From, #state{vhost=VHost}=State) ->
2294 + Pat = #stats{user='$1', at=Date, count='$2'},
2295 + mnesia:select(stats_table(VHost), [{Pat, [], [['$1', '$2']]}])
2298 + case mnesia:transaction(Fun) of
2299 + {atomic, Result} ->
2300 + {ok, lists:reverse(lists:keysort(2, [{User, Count} || [User, Count] <- Result]))};
2301 + {aborted, Reason} ->
2304 + {reply, Reply, State};
2305 +handle_call({get_user_stats, User}, _From, #state{vhost=VHost}=State) ->
2306 + {reply, get_user_stats_int(User, VHost), State};
2307 +handle_call({get_user_messages_at, User, Date}, _From, #state{vhost=VHost}=State) ->
2309 + case mnesia:transaction(fun() ->
2310 + Pat = #msg{owner_name=User, _='_'},
2311 + mnesia:select(table_name(VHost, Date),
2312 + [{Pat, [], ['$_']}])
2314 + {atomic, Result} -> {ok, Result};
2315 + {aborted, Reason} ->
2318 + {reply, Reply, State};
2319 +handle_call({get_dates}, _From, #state{vhost=VHost}=State) ->
2320 + {reply, get_dates_int(VHost), State};
2321 +handle_call({get_users_settings}, _From, #state{vhost=VHost}=State) ->
2322 + Reply = mnesia:dirty_match_object(settings_table(VHost), #user_settings{_='_'}),
2323 + {reply, {ok, Reply}, State};
2324 +handle_call({get_user_settings, User}, _From, #state{vhost=VHost}=State) ->
2326 + case mnesia:dirty_match_object(settings_table(VHost), #user_settings{owner_name=User, _='_'}) of
2331 + {reply, Reply, State};
2332 +handle_call({set_user_settings, _User, Set}, _From, #state{vhost=VHost}=State) ->
2333 + ?MYDEBUG("~p~n~p", [settings_table(VHost), Set]),
2334 + Reply = mnesia:dirty_write(settings_table(VHost), Set),
2335 + ?MYDEBUG("~p", [Reply]),
2336 + {reply, Reply, State};
2337 +handle_call({drop_user, User}, _From, #state{vhost=VHost}=State) ->
2338 + {ok, Dates} = get_user_stats_int(User, VHost),
2339 + MDResult = lists:map(fun({Date, _}) ->
2340 + delete_all_messages_by_user_at_int(User, VHost, Date)
2342 + SDResult = delete_user_settings_int(User, VHost),
2344 + case lists:all(fun(Result) when Result == ok ->
2346 + (Result) when Result == error ->
2348 + end, lists:append(MDResult, [SDResult])) of
2354 + {reply, Reply, State};
2355 +handle_call({stop}, _From, State) ->
2356 + {stop, normal, ok, State};
2357 +handle_call(Msg, _From, State) ->
2358 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
2361 +handle_cast(Msg, State) ->
2362 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
2365 +handle_info(Info, State) ->
2366 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
2369 +terminate(_Reason, _State) ->
2372 +code_change(_OldVsn, State, _Extra) ->
2375 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2377 +% gen_logdb callbacks
2379 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2380 +log_message(VHost, Msg) ->
2381 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2382 + gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
2383 +rebuild_stats(VHost) ->
2384 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2385 + gen_server:call(Proc, {rebuild_stats}, ?CALL_TIMEOUT).
2386 +rebuild_stats_at(VHost, Date) ->
2387 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2388 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
2389 +delete_messages_by_user_at(VHost, Msgs, Date) ->
2390 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2391 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
2392 +delete_all_messages_by_user_at(User, VHost, Date) ->
2393 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2394 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
2395 +delete_messages_at(VHost, Date) ->
2396 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2397 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
2398 +get_vhost_stats(VHost) ->
2399 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2400 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
2401 +get_vhost_stats_at(VHost, Date) ->
2402 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2403 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
2404 +get_user_stats(User, VHost) ->
2405 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2406 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
2407 +get_user_messages_at(User, VHost, Date) ->
2408 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2409 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
2410 +get_dates(VHost) ->
2411 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2412 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
2413 +get_user_settings(User, VHost) ->
2414 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2415 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
2416 +get_users_settings(VHost) ->
2417 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2418 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
2419 +set_user_settings(User, VHost, Set) ->
2420 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2421 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
2422 +drop_user(User, VHost) ->
2423 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2424 + gen_server:call(Proc, {drop_user, User}, ?CALL_TIMEOUT).
2426 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2430 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2431 +log_message_int(VHost, #msg{timestamp=Timestamp}=Msg) ->
2432 + Date = mod_logdb:convert_timestamp_brief(Timestamp),
2434 + ATable = table_name(VHost, Date),
2436 + mnesia:write_lock_table(ATable),
2437 + mnesia:write(ATable, Msg, write)
2439 + % log message, increment stats for both users
2440 + case mnesia:transaction(Fun) of
2441 + % if table does not exists - create it and try to log message again
2442 + {aborted,{no_exists, _Table}} ->
2443 + case create_msg_table(VHost, Date) of
2444 + {aborted, CReason} ->
2445 + ?ERROR_MSG("Failed to log message: ~p", [CReason]),
2448 + ?MYDEBUG("Created msg table for ~p at ~p", [VHost, Date]),
2449 + log_message_int(VHost, Msg)
2451 + {aborted, TReason} ->
2452 + ?ERROR_MSG("Failed to log message: ~p", [TReason]),
2455 + ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
2456 + Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
2457 + increment_user_stats(Msg#msg.owner_name, VHost, Date)
2460 +increment_user_stats(Owner, VHost, Date) ->
2462 + Pat = #stats{user=Owner, at=Date, count='$1'},
2463 + mnesia:write_lock_table(stats_table(VHost)),
2464 + case mnesia:select(stats_table(VHost), [{Pat, [], ['$_']}]) of
2466 + mnesia:write(stats_table(VHost),
2467 + #stats{user=Owner,
2472 + mnesia:delete_object(stats_table(VHost),
2473 + #stats{user=Owner,
2475 + count=Stats#stats.count},
2477 + New = Stats#stats{count = Stats#stats.count+1},
2479 + New#stats.count > 0 -> mnesia:write(stats_table(VHost),
2486 + case mnesia:transaction(Fun) of
2487 + {aborted, Reason} ->
2488 + ?ERROR_MSG("Failed to update stats for ~s@~s: ~p", [Owner, VHost, Reason]),
2491 + ?MYDEBUG("Updated stats for ~s@~s", [Owner, VHost]),
2495 +get_dates_int(VHost) ->
2496 + Tables = mnesia:system_info(tables),
2497 + lists:foldl(fun(ATable, Dates) ->
2498 + Table = atom_to_list(ATable),
2499 + case regexp:match(Table, VHost++"$") of
2501 + case regexp:match(Table,"_[0-9]+-[0-9]+-[0-9]+_") of
2503 + lists:append(Dates, [lists:sublist(Table,S+1,E-2)]);
2512 +rebuild_stats_at_int(VHost, Date) ->
2513 + Table = table_name(VHost, Date),
2514 + STable = stats_table(VHost),
2515 + CFun = fun(Msg, Stats) ->
2516 + Owner = Msg#msg.owner_name,
2517 + case lists:keysearch(Owner, 1, Stats) of
2518 + {value, {_, Count}} ->
2519 + lists:keyreplace(Owner, 1, Stats, {Owner, Count + 1});
2521 + lists:append(Stats, [{Owner, 1}])
2524 + DFun = fun(#stats{at=SDate} = Stat, _Acc)
2525 + when SDate == Date ->
2526 + mnesia:delete_object(stats_table(VHost), Stat, write);
2527 + (_Stat, _Acc) -> ok
2529 + % TODO: Maybe unregister hooks ?
2530 + case mnesia:transaction(fun() ->
2531 + mnesia:write_lock_table(Table),
2532 + mnesia:write_lock_table(STable),
2533 + % Calc stats for VHost at Date
2534 + case mnesia:foldl(CFun, [], Table) of
2537 + % Delete all stats for VHost at Date
2538 + mnesia:foldl(DFun, [], STable),
2539 + % Write new calc'ed stats
2540 + lists:foreach(fun({Owner, Count}) ->
2541 + WStat = #stats{user=Owner, at=Date, count=Count},
2542 + mnesia:write(stats_table(VHost), WStat, write)
2547 + {aborted, Reason} ->
2548 + ?ERROR_MSG("Failed to rebuild_stats_at for ~p at ~p: ~p", [VHost, Date, Reason]),
2552 + {atomic, empty} ->
2553 + {atomic,ok} = mnesia:delete_table(Table),
2554 + ?MYDEBUG("Dropped table at ~p", [Date]),
2558 +delete_nonexistent_stats(VHost) ->
2559 + Dates = get_dates_int(VHost),
2560 + mnesia:transaction(fun() ->
2561 + mnesia:foldl(fun(#stats{at=Date} = Stat, _Acc) ->
2562 + case lists:member(Date, Dates) of
2563 + false -> mnesia:delete_object(Stat);
2566 + end, ok, stats_table(VHost))
2569 +delete_stats_by_vhost_at_int(VHost, Date) ->
2570 + StatsDelete = fun(#stats{at=SDate} = Stat, _Acc)
2571 + when SDate == Date ->
2572 + mnesia:delete_object(stats_table(VHost), Stat, write),
2574 + (_Msg, _Acc) -> ok
2576 + case mnesia:transaction(fun() ->
2577 + mnesia:write_lock_table(stats_table(VHost)),
2578 + mnesia:foldl(StatsDelete, ok, stats_table(VHost))
2580 + {aborted, Reason} ->
2581 + ?ERROR_MSG("Failed to update stats at ~p for ~p: ~p", [Date, VHost, Reason]),
2582 + rebuild_stats_at_int(VHost, Date);
2584 + ?INFO_MSG("Updated stats at ~p for ~p", [Date, VHost]),
2588 +get_user_stats_int(User, VHost) ->
2589 + case mnesia:transaction(fun() ->
2590 + Pat = #stats{user=User, at='$1', count='$2'},
2591 + mnesia:select(stats_table(VHost), [{Pat, [], [['$1', '$2']]}])
2593 + {atomic, Result} ->
2594 + {ok, mod_logdb:sort_stats([{Date, Count} || [Date, Count] <- Result])};
2595 + {aborted, Reason} ->
2599 +delete_all_messages_by_user_at_int(User, VHost, Date) ->
2600 + Table = table_name(VHost, Date),
2601 + MsgDelete = fun(#msg{owner_name=Owner} = Msg, _Acc)
2602 + when Owner == User ->
2603 + mnesia:delete_object(Table, Msg, write),
2605 + (_Msg, _Acc) -> ok
2607 + DRez = case mnesia:transaction(fun() ->
2608 + mnesia:foldl(MsgDelete, ok, Table)
2610 + {aborted, Reason} ->
2611 + ?ERROR_MSG("Failed to delete_all_messages_by_user_at for ~p@~p at ~p: ~p", [User, VHost, Date, Reason]),
2616 + case rebuild_stats_at_int(VHost, Date) of
2623 +delete_user_settings_int(User, VHost) ->
2624 + STable = settings_table(VHost),
2625 + case mnesia:dirty_match_object(STable, #user_settings{owner_name=User, _='_'}) of
2629 + mnesia:dirty_delete_object(STable, UserSettings)
2632 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2636 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2637 +create_stats_table(VHost) ->
2638 + SName = stats_table(VHost),
2639 + case mnesia:create_table(SName,
2640 + [{disc_only_copies, [node()]},
2642 + {attributes, record_info(fields, stats)},
2643 + {record_name, stats}
2646 + ?MYDEBUG("Created stats table for ~p", [VHost]),
2647 + lists:foreach(fun(Date) ->
2648 + rebuild_stats_at_int(VHost, Date)
2649 + end, get_dates_int(VHost)),
2651 + {aborted, {already_exists, _}} ->
2652 + ?MYDEBUG("Stats table for ~p already exists", [VHost]),
2654 + {aborted, Reason} ->
2655 + ?ERROR_MSG("Failed to create stats table: ~p", [Reason]),
2659 +create_settings_table(VHost) ->
2660 + SName = settings_table(VHost),
2661 + case mnesia:create_table(SName,
2662 + [{disc_copies, [node()]},
2664 + {attributes, record_info(fields, user_settings)},
2665 + {record_name, user_settings}
2668 + ?MYDEBUG("Created settings table for ~p", [VHost]),
2670 + {aborted, {already_exists, _}} ->
2671 + ?MYDEBUG("Settings table for ~p already exists", [VHost]),
2673 + {aborted, Reason} ->
2674 + ?ERROR_MSG("Failed to create settings table: ~p", [Reason]),
2678 +create_msg_table(VHost, Date) ->
2679 + mnesia:create_table(
2680 + table_name(VHost, Date),
2681 + [{disc_only_copies, [node()]},
2683 + {attributes, record_info(fields, msg)},
2684 + {record_name, msg}]).
2685 --- src/mod_logdb_mysql.erl.orig 2009-02-05 19:21:29.000000000 +0200
2686 +++ src/mod_logdb_mysql.erl 2009-02-05 19:20:23.000000000 +0200
2688 +%%%----------------------------------------------------------------------
2689 +%%% File : mod_logdb_mysql.erl
2690 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
2691 +%%% Purpose : MySQL backend for mod_logdb
2692 +%%% Version : trunk
2694 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
2695 +%%%----------------------------------------------------------------------
2697 +-module(mod_logdb_mysql).
2698 +-author('o.palij@gmail.com').
2700 +-include("mod_logdb.hrl").
2701 +-include("ejabberd.hrl").
2702 +-include("jlib.hrl").
2704 +-behaviour(gen_logdb).
2705 +-behaviour(gen_server).
2708 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
2710 +-export([start/2, stop/1]).
2712 +-export([log_message/2,
2714 + rebuild_stats_at/2,
2715 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
2716 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
2718 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
2721 +% gen_server call timeout
2722 +-define(CALL_TIMEOUT, 30000).
2723 +-define(MYSQL_TIMEOUT, 60000).
2724 +-define(INDEX_SIZE, integer_to_list(170)).
2725 +-define(PROCNAME, mod_logdb_mysql).
2727 +-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
2728 + list_to_string/1, string_to_list/1,
2729 + convert_timestamp_brief/1]).
2731 +-record(state, {dbref, vhost, server, port, db, user, password}).
2733 +% replace "." with "_"
2734 +escape_vhost(VHost) -> lists:map(fun(46) -> 95;
2741 + "_" ++ escape_vhost(VHost) ++ "`".
2743 +messages_table(VHost, Date) ->
2744 + prefix() ++ "messages_" ++ Date ++ suffix(VHost).
2746 +stats_table(VHost) ->
2747 + prefix() ++ "stats" ++ suffix(VHost).
2749 +temp_table(VHost) ->
2750 + prefix() ++ "temp" ++ suffix(VHost).
2752 +settings_table(VHost) ->
2753 + prefix() ++ "settings" ++ suffix(VHost).
2755 +users_table(VHost) ->
2756 + prefix() ++ "users" ++ suffix(VHost).
2757 +servers_table(VHost) ->
2758 + prefix() ++ "servers" ++ suffix(VHost).
2759 +resources_table(VHost) ->
2760 + prefix() ++ "resources" ++ suffix(VHost).
2762 +ets_users_table(VHost) -> list_to_atom("logdb_users_" ++ VHost).
2763 +ets_servers_table(VHost) -> list_to_atom("logdb_servers_" ++ VHost).
2764 +ets_resources_table(VHost) -> list_to_atom("logdb_resources_" ++ VHost).
2766 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2768 +% gen_mod callbacks
2770 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2771 +start(VHost, Opts) ->
2772 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2773 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
2776 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2777 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
2779 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2781 +% gen_server callbacks
2783 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2784 +init([VHost, Opts]) ->
2787 + Server = gen_mod:get_opt(server, Opts, "localhost"),
2788 + Port = gen_mod:get_opt(port, Opts, 3306),
2789 + DB = gen_mod:get_opt(db, Opts, "logdb"),
2790 + User = gen_mod:get_opt(user, Opts, "root"),
2791 + Password = gen_mod:get_opt(password, Opts, ""),
2793 + St = #state{vhost=VHost,
2794 + server=Server, port=Port, db=DB,
2795 + user=User, password=Password},
2797 + case open_mysql_connection(St) of
2799 + State = St#state{dbref=DBRef},
2800 + ok = create_stats_table(State),
2801 + ok = create_settings_table(State),
2802 + ok = create_users_table(State),
2803 + % clear ets cache every ...
2804 + timer:send_interval(timer:hours(12), clear_ets_tables),
2805 + ok = create_servers_table(State),
2806 + ok = create_resources_table(State),
2807 + erlang:monitor(process, DBRef),
2809 + {error, Reason} ->
2810 + ?ERROR_MSG("MySQL connection failed: ~p~n", [Reason]),
2811 + {stop, db_connection_failed}
2814 +open_mysql_connection(#state{server=Server, port=Port, db=DB,
2815 + user=DBUser, password=Password} = _State) ->
2816 + LogFun = fun(debug, _Format, _Argument) ->
2817 + %?MYDEBUG(Format, Argument);
2819 + (error, Format, Argument) ->
2820 + ?ERROR_MSG(Format, Argument);
2821 + (Level, Format, Argument) ->
2822 + ?MYDEBUG("MySQL (~p)~n", [Level]),
2823 + ?MYDEBUG(Format, Argument)
2825 + mysql_conn:start(Server, Port, DBUser, Password, DB, LogFun).
2827 +close_mysql_connection(DBRef) ->
2828 + ?MYDEBUG("Closing ~p mysql connection", [DBRef]),
2829 + mysql_conn:stop(DBRef).
2831 +handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2832 + Date = convert_timestamp_brief(Msg#msg.timestamp),
2834 + Table = messages_table(VHost, Date),
2835 + Owner_id = get_user_id(DBRef, VHost, Msg#msg.owner_name),
2836 + Peer_name_id = get_user_id(DBRef, VHost, Msg#msg.peer_name),
2837 + Peer_server_id = get_server_id(DBRef, VHost, Msg#msg.peer_server),
2838 + Peer_resource_id = get_resource_id(DBRef, VHost, Msg#msg.peer_resource),
2840 + Query = ["INSERT INTO ",Table," ",
2843 + "peer_server_id,",
2844 + "peer_resource_id,",
2851 + "('", Owner_id, "',",
2852 + "'", Peer_name_id, "',",
2853 + "'", Peer_server_id, "',",
2854 + "'", Peer_resource_id, "',",
2855 + "'", atom_to_list(Msg#msg.direction), "',",
2856 + "'", Msg#msg.type, "',",
2857 + "'", ejabberd_odbc:escape(Msg#msg.subject), "',",
2858 + "'", ejabberd_odbc:escape(Msg#msg.body), "',",
2859 + "'", Msg#msg.timestamp, "');"],
2862 + case sql_query_internal_silent(DBRef, Query) of
2864 + ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
2865 + Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
2866 + increment_user_stats(DBRef, Msg#msg.owner_name, Owner_id, VHost, Peer_name_id, Peer_server_id, Date);
2867 + {error, Reason} ->
2868 + case regexp:match(Reason, "#42S02") of
2869 + % Table doesn't exist
2871 + case create_msg_table(DBRef, VHost, Date) of
2875 + {updated, _} = sql_query_internal(DBRef, Query),
2876 + increment_user_stats(DBRef, Msg#msg.owner_name, Owner_id, VHost, Peer_name_id, Peer_server_id, Date)
2879 + ?ERROR_MSG("Failed to log message: ~p", [Reason]),
2883 + {reply, Reply, State};
2884 +handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2885 + Reply = rebuild_stats_at_int(DBRef, VHost, Date),
2886 + {reply, Reply, State};
2887 +handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
2888 + {reply, error, State};
2889 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2890 + Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
2891 + ["\"",Timestamp,"\"",","]
2894 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
2896 + Query = ["DELETE FROM ",messages_table(VHost, Date)," ",
2897 + "WHERE timestamp IN (", Temp1],
2900 + case sql_query_internal(DBRef, Query) of
2902 + ?MYDEBUG("Aff=~p", [Aff]),
2903 + rebuild_stats_at_int(DBRef, VHost, Date);
2907 + {reply, Reply, State};
2908 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2909 + ok = delete_all_messages_by_user_at_int(DBRef, User, VHost, Date),
2910 + ok = delete_stats_by_user_at_int(DBRef, User, VHost, Date),
2911 + {reply, ok, State};
2912 +handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2914 + case sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Date),";"]) of
2916 + Query = ["DELETE FROM ",stats_table(VHost)," "
2917 + "WHERE at=\"",Date,"\";"],
2918 + case sql_query_internal(DBRef, Query) of
2927 + {reply, Reply, State};
2928 +handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2929 + SName = stats_table(VHost),
2930 + Query = ["SELECT at, sum(count) ",
2931 + "FROM ",SName," ",
2933 + "ORDER BY DATE(at) DESC;"
2936 + case sql_query_internal(DBRef, Query) of
2938 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
2939 + {error, Reason} ->
2940 + % TODO: Duplicate error message ?
2943 + {reply, Reply, State};
2944 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2945 + SName = stats_table(VHost),
2946 + Query = ["SELECT username, sum(count) AS allcount ",
2947 + "FROM ",SName," ",
2948 + "JOIN ",users_table(VHost)," ON owner_id=user_id "
2949 + "WHERE at=\"",Date,"\" "
2950 + "GROUP BY username ",
2951 + "ORDER BY allcount DESC;"
2954 + case sql_query_internal(DBRef, Query) of
2956 + {ok, lists:reverse(
2958 + [ {User, list_to_integer(Count)} || [User, Count] <- Result]))};
2959 + {error, Reason} ->
2963 + {reply, Reply, State};
2964 +handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2965 + {reply, get_user_stats_int(DBRef, User, VHost), State};
2966 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2967 + TName = messages_table(VHost, Date),
2968 + UName = users_table(VHost),
2969 + SName = servers_table(VHost),
2970 + RName = resources_table(VHost),
2971 + Query = ["SELECT users.username,",
2972 + "servers.server,",
2973 + "resources.resource,",
2974 + "messages.direction,"
2976 + "messages.subject,"
2978 + "messages.timestamp "
2979 + "FROM ",TName," AS messages "
2980 + "JOIN ",UName," AS users ON peer_name_id=user_id ",
2981 + "JOIN ",SName," AS servers ON peer_server_id=server_id ",
2982 + "JOIN ",RName," AS resources ON peer_resource_id=resource_id ",
2983 + "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\" ",
2984 + "ORDER BY timestamp ASC;"],
2986 + case sql_query_internal(DBRef, Query) of
2988 + Fun = fun([Peer_name, Peer_server, Peer_resource,
2993 + #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
2994 + direction=list_to_atom(Direction),
2996 + subject=Subject, body=Body,
2997 + timestamp=Timestamp}
2999 + {ok, lists:map(Fun, Result)};
3000 + {error, Reason} ->
3003 + {reply, Reply, State};
3004 +handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3005 + SName = stats_table(VHost),
3006 + Query = ["SELECT at ",
3007 + "FROM ",SName," ",
3009 + "ORDER BY DATE(at) DESC;"
3012 + case sql_query_internal(DBRef, Query) of
3014 + [ Date || [Date] <- Result ];
3015 + {error, Reason} ->
3018 + {reply, Reply, State};
3019 +handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3020 + Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
3021 + "FROM ",settings_table(VHost)," ",
3022 + "JOIN ",users_table(VHost)," ON user_id=owner_id;"],
3024 + case sql_query_internal(DBRef, Query) of
3026 + {ok, lists:map(fun([Owner, DoLogDef, DoLogL, DoNotLogL]) ->
3027 + #user_settings{owner_name=Owner,
3028 + dolog_default=list_to_bool(DoLogDef),
3029 + dolog_list=string_to_list(DoLogL),
3030 + donotlog_list=string_to_list(DoNotLogL)
3036 + {reply, Reply, State};
3037 +handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3038 + Query = ["SELECT dolog_default,dolog_list,donotlog_list FROM ",settings_table(VHost)," ",
3039 + "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\";"],
3041 + case sql_query_internal(DBRef, Query) of
3044 + {data, [[Owner, DoLogDef, DoLogL, DoNotLogL]]} ->
3045 + {ok, #user_settings{owner_name=Owner,
3046 + dolog_default=list_to_bool(DoLogDef),
3047 + dolog_list=string_to_list(DoLogL),
3048 + donotlog_list=string_to_list(DoNotLogL)}};
3052 + {reply, Reply, State};
3053 +handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
3054 + dolog_list=DoLogL,
3055 + donotlog_list=DoNotLogL}},
3056 + _From, #state{dbref=DBRef, vhost=VHost} = State) ->
3057 + User_id = get_user_id(DBRef, VHost, User),
3059 + Query = ["UPDATE ",settings_table(VHost)," ",
3060 + "SET dolog_default=",bool_to_list(DoLogDef),", ",
3061 + "dolog_list='",list_to_string(DoLogL),"', ",
3062 + "donotlog_list='",list_to_string(DoNotLogL),"' ",
3063 + "WHERE owner_id=\"",User_id,"\";"],
3066 + case sql_query_internal(DBRef, Query) of
3068 + IQuery = ["INSERT INTO ",settings_table(VHost)," ",
3069 + "(owner_id, dolog_default, dolog_list, donotlog_list) ",
3071 + "('",User_id,"', ",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
3072 + case sql_query_internal_silent(DBRef, IQuery) of
3074 + ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
3076 + {error, Reason} ->
3077 + case regexp:match(Reason, "#23000") of
3082 + ?ERROR_MSG("Failed setup user ~p@~p: ~p", [User, VHost, Reason]),
3087 + ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
3092 + {reply, Reply, State};
3093 +handle_call({stop}, _From, #state{vhost=VHost}=State) ->
3094 + ets:delete(ets_users_table(VHost)),
3095 + ets:delete(ets_servers_table(VHost)),
3096 + ?MYDEBUG("Stoping mysql backend for ~p", [VHost]),
3097 + {stop, normal, ok, State};
3098 +handle_call(Msg, _From, State) ->
3099 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
3102 +handle_cast({rebuild_stats}, State) ->
3103 + rebuild_all_stats_int(State),
3105 +handle_cast({drop_user, User}, #state{vhost=VHost} = State) ->
3107 + {ok, DBRef} = open_mysql_connection(State),
3108 + {ok, Dates} = get_user_stats_int(DBRef, User, VHost),
3109 + MDResult = lists:map(fun({Date, _}) ->
3110 + delete_all_messages_by_user_at_int(DBRef, User, VHost, Date)
3112 + StDResult = delete_all_stats_by_user_int(DBRef, User, VHost),
3113 + SDResult = delete_user_settings_int(DBRef, User, VHost),
3114 + case lists:all(fun(Result) when Result == ok ->
3116 + (Result) when Result == error ->
3118 + end, lists:append([MDResult, [StDResult], [SDResult]])) of
3120 + ?INFO_MSG("Removed ~s@~s", [User, VHost]);
3122 + ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost])
3124 + close_mysql_connection(DBRef)
3128 +handle_cast(Msg, State) ->
3129 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
3132 +handle_info(clear_ets_tables, State) ->
3133 + ets:delete_all_objects(ets_users_table(State#state.vhost)),
3134 + ets:delete_all_objects(ets_resources_table(State#state.vhost)),
3136 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
3137 + {stop, connection_dropped, State};
3138 +handle_info(Info, State) ->
3139 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
3142 +terminate(_Reason, #state{dbref=DBRef}=_State) ->
3143 + close_mysql_connection(DBRef),
3146 +code_change(_OldVsn, State, _Extra) ->
3149 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3151 +% gen_logdb callbacks
3153 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3154 +log_message(VHost, Msg) ->
3155 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3156 + gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
3157 +rebuild_stats(VHost) ->
3158 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3159 + gen_server:cast(Proc, {rebuild_stats}).
3160 +rebuild_stats_at(VHost, Date) ->
3161 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3162 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
3163 +delete_messages_by_user_at(VHost, Msgs, Date) ->
3164 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3165 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
3166 +delete_all_messages_by_user_at(User, VHost, Date) ->
3167 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3168 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
3169 +delete_messages_at(VHost, Date) ->
3170 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3171 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
3172 +get_vhost_stats(VHost) ->
3173 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3174 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
3175 +get_vhost_stats_at(VHost, Date) ->
3176 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3177 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
3178 +get_user_stats(User, VHost) ->
3179 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3180 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
3181 +get_user_messages_at(User, VHost, Date) ->
3182 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3183 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
3184 +get_dates(VHost) ->
3185 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3186 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
3187 +get_users_settings(VHost) ->
3188 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3189 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
3190 +get_user_settings(User, VHost) ->
3191 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3192 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
3193 +set_user_settings(User, VHost, Set) ->
3194 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3195 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
3196 +drop_user(User, VHost) ->
3197 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3198 + gen_server:cast(Proc, {drop_user, User}).
3200 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3204 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3205 +increment_user_stats(DBRef, User_name, User_id, VHost, PNameID, PServerID, Date) ->
3206 + SName = stats_table(VHost),
3207 + UQuery = ["UPDATE ",SName," ",
3208 + "SET count=count+1 ",
3209 + "WHERE owner_id=\"",User_id,"\" AND peer_name_id=\"",PNameID,"\" AND peer_server_id=\"",PServerID,"\" AND at=\"",Date,"\";"],
3211 + case sql_query_internal(DBRef, UQuery) of
3213 + IQuery = ["INSERT INTO ",SName," ",
3214 + "(owner_id, peer_name_id, peer_server_id, at, count) ",
3216 + "('",User_id,"', '",PNameID,"', '",PServerID,"', '",Date,"', '1');"],
3217 + case sql_query_internal(DBRef, IQuery) of
3219 + ?MYDEBUG("New stats for ~s@~s at ~s", [User_name, VHost, Date]),
3225 + ?MYDEBUG("Updated stats for ~s@~s at ~s", [User_name, VHost, Date]),
3231 +get_dates_int(DBRef, VHost) ->
3232 + case sql_query_internal(DBRef, ["SHOW TABLES"]) of
3234 + lists:foldl(fun([Table], Dates) ->
3235 + Reg = lists:sublist(prefix(),2,length(prefix())) ++ ".*" ++ escape_vhost(VHost),
3236 + case regexp:match(Table, Reg) of
3238 + ?MYDEBUG("matched ~p against ~p", [Table, Reg]),
3239 + case regexp:match(Table,"[0-9]+-[0-9]+-[0-9]+") of
3241 + lists:append(Dates, [lists:sublist(Table,S,E)]);
3254 +rebuild_all_stats_int(#state{vhost=VHost}=State) ->
3256 + {ok, DBRef} = open_mysql_connection(State),
3257 + ok = delete_nonexistent_stats(DBRef, VHost),
3258 + case lists:filter(fun(Date) ->
3259 + case catch rebuild_stats_at_int(DBRef, VHost, Date) of
3262 + {'EXIT', _} -> true
3264 + end, get_dates_int(DBRef, VHost)) of
3267 + ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]),
3270 + close_mysql_connection(DBRef)
3274 +rebuild_stats_at_int(DBRef, VHost, Date) ->
3275 + TempTable = temp_table(VHost),
3277 + Table = messages_table(VHost, Date),
3278 + STable = stats_table(VHost),
3280 + DQuery = [ "DELETE FROM ",STable," ",
3281 + "WHERE at='",Date,"';"],
3283 + ok = create_temp_table(DBRef, TempTable),
3284 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," WRITE, ",TempTable," WRITE;"]),
3285 + SQuery = ["INSERT INTO ",TempTable," ",
3286 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
3287 + "SELECT owner_id,peer_name_id,peer_server_id,\"",Date,"\",count(*) ",
3288 + "FROM ",Table," GROUP BY owner_id,peer_name_id,peer_server_id;"],
3289 + case sql_query_internal(DBRef, SQuery) of
3291 + Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]),
3293 + {data, [["0"]]} ->
3294 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]),
3295 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE;"]),
3296 + {updated, _} = sql_query_internal(DBRef, DQuery),
3299 + ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]),
3303 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE, ",TempTable," WRITE;"]),
3304 + {updated, _} = sql_query_internal(DBRef, DQuery),
3305 + SQuery1 = ["INSERT INTO ",STable," ",
3306 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
3307 + "SELECT owner_id,peer_name_id,peer_server_id,at,count ",
3308 + "FROM ",TempTable,";"],
3309 + case sql_query_internal(DBRef, SQuery1) of
3310 + {updated, _} -> ok;
3311 + {error, _} -> error
3313 + {error, _} -> error
3317 + case catch apply(Fun, []) of
3319 + ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
3323 + {'EXIT', Reason} ->
3324 + ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]),
3327 + sql_query_internal(DBRef, ["UNLOCK TABLES;"]),
3328 + sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]),
3332 +delete_nonexistent_stats(DBRef, VHost) ->
3333 + Dates = get_dates_int(DBRef, VHost),
3334 + STable = stats_table(VHost),
3336 + Temp = lists:flatmap(fun(Date) ->
3337 + ["\"",Date,"\"",","]
3344 + % replace last "," with ");"
3345 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
3346 + Query = ["DELETE FROM ",STable," ",
3347 + "WHERE at NOT IN (", Temp1],
3348 + case sql_query_internal(DBRef, Query) of
3356 +get_user_stats_int(DBRef, User, VHost) ->
3357 + SName = stats_table(VHost),
3358 + Query = ["SELECT at, sum(count) as allcount ",
3359 + "FROM ",SName," ",
3360 + "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\" ",
3362 + "ORDER BY DATE(at) DESC;"
3364 + case sql_query_internal(DBRef, Query) of
3366 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result]};
3367 + {error, Result} ->
3371 +delete_all_messages_by_user_at_int(DBRef, User, VHost, Date) ->
3372 + DQuery = ["DELETE FROM ",messages_table(VHost, Date)," ",
3373 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
3374 + case sql_query_internal(DBRef, DQuery) of
3376 + ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]),
3382 +delete_all_stats_by_user_int(DBRef, User, VHost) ->
3383 + SQuery = ["DELETE FROM ",stats_table(VHost)," ",
3384 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
3385 + case sql_query_internal(DBRef, SQuery) of
3387 + ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]),
3389 + {error, _} -> error
3392 +delete_stats_by_user_at_int(DBRef, User, VHost, Date) ->
3393 + SQuery = ["DELETE FROM ",stats_table(VHost)," ",
3394 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\") ",
3395 + "AND at=\"",Date,"\";"],
3396 + case sql_query_internal(DBRef, SQuery) of
3398 + ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]),
3400 + {error, _} -> error
3403 +delete_user_settings_int(DBRef, User, VHost) ->
3404 + Query = ["DELETE FROM ",settings_table(VHost)," ",
3405 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
3406 + case sql_query_internal(DBRef, Query) of
3408 + ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]),
3410 + {error, Reason} ->
3411 + ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]),
3415 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3419 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3420 +create_temp_table(DBRef, Name) ->
3421 + Query = ["CREATE TABLE ",Name," (",
3422 + "owner_id MEDIUMINT UNSIGNED, ",
3423 + "peer_name_id MEDIUMINT UNSIGNED, ",
3424 + "peer_server_id MEDIUMINT UNSIGNED, ",
3425 + "at VARCHAR(11), ",
3427 + ") ENGINE=MyISAM CHARACTER SET utf8;"
3429 + case sql_query_internal(DBRef, Query) of
3430 + {updated, _} -> ok;
3431 + {error, _Reason} -> error
3434 +create_stats_table(#state{dbref=DBRef, vhost=VHost}=State) ->
3435 + SName = stats_table(VHost),
3436 + Query = ["CREATE TABLE ",SName," (",
3437 + "owner_id MEDIUMINT UNSIGNED, ",
3438 + "peer_name_id MEDIUMINT UNSIGNED, ",
3439 + "peer_server_id MEDIUMINT UNSIGNED, ",
3440 + "at varchar(20), ",
3441 + "count int(11), ",
3442 + "INDEX(owner_id, peer_name_id, peer_server_id), ",
3444 + ") ENGINE=InnoDB CHARACTER SET utf8;"
3446 + case sql_query_internal_silent(DBRef, Query) of
3448 + ?INFO_MSG("Created stats table for ~p", [VHost]),
3449 + rebuild_all_stats_int(State),
3451 + {error, Reason} ->
3452 + case regexp:match(Reason, "#42S01") of
3454 + ?MYDEBUG("Stats table for ~p already exists", [VHost]),
3455 + CheckQuery = ["SHOW COLUMNS FROM ",SName," LIKE 'peer_%_id';"],
3456 + case sql_query_internal(DBRef, CheckQuery) of
3457 + {data, Elems} when length(Elems) == 2 ->
3458 + ?MYDEBUG("Stats table structure is ok", []),
3461 + ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []),
3462 + case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of
3464 + ?INFO_MSG("Successfully dropped ~p", [SName]);
3466 + ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName])
3471 + ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
3476 +create_settings_table(#state{dbref=DBRef, vhost=VHost}) ->
3477 + SName = settings_table(VHost),
3478 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
3479 + "owner_id MEDIUMINT UNSIGNED PRIMARY KEY, ",
3480 + "dolog_default TINYINT(1) NOT NULL DEFAULT 1, ",
3481 + "dolog_list TEXT, ",
3482 + "donotlog_list TEXT ",
3483 + ") ENGINE=InnoDB CHARACTER SET utf8;"
3485 + case sql_query_internal(DBRef, Query) of
3487 + ?MYDEBUG("Created settings table for ~p", [VHost]),
3493 +create_users_table(#state{dbref=DBRef, vhost=VHost}) ->
3494 + SName = users_table(VHost),
3495 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
3496 + "username TEXT NOT NULL, ",
3497 + "user_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
3498 + "UNIQUE INDEX(username(",?INDEX_SIZE,")) ",
3499 + ") ENGINE=InnoDB CHARACTER SET utf8;"
3501 + case sql_query_internal(DBRef, Query) of
3503 + ?MYDEBUG("Created users table for ~p", [VHost]),
3504 + ets:new(ets_users_table(VHost), [named_table, set, public]),
3505 + %update_users_from_db(DBRef, VHost),
3511 +create_servers_table(#state{dbref=DBRef, vhost=VHost}) ->
3512 + SName = servers_table(VHost),
3513 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
3514 + "server TEXT NOT NULL, ",
3515 + "server_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
3516 + "UNIQUE INDEX(server(",?INDEX_SIZE,")) ",
3517 + ") ENGINE=InnoDB CHARACTER SET utf8;"
3519 + case sql_query_internal(DBRef, Query) of
3521 + ?MYDEBUG("Created servers table for ~p", [VHost]),
3522 + ets:new(ets_servers_table(VHost), [named_table, set, public]),
3523 + update_servers_from_db(DBRef, VHost),
3529 +create_resources_table(#state{dbref=DBRef, vhost=VHost}) ->
3530 + RName = resources_table(VHost),
3531 + Query = ["CREATE TABLE IF NOT EXISTS ",RName," (",
3532 + "resource TEXT NOT NULL, ",
3533 + "resource_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
3534 + "UNIQUE INDEX(resource(",?INDEX_SIZE,")) ",
3535 + ") ENGINE=InnoDB CHARACTER SET utf8;"
3537 + case sql_query_internal(DBRef, Query) of
3539 + ?MYDEBUG("Created resources table for ~p", [VHost]),
3540 + ets:new(ets_resources_table(VHost), [named_table, set, public]),
3546 +create_msg_table(DBRef, VHost, Date) ->
3547 + TName = messages_table(VHost, Date),
3548 + Query = ["CREATE TABLE ",TName," (",
3549 + "owner_id MEDIUMINT UNSIGNED, ",
3550 + "peer_name_id MEDIUMINT UNSIGNED, ",
3551 + "peer_server_id MEDIUMINT UNSIGNED, ",
3552 + "peer_resource_id MEDIUMINT(8) UNSIGNED, ",
3553 + "direction ENUM('to', 'from'), ",
3554 + "type ENUM('chat','error','groupchat','headline','normal') NOT NULL, ",
3557 + "timestamp DOUBLE, ",
3558 + "INDEX search_i (owner_id, peer_name_id, peer_server_id, peer_resource_id), ",
3559 + "FULLTEXT (body) "
3560 + ") ENGINE=MyISAM CHARACTER SET utf8;"
3562 + case sql_query_internal(DBRef, Query) of
3563 + {updated, _MySQLRes} ->
3564 + ?MYDEBUG("Created msg table for ~p at ~p", [VHost, Date]),
3570 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3572 +% internal ets cache (users, servers, resources)
3574 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3575 +update_servers_from_db(DBRef, VHost) ->
3576 + ?INFO_MSG("Reading servers from db for ~p", [VHost]),
3577 + SQuery = ["SELECT server, server_id FROM ",servers_table(VHost),";"],
3578 + {data, Result} = sql_query_internal(DBRef, SQuery),
3579 + true = ets:delete_all_objects(ets_servers_table(VHost)),
3580 + true = ets:insert(ets_servers_table(VHost), [ {Server, Server_id} || [Server, Server_id] <- Result]).
3582 +%update_users_from_db(DBRef, VHost) ->
3583 +% ?INFO_MSG("Reading users from db for ~p", [VHost]),
3584 +% SQuery = ["SELECT username, user_id FROM ",users_table(VHost),";"],
3585 +% {data, Result} = sql_query_internal(DBRef, SQuery),
3586 +% true = ets:delete_all_objects(ets_users_table(VHost)),
3587 +% true = ets:insert(ets_users_table(VHost), [ {Username, User_id} || [Username, User_id] <- Result]).
3589 +%get_user_name(DBRef, VHost, User_id) ->
3590 +% case ets:match(ets_users_table(VHost), {'$1', User_id}) of
3591 +% [[User]] -> User;
3592 +% % this can be in clustered environment
3594 +% %update_users_from_db(DBRef, VHost),
3595 +% SQuery = ["SELECT username FROM ",users_table(VHost)," ",
3596 +% "WHERE user_id=\"",User_id,"\";"],
3597 +% {data, [[Name]]} = sql_query_internal(DBRef, SQuery),
3598 +% % cache {user, id} pair
3599 +% ets:insert(ets_users_table(VHost), {Name, User_id}),
3603 +%get_server_name(DBRef, VHost, Server_id) ->
3604 +% case ets:match(ets_servers_table(VHost), {'$1', Server_id}) of
3605 +% [[Server]] -> Server;
3606 + % this can be in clustered environment
3608 +% update_servers_from_db(DBRef, VHost),
3609 +% [[Server1]] = ets:match(ets_servers_table(VHost), {'$1', Server_id}),
3613 +get_user_id_from_db(DBRef, VHost, User) ->
3614 + SQuery = ["SELECT user_id FROM ",users_table(VHost)," ",
3615 + "WHERE username=\"",User,"\";"],
3616 + case sql_query_internal(DBRef, SQuery) of
3617 + % no such user in db
3620 + {data, [[DBId]]} ->
3621 + % cache {user, id} pair
3622 + ets:insert(ets_users_table(VHost), {User, DBId}),
3625 +get_user_id(DBRef, VHost, User) ->
3627 + case ets:match(ets_users_table(VHost), {User, '$1'}) of
3630 + case get_user_id_from_db(DBRef, VHost, User) of
3631 + % no such user in db
3633 + IQuery = ["INSERT INTO ",users_table(VHost)," ",
3634 + "SET username=\"",User,"\";"],
3635 + case sql_query_internal_silent(DBRef, IQuery) of
3637 + {ok, NewId} = get_user_id_from_db(DBRef, VHost, User),
3639 + {error, Reason} ->
3640 + % this can be in clustered environment
3641 + {match, _, _} = regexp:match(Reason, "#23000"),
3642 + ?ERROR_MSG("Duplicate key name for ~p", [User]),
3643 + {ok, ClID} = get_user_id_from_db(DBRef, VHost, User),
3649 + [[EtsId]] -> EtsId
3652 +get_server_id(DBRef, VHost, Server) ->
3653 + case ets:match(ets_servers_table(VHost), {Server, '$1'}) of
3655 + IQuery = ["INSERT INTO ",servers_table(VHost)," ",
3656 + "SET server=\"",Server,"\";"],
3657 + case sql_query_internal_silent(DBRef, IQuery) of
3659 + SQuery = ["SELECT server_id FROM ",servers_table(VHost)," ",
3660 + "WHERE server=\"",Server,"\";"],
3661 + {data, [[Id]]} = sql_query_internal(DBRef, SQuery),
3662 + ets:insert(ets_servers_table(VHost), {Server, Id}),
3664 + {error, Reason} ->
3665 + % this can be in clustered environment
3666 + {match, _, _} = regexp:match(Reason, "#23000"),
3667 + ?ERROR_MSG("Duplicate key name for ~p", [Server]),
3668 + update_servers_from_db(DBRef, VHost),
3669 + [[Id1]] = ets:match(ets_servers_table(VHost), {Server, '$1'}),
3675 +get_resource_id_from_db(DBRef, VHost, Resource) ->
3676 + SQuery = ["SELECT resource_id FROM ",resources_table(VHost)," ",
3677 + "WHERE resource=\"",ejabberd_odbc:escape(Resource),"\";"],
3678 + case sql_query_internal(DBRef, SQuery) of
3679 + % no such resource in db
3682 + {data, [[DBId]]} ->
3683 + % cache {resource, id} pair
3684 + ets:insert(ets_resources_table(VHost), {Resource, DBId}),
3687 +get_resource_id(DBRef, VHost, Resource) ->
3689 + case ets:match(ets_resources_table(VHost), {Resource, '$1'}) of
3692 + case get_resource_id_from_db(DBRef, VHost, Resource) of
3693 + % no such resource in db
3695 + IQuery = ["INSERT INTO ",resources_table(VHost)," ",
3696 + "SET resource=\"",ejabberd_odbc:escape(Resource),"\";"],
3697 + case sql_query_internal_silent(DBRef, IQuery) of
3699 + {ok, NewId} = get_resource_id_from_db(DBRef, VHost, Resource),
3701 + {error, Reason} ->
3702 + % this can be in clustered environment
3703 + {match, _, _} = regexp:match(Reason, "#23000"),
3704 + ?ERROR_MSG("Duplicate key name for ~p", [Resource]),
3705 + {ok, ClID} = get_resource_id_from_db(DBRef, VHost, Resource),
3711 + [[EtsId]] -> EtsId
3714 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3718 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3719 +sql_query_internal(DBRef, Query) ->
3720 + case sql_query_internal_silent(DBRef, Query) of
3721 + {error, Reason} ->
3722 + ?ERROR_MSG("~p while ~p", [Reason, lists:append(Query)]),
3727 +sql_query_internal_silent(DBRef, Query) ->
3728 + ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
3729 + get_result(mysql_conn:fetch(DBRef, Query, self(), ?MYSQL_TIMEOUT)).
3731 +get_result({updated, MySQLRes}) ->
3732 + {updated, mysql:get_result_affected_rows(MySQLRes)};
3733 +get_result({data, MySQLRes}) ->
3734 + {data, mysql:get_result_rows(MySQLRes)};
3735 +get_result({error, "query timed out"}) ->
3736 + {error, "query timed out"};
3737 +get_result({error, MySQLRes}) ->
3738 + Reason = mysql:get_result_reason(MySQLRes),
3740 --- src/mod_logdb_mysql5.erl.orig 2009-02-05 19:21:29.000000000 +0200
3741 +++ src/mod_logdb_mysql5.erl 2009-02-05 19:20:14.000000000 +0200
3743 +%%%----------------------------------------------------------------------
3744 +%%% File : mod_logdb_mysql5.erl
3745 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
3746 +%%% Purpose : MySQL 5 backend for mod_logdb
3747 +%%% Version : trunk
3749 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
3750 +%%%----------------------------------------------------------------------
3752 +-module(mod_logdb_mysql5).
3753 +-author('o.palij@gmail.com').
3755 +-include("mod_logdb.hrl").
3756 +-include("ejabberd.hrl").
3757 +-include("jlib.hrl").
3759 +-behaviour(gen_logdb).
3760 +-behaviour(gen_server).
3763 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
3765 +-export([start/2, stop/1]).
3767 +-export([log_message/2,
3769 + rebuild_stats_at/2,
3770 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
3771 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
3773 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
3776 +% gen_server call timeout
3777 +-define(CALL_TIMEOUT, 30000).
3778 +-define(MYSQL_TIMEOUT, 60000).
3779 +-define(INDEX_SIZE, integer_to_list(170)).
3780 +-define(PROCNAME, mod_logdb_mysql5).
3782 +-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
3783 + list_to_string/1, string_to_list/1,
3784 + convert_timestamp_brief/1]).
3786 +-record(state, {dbref, vhost, server, port, db, user, password}).
3788 +% replace "." with "_"
3789 +escape_vhost(VHost) -> lists:map(fun(46) -> 95;
3796 + "_" ++ escape_vhost(VHost) ++ "`".
3798 +messages_table(VHost, Date) ->
3799 + prefix() ++ "messages_" ++ Date ++ suffix(VHost).
3801 +% TODO: this needs to be redone to unify view name in stored procedure and in delete_messages_at/2
3802 +view_table(VHost, Date) ->
3803 + Table = messages_table(VHost, Date),
3804 + TablewoQ = lists:sublist(Table, 2, length(Table) - 2),
3805 + lists:append(["`v_", TablewoQ, "`"]).
3807 +stats_table(VHost) ->
3808 + prefix() ++ "stats" ++ suffix(VHost).
3810 +temp_table(VHost) ->
3811 + prefix() ++ "temp" ++ suffix(VHost).
3813 +settings_table(VHost) ->
3814 + prefix() ++ "settings" ++ suffix(VHost).
3816 +users_table(VHost) ->
3817 + prefix() ++ "users" ++ suffix(VHost).
3818 +servers_table(VHost) ->
3819 + prefix() ++ "servers" ++ suffix(VHost).
3820 +resources_table(VHost) ->
3821 + prefix() ++ "resources" ++ suffix(VHost).
3823 +logmessage_name(VHost) ->
3824 + prefix() ++ "logmessage" ++ suffix(VHost).
3826 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3828 +% gen_mod callbacks
3830 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3831 +start(VHost, Opts) ->
3832 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3833 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
3836 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3837 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
3839 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3841 +% gen_server callbacks
3843 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3844 +init([VHost, Opts]) ->
3847 + Server = gen_mod:get_opt(server, Opts, "localhost"),
3848 + Port = gen_mod:get_opt(port, Opts, 3306),
3849 + DB = gen_mod:get_opt(db, Opts, "logdb"),
3850 + User = gen_mod:get_opt(user, Opts, "root"),
3851 + Password = gen_mod:get_opt(password, Opts, ""),
3853 + St = #state{vhost=VHost,
3854 + server=Server, port=Port, db=DB,
3855 + user=User, password=Password},
3857 + case open_mysql_connection(St) of
3859 + State = St#state{dbref=DBRef},
3860 + ok = create_internals(State),
3861 + ok = create_stats_table(State),
3862 + ok = create_settings_table(State),
3863 + ok = create_users_table(State),
3864 + ok = create_servers_table(State),
3865 + ok = create_resources_table(State),
3866 + erlang:monitor(process, DBRef),
3868 + {error, Reason} ->
3869 + ?ERROR_MSG("MySQL connection failed: ~p~n", [Reason]),
3870 + {stop, db_connection_failed}
3873 +open_mysql_connection(#state{server=Server, port=Port, db=DB,
3874 + user=DBUser, password=Password} = _State) ->
3875 + LogFun = fun(debug, _Format, _Argument) ->
3876 + %?MYDEBUG(Format, Argument);
3878 + (error, Format, Argument) ->
3879 + ?ERROR_MSG(Format, Argument);
3880 + (Level, Format, Argument) ->
3881 + ?MYDEBUG("MySQL (~p)~n", [Level]),
3882 + ?MYDEBUG(Format, Argument)
3884 + mysql_conn:start(Server, Port, DBUser, Password, DB, [65536, 131072], LogFun).
3886 +close_mysql_connection(DBRef) ->
3887 + ?MYDEBUG("Closing ~p mysql connection", [DBRef]),
3888 + mysql_conn:stop(DBRef).
3890 +handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3891 + Reply = rebuild_stats_at_int(DBRef, VHost, Date),
3892 + {reply, Reply, State};
3893 +handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
3894 + {reply, error, State};
3895 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3896 + Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
3897 + ["\"",Timestamp,"\"",","]
3900 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
3902 + Query = ["DELETE FROM ",messages_table(VHost, Date)," ",
3903 + "WHERE timestamp IN (", Temp1],
3906 + case sql_query_internal(DBRef, Query) of
3908 + ?MYDEBUG("Aff=~p", [Aff]),
3909 + rebuild_stats_at_int(DBRef, VHost, Date);
3913 + {reply, Reply, State};
3914 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3915 + ok = delete_all_messages_by_user_at_int(DBRef, User, VHost, Date),
3916 + ok = delete_stats_by_user_at_int(DBRef, User, VHost, Date),
3917 + {reply, ok, State};
3918 +handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3920 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Date),";"]),
3921 + TQuery = ["DELETE FROM ",stats_table(VHost)," "
3922 + "WHERE at=\"",Date,"\";"],
3923 + {updated, _} = sql_query_internal(DBRef, TQuery),
3924 + VQuery = ["DROP VIEW IF EXISTS ",view_table(VHost,Date),";"],
3925 + {updated, _} = sql_query_internal(DBRef, VQuery),
3929 + case catch apply(Fun, []) of
3935 + {reply, Reply, State};
3936 +handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3937 + SName = stats_table(VHost),
3938 + Query = ["SELECT at, sum(count) ",
3939 + "FROM ",SName," ",
3941 + "ORDER BY DATE(at) DESC;"
3944 + case sql_query_internal(DBRef, Query) of
3946 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
3947 + {error, Reason} ->
3948 + % TODO: Duplicate error message ?
3951 + {reply, Reply, State};
3952 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3953 + SName = stats_table(VHost),
3954 + Query = ["SELECT username, sum(count) as allcount ",
3955 + "FROM ",SName," ",
3956 + "JOIN ",users_table(VHost)," ON owner_id=user_id "
3957 + "WHERE at=\"",Date,"\" ",
3958 + "GROUP BY username ",
3959 + "ORDER BY allcount DESC;"
3962 + case sql_query_internal(DBRef, Query) of
3964 + {ok, [ {User, list_to_integer(Count)} || [User, Count] <- Result ]};
3965 + {error, Reason} ->
3968 + {reply, Reply, State};
3969 +handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3970 + {reply, get_user_stats_int(DBRef, User, VHost), State};
3971 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3972 + Query = ["SELECT peer_name,",
3980 + "FROM ",view_table(VHost, Date)," "
3981 + "WHERE owner_name=\"",User,"\";"],
3983 + case sql_query_internal(DBRef, Query) of
3985 + Fun = fun([Peer_name, Peer_server, Peer_resource,
3990 + #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
3991 + direction=list_to_atom(Direction),
3993 + subject=Subject, body=Body,
3994 + timestamp=Timestamp}
3996 + {ok, lists:map(Fun, Result)};
3997 + {error, Reason} ->
4000 + {reply, Reply, State};
4001 +handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4002 + SName = stats_table(VHost),
4003 + Query = ["SELECT at ",
4004 + "FROM ",SName," ",
4006 + "ORDER BY DATE(at) DESC;"
4009 + case sql_query_internal(DBRef, Query) of
4011 + [ Date || [Date] <- Result ];
4012 + {error, Reason} ->
4015 + {reply, Reply, State};
4016 +handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4017 + Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
4018 + "FROM ",settings_table(VHost)," ",
4019 + "JOIN ",users_table(VHost)," ON user_id=owner_id;"],
4021 + case sql_query_internal(DBRef, Query) of
4023 + {ok, lists:map(fun([Owner, DoLogDef, DoLogL, DoNotLogL]) ->
4024 + #user_settings{owner_name=Owner,
4025 + dolog_default=list_to_bool(DoLogDef),
4026 + dolog_list=string_to_list(DoLogL),
4027 + donotlog_list=string_to_list(DoNotLogL)
4033 + {reply, Reply, State};
4034 +handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4035 + Query = ["SELECT dolog_default,dolog_list,donotlog_list FROM ",settings_table(VHost)," ",
4036 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4038 + case sql_query_internal(DBRef, Query) of
4041 + {data, [[Owner, DoLogDef, DoLogL, DoNotLogL]]} ->
4042 + {ok, #user_settings{owner_name=Owner,
4043 + dolog_default=list_to_bool(DoLogDef),
4044 + dolog_list=string_to_list(DoLogL),
4045 + donotlog_list=string_to_list(DoNotLogL)}};
4049 + {reply, Reply, State};
4050 +handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
4051 + dolog_list=DoLogL,
4052 + donotlog_list=DoNotLogL}},
4053 + _From, #state{dbref=DBRef, vhost=VHost} = State) ->
4054 + User_id = get_user_id(DBRef, VHost, User),
4055 + Query = ["UPDATE ",settings_table(VHost)," ",
4056 + "SET dolog_default=",bool_to_list(DoLogDef),", ",
4057 + "dolog_list='",list_to_string(DoLogL),"', ",
4058 + "donotlog_list='",list_to_string(DoNotLogL),"' ",
4059 + "WHERE owner_id=",User_id,";"],
4062 + case sql_query_internal(DBRef, Query) of
4064 + IQuery = ["INSERT INTO ",settings_table(VHost)," ",
4065 + "(owner_id, dolog_default, dolog_list, donotlog_list) ",
4067 + "(",User_id,",",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
4068 + case sql_query_internal_silent(DBRef, IQuery) of
4070 + ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
4072 + {error, Reason} ->
4073 + case regexp:match(Reason, "#23000") of
4078 + ?ERROR_MSG("Failed setup user ~p@~p: ~p", [User, VHost, Reason]),
4083 + ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
4088 + {reply, Reply, State};
4089 +handle_call({stop}, _From, #state{vhost=VHost}=State) ->
4090 + ?MYDEBUG("Stoping mysql5 backend for ~p", [VHost]),
4091 + {stop, normal, ok, State};
4092 +handle_call(Msg, _From, State) ->
4093 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
4096 +handle_cast({log_message, Msg}, #state{dbref=DBRef, vhost=VHost}=State) ->
4098 + Date = convert_timestamp_brief(Msg#msg.timestamp),
4099 + TableName = messages_table(VHost, Date),
4101 + Query = [ "CALL ",logmessage_name(VHost)," "
4102 + "('", TableName, "',",
4104 + "'", Msg#msg.owner_name, "',",
4105 + "'", Msg#msg.peer_name, "',",
4106 + "'", Msg#msg.peer_server, "',",
4107 + "'", ejabberd_odbc:escape(Msg#msg.peer_resource), "',",
4108 + "'", atom_to_list(Msg#msg.direction), "',",
4109 + "'", Msg#msg.type, "',",
4110 + "'", ejabberd_odbc:escape(Msg#msg.subject), "',",
4111 + "'", ejabberd_odbc:escape(Msg#msg.body), "',",
4112 + "'", Msg#msg.timestamp, "');"],
4114 + case sql_query_internal(DBRef, Query) of
4116 + ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
4117 + Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
4119 + {error, _Reason} ->
4125 +handle_cast({rebuild_stats}, State) ->
4126 + rebuild_all_stats_int(State),
4128 +handle_cast({drop_user, User}, #state{vhost=VHost} = State) ->
4130 + {ok, DBRef} = open_mysql_connection(State),
4131 + {ok, Dates} = get_user_stats_int(DBRef, User, VHost),
4132 + MDResult = lists:map(fun({Date, _}) ->
4133 + delete_all_messages_by_user_at_int(DBRef, User, VHost, Date)
4135 + StDResult = delete_all_stats_by_user_int(DBRef, User, VHost),
4136 + SDResult = delete_user_settings_int(DBRef, User, VHost),
4137 + case lists:all(fun(Result) when Result == ok ->
4139 + (Result) when Result == error ->
4141 + end, lists:append([MDResult, [StDResult], [SDResult]])) of
4143 + ?INFO_MSG("Removed ~s@~s", [User, VHost]);
4145 + ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost])
4147 + close_mysql_connection(DBRef)
4151 +handle_cast(Msg, State) ->
4152 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
4155 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
4156 + {stop, connection_dropped, State};
4157 +handle_info(Info, State) ->
4158 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
4161 +terminate(_Reason, #state{dbref=DBRef}=_State) ->
4162 + close_mysql_connection(DBRef),
4165 +code_change(_OldVsn, State, _Extra) ->
4168 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4170 +% gen_logdb callbacks
4172 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4173 +log_message(VHost, Msg) ->
4174 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4175 + gen_server:cast(Proc, {log_message, Msg}).
4176 +rebuild_stats(VHost) ->
4177 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4178 + gen_server:cast(Proc, {rebuild_stats}).
4179 +rebuild_stats_at(VHost, Date) ->
4180 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4181 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
4182 +delete_messages_by_user_at(VHost, Msgs, Date) ->
4183 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4184 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
4185 +delete_all_messages_by_user_at(User, VHost, Date) ->
4186 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4187 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
4188 +delete_messages_at(VHost, Date) ->
4189 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4190 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
4191 +get_vhost_stats(VHost) ->
4192 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4193 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
4194 +get_vhost_stats_at(VHost, Date) ->
4195 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4196 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
4197 +get_user_stats(User, VHost) ->
4198 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4199 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
4200 +get_user_messages_at(User, VHost, Date) ->
4201 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4202 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
4203 +get_dates(VHost) ->
4204 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4205 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
4206 +get_users_settings(VHost) ->
4207 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4208 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
4209 +get_user_settings(User, VHost) ->
4210 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4211 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
4212 +set_user_settings(User, VHost, Set) ->
4213 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4214 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
4215 +drop_user(User, VHost) ->
4216 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4217 + gen_server:cast(Proc, {drop_user, User}).
4219 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4223 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4224 +get_dates_int(DBRef, VHost) ->
4225 + case sql_query_internal(DBRef, ["SHOW TABLES"]) of
4227 + lists:foldl(fun([Table], Dates) ->
4228 + Reg = lists:sublist(prefix(),2,length(prefix())) ++ ".*" ++ escape_vhost(VHost),
4229 + case regexp:match(Table, Reg) of
4231 + case regexp:match(Table,"[0-9]+-[0-9]+-[0-9]+") of
4233 + lists:append(Dates, [lists:sublist(Table,S,E)]);
4245 +rebuild_all_stats_int(#state{vhost=VHost}=State) ->
4247 + {ok, DBRef} = open_mysql_connection(State),
4248 + ok = delete_nonexistent_stats(DBRef, VHost),
4249 + case lists:filter(fun(Date) ->
4250 + case catch rebuild_stats_at_int(DBRef, VHost, Date) of
4253 + {'EXIT', _} -> true
4255 + end, get_dates_int(DBRef, VHost)) of
4258 + ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]),
4261 + close_mysql_connection(DBRef)
4265 +rebuild_stats_at_int(DBRef, VHost, Date) ->
4266 + TempTable = temp_table(VHost),
4268 + Table = messages_table(VHost, Date),
4269 + STable = stats_table(VHost),
4271 + DQuery = [ "DELETE FROM ",STable," ",
4272 + "WHERE at='",Date,"';"],
4274 + ok = create_temp_table(DBRef, TempTable),
4275 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," WRITE, ",TempTable," WRITE;"]),
4276 + SQuery = ["INSERT INTO ",TempTable," ",
4277 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
4278 + "SELECT owner_id,peer_name_id,peer_server_id,\"",Date,"\",count(*) ",
4279 + "FROM ",Table," WHERE ext is NULL GROUP BY owner_id,peer_name_id,peer_server_id;"],
4280 + case sql_query_internal(DBRef, SQuery) of
4282 + Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]),
4284 + {data, [["0"]]} ->
4285 + {updated, _} = sql_query_internal(DBRef, ["DROP VIEW IF EXISTS ",view_table(VHost,Date),";"]),
4286 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]),
4287 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE;"]),
4288 + {updated, _} = sql_query_internal(DBRef, DQuery),
4291 + ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]),
4295 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE, ",TempTable," WRITE;"]),
4296 + {updated, _} = sql_query_internal(DBRef, DQuery),
4297 + SQuery1 = ["INSERT INTO ",STable," ",
4298 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
4299 + "SELECT owner_id,peer_name_id,peer_server_id,at,count ",
4300 + "FROM ",TempTable,";"],
4301 + case sql_query_internal(DBRef, SQuery1) of
4302 + {updated, _} -> ok;
4303 + {error, _} -> error
4305 + {error, _} -> error
4309 + case catch apply(Fun, []) of
4311 + ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
4315 + {'EXIT', Reason} ->
4316 + ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]),
4319 + sql_query_internal(DBRef, ["UNLOCK TABLES;"]),
4320 + sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]),
4323 +delete_nonexistent_stats(DBRef, VHost) ->
4324 + Dates = get_dates_int(DBRef, VHost),
4325 + STable = stats_table(VHost),
4327 + Temp = lists:flatmap(fun(Date) ->
4328 + ["\"",Date,"\"",","]
4334 + % replace last "," with ");"
4335 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
4336 + Query = ["DELETE FROM ",STable," ",
4337 + "WHERE at NOT IN (", Temp1],
4338 + case sql_query_internal(DBRef, Query) of
4346 +get_user_stats_int(DBRef, User, VHost) ->
4347 + SName = stats_table(VHost),
4348 + UName = users_table(VHost),
4349 + Query = ["SELECT stats.at, sum(stats.count) ",
4350 + "FROM ",UName," AS users ",
4351 + "JOIN ",SName," AS stats ON owner_id=user_id "
4352 + "WHERE users.username=\"",User,"\" ",
4353 + "GROUP BY stats.at "
4354 + "ORDER BY DATE(stats.at) DESC;"
4356 + case sql_query_internal(DBRef, Query) of
4358 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
4359 + {error, Result} ->
4363 +delete_all_messages_by_user_at_int(DBRef, User, VHost, Date) ->
4364 + DQuery = ["DELETE FROM ",messages_table(VHost, Date)," ",
4365 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4366 + case sql_query_internal(DBRef, DQuery) of
4368 + ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]),
4374 +delete_all_stats_by_user_int(DBRef, User, VHost) ->
4375 + SQuery = ["DELETE FROM ",stats_table(VHost)," ",
4376 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4377 + case sql_query_internal(DBRef, SQuery) of
4379 + ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]),
4381 + {error, _} -> error
4384 +delete_stats_by_user_at_int(DBRef, User, VHost, Date) ->
4385 + SQuery = ["DELETE FROM ",stats_table(VHost)," ",
4386 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\") ",
4387 + "AND at=\"",Date,"\";"],
4388 + case sql_query_internal(DBRef, SQuery) of
4390 + ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]),
4392 + {error, _} -> error
4395 +delete_user_settings_int(DBRef, User, VHost) ->
4396 + Query = ["DELETE FROM ",settings_table(VHost)," ",
4397 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4398 + case sql_query_internal(DBRef, Query) of
4400 + ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]),
4402 + {error, Reason} ->
4403 + ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]),
4407 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4411 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4412 +create_temp_table(DBRef, Name) ->
4413 + Query = ["CREATE TABLE ",Name," (",
4414 + "owner_id MEDIUMINT UNSIGNED, ",
4415 + "peer_name_id MEDIUMINT UNSIGNED, ",
4416 + "peer_server_id MEDIUMINT UNSIGNED, ",
4417 + "at VARCHAR(11), ",
4419 + ") ENGINE=MyISAM CHARACTER SET utf8;"
4421 + case sql_query_internal(DBRef, Query) of
4422 + {updated, _} -> ok;
4423 + {error, _Reason} -> error
4426 +create_stats_table(#state{dbref=DBRef, vhost=VHost}=State) ->
4427 + SName = stats_table(VHost),
4428 + Query = ["CREATE TABLE ",SName," (",
4429 + "owner_id MEDIUMINT UNSIGNED, ",
4430 + "peer_name_id MEDIUMINT UNSIGNED, ",
4431 + "peer_server_id MEDIUMINT UNSIGNED, ",
4432 + "at VARCHAR(11), ",
4433 + "count INT(11), ",
4434 + "ext INTEGER DEFAULT NULL, "
4435 + "INDEX ext_i (ext), "
4436 + "INDEX(owner_id,peer_name_id,peer_server_id), ",
4438 + ") ENGINE=MyISAM CHARACTER SET utf8;"
4440 + case sql_query_internal_silent(DBRef, Query) of
4442 + ?MYDEBUG("Created stats table for ~p", [VHost]),
4443 + rebuild_all_stats_int(State),
4445 + {error, Reason} ->
4446 + case regexp:match(Reason, "#42S01") of
4448 + ?MYDEBUG("Stats table for ~p already exists", [VHost]),
4449 + CheckQuery = ["SHOW COLUMNS FROM ",SName," LIKE 'peer_%_id';"],
4450 + case sql_query_internal(DBRef, CheckQuery) of
4451 + {data, Elems} when length(Elems) == 2 ->
4452 + ?MYDEBUG("Stats table structure is ok", []),
4455 + ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []),
4456 + case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of
4458 + ?INFO_MSG("Successfully dropped ~p", [SName]);
4460 + ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName])
4465 + ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
4470 +create_settings_table(#state{dbref=DBRef, vhost=VHost}) ->
4471 + SName = settings_table(VHost),
4472 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
4473 + "owner_id MEDIUMINT UNSIGNED PRIMARY KEY, ",
4474 + "dolog_default TINYINT(1) NOT NULL DEFAULT 1, ",
4475 + "dolog_list TEXT, ",
4476 + "donotlog_list TEXT ",
4477 + ") ENGINE=InnoDB CHARACTER SET utf8;"
4479 + case sql_query_internal(DBRef, Query) of
4481 + ?MYDEBUG("Created settings table for ~p", [VHost]),
4487 +create_users_table(#state{dbref=DBRef, vhost=VHost}) ->
4488 + SName = users_table(VHost),
4489 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
4490 + "username TEXT NOT NULL, ",
4491 + "user_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
4492 + "UNIQUE INDEX(username(",?INDEX_SIZE,")) ",
4493 + ") ENGINE=InnoDB CHARACTER SET utf8;"
4495 + case sql_query_internal(DBRef, Query) of
4497 + ?MYDEBUG("Created users table for ~p", [VHost]),
4503 +create_servers_table(#state{dbref=DBRef, vhost=VHost}) ->
4504 + SName = servers_table(VHost),
4505 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
4506 + "server TEXT NOT NULL, ",
4507 + "server_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
4508 + "UNIQUE INDEX(server(",?INDEX_SIZE,")) ",
4509 + ") ENGINE=InnoDB CHARACTER SET utf8;"
4511 + case sql_query_internal(DBRef, Query) of
4513 + ?MYDEBUG("Created servers table for ~p", [VHost]),
4519 +create_resources_table(#state{dbref=DBRef, vhost=VHost}) ->
4520 + RName = resources_table(VHost),
4521 + Query = ["CREATE TABLE IF NOT EXISTS ",RName," (",
4522 + "resource TEXT NOT NULL, ",
4523 + "resource_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
4524 + "UNIQUE INDEX(resource(",?INDEX_SIZE,")) ",
4525 + ") ENGINE=InnoDB CHARACTER SET utf8;"
4527 + case sql_query_internal(DBRef, Query) of
4529 + ?MYDEBUG("Created resources table for ~p", [VHost]),
4535 +create_internals(#state{dbref=DBRef, vhost=VHost}) ->
4536 + sql_query_internal(DBRef, ["DROP PROCEDURE IF EXISTS ",logmessage_name(VHost),";"]),
4537 + case sql_query_internal(DBRef, [get_logmessage(VHost)]) of
4539 + ?MYDEBUG("Created logmessage for ~p", [VHost]),
4545 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4549 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4550 +sql_query_internal(DBRef, Query) ->
4551 + case sql_query_internal_silent(DBRef, Query) of
4552 + {error, Reason} ->
4553 + ?ERROR_MSG("~p while ~p", [Reason, lists:append(Query)]),
4558 +sql_query_internal_silent(DBRef, Query) ->
4559 + ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
4560 + get_result(mysql_conn:fetch(DBRef, Query, self(), ?MYSQL_TIMEOUT)).
4562 +get_result({updated, MySQLRes}) ->
4563 + {updated, mysql:get_result_affected_rows(MySQLRes)};
4564 +get_result({data, MySQLRes}) ->
4565 + {data, mysql:get_result_rows(MySQLRes)};
4566 +get_result({error, "query timed out"}) ->
4567 + {error, "query timed out"};
4568 +get_result({error, MySQLRes}) ->
4569 + Reason = mysql:get_result_reason(MySQLRes),
4572 +get_user_id(DBRef, VHost, User) ->
4573 + SQuery = ["SELECT user_id FROM ",users_table(VHost)," ",
4574 + "WHERE username=\"",User,"\";"],
4575 + case sql_query_internal(DBRef, SQuery) of
4577 + IQuery = ["INSERT INTO ",users_table(VHost)," ",
4578 + "SET username=\"",User,"\";"],
4579 + case sql_query_internal_silent(DBRef, IQuery) of
4581 + {data, [[DBIdNew]]} = sql_query_internal(DBRef, SQuery),
4583 + {error, Reason} ->
4584 + % this can be in clustered environment
4585 + {match, _, _} = regexp:match(Reason, "#23000"),
4586 + ?ERROR_MSG("Duplicate key name for ~p", [User]),
4587 + {data, [[ClID]]} = sql_query_internal(DBRef, SQuery),
4590 + {data, [[DBId]]} ->
4594 +get_logmessage(VHost) ->
4595 + UName = users_table(VHost),
4596 + SName = servers_table(VHost),
4597 + RName = resources_table(VHost),
4598 + StName = stats_table(VHost),
4600 +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)
4602 + DECLARE ownerID MEDIUMINT UNSIGNED;
4603 + DECLARE peer_nameID MEDIUMINT UNSIGNED;
4604 + DECLARE peer_serverID MEDIUMINT UNSIGNED;
4605 + DECLARE peer_resourceID MEDIUMINT UNSIGNED;
4606 + DECLARE Vmtype VARCHAR(10);
4607 + DECLARE Vmtimestamp DOUBLE;
4608 + DECLARE Vmdirection VARCHAR(4);
4609 + DECLARE Vmbody TEXT;
4610 + DECLARE Vmsubject TEXT;
4613 + DECLARE viewname TEXT;
4614 + DECLARE notable INT;
4615 + DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' SET @notable = 1;
4618 + SET @ownerID = NULL;
4619 + SET @peer_nameID = NULL;
4620 + SET @peer_serverID = NULL;
4621 + SET @peer_resourceID = NULL;
4623 + SET @Vmtype = mtype;
4624 + SET @Vmtimestamp = mtimestamp;
4625 + SET @Vmdirection = mdirection;
4626 + SET @Vmbody = mbody;
4627 + SET @Vmsubject = msubject;
4629 + SELECT user_id INTO @ownerID FROM ~s WHERE username=owner;
4630 + IF @ownerID IS NULL THEN
4631 + INSERT INTO ~s SET username=owner;
4632 + SET @ownerID = LAST_INSERT_ID();
4635 + SELECT user_id INTO @peer_nameID FROM ~s WHERE username=peer_name;
4636 + IF @peer_nameID IS NULL THEN
4637 + INSERT INTO ~s SET username=peer_name;
4638 + SET @peer_nameID = LAST_INSERT_ID();
4641 + SELECT server_id INTO @peer_serverID FROM ~s WHERE server=peer_server;
4642 + IF @peer_serverID IS NULL THEN
4643 + INSERT INTO ~s SET server=peer_server;
4644 + SET @peer_serverID = LAST_INSERT_ID();
4647 + SELECT resource_id INTO @peer_resourceID FROM ~s WHERE resource=peer_resource;
4648 + IF @peer_resourceID IS NULL THEN
4649 + INSERT INTO ~s SET resource=peer_resource;
4650 + SET @peer_resourceID = LAST_INSERT_ID();
4653 + 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);\");
4654 + PREPARE insertmsg FROM @iq;
4656 + IF @notable = 1 THEN
4657 + SET @cq = CONCAT(\"CREATE TABLE \",tablename,\" (
4658 + owner_id MEDIUMINT UNSIGNED NOT NULL,
4659 + peer_name_id MEDIUMINT UNSIGNED NOT NULL,
4660 + peer_server_id MEDIUMINT UNSIGNED NOT NULL,
4661 + peer_resource_id MEDIUMINT(8) UNSIGNED NOT NULL,
4662 + direction ENUM('to', 'from') NOT NULL,
4663 + type ENUM('chat','error','groupchat','headline','normal') NOT NULL,
4666 + timestamp DOUBLE NOT NULL,
4667 + ext INTEGER DEFAULT NULL,
4668 + INDEX search_i (owner_id, peer_name_id, peer_server_id, peer_resource_id),
4669 + INDEX ext_i (ext),
4673 + CHARACTER SET utf8;\");
4674 + PREPARE createtable FROM @cq;
4675 + EXECUTE createtable;
4676 + DEALLOCATE PREPARE createtable;
4678 + SET @viewname = CONCAT(\"`v_\", TRIM(BOTH '`' FROM tablename), \"`\");
4679 + SET @cq = CONCAT(\"CREATE OR REPLACE VIEW \",@viewname,\" AS
4680 + SELECT owner.username AS owner_name,
4681 + peer.username AS peer_name,
4682 + servers.server AS peer_server,
4683 + resources.resource AS peer_resource,
4684 + messages.direction,
4688 + messages.timestamp
4694 + \", tablename,\" messages
4696 + owner.user_id=messages.owner_id and
4697 + peer.user_id=messages.peer_name_id and
4698 + servers.server_id=messages.peer_server_id and
4699 + resources.resource_id=messages.peer_resource_id
4700 + ORDER BY messages.timestamp;\");
4701 + PREPARE createview FROM @cq;
4702 + EXECUTE createview;
4703 + DEALLOCATE PREPARE createview;
4706 + PREPARE insertmsg FROM @iq;
4707 + EXECUTE insertmsg;
4708 + ELSEIF @notable = 0 THEN
4709 + EXECUTE insertmsg;
4712 + DEALLOCATE PREPARE insertmsg;
4714 + IF @notable = 0 THEN
4715 + 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;
4716 + IF ROW_COUNT() = 0 THEN
4717 + INSERT INTO ~s (owner_id, peer_name_id, peer_server_id, at, count) VALUES (@ownerID, @peer_nameID, @peer_serverID, atdate, 1);
4720 +END;", [logmessage_name(VHost),UName,UName,UName,UName,SName,SName,RName,RName,UName,UName,SName,RName,StName,StName]).
4721 --- src/mod_logdb_pgsql.erl.orig 2009-02-05 19:21:29.000000000 +0200
4722 +++ src/mod_logdb_pgsql.erl 2009-02-05 19:20:29.000000000 +0200
4724 +%%%----------------------------------------------------------------------
4725 +%%% File : mod_logdb_pgsql.erl
4726 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
4727 +%%% Purpose : Posgresql backend for mod_logdb
4728 +%%% Version : trunk
4730 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
4731 +%%%----------------------------------------------------------------------
4733 +-module(mod_logdb_pgsql).
4734 +-author('o.palij@gmail.com').
4736 +-include("mod_logdb.hrl").
4737 +-include("ejabberd.hrl").
4738 +-include("jlib.hrl").
4740 +-behaviour(gen_logdb).
4741 +-behaviour(gen_server).
4744 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
4746 +-export([start/2, stop/1]).
4748 +-export([log_message/2,
4750 + rebuild_stats_at/2,
4751 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
4752 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
4754 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
4757 +-export([view_table/3]).
4759 +% gen_server call timeout
4760 +-define(CALL_TIMEOUT, 30000).
4761 +-define(PGSQL_TIMEOUT, 60000).
4762 +-define(PROCNAME, mod_logdb_pgsql).
4764 +-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
4765 + list_to_string/1, string_to_list/1,
4766 + convert_timestamp_brief/1]).
4768 +-record(state, {dbref, vhost, server, port, db, user, password, schema}).
4770 +% replace "." with "_"
4771 +escape_vhost(VHost) -> lists:map(fun(46) -> 95;
4776 + Schema ++ ".\"" ++ "logdb_".
4779 + "_" ++ escape_vhost(VHost) ++ "\"".
4781 +messages_table(VHost, Schema, Date) ->
4782 + prefix(Schema) ++ "messages_" ++ Date ++ suffix(VHost).
4784 +view_table(VHost, Schema, Date) ->
4785 + Table = messages_table(VHost, Schema, Date),
4786 + TablewoS = lists:sublist(Table, length(Schema) + 3, length(Table) - length(Schema) - 3),
4787 + lists:append([Schema, ".\"v_", TablewoS, "\""]).
4789 +stats_table(VHost, Schema) ->
4790 + prefix(Schema) ++ "stats" ++ suffix(VHost).
4792 +temp_table(VHost, Schema) ->
4793 + prefix(Schema) ++ "temp" ++ suffix(VHost).
4795 +settings_table(VHost, Schema) ->
4796 + prefix(Schema) ++ "settings" ++ suffix(VHost).
4798 +users_table(VHost, Schema) ->
4799 + prefix(Schema) ++ "users" ++ suffix(VHost).
4800 +servers_table(VHost, Schema) ->
4801 + prefix(Schema) ++ "servers" ++ suffix(VHost).
4802 +resources_table(VHost, Schema) ->
4803 + prefix(Schema) ++ "resources" ++ suffix(VHost).
4805 +logmessage_name(VHost, Schema) ->
4806 + prefix(Schema) ++ "logmessage" ++ suffix(VHost).
4808 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4810 +% gen_mod callbacks
4812 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4813 +start(VHost, Opts) ->
4814 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4815 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
4818 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4819 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
4821 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4823 +% gen_server callbacks
4825 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4826 +init([VHost, Opts]) ->
4827 + Server = gen_mod:get_opt(server, Opts, "localhost"),
4828 + DB = gen_mod:get_opt(db, Opts, "ejabberd_logdb"),
4829 + User = gen_mod:get_opt(user, Opts, "root"),
4830 + Port = gen_mod:get_opt(port, Opts, 5432),
4831 + Password = gen_mod:get_opt(password, Opts, ""),
4832 + Schema = gen_mod:get_opt(schema, Opts, "public"),
4834 + ?MYDEBUG("Starting pgsql backend for ~p", [VHost]),
4836 + St = #state{vhost=VHost,
4837 + server=Server, port=Port, db=DB,
4838 + user=User, password=Password,
4841 + case open_pgsql_connection(St) of
4843 + State = St#state{dbref=DBRef},
4844 + ok = create_internals(State),
4845 + ok = create_stats_table(State),
4846 + ok = create_settings_table(State),
4847 + ok = create_users_table(State),
4848 + ok = create_servers_table(State),
4849 + ok = create_resources_table(State),
4850 + erlang:monitor(process, DBRef),
4852 + % this does not work
4853 + {error, Reason} ->
4854 + ?ERROR_MSG("PgSQL connection failed: ~p~n", [Reason]),
4855 + {stop, db_connection_failed};
4856 + % and this too, becouse pgsql_conn do exit() which can not be catched
4858 + ?ERROR_MSG("Rez: ~p~n", [Rez]),
4859 + {stop, db_connection_failed}
4862 +open_pgsql_connection(#state{server=Server, port=Port, db=DB, schema=Schema,
4863 + user=User, password=Password} = _State) ->
4864 + {ok, DBRef} = pgsql:connect(Server, DB, User, Password, Port),
4865 + {updated, _} = sql_query_internal(DBRef, ["SET SEARCH_PATH TO ",Schema,";"]),
4868 +close_pgsql_connection(DBRef) ->
4869 + ?MYDEBUG("Closing ~p pgsql connection", [DBRef]),
4870 + pgsql:terminate(DBRef).
4872 +handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4873 + Date = convert_timestamp_brief(Msg#msg.timestamp),
4874 + TableName = messages_table(VHost, Schema, Date),
4875 + ViewName = view_table(VHost, Schema, Date),
4877 + Query = [ "SELECT ", logmessage_name(VHost, Schema)," "
4878 + "('", TableName, "',",
4879 + "'", ViewName, "',",
4881 + "'", Msg#msg.owner_name, "',",
4882 + "'", Msg#msg.peer_name, "',",
4883 + "'", Msg#msg.peer_server, "',",
4884 + "'", ejabberd_odbc:escape(Msg#msg.peer_resource), "',",
4885 + "'", atom_to_list(Msg#msg.direction), "',",
4886 + "'", Msg#msg.type, "',",
4887 + "'", ejabberd_odbc:escape(Msg#msg.subject), "',",
4888 + "'", ejabberd_odbc:escape(Msg#msg.body), "',",
4889 + "'", Msg#msg.timestamp, "');"],
4891 + case sql_query_internal_silent(DBRef, Query) of
4892 + % TODO: change this
4893 + {data, [{"0"}]} ->
4894 + ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
4895 + Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
4897 + {error, _Reason} ->
4900 + {reply, ok, State};
4901 +handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4902 + Reply = rebuild_stats_at_int(DBRef, VHost, Schema, Date),
4903 + {reply, Reply, State};
4904 +handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
4905 + {reply, error, State};
4906 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4907 + Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
4908 + ["'",Timestamp,"'",","]
4911 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
4913 + Query = ["DELETE FROM ",messages_table(VHost, Schema, Date)," ",
4914 + "WHERE timestamp IN (", Temp1],
4917 + case sql_query_internal(DBRef, Query) of
4919 + rebuild_stats_at_int(DBRef, VHost, Schema, Date);
4923 + {reply, Reply, State};
4924 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4925 + ok = delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date),
4926 + ok = delete_stats_by_user_at_int(DBRef, Schema, User, VHost, Date),
4927 + {reply, ok, State};
4928 +handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4929 + {updated, _} = sql_query_internal(DBRef, ["DROP VIEW ",view_table(VHost, Schema, Date),";"]),
4931 + case sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Schema, Date)," CASCADE;"]) of
4933 + Query = ["DELETE FROM ",stats_table(VHost, Schema)," "
4934 + "WHERE at='",Date,"';"],
4935 + case sql_query_internal(DBRef, Query) of
4944 + {reply, Reply, State};
4945 +handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4946 + SName = stats_table(VHost, Schema),
4947 + Query = ["SELECT at, sum(count) ",
4948 + "FROM ",SName," ",
4950 + "ORDER BY DATE(at) DESC;"
4953 + case sql_query_internal(DBRef, Query) of
4955 + {ok, [ {Date, list_to_integer(Count)} || {Date, Count} <- Recs]};
4956 + {error, Reason} ->
4957 + % TODO: Duplicate error message ?
4960 + {reply, Reply, State};
4961 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4962 + SName = stats_table(VHost, Schema),
4963 + Query = ["SELECT username, sum(count) AS allcount ",
4964 + "FROM ",SName," ",
4965 + "JOIN ",users_table(VHost, Schema)," ON owner_id=user_id ",
4966 + "WHERE at='",Date,"' ",
4967 + "GROUP BY username ",
4968 + "ORDER BY allcount DESC;"
4971 + case sql_query_internal(DBRef, Query) of
4973 + RFun = fun({User, Count}) ->
4974 + {User, list_to_integer(Count)}
4976 + {ok, lists:reverse(lists:keysort(2, lists:map(RFun, Recs)))};
4977 + {error, Reason} ->
4981 + {reply, Reply, State};
4982 +handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4983 + {reply, get_user_stats_int(DBRef, Schema, User, VHost), State};
4984 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4985 + Query = ["SELECT peer_name,",
4993 + "FROM ",view_table(VHost, Schema, Date)," "
4994 + "WHERE owner_name='",User,"';"],
4996 + case sql_query_internal(DBRef, Query) of
4998 + Fun = fun({Peer_name, Peer_server, Peer_resource,
5003 + #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
5004 + direction=list_to_atom(Direction),
5006 + subject=Subject, body=Body,
5007 + timestamp=Timestamp}
5009 + {ok, lists:map(Fun, Recs)};
5010 + {error, Reason} ->
5013 + {reply, Reply, State};
5014 +handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5015 + SName = stats_table(VHost, Schema),
5016 + Query = ["SELECT at ",
5017 + "FROM ",SName," ",
5019 + "ORDER BY at DESC;"
5022 + case sql_query_internal(DBRef, Query) of
5024 + [ Date || {Date} <- Result ];
5025 + {error, Reason} ->
5028 + {reply, Reply, State};
5029 +handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5030 + Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
5031 + "FROM ",settings_table(VHost, Schema)," ",
5032 + "JOIN ",users_table(VHost, Schema)," ON user_id=owner_id;"],
5034 + case sql_query_internal(DBRef, Query) of
5036 + {ok, [#user_settings{owner_name=Owner,
5037 + dolog_default=list_to_bool(DoLogDef),
5038 + dolog_list=string_to_list(DoLogL),
5039 + donotlog_list=string_to_list(DoNotLogL)
5040 + } || {Owner, DoLogDef, DoLogL, DoNotLogL} <- Recs]};
5041 + {error, Reason} ->
5044 + {reply, Reply, State};
5045 +handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5046 + Query = ["SELECT dolog_default,dolog_list,donotlog_list ",
5047 + "FROM ",settings_table(VHost, Schema)," ",
5048 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
5050 + case sql_query_internal_silent(DBRef, Query) of
5053 + {data, [{DoLogDef, DoLogL, DoNotLogL}]} ->
5054 + {ok, #user_settings{owner_name=User,
5055 + dolog_default=list_to_bool(DoLogDef),
5056 + dolog_list=string_to_list(DoLogL),
5057 + donotlog_list=string_to_list(DoNotLogL)}};
5058 + {error, Reason} ->
5059 + ?ERROR_MSG("Failed to get_user_settings for ~p@~p: ~p", [User, VHost, Reason]),
5062 + {reply, Reply, State};
5063 +handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
5064 + dolog_list=DoLogL,
5065 + donotlog_list=DoNotLogL}},
5066 + _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5067 + User_id = get_user_id(DBRef, VHost, Schema, User),
5068 + Query = ["UPDATE ",settings_table(VHost, Schema)," ",
5069 + "SET dolog_default=",bool_to_list(DoLogDef),", ",
5070 + "dolog_list='",list_to_string(DoLogL),"', ",
5071 + "donotlog_list='",list_to_string(DoNotLogL),"' ",
5072 + "WHERE owner_id=",User_id,";"],
5075 + case sql_query_internal(DBRef, Query) of
5077 + IQuery = ["INSERT INTO ",settings_table(VHost, Schema)," ",
5078 + "(owner_id, dolog_default, dolog_list, donotlog_list) ",
5080 + "(",User_id,", ",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
5081 + case sql_query_internal(DBRef, IQuery) of
5083 + ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
5089 + ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
5094 + {reply, Reply, State};
5095 +handle_call({stop}, _From, State) ->
5096 + ?MYDEBUG("Stoping pgsql backend for ~p", [State#state.vhost]),
5097 + {stop, normal, ok, State};
5098 +handle_call(Msg, _From, State) ->
5099 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
5103 +handle_cast({rebuild_stats}, State) ->
5104 + rebuild_all_stats_int(State),
5106 +handle_cast({drop_user, User}, #state{vhost=VHost, schema=Schema}=State) ->
5108 + {ok, DBRef} = open_pgsql_connection(State),
5109 + {ok, Dates} = get_user_stats_int(DBRef, Schema, User, VHost),
5110 + MDResult = lists:map(fun({Date, _}) ->
5111 + delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date)
5113 + StDResult = delete_all_stats_by_user_int(DBRef, Schema, User, VHost),
5114 + SDResult = delete_user_settings_int(DBRef, Schema, User, VHost),
5115 + case lists:all(fun(Result) when Result == ok ->
5117 + (Result) when Result == error ->
5119 + end, lists:append([MDResult, [StDResult], [SDResult]])) of
5121 + ?INFO_MSG("Removed ~s@~s", [User, VHost]);
5123 + ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost])
5125 + close_pgsql_connection(DBRef)
5129 +handle_cast(Msg, State) ->
5130 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
5133 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
5134 + {stop, connection_dropped, State};
5135 +handle_info(Info, State) ->
5136 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
5139 +terminate(_Reason, #state{dbref=DBRef}=_State) ->
5140 + close_pgsql_connection(DBRef),
5143 +code_change(_OldVsn, State, _Extra) ->
5146 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5148 +% gen_logdb callbacks
5150 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5151 +log_message(VHost, Msg) ->
5152 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5153 + gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
5154 +rebuild_stats(VHost) ->
5155 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5156 + gen_server:cast(Proc, {rebuild_stats}).
5157 +rebuild_stats_at(VHost, Date) ->
5158 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5159 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
5160 +delete_messages_by_user_at(VHost, Msgs, Date) ->
5161 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5162 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
5163 +delete_all_messages_by_user_at(User, VHost, Date) ->
5164 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5165 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
5166 +delete_messages_at(VHost, Date) ->
5167 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5168 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
5169 +get_vhost_stats(VHost) ->
5170 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5171 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
5172 +get_vhost_stats_at(VHost, Date) ->
5173 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5174 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
5175 +get_user_stats(User, VHost) ->
5176 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5177 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
5178 +get_user_messages_at(User, VHost, Date) ->
5179 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5180 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
5181 +get_dates(VHost) ->
5182 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5183 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
5184 +get_users_settings(VHost) ->
5185 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5186 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
5187 +get_user_settings(User, VHost) ->
5188 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5189 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
5190 +set_user_settings(User, VHost, Set) ->
5191 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5192 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
5193 +drop_user(User, VHost) ->
5194 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5195 + gen_server:cast(Proc, {drop_user, User}).
5197 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5201 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5202 +get_dates_int(DBRef, VHost) ->
5203 + Query = ["SELECT n.nspname as \"Schema\",
5204 + c.relname as \"Name\",
5205 + 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\",
5206 + r.rolname as \"Owner\"
5207 + FROM pg_catalog.pg_class c
5208 + JOIN pg_catalog.pg_roles r ON r.oid = c.relowner
5209 + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
5210 + WHERE c.relkind IN ('r','')
5211 + AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
5212 + AND c.relname ~ '^(.*",escape_vhost(VHost),".*)$'
5213 + AND pg_catalog.pg_table_is_visible(c.oid)
5215 + case sql_query_internal(DBRef, Query) of
5217 + lists:foldl(fun({_Schema, Table, _Type, _Owner}, Dates) ->
5218 + case regexp:match(Table,"[0-9]+-[0-9]+-[0-9]+") of
5220 + lists:append(Dates, [lists:sublist(Table,S,E)]);
5229 +rebuild_all_stats_int(#state{vhost=VHost, schema=Schema}=State) ->
5231 + {ok, DBRef} = open_pgsql_connection(State),
5232 + ok = delete_nonexistent_stats(DBRef, Schema, VHost),
5233 + case lists:filter(fun(Date) ->
5234 + case catch rebuild_stats_at_int(DBRef, VHost, Schema, Date) of
5237 + {'EXIT', _} -> true
5239 + end, get_dates_int(DBRef, VHost)) of
5242 + ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]),
5245 + close_pgsql_connection(DBRef)
5249 +rebuild_stats_at_int(DBRef, VHost, Schema, Date) ->
5250 + TempTable = temp_table(VHost, Schema),
5253 + Table = messages_table(VHost, Schema, Date),
5254 + STable = stats_table(VHost, Schema),
5256 + DQuery = [ "DELETE FROM ",STable," ",
5257 + "WHERE at='",Date,"';"],
5259 + ok = create_temp_table(DBRef, VHost, Schema),
5260 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," IN ACCESS EXCLUSIVE MODE;"]),
5261 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",TempTable," IN ACCESS EXCLUSIVE MODE;"]),
5262 + SQuery = ["INSERT INTO ",TempTable," ",
5263 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
5264 + "SELECT owner_id,peer_name_id,peer_server_id,'",Date,"'",",count(*) ",
5265 + "FROM ",Table," GROUP BY owner_id,peer_name_id,peer_server_id;"],
5266 + case sql_query_internal(DBRef, SQuery) of
5268 + Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]),
5270 + {data, [{"0"}]} ->
5271 + {updated, _} = sql_query_internal(DBRef, ["DROP VIEW ",view_table(VHost, Schema, Date),";"]),
5272 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table," CASCADE;"]),
5273 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," IN ACCESS EXCLUSIVE MODE;"]),
5274 + {updated, _} = sql_query_internal(DBRef, DQuery),
5277 + ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]),
5281 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," IN ACCESS EXCLUSIVE MODE;"]),
5282 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",TempTable," IN ACCESS EXCLUSIVE MODE;"]),
5283 + {updated, _} = sql_query_internal(DBRef, DQuery),
5284 + SQuery1 = ["INSERT INTO ",STable," ",
5285 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
5286 + "SELECT owner_id,peer_name_id,peer_server_id,at,count ",
5287 + "FROM ",TempTable,";"],
5288 + case sql_query_internal(DBRef, SQuery1) of
5289 + {updated, _} -> ok;
5290 + {error, _} -> error
5292 + {error, _} -> error
5296 + case sql_transaction_internal(DBRef, Fun) of
5298 + ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
5300 + {aborted, Reason} ->
5301 + ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]),
5304 + sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]),
5307 +delete_nonexistent_stats(DBRef, Schema, VHost) ->
5308 + Dates = get_dates_int(DBRef, VHost),
5309 + STable = stats_table(VHost, Schema),
5311 + Temp = lists:flatmap(fun(Date) ->
5312 + ["'",Date,"'",","]
5319 + % replace last "," with ");"
5320 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
5321 + Query = ["DELETE FROM ",STable," ",
5322 + "WHERE at NOT IN (", Temp1],
5323 + case sql_query_internal(DBRef, Query) of
5331 +get_user_stats_int(DBRef, Schema, User, VHost) ->
5332 + SName = stats_table(VHost, Schema),
5333 + UName = users_table(VHost, Schema),
5334 + Query = ["SELECT stats.at, sum(stats.count) ",
5335 + "FROM ",UName," AS users ",
5336 + "JOIN ",SName," AS stats ON owner_id=user_id "
5337 + "WHERE users.username='",User,"' ",
5338 + "GROUP BY stats.at "
5339 + "ORDER BY DATE(at) DESC;"
5341 + case sql_query_internal(DBRef, Query) of
5343 + {ok, [ {Date, list_to_integer(Count)} || {Date, Count} <- Recs ]};
5344 + {error, Result} ->
5348 +delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date) ->
5349 + DQuery = ["DELETE FROM ",messages_table(VHost, Schema, Date)," ",
5350 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
5351 + case sql_query_internal(DBRef, DQuery) of
5353 + ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]),
5359 +delete_all_stats_by_user_int(DBRef, Schema, User, VHost) ->
5360 + SQuery = ["DELETE FROM ",stats_table(VHost, Schema)," ",
5361 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
5362 + case sql_query_internal(DBRef, SQuery) of
5364 + ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]),
5366 + {error, _} -> error
5369 +delete_stats_by_user_at_int(DBRef, Schema, User, VHost, Date) ->
5370 + SQuery = ["DELETE FROM ",stats_table(VHost, Schema)," ",
5371 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"') ",
5372 + "AND at='",Date,"';"],
5373 + case sql_query_internal(DBRef, SQuery) of
5375 + ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]),
5377 + {error, _} -> error
5380 +delete_user_settings_int(DBRef, Schema, User, VHost) ->
5381 + Query = ["DELETE FROM ",settings_table(VHost, Schema)," ",
5382 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
5383 + case sql_query_internal(DBRef, Query) of
5385 + ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]),
5387 + {error, Reason} ->
5388 + ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]),
5392 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5396 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5397 +create_temp_table(DBRef, VHost, Schema) ->
5398 + TName = temp_table(VHost, Schema),
5399 + Query = ["CREATE TABLE ",TName," (",
5400 + "owner_id INTEGER, ",
5401 + "peer_name_id INTEGER, ",
5402 + "peer_server_id INTEGER, ",
5403 + "at VARCHAR(20), ",
5407 + case sql_query_internal(DBRef, Query) of
5408 + {updated, _} -> ok;
5409 + {error, _Reason} -> error
5412 +create_stats_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5413 + SName = stats_table(VHost, Schema),
5417 + Query = ["CREATE TABLE ",SName," (",
5418 + "owner_id INTEGER, ",
5419 + "peer_name_id INTEGER, ",
5420 + "peer_server_id INTEGER, ",
5421 + "at VARCHAR(20), ",
5425 + case sql_query_internal_silent(DBRef, Query) of
5427 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"s_search_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (owner_id, peer_name_id, peer_server_id);"]),
5428 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"s_at_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (at);"]),
5430 + {error, Reason} ->
5431 + case lists:keysearch(code, 1, Reason) of
5432 + {value, {code, "42P07"}} ->
5435 + ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
5440 + case sql_transaction_internal(DBRef, Fun) of
5441 + {atomic, created} ->
5442 + ?MYDEBUG("Created stats table for ~p", [VHost]),
5443 + rebuild_all_stats_int(State),
5445 + {atomic, exists} ->
5446 + ?MYDEBUG("Stats table for ~p already exists", [VHost]),
5447 + {match, F, L} = regexp:match(SName, "\".*\""),
5448 + QTable = lists:sublist(SName, F+1, L-2),
5449 + 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);"],
5450 + {data,[{OID}]} = sql_query_internal(DBRef, OIDQuery),
5451 + 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$';"],
5452 + case sql_query_internal(DBRef, CheckQuery) of
5453 + {data, Elems} when length(Elems) == 2 ->
5454 + ?MYDEBUG("Stats table structure is ok", []),
5457 + ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []),
5458 + case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of
5460 + ?INFO_MSG("Successfully dropped ~p", [SName]);
5462 + ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName])
5466 + {error, _} -> error
5469 +create_settings_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
5470 + SName = settings_table(VHost, Schema),
5471 + Query = ["CREATE TABLE ",SName," (",
5472 + "owner_id INTEGER PRIMARY KEY, ",
5473 + "dolog_default BOOLEAN, ",
5474 + "dolog_list TEXT DEFAULT '', ",
5475 + "donotlog_list TEXT DEFAULT ''",
5478 + case sql_query_internal_silent(DBRef, Query) of
5480 + ?MYDEBUG("Created settings table for ~p", [VHost]),
5482 + {error, Reason} ->
5483 + case lists:keysearch(code, 1, Reason) of
5484 + {value, {code, "42P07"}} ->
5485 + ?MYDEBUG("Settings table for ~p already exists", [VHost]),
5488 + ?ERROR_MSG("Failed to create settings table for ~p: ~p", [VHost, Reason]),
5493 +create_users_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
5494 + SName = users_table(VHost, Schema),
5498 + Query = ["CREATE TABLE ",SName," (",
5499 + "username TEXT UNIQUE, ",
5500 + "user_id SERIAL PRIMARY KEY",
5503 + case sql_query_internal_silent(DBRef, Query) of
5505 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"username_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (username);"]),
5507 + {error, Reason} ->
5508 + case lists:keysearch(code, 1, Reason) of
5509 + {value, {code, "42P07"}} ->
5512 + ?ERROR_MSG("Failed to create users table for ~p: ~p", [VHost, Reason]),
5517 + case sql_transaction_internal(DBRef, Fun) of
5518 + {atomic, created} ->
5519 + ?MYDEBUG("Created users table for ~p", [VHost]),
5521 + {atomic, exists} ->
5522 + ?MYDEBUG("Users table for ~p already exists", [VHost]),
5524 + {aborted, _} -> error
5527 +create_servers_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
5528 + SName = servers_table(VHost, Schema),
5531 + Query = ["CREATE TABLE ",SName," (",
5532 + "server TEXT UNIQUE, ",
5533 + "server_id SERIAL PRIMARY KEY",
5536 + case sql_query_internal_silent(DBRef, Query) of
5538 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"server_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (server);"]),
5540 + {error, Reason} ->
5541 + case lists:keysearch(code, 1, Reason) of
5542 + {value, {code, "42P07"}} ->
5545 + ?ERROR_MSG("Failed to create servers table for ~p: ~p", [VHost, Reason]),
5550 + case sql_transaction_internal(DBRef, Fun) of
5551 + {atomic, created} ->
5552 + ?MYDEBUG("Created servers table for ~p", [VHost]),
5554 + {atomic, exists} ->
5555 + ?MYDEBUG("Servers table for ~p already exists", [VHost]),
5557 + {aborted, _} -> error
5560 +create_resources_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
5561 + RName = resources_table(VHost, Schema),
5563 + Query = ["CREATE TABLE ",RName," (",
5564 + "resource TEXT UNIQUE, ",
5565 + "resource_id SERIAL PRIMARY KEY",
5568 + case sql_query_internal_silent(DBRef, Query) of
5570 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"resource_i_",Schema,"_",escape_vhost(VHost),"\" ON ",RName," (resource);"]),
5572 + {error, Reason} ->
5573 + case lists:keysearch(code, 1, Reason) of
5574 + {value, {code, "42P07"}} ->
5577 + ?ERROR_MSG("Failed to create users table for ~p: ~p", [VHost, Reason]),
5582 + case sql_transaction_internal(DBRef, Fun) of
5583 + {atomic, created} ->
5584 + ?MYDEBUG("Created resources table for ~p", [VHost]),
5586 + {atomic, exists} ->
5587 + ?MYDEBUG("Resources table for ~p already exists", [VHost]),
5589 + {aborted, _} -> error
5592 +create_internals(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
5593 + 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);"]),
5594 + case sql_query_internal(DBRef, [get_logmessage(VHost, Schema)]) of
5596 + ?MYDEBUG("Created logmessage for ~p", [VHost]),
5602 +get_user_id(DBRef, VHost, Schema, User) ->
5603 + SQuery = ["SELECT user_id FROM ",users_table(VHost, Schema)," ",
5604 + "WHERE username='",User,"';"],
5605 + case sql_query_internal(DBRef, SQuery) of
5607 + IQuery = ["INSERT INTO ",users_table(VHost, Schema)," ",
5608 + "VALUES ('",User,"');"],
5609 + case sql_query_internal_silent(DBRef, IQuery) of
5611 + {data, [{DBIdNew}]} = sql_query_internal(DBRef, SQuery),
5613 + {error, Reason} ->
5614 + % this can be in clustered environment
5615 + {value, {code, "23505"}} = lists:keysearch(code, 1, Reason),
5616 + ?ERROR_MSG("Duplicate key name for ~p", [User]),
5617 + {data, [{ClID}]} = sql_query_internal(DBRef, SQuery),
5620 + {data, [{DBId}]} ->
5624 +get_logmessage(VHost,Schema) ->
5625 + UName = users_table(VHost,Schema),
5626 + SName = servers_table(VHost,Schema),
5627 + RName = resources_table(VHost,Schema),
5628 + StName = stats_table(VHost,Schema),
5629 + 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 $$
5632 + peer_nameID INTEGER;
5633 + peer_serverID INTEGER;
5634 + peer_resourceID INTEGER;
5635 + tablename ALIAS for $1;
5636 + viewname ALIAS for $2;
5637 + atdate ALIAS for $3;
5639 + SELECT INTO ownerID user_id FROM ~s WHERE username = owner;
5641 + INSERT INTO ~s (username) VALUES (owner);
5642 + ownerID := lastval();
5645 + SELECT INTO peer_nameID user_id FROM ~s WHERE username = peer_name;
5647 + INSERT INTO ~s (username) VALUES (peer_name);
5648 + peer_nameID := lastval();
5651 + SELECT INTO peer_serverID server_id FROM ~s WHERE server = peer_server;
5653 + INSERT INTO ~s (server) VALUES (peer_server);
5654 + peer_serverID := lastval();
5657 + SELECT INTO peer_resourceID resource_id FROM ~s WHERE resource = peer_resource;
5659 + INSERT INTO ~s (resource) VALUES (peer_resource);
5660 + peer_resourceID := lastval();
5664 + 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 || ')';
5665 + EXCEPTION WHEN undefined_table THEN
5666 + EXECUTE 'CREATE TABLE ' || tablename || ' (' ||
5667 + 'owner_id INTEGER, ' ||
5668 + 'peer_name_id INTEGER, ' ||
5669 + 'peer_server_id INTEGER, ' ||
5670 + 'peer_resource_id INTEGER, ' ||
5671 + 'direction VARCHAR(4) CHECK (direction IN (''to'',''from'')), ' ||
5672 + 'type VARCHAR(9) CHECK (type IN (''chat'',''error'',''groupchat'',''headline'',''normal'')), ' ||
5673 + 'subject TEXT, ' ||
5675 + 'timestamp DOUBLE PRECISION)';
5676 + EXECUTE 'CREATE INDEX \"search_i_' || '~s' || '_' || atdate || '_' || '~s' || '\"' || ' ON ' || tablename || ' (owner_id, peer_name_id, peer_server_id, peer_resource_id)';
5678 + EXECUTE 'CREATE OR REPLACE VIEW ' || viewname || ' AS ' ||
5679 + 'SELECT owner.username AS owner_name, ' ||
5680 + 'peer.username AS peer_name, ' ||
5681 + 'servers.server AS peer_server, ' ||
5682 + 'resources.resource AS peer_resource, ' ||
5683 + 'messages.direction, ' ||
5684 + 'messages.type, ' ||
5685 + 'messages.subject, ' ||
5686 + 'messages.body, ' ||
5687 + 'messages.timestamp ' ||
5692 + '~s resources, ' ||
5693 + tablename || ' messages ' ||
5695 + 'owner.user_id=messages.owner_id and ' ||
5696 + 'peer.user_id=messages.peer_name_id and ' ||
5697 + 'servers.server_id=messages.peer_server_id and ' ||
5698 + 'resources.resource_id=messages.peer_resource_id ' ||
5699 + 'ORDER BY messages.timestamp';
5701 + 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 || ')';
5704 + 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;
5706 + INSERT INTO ~s (owner_id, peer_name_id, peer_server_id, at, count) VALUES (ownerID, peer_nameID, peer_serverID, atdate, 1);
5710 +$$ LANGUAGE plpgsql;
5711 +", [logmessage_name(VHost,Schema),UName,UName,UName,UName,SName,SName,RName,RName,Schema,escape_vhost(VHost),UName,UName,SName,RName,StName,StName]).
5713 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5717 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5718 +% like do_transaction/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
5719 +sql_transaction_internal(DBRef, Fun) ->
5720 + case sql_query_internal(DBRef, ["BEGIN;"]) of
5722 + case catch Fun() of
5724 + rollback_internal(DBRef, Err);
5725 + {error, _} = Err ->
5726 + rollback_internal(DBRef, Err);
5727 + {'EXIT', _} = Err ->
5728 + rollback_internal(DBRef, Err);
5730 + case sql_query_internal(DBRef, ["COMMIT;"]) of
5731 + {error, _} -> rollback_internal(DBRef, {commit_error});
5734 + {atomic, _} -> Res;
5735 + _ -> {atomic, Res}
5740 + {aborted, {begin_error}}
5743 +% like rollback/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
5744 +rollback_internal(DBRef, Reason) ->
5745 + Res = sql_query_internal(DBRef, ["ROLLBACK;"]),
5746 + {aborted, {Reason, {rollback_result, Res}}}.
5748 +sql_query_internal(DBRef, Query) ->
5749 + case sql_query_internal_silent(DBRef, Query) of
5750 + {error, undefined, Rez} ->
5751 + ?ERROR_MSG("Got undefined result: ~p while ~p", [Rez, lists:append(Query)]),
5752 + {error, undefined};
5754 + ?ERROR_MSG("Failed: ~p while ~p", [Error, lists:append(Query)]),
5759 +sql_query_internal_silent(DBRef, Query) ->
5760 + ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
5761 + % TODO: use pquery?
5762 + get_result(pgsql:squery(DBRef, Query)).
5764 +get_result({ok, ["CREATE TABLE"]}) ->
5766 +get_result({ok, ["DROP TABLE"]}) ->
5768 +get_result({ok, ["ALTER TABLE"]}) ->
5770 +get_result({ok,["DROP VIEW"]}) ->
5772 +get_result({ok,["DROP FUNCTION"]}) ->
5774 +get_result({ok, ["CREATE INDEX"]}) ->
5776 +get_result({ok, ["CREATE FUNCTION"]}) ->
5778 +get_result({ok, [{"SELECT", _Rows, Recs}]}) ->
5779 + {data, [list_to_tuple(Rec) || Rec <- Recs]};
5780 +get_result({ok, ["INSERT " ++ OIDN]}) ->
5781 + [_OID, N] = string:tokens(OIDN, " "),
5782 + {updated, list_to_integer(N)};
5783 +get_result({ok, ["DELETE " ++ N]}) ->
5784 + {updated, list_to_integer(N)};
5785 +get_result({ok, ["UPDATE " ++ N]}) ->
5786 + {updated, list_to_integer(N)};
5787 +get_result({ok, ["BEGIN"]}) ->
5789 +get_result({ok, ["LOCK TABLE"]}) ->
5791 +get_result({ok, ["ROLLBACK"]}) ->
5793 +get_result({ok, ["COMMIT"]}) ->
5795 +get_result({ok, ["SET"]}) ->
5797 +get_result({ok, [{error, Error}]}) ->
5800 + {error, undefined, Rez}.
5802 --- src/mod_logdb_mnesia_old.erl.orig 2009-02-05 19:21:29.000000000 +0200
5803 +++ src/mod_logdb_mnesia_old.erl 2009-02-05 19:20:07.000000000 +0200
5805 +%%%----------------------------------------------------------------------
5806 +%%% File : mod_logdb_mnesia_old.erl
5807 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
5808 +%%% Purpose : mod_logmnesia backend for mod_logdb (should be used only for copy_tables functionality)
5809 +%%% Version : trunk
5811 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
5812 +%%%----------------------------------------------------------------------
5814 +-module(mod_logdb_mnesia_old).
5815 +-author('o.palij@gmail.com').
5817 +-include("ejabberd.hrl").
5818 +-include("jlib.hrl").
5820 +-behaviour(gen_logdb).
5822 +-export([start/2, stop/1,
5825 + rebuild_stats_at/2,
5826 + rebuild_stats_at1/2,
5827 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
5828 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
5830 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
5833 +-record(stats, {user, server, table, count}).
5834 +-record(msg, {to_user, to_server, to_resource, from_user, from_server, from_resource, id, type, subject, body, timestamp}).
5836 +tables_prefix() -> "messages_".
5837 +% stats_table should not start with tables_prefix(VHost) !
5838 +% i.e. lists:prefix(tables_prefix(VHost), atom_to_list(stats_table())) must be /= true
5839 +stats_table() -> list_to_atom("messages-stats").
5840 +% table name as atom from Date
5841 +-define(ATABLE(Date), list_to_atom(tables_prefix() ++ Date)).
5842 +-define(LTABLE(Date), tables_prefix() ++ Date).
5844 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5846 +% gen_logdb callbacks
5848 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5849 +start(_Opts, _VHost) ->
5850 + case mnesia:system_info(is_running) of
5852 + ok = create_stats_table(),
5855 + ?ERROR_MSG("Mnesia not running", []),
5858 + ?ERROR_MSG("Mnesia status: ~p", [Status]),
5865 +log_message(_VHost, _Msg) ->
5868 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5870 +% gen_logdb callbacks (maintaince)
5872 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5873 +rebuild_stats(_VHost) ->
5876 +rebuild_stats_at(VHost, Date) ->
5877 + Table = ?LTABLE(Date),
5878 + {Time, Value}=timer:tc(?MODULE, rebuild_stats_at1, [VHost, Table]),
5879 + ?INFO_MSG("rebuild_stats_at ~p elapsed ~p sec: ~p~n", [Date, Time/1000000, Value]),
5881 +rebuild_stats_at1(VHost, Table) ->
5882 + CFun = fun(Msg, Stats) ->
5883 + To = Msg#msg.to_user ++ "@" ++ Msg#msg.to_server,
5885 + Msg#msg.to_server == VHost ->
5886 + case lists:keysearch(To, 1, Stats) of
5887 + {value, {Who_to, Count_to}} ->
5888 + lists:keyreplace(To, 1, Stats, {Who_to, Count_to + 1});
5890 + lists:append(Stats, [{To, 1}])
5895 + From = Msg#msg.from_user ++ "@" ++ Msg#msg.from_server,
5897 + Msg#msg.from_server == VHost ->
5898 + case lists:keysearch(From, 1, Stats_to) of
5899 + {value, {Who_from, Count_from}} ->
5900 + lists:keyreplace(From, 1, Stats_to, {Who_from, Count_from + 1});
5902 + lists:append(Stats_to, [{From, 1}])
5909 + DFun = fun(#stats{table=STable, server=Server} = Stat, _Acc)
5910 + when STable == Table, Server == VHost ->
5911 + mnesia:delete_object(stats_table(), Stat, write);
5912 + (_Stat, _Acc) -> ok
5914 + case mnesia:transaction(fun() ->
5915 + mnesia:write_lock_table(list_to_atom(Table)),
5916 + mnesia:write_lock_table(stats_table()),
5917 + % Calc stats for VHost at Date
5918 + AStats = mnesia:foldl(CFun, [], list_to_atom(Table)),
5919 + % Delete all stats for VHost at Date
5920 + mnesia:foldl(DFun, [], stats_table()),
5921 + % Write new calc'ed stats
5922 + lists:foreach(fun({Who, Count}) ->
5923 + Jid = jlib:string_to_jid(Who),
5924 + JUser = Jid#jid.user,
5925 + WStat = #stats{user=JUser, server=VHost, table=Table, count=Count},
5926 + mnesia:write(stats_table(), WStat, write)
5929 + {aborted, Reason} ->
5930 + ?ERROR_MSG("Failed to rebuild_stats_at for ~p at ~p: ~p", [VHost, Table, Reason]),
5936 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5938 +% gen_logdb callbacks (delete)
5940 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5941 +delete_messages_by_user_at(_VHost, _Msgs, _Date) ->
5944 +delete_all_messages_by_user_at(_User, _VHost, _Date) ->
5947 +delete_messages_at(VHost, Date) ->
5948 + Table = list_to_atom(tables_prefix() ++ Date),
5950 + DFun = fun(#msg{to_server=To_server, from_server=From_server}=Msg, _Acc)
5951 + when To_server == VHost; From_server == VHost ->
5952 + mnesia:delete_object(Table, Msg, write);
5953 + (_Msg, _Acc) -> ok
5956 + case mnesia:transaction(fun() ->
5957 + mnesia:foldl(DFun, [], Table)
5959 + {aborted, Reason} ->
5960 + ?ERROR_MSG("Failed to delete_messages_at for ~p at ~p: ~p", [VHost, Date, Reason]),
5966 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5968 +% gen_logdb callbacks (get)
5970 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5971 +get_vhost_stats(_VHost) ->
5972 + {error, "does not emplemented"}.
5974 +get_vhost_stats_at(VHost, Date) ->
5976 + Pat = #stats{user='$1', server=VHost, table=tables_prefix()++Date, count = '$2'},
5977 + mnesia:select(stats_table(), [{Pat, [], [['$1', '$2']]}])
5979 + case mnesia:transaction(Fun) of
5980 + {atomic, Result} ->
5981 + RFun = fun([User, Count]) ->
5984 + {ok, lists:reverse(lists:keysort(2, lists:map(RFun, Result)))};
5985 + {aborted, Reason} -> {error, Reason}
5988 +get_user_stats(_User, _VHost) ->
5989 + {error, "does not emplemented"}.
5991 +get_user_messages_at(User, VHost, Date) ->
5992 + Table_name = tables_prefix() ++ Date,
5993 + case mnesia:transaction(fun() ->
5994 + Pat_to = #msg{to_user=User, to_server=VHost, _='_'},
5995 + Pat_from = #msg{from_user=User, from_server=VHost, _='_'},
5996 + mnesia:select(list_to_atom(Table_name),
5997 + [{Pat_to, [], ['$_']},
5998 + {Pat_from, [], ['$_']}])
6000 + {atomic, Result} ->
6001 + Msgs = lists:map(fun(#msg{to_user=To_user, to_server=To_server, to_resource=To_res,
6002 + from_user=From_user, from_server=From_server, from_resource=From_res,
6005 + body=Body, timestamp=Timestamp} = _Msg) ->
6006 + Subject = case Subj of
6010 + {msg, To_user, To_server, To_res, From_user, From_server, From_res, Type, Subject, Body, Timestamp}
6013 + {aborted, Reason} ->
6017 +get_dates(_VHost) ->
6018 + Tables = mnesia:system_info(tables),
6020 + lists:filter(fun(Table) ->
6021 + lists:prefix(tables_prefix(), atom_to_list(Table))
6024 + lists:map(fun(Table) ->
6025 + lists:sublist(atom_to_list(Table),
6026 + length(tables_prefix())+1,
6027 + length(atom_to_list(Table)))
6031 +get_users_settings(_VHost) ->
6033 +get_user_settings(_User, _VHost) ->
6035 +set_user_settings(_User, _VHost, _Set) ->
6037 +drop_user(_User, _VHost) ->
6040 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6044 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6045 +% called from db_logon/2
6046 +create_stats_table() ->
6047 + SName = stats_table(),
6048 + case mnesia:create_table(SName,
6049 + [{disc_only_copies, [node()]},
6051 + {attributes, record_info(fields, stats)},
6052 + {record_name, stats}
6055 + ?INFO_MSG("Created stats table", []),
6057 + {aborted, {already_exists, _}} ->
6059 + {aborted, Reason} ->
6060 + ?ERROR_MSG("Failed to create stats table: ~p", [Reason]),
6063 --- src/gen_logdb.erl.orig 2009-02-05 19:21:29.000000000 +0200
6064 +++ src/gen_logdb.erl 2009-02-05 19:19:39.000000000 +0200
6066 +%%%----------------------------------------------------------------------
6067 +%%% File : gen_logdb.erl
6068 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
6069 +%%% Purpose : Describes generic behaviour for mod_logdb backends.
6070 +%%% Version : trunk
6072 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
6073 +%%%----------------------------------------------------------------------
6075 +-module(gen_logdb).
6076 +-author('o.palij@gmail.com').
6078 +-export([behaviour_info/1]).
6080 +behaviour_info(callbacks) ->
6082 + % called from handle_info(start, _)
6083 + % it should logon database and return reference to started instance
6084 + % start(VHost, Opts) -> {ok, SPid} | error
6085 + % Options - list of options to connect to db
6086 + % Types: Options = list() -> [] |
6087 + % [{user, "logdb"},
6089 + % {db, "logdb"}] | ...
6090 + % VHost = list() -> "jabber.example.org"
6093 + % called from cleanup/1
6094 + % it should logoff database and do cleanup
6096 + % Types: VHost = list() -> "jabber.example.org"
6099 + % called from handle_call({addlog, _}, _, _)
6100 + % it should log messages to database
6101 + % log_message(VHost, Msg) -> ok | error
6103 + % VHost = list() -> "jabber.example.org"
6104 + % Msg = record() -> #msg
6107 + % called from ejabberdctl rebuild_stats
6108 + % it should rebuild stats table (if used) for vhost
6109 + % rebuild_stats(VHost)
6111 + % VHost = list() -> "jabber.example.org"
6112 + {rebuild_stats, 1},
6114 + % it should rebuild stats table (if used) for vhost at Date
6115 + % rebuild_stats_at(VHost, Date)
6117 + % VHost = list() -> "jabber.example.org"
6118 + % Date = list() -> "2007-02-12"
6119 + {rebuild_stats_at, 2},
6121 + % called from user_messages_at_parse_query/5
6122 + % it should delete selected user messages at date
6123 + % delete_messages_by_user_at(VHost, Msgs, Date) -> ok | error
6125 + % VHost = list() -> "jabber.example.org"
6126 + % Msgs = list() -> [ #msg1, msg2, ... ]
6127 + % Date = list() -> "2007-02-12"
6128 + {delete_messages_by_user_at, 3},
6130 + % called from user_messages_parse_query/4 | vhost_messages_at_parse_query/4
6131 + % it should delete all user messages at date
6132 + % delete_all_messages_by_user_at(User, VHost, Date) -> ok | error
6134 + % User = list() -> "admin"
6135 + % VHost = list() -> "jabber.example.org"
6136 + % Date = list() -> "2007-02-12"
6137 + {delete_all_messages_by_user_at, 3},
6139 + % called from vhost_messages_parse_query/3
6140 + % it should delete messages for vhost at date and update stats
6141 + % delete_messages_at(VHost, Date) -> ok | error
6143 + % VHost = list() -> "jabber.example.org"
6144 + % Date = list() -> "2007-02-12"
6145 + {delete_messages_at, 2},
6147 + % called from ejabberd_web_admin:vhost_messages_stats/3
6148 + % it should return sorted list of count of messages by dates for vhost
6149 + % get_vhost_stats(VHost) -> {ok, [{Date1, Msgs_count1}, {Date2, Msgs_count2}, ... ]} |
6152 + % VHost = list() -> "jabber.example.org"
6153 + % DateN = list() -> "2007-02-12"
6154 + % Msgs_countN = number() -> 241
6155 + {get_vhost_stats, 1},
6157 + % called from ejabberd_web_admin:vhost_messages_stats_at/4
6158 + % it should return sorted list of count of messages by users at date for vhost
6159 + % get_vhost_stats_at(VHost, Date) -> {ok, [{User1, Msgs_count1}, {User2, Msgs_count2}, ....]} |
6162 + % VHost = list() -> "jabber.example.org"
6163 + % Date = list() -> "2007-02-12"
6164 + % UserN = list() -> "admin"
6165 + % Msgs_countN = number() -> 241
6166 + {get_vhost_stats_at, 2},
6168 + % called from ejabberd_web_admin:user_messages_stats/4
6169 + % it should return sorted list of count of messages by date for user at vhost
6170 + % get_user_stats(User, VHost) -> {ok, [{Date1, Msgs_count1}, {Date2, Msgs_count2}, ...]} |
6173 + % User = list() -> "admin"
6174 + % VHost = list() -> "jabber.example.org"
6175 + % DateN = list() -> "2007-02-12"
6176 + % Msgs_countN = number() -> 241
6177 + {get_user_stats, 2},
6179 + % called from ejabberd_web_admin:user_messages_stats_at/5
6180 + % it should return all user messages at date
6181 + % get_user_messages_at(User, VHost, Date) -> {ok, Msgs} | {error, Reason}
6183 + % User = list() -> "admin"
6184 + % VHost = list() -> "jabber.example.org"
6185 + % Date = list() -> "2007-02-12"
6186 + % Msgs = list() -> [ #msg1, msg2, ... ]
6187 + {get_user_messages_at, 3},
6189 + % called from many places
6190 + % it should return list of dates for vhost
6191 + % get_dates(VHost) -> [Date1, Date2, ... ]
6193 + % VHost = list() -> "jabber.example.org"
6194 + % DateN = list() -> "2007-02-12"
6197 + % called from start
6198 + % it should return list with users settings for VHost in db
6199 + % get_users_settings(VHost) -> [#user_settings1, #user_settings2, ... ] | error
6201 + % VHost = list() -> "jabber.example.org"
6202 + {get_users_settings, 1},
6204 + % called from many places
6205 + % it should return User settings at VHost from db
6206 + % get_user_settings(User, VHost) -> error | {ok, #user_settings}
6208 + % User = list() -> "admin"
6209 + % VHost = list() -> "jabber.example.org"
6210 + {get_user_settings, 2},
6212 + % called from web admin
6213 + % it should set User settings at VHost
6214 + % set_user_settings(User, VHost, #user_settings) -> ok | error
6216 + % User = list() -> "admin"
6217 + % VHost = list() -> "jabber.example.org"
6218 + {set_user_settings, 3},
6220 + % called from remove_user (ejabberd hook)
6221 + % it should remove user messages and settings at VHost
6222 + % drop_user(User, VHost) -> ok | error
6224 + % User = list() -> "admin"
6225 + % VHost = list() -> "jabber.example.org"
6228 +behaviour_info(_) ->
6230 --- src/web/ejabberd_web_admin-2.0.3.erl 2009-02-03 08:27:39.000000000 +0200
6231 +++ src/web/ejabberd_web_admin.erl 2009-02-03 08:40:57.000000000 +0200
6232 @@ -1514,25 +1514,31 @@
6235 user_parse_query(User, Server, Query) ->
6236 - case lists:keysearch("chpassword", 1, Query) of
6238 - case lists:keysearch("password", 1, Query) of
6239 - {value, {_, undefined}} ->
6241 - {value, {_, Password}} ->
6242 - ejabberd_auth:set_password(User, Server, Password),
6248 - case lists:keysearch("removeuser", 1, Query) of
6250 - ejabberd_auth:remove_user(User, Server),
6255 + lists:foldl(fun({Action, Value}, Acc) when Acc == nothing ->
6256 + user_parse_query1(Action, User, Server, Query);
6257 + ({Action, Value}, Acc) ->
6259 + end, nothing, Query).
6261 +user_parse_query1("password", User, Server, Query) ->
6263 +user_parse_query1("chpassword", User, Server, Query) ->
6264 + case lists:keysearch("password", 1, Query) of
6265 + {value, {_, undefined}} ->
6267 + {value, {_, Password}} ->
6268 + ejabberd_auth:set_password(User, Server, Password),
6273 +user_parse_query1("removeuser", User, Server, Query) ->
6274 + ejabberd_auth:remove_user(User, Server),
6276 +user_parse_query1(Action, User, Server, Query) ->
6277 + case ejabberd_hooks:run_fold(webadmin_user_parse_query, Server, [], [Action, User, Server, Query]) of
6283 --- src/mod_muc/mod_muc_room-2.0.3.erl 2009-02-03 08:27:59.000000000 +0200
6284 +++ src/mod_muc/mod_muc_room.erl 2009-02-03 08:37:26.000000000 +0200
6285 @@ -695,6 +695,12 @@
6286 handle_sync_event({change_config, Config}, _From, StateName, StateData) ->
6287 {result, [], NSD} = change_config(Config, StateData),
6288 {reply, {ok, NSD#state.config}, StateName, NSD};
6289 +handle_sync_event({get_jid_nick, Jid}, _From, StateName, StateData) ->
6290 + R = case ?DICT:find(jlib:jid_tolower(Jid), StateData#state.users) of
6292 + {ok, {user, _, Nick, _, _}} -> Nick
6294 + {reply, R, StateName, StateData};
6295 handle_sync_event(_Event, _From, StateName, StateData) ->
6297 {reply, Reply, StateName, StateData}.
6298 --- src/msgs/uk-2.0.3.msg 2009-01-14 11:54:15.000000000 +0200
6299 +++ src/msgs/uk.msg 2009-02-03 08:26:20.000000000 +0200
6300 @@ -388,6 +388,35 @@
6301 % mod_offline_odbc.erl
6302 {"Your contact offline message queue is full. The message has been discarded.", "Черга повідомлень, що не були доставлені, переповнена. Повідомлення не було збережено."}.
6305 +{"Users Messages", "Повідомлення користувачів"}.
6307 +{"Count", "Кількість"}.
6308 +{"Logged messages for ", "Збережені повідомлення для "}.
6310 +{"No logged messages for ", "Відсутні повідомлення для "}.
6311 +{"Date, Time", "Дата, Час"}.
6312 +{"Direction: Jid", "Напрямок: Jid"}.
6313 +{"Subject", "Тема"}.
6315 +{"Messages", "Повідомлення"}.
6316 +{"Filter Selected", "Відфільтрувати виділені"}.
6317 +{"Do Not Log Messages", "Не зберігати повідомлення"}.
6318 +{"Log Messages", "Зберігати повідомлення"}.
6319 +{"Messages logging engine", "Система збереження повідомлень"}.
6320 +{"Default", "За замовчуванням"}.
6321 +{"Set logging preferences", "Вкажіть налагоджування збереження повідомлень"}.
6322 +{"Messages logging engine users", "Користувачі системи збереження повідомлень"}.
6323 +{"Messages logging engine settings", "Налагоджування системи збереження повідомлень"}.
6324 +{"Set run-time settings", "Вкажіть поточні налагоджування"}.
6325 +{"Groupchat messages logging", "Збереження повідомлень типу groupchat"}.
6326 +{"Jids/Domains to ignore", "Ігнорувати наступні jids/домени"}.
6327 +{"Purge messages older than (days)", "Видаляти повідомлення старіші ніж (дні)"}.
6328 +{"Poll users settings (seconds)", "Оновлювати налагоджування користувачів кожні (секунд)"}.
6329 +{"Drop", "Видаляти"}.
6330 +{"Do not drop", "Не видаляти"}.
6331 +{"Drop messages on user removal", "Видаляти повідомлення під час видалення користувача"}.
6336 --- src/msgs/ru-2.0.3.msg 2009-01-14 11:54:15.000000000 +0200
6337 +++ src/msgs/ru.msg 2009-02-03 08:25:31.000000000 +0200
6338 @@ -388,6 +388,35 @@
6339 % mod_offline_odbc.erl
6340 {"Your contact offline message queue is full. The message has been discarded.", "Очередь недоставленных сообщений Вашего адресата переполнена. Сообщение не было сохранено."}.
6343 +{"Users Messages", "Сообщения пользователей"}.
6345 +{"Count", "Количество"}.
6346 +{"Logged messages for ", "Сохранённые cообщения для "}.
6348 +{"No logged messages for ", "Отсутствуют сообщения для "}.
6349 +{"Date, Time", "Дата, Время"}.
6350 +{"Direction: Jid", "Направление: Jid"}.
6351 +{"Subject", "Тема"}.
6353 +{"Messages", "Сообщения"}.
6354 +{"Filter Selected", "Отфильтровать выделенные"}.
6355 +{"Do Not Log Messages", "Не сохранять сообщения"}.
6356 +{"Log Messages", "Сохранять сообщения"}.
6357 +{"Messages logging engine", "Система логирования сообщений"}.
6358 +{"Default", "По умолчанию"}.
6359 +{"Set logging preferences", "Задайте настройки логирования"}.
6360 +{"Messages logging engine users", "Пользователи системы логирования сообщений"}.
6361 +{"Messages logging engine settings", "Настройки системы логирования сообщений"}.
6362 +{"Set run-time settings", "Задайте текущие настройки"}.
6363 +{"Groupchat messages logging", "Логирование сообщений типа groupchat"}.
6364 +{"Jids/Domains to ignore", "Игнорировать следующие jids/домены"}.
6365 +{"Purge messages older than (days)", "Удалять сообщения старее чем (дни)"}.
6366 +{"Poll users settings (seconds)", "Обновлять настройки пользователей через (секунд)"}.
6367 +{"Drop", "Удалять"}.
6368 +{"Do not drop", "Не удалять"}.
6369 +{"Drop messages on user removal", "Удалять сообщения при удалении пользователя"}.
6374 --- src/msgs/pl-2.0.3.msg 2009-01-14 11:54:15.000000000 +0200
6375 +++ src/msgs/pl.msg 2009-02-03 08:24:33.000000000 +0200
6376 @@ -408,6 +408,31 @@
6378 {"Your contact offline message queue is full. The message has been discarded.", "Twoja kolejka wiadomoci offline jest pełna. Wiadomoć została odrzucona."}.
6381 +{"Users Messages", "Wiadomości użytkownika"}.
6383 +{"Count", "Liczba"}.
6384 +{"Logged messages for ", "Zapisane wiadomości dla "}.
6386 +{"No logged messages for ", "Brak zapisanych wiadomości dla "}.
6387 +{"Date, Time", "Data, Godzina"}.
6388 +{"Direction: Jid", "Kierunek: Jid"}.
6389 +{"Subject", "Temat"}.
6391 +{"Messages","Wiadomości"}.
6392 +{"Filter Selected", "Odfiltruj zaznaczone"}.
6393 +{"Do Not Log Messages", "Nie zapisuj wiadomości"}.
6394 +{"Log Messages", "Zapisuj wiadomości"}.
6395 +{"Messages logging engine", "System zapisywania historii rozmów"}.
6396 +{"Default", "Domyślne"}.
6397 +{"Set logging preferences", "Ustaw preferencje zapisywania"}.
6398 +{"Messages logging engine settings", "Ustawienia systemu logowania"}.
6399 +{"Set run-time settings", "Zapisz ustawienia systemu logowania"}.
6400 +{"Groupchat messages logging", "Zapisywanie rozmów z konferencji"}.
6401 +{"Jids/Domains to ignore", "JID/Domena która ma być ignorowana"}.
6402 +{"Purge messages older than (days)", "Usuń wiadomości starsze niż (w dniach)"}.
6403 +{"Poll users settings (seconds)", "Czas aktualizacji preferencji użytkowników (sekundy)"}.
6408 --- src/msgs/nl-2.0.3.msg 2009-01-14 11:54:15.000000000 +0200
6409 +++ src/msgs/nl.msg 1970-01-01 03:00:00.000000000 +0300
6410 @@ -379,6 +379,19 @@
6411 % mod_proxy65/mod_proxy65_service.erl
6412 {"ejabberd SOCKS5 Bytestreams module", "ejabberd SOCKS5 Bytestreams module"}.
6415 +{"Users Messages", "Gebruikersberichten"}.
6417 +{"Count", "Aantal"}.
6418 +{"Logged messages for ", "Gelogde berichten van "}.
6420 +{"No logged messages for ", "Geen gelogde berichten van "}.
6421 +{"Date, Time", "Datum en tijd"}.
6422 +{"Direction: Jid", "Richting: Jabber ID"}.
6423 +{"Subject", "Onderwerp"}.
6424 +{"Body", "Berichtveld"}.
6425 +{"Messages", "Berichten"}.
6430 --- src/mod_roster-2.0.3.erl 2009-02-03 08:28:12.000000000 +0200
6431 +++ src/mod_roster.erl 2009-02-03 08:32:14.000000000 +0200
6433 -include("mod_roster.hrl").
6434 -include("web/ejabberd_http.hrl").
6435 -include("web/ejabberd_web_admin.hrl").
6437 +-include("mod_logdb.hrl").
6439 start(Host, Opts) ->
6440 IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
6441 @@ -829,6 +829,14 @@
6442 Res = user_roster_parse_query(User, Server, Items1, Query),
6443 Items = mnesia:dirty_index_read(roster, US, #roster.us),
6444 SItems = lists:sort(Items),
6446 + Settings = case gen_mod:is_loaded(Server, mod_logdb) of
6448 + mod_logdb:get_user_settings(User, Server);
6456 @@ -876,7 +884,33 @@
6459 ejabberd_web_admin:term_to_id(R#roster.jid),
6462 + case gen_mod:is_loaded(Server, mod_logdb) of
6464 + Peer = jlib:jid_to_string(R#roster.jid),
6465 + A = lists:member(Peer, Settings#user_settings.dolog_list),
6466 + B = lists:member(Peer, Settings#user_settings.donotlog_list),
6470 + {"donotlog", "Do Not Log Messages"};
6472 + {"dolog", "Log Messages"};
6473 + Settings#user_settings.dolog_default == true ->
6474 + {"donotlog", "Do Not Log Messages"};
6475 + Settings#user_settings.dolog_default == false ->
6476 + {"dolog", "Log Messages"}
6479 + ?XAE("td", [{"class", "valign"}],
6480 + [?INPUTT("submit",
6482 + ejabberd_web_admin:term_to_id(R#roster.jid),
6490 [?XC("h1", ?T("Roster of ") ++ us_to_list(US))] ++
6491 @@ -958,11 +992,42 @@
6492 {"subscription", "remove"}],
6501 + case lists:keysearch(
6502 + "donotlog" ++ ejabberd_web_admin:term_to_id(JID), 1, Query) of
6504 + Peer = jlib:jid_to_string(JID),
6505 + Settings = mod_logdb:get_user_settings(User, Server),
6506 + DNLL = case lists:member(Peer, Settings#user_settings.donotlog_list) of
6507 + false -> lists:append(Settings#user_settings.donotlog_list, [Peer]);
6508 + true -> Settings#user_settings.donotlog_list
6510 + DLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.dolog_list),
6511 + Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
6512 + % TODO: check returned value
6513 + ok = mod_logdb:set_user_settings(User, Server, Sett),
6516 + case lists:keysearch(
6517 + "dolog" ++ ejabberd_web_admin:term_to_id(JID), 1, Query) of
6519 + Peer = jlib:jid_to_string(JID),
6520 + Settings = mod_logdb:get_user_settings(User, Server),
6521 + DLL = case lists:member(Peer, Settings#user_settings.dolog_list) of
6522 + false -> lists:append(Settings#user_settings.dolog_list, [Peer]);
6523 + true -> Settings#user_settings.dolog_list
6525 + DNLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.donotlog_list),
6526 + Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
6527 + % TODO: check returned value
6528 + ok = mod_logdb:set_user_settings(User, Server, Sett),
6539 --- src/mod_roster_odbc-2.0.3.erl 2009-02-03 08:28:26.000000000 +0200
6540 +++ src/mod_roster_odbc.erl 2009-02-03 08:47:04.000000000 +0200
6542 -include("mod_roster.hrl").
6543 -include("web/ejabberd_http.hrl").
6544 -include("web/ejabberd_web_admin.hrl").
6546 +-include("mod_logdb.hrl").
6548 start(Host, Opts) ->
6549 IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
6550 @@ -937,6 +937,14 @@
6551 Res = user_roster_parse_query(User, Server, Items1, Query),
6552 Items = get_roster(LUser, LServer),
6553 SItems = lists:sort(Items),
6555 + Settings = case gen_mod:is_loaded(Server, mod_logdb) of
6557 + mod_logdb:get_user_settings(User, Server);
6565 @@ -984,7 +992,33 @@
6568 ejabberd_web_admin:term_to_id(R#roster.jid),
6571 + case gen_mod:is_loaded(Server, mod_logdb) of
6573 + Peer = jlib:jid_to_string(R#roster.jid),
6574 + A = lists:member(Peer, Settings#user_settings.dolog_list),
6575 + B = lists:member(Peer, Settings#user_settings.donotlog_list),
6579 + {"donotlog", "Do Not Log Messages"};
6581 + {"dolog", "Log Messages"};
6582 + Settings#user_settings.dolog_default == true ->
6583 + {"donotlog", "Do Not Log Messages"};
6584 + Settings#user_settings.dolog_default == false ->
6585 + {"dolog", "Log Messages"}
6588 + ?XAE("td", [{"class", "valign"}],
6589 + [?INPUTT("submit",
6591 + ejabberd_web_admin:term_to_id(R#roster.jid),
6599 [?XC("h1", ?T("Roster of ") ++ us_to_list(US))] ++
6600 @@ -1066,11 +1100,42 @@
6601 {"subscription", "remove"}],
6610 + case lists:keysearch(
6611 + "donotlog" ++ ejabberd_web_admin:term_to_id(JID), 1, Query) of
6613 + Peer = jlib:jid_to_string(JID),
6614 + Settings = mod_logdb:get_user_settings(User, Server),
6615 + DNLL = case lists:member(Peer, Settings#user_settings.donotlog_list) of
6616 + false -> lists:append(Settings#user_settings.donotlog_list, [Peer]);
6617 + true -> Settings#user_settings.donotlog_list
6619 + DLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.dolog_list),
6620 + Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
6621 + % TODO: check returned value
6622 + ok = mod_logdb:set_user_settings(User, Server, Sett),
6625 + case lists:keysearch(
6626 + "dolog" ++ ejabberd_web_admin:term_to_id(JID), 1, Query) of
6628 + Peer = jlib:jid_to_string(JID),
6629 + Settings = mod_logdb:get_user_settings(User, Server),
6630 + DLL = case lists:member(Peer, Settings#user_settings.dolog_list) of
6631 + false -> lists:append(Settings#user_settings.dolog_list, [Peer]);
6632 + true -> Settings#user_settings.dolog_list
6634 + DNLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.donotlog_list),
6635 + Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
6636 + % TODO: check returned value
6637 + ok = mod_logdb:set_user_settings(User, Server, Sett),