]> git.pld-linux.org Git - packages/ejabberd.git/blame - ejabberd-mod_logdb.patch
- up to 2.0.1
[packages/ejabberd.git] / ejabberd-mod_logdb.patch
CommitLineData
f7ce3e3a 1--- src/mod_logdb.erl.orig Tue Dec 11 14:23:19 2007
2+++ src/mod_logdb.erl Thu Sep 20 15:26:21 2007
3@@ -0,0 +1,1656 @@
4+%%%----------------------------------------------------------------------
5+%%% File : mod_logdb.erl
6+%%% Author : Oleg Palij (mailto:o.palij@gmail.com xmpp://malik@jabber.te.ua)
7+%%% Purpose : Frontend for log user messages to db
8+%%% Version : trunk
9+%%% Id : $Id$
10+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
11+%%%----------------------------------------------------------------------
12+
13+-module(mod_logdb).
14+-author('o.palij@gmail.com').
15+-vsn('$Revision$').
16+
17+-behaviour(gen_server).
18+-behaviour(gen_mod).
19+
20+% supervisor
21+-export([start_link/2]).
22+% gen_mod
23+-export([start/2,stop/1]).
24+% gen_server
25+-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
26+% hooks
27+-export([send_packet/3, receive_packet/4, offline_packet/3]).
28+-export([get_local_identity/5,
29+ get_local_features/5,
30+ get_local_items/5,
31+ adhoc_local_items/4,
32+ adhoc_local_commands/4
33+% get_sm_identity/5,
34+% get_sm_features/5,
35+% get_sm_items/5,
36+% adhoc_sm_items/4,
37+% adhoc_sm_commands/4]).
38+ ]).
39+% ejabberdctl
40+-export([rebuild_stats/3,
41+ copy_messages/1, copy_messages_ctl/3, copy_messages_int_tc/1]).
42+%
43+-export([get_vhost_stats/1, get_vhost_stats_at/2,
44+ get_user_stats/2, get_user_messages_at/3,
45+ get_dates/1,
46+ sort_stats/1,
47+ convert_timestamp/1, convert_timestamp_brief/1,
48+ get_user_settings/2, set_user_settings/3,
49+ user_messages_at_parse_query/4, user_messages_parse_query/3,
50+ vhost_messages_parse_query/2, vhost_messages_at_parse_query/4,
51+ list_to_bool/1, bool_to_list/1,
52+ list_to_string/1, string_to_list/1,
53+ get_module_settings/1, set_module_settings/2,
54+ purge_old_records/2]).
55+
56+-include("mod_logdb.hrl").
57+-include("ejabberd.hrl").
58+-include("jlib.hrl").
59+-include("ejabberd_ctl.hrl").
60+-include("adhoc.hrl").
61+
62+-define(PROCNAME, ejabberd_mod_logdb).
63+% gen_server call timeout
64+-define(CALL_TIMEOUT, 60000).
65+
66+-record(state, {vhost, dbmod, backendPid, monref, purgeRef, pollRef, dbopts, dbs, dolog_default, ignore_jids, groupchat, purge_older_days, poll_users_settings}).
67+
68+ets_settings_table(VHost) -> list_to_atom("ets_logdb_settings_" ++ VHost).
69+
70+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
71+%
72+% gen_mod/gen_server callbacks
73+%
74+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
75+% ejabberd starts module
76+start(VHost, Opts) ->
77+ ChildSpec =
78+ {gen_mod:get_module_proc(VHost, ?PROCNAME),
79+ {?MODULE, start_link, [VHost, Opts]},
80+ permanent,
81+ 1000,
82+ worker,
83+ [?MODULE]},
84+ % add child to ejabberd_sup
85+ supervisor:start_child(ejabberd_sup, ChildSpec).
86+
87+% supervisor starts gen_server
88+start_link(VHost, Opts) ->
89+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
90+ gen_server:start_link({local, Proc}, ?MODULE, [VHost, Opts], []).
91+
92+init([VHost, Opts]) ->
93+ process_flag(trap_exit, true),
94+ DBs = gen_mod:get_opt(dbs, Opts, [{mnesia, []}]),
95+ VHostDB = gen_mod:get_opt(vhosts, Opts, [{VHost, mnesia}]),
96+ % 10 is default becouse of using in clustered environment
97+ PollUsersSettings = gen_mod:get_opt(poll_users_settings, Opts, 10),
98+
99+ {value,{_, DBName}} = lists:keysearch(VHost, 1, VHostDB),
100+ {value, {DBName, DBOpts}} = lists:keysearch(DBName, 1, DBs),
101+
102+ ?MYDEBUG("Starting mod_logdb for ~p with ~p backend", [VHost, DBName]),
103+
104+ DBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(DBName)),
105+
106+ % actually all work begin on receiving start signal
107+ timer:send_after(1000, start),
108+
109+ {ok, #state{vhost=VHost,
110+ dbmod=DBMod,
111+ dbopts=DBOpts,
112+ % dbs used for convert messages from one backend to other
113+ dbs=DBs,
114+ dolog_default=gen_mod:get_opt(dolog_default, Opts, true),
115+ ignore_jids=gen_mod:get_opt(ignore_jids, Opts, []),
116+ groupchat=gen_mod:get_opt(groupchat, Opts, none),
117+ purge_older_days=gen_mod:get_opt(purge_older_days, Opts, never),
118+ poll_users_settings=PollUsersSettings}}.
119+
120+cleanup(#state{vhost=VHost} = State) ->
121+ ?MYDEBUG("Stopping ~s for ~p", [?MODULE, VHost]),
122+
123+ %ets:delete(ets_settings_table(VHost)),
124+
125+ ejabberd_hooks:delete(user_send_packet, VHost, ?MODULE, send_packet, 90),
126+ ejabberd_hooks:delete(user_receive_packet, VHost, ?MODULE, receive_packet, 90),
127+ ejabberd_hooks:delete(offline_message_hook, VHost, ?MODULE, offline_packet, 10),
128+ %ejabberd_hooks:delete(adhoc_sm_commands, VHost, ?MODULE, adhoc_sm_commands, 110),
129+ %ejabberd_hooks:delete(adhoc_sm_items, VHost, ?MODULE, adhoc_sm_items, 110),
130+ ejabberd_hooks:delete(adhoc_local_commands, VHost, ?MODULE, adhoc_local_commands, 110),
131+ ejabberd_hooks:delete(adhoc_local_items, VHost, ?MODULE, adhoc_local_items, 110),
132+ %ejabberd_hooks:delete(disco_sm_identity, VHost, ?MODULE, get_sm_identity, 110),
133+ %ejabberd_hooks:delete(disco_sm_features, VHost, ?MODULE, get_sm_features, 110),
134+ %ejabberd_hooks:delete(disco_sm_items, VHost, ?MODULE, get_sm_items, 110),
135+ ejabberd_hooks:delete(disco_local_identity, VHost, ?MODULE, get_local_identity, 110),
136+ ejabberd_hooks:delete(disco_local_features, VHost, ?MODULE, get_local_features, 110),
137+ ejabberd_hooks:delete(disco_local_items, VHost, ?MODULE, get_local_items, 110),
138+
139+ ?MYDEBUG("Removed hooks for ~p", [VHost]),
140+
141+ ejabberd_ctl:unregister_commands(VHost, [{"rebuild_stats", "rebuild mod_logdb module stats for vhost"}], ?MODULE, rebuild_stats),
142+ Supported_backends = lists:flatmap(fun({Backend, _Opts}) ->
143+ [atom_to_list(Backend), " "]
144+ end, State#state.dbs),
145+ ejabberd_ctl:unregister_commands(
146+ VHost,
147+ [{"copy_messages backend", "copy messages from backend to current backend. backends could be: " ++ Supported_backends }],
148+ ?MODULE, copy_messages_ctl),
149+ ?MYDEBUG("Unregistered commands for ~p", [VHost]).
150+
151+stop(VHost) ->
152+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
153+ %gen_server:call(Proc, {cleanup}),
154+ %?MYDEBUG("Cleanup in stop finished!!!!", []),
155+ %timer:sleep(10000),
156+ ok = supervisor:terminate_child(ejabberd_sup, Proc),
157+ ok = supervisor:delete_child(ejabberd_sup, Proc).
158+
159+handle_call({cleanup}, _From, State) ->
160+ cleanup(State),
161+ ?MYDEBUG("Cleanup finished!!!!!", []),
162+ {reply, ok, State};
163+handle_call({get_dates}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
164+ Reply = DBMod:get_dates(VHost),
165+ {reply, Reply, State};
166+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
167+% ejabberd_web_admin callbacks
168+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
169+handle_call({delete_messages_by_user_at, PMsgs, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
170+ Reply = DBMod:delete_messages_by_user_at(VHost, PMsgs, Date),
171+ {reply, Reply, State};
172+handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
173+ Reply = DBMod:delete_all_messages_by_user_at(User, VHost, Date),
174+ {reply, Reply, State};
175+handle_call({delete_messages_at, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
176+ Reply = DBMod:delete_messages_at(VHost, Date),
177+ {reply, Reply, State};
178+handle_call({get_vhost_stats}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
179+ Reply = DBMod:get_vhost_stats(VHost),
180+ {reply, Reply, State};
181+handle_call({get_vhost_stats_at, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
182+ Reply = DBMod:get_vhost_stats_at(VHost, Date),
183+ {reply, Reply, State};
184+handle_call({get_user_stats, User}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
185+ Reply = DBMod:get_user_stats(User, VHost),
186+ {reply, Reply, State};
187+handle_call({get_user_messages_at, User, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
188+ Reply = DBMod:get_user_messages_at(User, VHost, Date),
189+ {reply, Reply, State};
190+handle_call({get_user_settings, User}, _From, #state{dbmod=_DBMod, vhost=VHost}=State) ->
191+ Reply = case ets:match_object(ets_settings_table(VHost),
192+ #user_settings{owner_name=User, _='_'}) of
193+ [Set] -> Set;
194+ _ -> #user_settings{owner_name=User,
195+ dolog_default=State#state.dolog_default,
196+ dolog_list=[],
197+ donotlog_list=[]}
198+ end,
199+ {reply, Reply, State};
200+% TODO: remove User ??
201+handle_call({set_user_settings, User, GSet}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
202+ Set = GSet#user_settings{owner_name=User},
203+ Reply =
204+ case ets:match_object(ets_settings_table(VHost),
205+ #user_settings{owner_name=User, _='_'}) of
206+ [Set] ->
207+ ?MYDEBUG("Settings is equal", []),
208+ ok;
209+ _ ->
210+ case DBMod:set_user_settings(User, VHost, Set) of
211+ error ->
212+ error;
213+ ok ->
214+ true = ets:insert(ets_settings_table(VHost), Set),
215+ ok
216+ end
217+ end,
218+ {reply, Reply, State};
219+handle_call({get_module_settings}, _From, State) ->
220+ {reply, State, State};
221+handle_call({set_module_settings, #state{purge_older_days=PurgeDays,
222+ poll_users_settings=PollSec} = Settings},
223+ _From,
224+ #state{purgeRef=PurgeRefOld,
225+ pollRef=PollRefOld,
226+ purge_older_days=PurgeDaysOld,
227+ poll_users_settings=PollSecOld} = State) ->
228+ PurgeRef = if
229+ PurgeDays == never, PurgeDaysOld /= never ->
230+ {ok, cancel} = timer:cancel(PurgeRefOld),
231+ disabled;
232+ is_integer(PurgeDays), PurgeDaysOld == never ->
233+ set_purge_timer(PurgeDays);
234+ true ->
235+ PurgeRefOld
236+ end,
237+
238+ PollRef = if
239+ PollSec == PollSecOld ->
240+ PollRefOld;
241+ PollSec == 0, PollSecOld /= 0 ->
242+ {ok, cancel} = timer:cancel(PollRefOld),
243+ disabled;
244+ is_integer(PollSec), PollSecOld == 0 ->
245+ set_poll_timer(PollSec);
246+ is_integer(PollSec), PollSecOld /= 0 ->
247+ {ok, cancel} = timer:cancel(PollRefOld),
248+ set_poll_timer(PollSec)
249+ end,
250+
251+ NewState = State#state{dolog_default=Settings#state.dolog_default,
252+ ignore_jids=Settings#state.ignore_jids,
253+ groupchat=Settings#state.groupchat,
254+ purge_older_days=PurgeDays,
255+ poll_users_settings=PollSec,
256+ purgeRef=PurgeRef,
257+ pollRef=PollRef},
258+ {reply, ok, NewState};
259+handle_call(Msg, _From, State) ->
260+ ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
261+ {noreply, State}.
262+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
263+% end ejabberd_web_admin callbacks
264+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
265+
266+% ejabberd_hooks call
267+handle_cast({addlog, Direction, Owner, Peer, Packet}, #state{dbmod=DBMod, vhost=VHost}=State) ->
268+ case filter(Owner, Peer, State) of
269+ true ->
270+ case catch packet_parse(Owner, Peer, Packet, Direction, State) of
271+ ignore ->
272+ ok;
273+ {'EXIT', Reason} ->
274+ ?ERROR_MSG("Failed to parse: ~p", [Reason]);
275+ Msg ->
276+ DBMod:log_message(VHost, Msg)
277+ end;
278+ false ->
279+ ok
280+ end,
281+ {noreply, State};
282+% ejabberdctl rebuild_stats/3
283+handle_cast({rebuild_stats}, #state{dbmod=DBMod, vhost=VHost}=State) ->
284+ % TODO: maybe spawn?
285+ DBMod:rebuild_stats(VHost),
286+ {noreply, State};
287+handle_cast({copy_messages, Backend}, State) ->
288+ spawn(?MODULE, copy_messages, [[State, Backend]]),
289+ {noreply, State};
290+handle_cast({copy_messages, Backend, Date}, State) ->
291+ spawn(?MODULE, copy_messages, [[State, Backend, Date]]),
292+ {noreply, State};
293+handle_cast(Msg, State) ->
294+ ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
295+ {noreply, State}.
296+
297+% return: disabled | timer reference
298+set_purge_timer(PurgeDays) ->
299+ case PurgeDays of
300+ never -> disabled;
301+ Days when is_integer(Days) ->
302+ {ok, Ref1} = timer:send_interval(timer:hours(24), scheduled_purging),
303+ Ref1
304+ end.
305+
306+% return: disabled | timer reference
307+set_poll_timer(PollSec) ->
308+ if
309+ PollSec > 0 ->
310+ {ok, Ref2} = timer:send_interval(timer:seconds(PollSec), poll_users_settings),
311+ Ref2;
312+ % db polling disabled
313+ PollSec == 0 ->
314+ disabled;
315+ true ->
316+ {ok, Ref3} = timer:send_interval(timer:seconds(10), poll_users_settings),
317+ Ref3
318+ end.
319+
320+% actual starting of logging
321+% from timer:send_after (in init)
322+handle_info(start, #state{dbmod=DBMod, vhost=VHost}=State) ->
323+ case DBMod:start(VHost, State#state.dbopts) of
324+ {error, _Reason} ->
325+ timer:sleep(30000),
326+ {stop, db_connection_failed, State};
327+ {ok, SPid} ->
328+
329+ ?INFO_MSG("~p connection established", [DBMod]),
330+
331+ MonRef = erlang:monitor(process, SPid),
332+
333+ ets:new(ets_settings_table(VHost), [named_table,public,set,{keypos, #user_settings.owner_name}]),
334+ {ok, DoLog} = DBMod:get_users_settings(VHost),
335+ ets:insert(ets_settings_table(VHost), DoLog),
336+
337+ TrefPurge = set_purge_timer(State#state.purge_older_days),
338+ TrefPoll = set_poll_timer(State#state.poll_users_settings),
339+
340+ ejabberd_hooks:add(user_send_packet, VHost, ?MODULE, send_packet, 90),
341+ ejabberd_hooks:add(user_receive_packet, VHost, ?MODULE, receive_packet, 90),
342+ ejabberd_hooks:add(offline_message_hook, VHost, ?MODULE, offline_packet, 10),
343+
344+ ejabberd_hooks:add(disco_local_items, VHost, ?MODULE, get_local_items, 110),
345+ ejabberd_hooks:add(disco_local_features, VHost, ?MODULE, get_local_features, 110),
346+ ejabberd_hooks:add(disco_local_identity, VHost, ?MODULE, get_local_identity, 110),
347+ %ejabberd_hooks:add(disco_sm_items, VHost, ?MODULE, get_sm_items, 110),
348+ %ejabberd_hooks:add(disco_sm_features, VHost, ?MODULE, get_sm_features, 110),
349+ %ejabberd_hooks:add(disco_sm_identity, VHost, ?MODULE, get_sm_identity, 110),
350+ ejabberd_hooks:add(adhoc_local_items, VHost, ?MODULE, adhoc_local_items, 110),
351+ ejabberd_hooks:add(adhoc_local_commands, VHost, ?MODULE, adhoc_local_commands, 110),
352+ %ejabberd_hooks:add(adhoc_sm_items, VHost, ?MODULE, adhoc_sm_items, 110),
353+ %ejabberd_hooks:add(adhoc_sm_commands, VHost, ?MODULE, adhoc_sm_commands, 110),
354+
355+ ?MYDEBUG("Added hooks for ~p", [VHost]),
356+
357+ ejabberd_ctl:register_commands(
358+ VHost,
359+ [{"rebuild_stats", "rebuild mod_logdb module stats for vhost"}],
360+ ?MODULE, rebuild_stats),
361+ Supported_backends = lists:flatmap(fun({Backend, _Opts}) ->
362+ [atom_to_list(Backend), " "]
363+ end, State#state.dbs),
364+ ejabberd_ctl:register_commands(
365+ VHost,
366+ [{"copy_messages backend", "copy messages from backend to current backend. backends could be: " ++ Supported_backends }],
367+ ?MODULE, copy_messages_ctl),
368+ ?MYDEBUG("Registered commands for ~p", [VHost]),
369+
370+ NewState=State#state{monref = MonRef, backendPid=SPid, purgeRef=TrefPurge, pollRef=TrefPoll},
371+ {noreply, NewState};
372+ Rez ->
373+ ?ERROR_MSG("Rez=~p", [Rez]),
374+ timer:sleep(30000),
375+ {stop, db_connection_failed, State}
376+ end;
377+% from timer:send_interval/2 (in start handle_info)
378+handle_info(scheduled_purging, #state{vhost=VHost, purge_older_days=Days} = State) ->
379+ ?MYDEBUG("Starting scheduled purging of old records for ~p", [VHost]),
380+ spawn(?MODULE, purge_old_records, [VHost, integer_to_list(Days)]),
381+ {noreply, State};
382+% from timer:send_interval/2 (in start handle_info)
383+handle_info(poll_users_settings, #state{dbmod=DBMod, vhost=VHost}=State) ->
384+ {ok, DoLog} = DBMod:get_users_settings(VHost),
385+ ?MYDEBUG("DoLog=~p", [DoLog]),
386+ true = ets:delete_all_objects(ets_settings_table(VHost)),
387+ ets:insert(ets_settings_table(VHost), DoLog),
388+ {noreply, State};
389+handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
390+ {stop, db_connection_dropped, State};
391+handle_info({fetch_result, _, _}, State) ->
392+ ?MYDEBUG("Got timed out mysql fetch result", []),
393+ {noreply, State};
394+handle_info(Info, State) ->
395+ ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
396+ {noreply, State}.
397+
398+terminate(db_connection_failed, _State) ->
399+ ok;
400+terminate(db_connection_dropped, State) ->
401+ cleanup(State),
402+ ok;
403+terminate(_Reason, #state{monref=undefined} = State) ->
404+ cleanup(State),
405+ ok;
406+terminate(Reason, #state{dbmod=DBMod, vhost=VHost, monref=MonRef, backendPid=Pid} = State) ->
407+ ?INFO_MSG("Reason: ~p", [Reason]),
408+ case erlang:is_process_alive(Pid) of
409+ true ->
410+ erlang:demonitor(MonRef, [flush]),
411+ DBMod:stop(VHost);
412+ false ->
413+ ok
414+ end,
415+ cleanup(State),
416+ ok.
417+
418+code_change(_OldVsn, State, _Extra) ->
419+ {ok, State}.
420+
421+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
422+%
423+% ejabberd_hooks callbacks
424+%
425+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
426+% TODO: change to/from to list as sql stores it as list
427+send_packet(Owner, Peer, P) ->
428+ VHost = Owner#jid.lserver,
429+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
430+ gen_server:cast(Proc, {addlog, to, Owner, Peer, P}).
431+
432+offline_packet(Peer, Owner, P) ->
433+ VHost = Owner#jid.lserver,
434+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
435+ gen_server:cast(Proc, {addlog, from, Owner, Peer, P}).
436+
437+receive_packet(_JID, Peer, Owner, P) ->
438+ VHost = Owner#jid.lserver,
439+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
440+ gen_server:cast(Proc, {addlog, from, Owner, Peer, P}).
441+
442+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
443+%
444+% ejabberdctl
445+%
446+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
447+rebuild_stats(_Val, VHost, ["rebuild_stats"]) ->
448+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
449+ gen_server:cast(Proc, {rebuild_stats}),
450+ {stop, ?STATUS_SUCCESS};
451+rebuild_stats(Val, _VHost, _Args) ->
452+ Val.
453+
454+copy_messages_ctl(_Val, VHost, ["copy_messages", Backend]) ->
455+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
456+ gen_server:cast(Proc, {copy_messages, Backend}),
457+ {stop, ?STATUS_SUCCESS};
458+copy_messages_ctl(_Val, VHost, ["copy_messages", Backend, Date]) ->
459+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
460+ gen_server:cast(Proc, {copy_messages, Backend, Date}),
461+ {stop, ?STATUS_SUCCESS};
462+copy_messages_ctl(Val, _VHost, _Args) ->
463+ Val.
464+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
465+%
466+% misc operations
467+%
468+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
469+
470+% handle_cast({addlog, E}, _)
471+% raw packet -> #msg
472+packet_parse(Owner, Peer, Packet, Direction, State) ->
473+ case xml:get_subtag(Packet, "body") of
474+ false ->
475+ ignore;
476+ Body_xml ->
477+ Message_type =
478+ case xml:get_tag_attr_s("type", Packet) of
479+ [] -> "normal";
480+ MType -> MType
481+ end,
482+
483+ case Message_type of
484+ "groupchat" when State#state.groupchat == send, Direction == to ->
485+ ok;
486+ "groupchat" when State#state.groupchat == send, Direction == from ->
487+ throw(ignore);
488+ "groupchat" when State#state.groupchat == half ->
489+ Rooms = ets:match(muc_online_room, '$1'),
490+ Ni=lists:foldl(fun([{muc_online_room, {GName, GHost}, Pid}], Names) ->
491+ case gen_fsm:sync_send_all_state_event(Pid, {get_jid_nick,Owner}) of
492+ [] -> Names;
493+ Nick ->
494+ lists:append(Names, [jlib:jid_to_string({GName, GHost, Nick})])
495+ end
496+ end, [], Rooms),
497+ case lists:member(jlib:jid_to_string(Peer), Ni) of
498+ true when Direction == from ->
499+ throw(ignore);
500+ _ ->
501+ ok
502+ end;
503+ "groupchat" when State#state.groupchat == none ->
504+ throw(ignore);
505+ _ ->
506+ ok
507+ end,
508+
509+ Message_body = xml:get_tag_cdata(Body_xml),
510+ Message_subject =
511+ case xml:get_subtag(Packet, "subject") of
512+ false ->
513+ "";
514+ Subject_xml ->
515+ xml:get_tag_cdata(Subject_xml)
516+ end,
517+
518+ OwnerName = stringprep:tolower(Owner#jid.user),
519+ PName = stringprep:tolower(Peer#jid.user),
520+ PServer = stringprep:tolower(Peer#jid.server),
521+ PResource = Peer#jid.resource,
522+
523+ #msg{timestamp=get_timestamp(),
524+ owner_name=OwnerName,
525+ peer_name=PName,
526+ peer_server=PServer,
527+ peer_resource=PResource,
528+ direction=Direction,
529+ type=Message_type,
530+ subject=Message_subject,
531+ body=Message_body}
532+ end.
533+
534+% called from handle_cast({addlog, _}, _) -> true (log messages) | false (do not log messages)
535+filter(Owner, Peer, State) ->
536+ OwnerStr = Owner#jid.luser++"@"++Owner#jid.lserver,
537+ OwnerServ = "@"++Owner#jid.lserver,
538+ PeerStr = Peer#jid.luser++"@"++Peer#jid.lserver,
539+ PeerServ = "@"++Peer#jid.lserver,
540+
541+ LogTo = case ets:match_object(ets_settings_table(State#state.vhost),
542+ #user_settings{owner_name=Owner#jid.luser, _='_'}) of
543+ [#user_settings{dolog_default=Default,
544+ dolog_list=DLL,
545+ donotlog_list=DNLL}] ->
546+ A = lists:member(PeerStr, DLL),
547+ B = lists:member(PeerStr, DNLL),
548+ if
549+ A -> true;
550+ B -> false;
551+ Default == true -> true;
552+ Default == false -> false;
553+ true -> State#state.dolog_default
554+ end;
555+ _ -> State#state.dolog_default
556+ end,
557+
558+ lists:all(fun(O) -> O end,
559+ [not lists:member(OwnerStr, State#state.ignore_jids),
560+ not lists:member(PeerStr, State#state.ignore_jids),
561+ not lists:member(OwnerServ, State#state.ignore_jids),
562+ not lists:member(PeerServ, State#state.ignore_jids),
563+ LogTo]).
564+
565+purge_old_records(VHost, Days) ->
566+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
567+
568+ Dates = gen_server:call(Proc, {get_dates, {VHost}}),
569+ DateNow = calendar:datetime_to_gregorian_seconds({date(), {0,0,1}}),
570+ DateDiff = list_to_integer(Days)*24*60*60,
571+ ?MYDEBUG("Purging tables older than ~s days", [Days]),
572+ lists:foreach(fun(Date) ->
573+ {ok, [Year, Month, Day]} = regexp:split(Date, "[^0-9]+"),
574+ DateInSec = calendar:datetime_to_gregorian_seconds({{list_to_integer(Year), list_to_integer(Month), list_to_integer(Day)}, {0,0,1}}),
575+ if
576+ (DateNow - DateInSec) > DateDiff ->
577+ gen_server:call(Proc, {delete_messages_at, Date});
578+ true ->
579+ ?MYDEBUG("Skipping messages at ~p", [Date])
580+ end
581+ end, Dates).
582+
583+% called from get_vhost_stats/2, get_user_stats/3
584+sort_stats(Stats) ->
585+ % Stats = [{"2003-4-15",1}, {"2006-8-18",1}, ... ]
586+ CFun = fun({TableName, Count}) ->
587+ {ok, [Year, Month, Day]} = regexp:split(TableName, "[^0-9]+"),
588+ { calendar:datetime_to_gregorian_seconds({{list_to_integer(Year), list_to_integer(Month), list_to_integer(Day)}, {0,0,1}}), Count }
589+ end,
590+ % convert to [{63364377601,1}, {63360662401,1}, ... ]
591+ CStats = lists:map(CFun, Stats),
592+ % sort by date
593+ SortedStats = lists:reverse(lists:keysort(1, CStats)),
594+ % convert to [{"2007-12-9",1}, {"2007-10-27",1}, ... ] sorted list
595+ [{mod_logdb:convert_timestamp_brief(TableSec), Count} || {TableSec, Count} <- SortedStats].
596+
597+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
598+%
599+% Date/Time operations
600+%
601+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
602+% return float seconds elapsed from "zero hour" as list
603+get_timestamp() ->
604+ {MegaSec, Sec, MicroSec} = now(),
605+ [List] = io_lib:format("~.5f", [MegaSec*1000000 + Sec + MicroSec/1000000]),
606+ List.
607+
608+% convert float seconds elapsed from "zero hour" to local time "%Y-%m-%d %H:%M:%S" string
609+convert_timestamp(Seconds) when is_list(Seconds) ->
610+ case string:to_float(Seconds++".0") of
611+ {F,_} when is_float(F) -> convert_timestamp(F);
612+ _ -> erlang:error(badarg, [Seconds])
613+ end;
614+convert_timestamp(Seconds) when is_float(Seconds) ->
615+ GregSec = trunc(Seconds + 719528*86400),
616+ UnivDT = calendar:gregorian_seconds_to_datetime(GregSec),
617+ {{Year, Month, Day},{Hour, Minute, Sec}} = calendar:universal_time_to_local_time(UnivDT),
618+ integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day) ++ " " ++ integer_to_list(Hour) ++ ":" ++ integer_to_list(Minute) ++ ":" ++ integer_to_list(Sec).
619+
620+% convert float seconds elapsed from "zero hour" to local time "%Y-%m-%d" string
621+convert_timestamp_brief(Seconds) when is_list(Seconds) ->
622+ convert_timestamp_brief(list_to_float(Seconds));
623+convert_timestamp_brief(Seconds) when is_float(Seconds) ->
624+ GregSec = trunc(Seconds + 719528*86400),
625+ UnivDT = calendar:gregorian_seconds_to_datetime(GregSec),
626+ {{Year, Month, Day},{_Hour, _Minute, _Sec}} = calendar:universal_time_to_local_time(UnivDT),
627+ integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day);
628+convert_timestamp_brief(Seconds) when is_integer(Seconds) ->
629+ {{Year, Month, Day},{_Hour, _Minute, _Sec}} = calendar:gregorian_seconds_to_datetime(Seconds),
630+ integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day).
631+
632+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
633+%
634+% DB operations (get)
635+%
636+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
637+get_vhost_stats(VHost) ->
638+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
639+ gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
640+
641+get_vhost_stats_at(VHost, Date) ->
642+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
643+ gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
644+
645+get_user_stats(User, VHost) ->
646+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
647+ gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
648+
649+get_user_messages_at(User, VHost, Date) ->
650+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
651+ gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
652+
653+get_dates(VHost) ->
654+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
655+ gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
656+
657+get_user_settings(User, VHost) ->
658+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
659+ gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
660+
661+set_user_settings(User, VHost, Set) ->
662+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
663+ gen_server:call(Proc, {set_user_settings, User, Set}).
664+
665+get_module_settings(VHost) ->
666+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
667+ gen_server:call(Proc, {get_module_settings}).
668+
669+set_module_settings(VHost, Settings) ->
670+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
671+ gen_server:call(Proc, {set_module_settings, Settings}).
672+
673+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
674+%
675+% Web admin callbacks (delete)
676+%
677+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
678+user_messages_at_parse_query(VHost, Date, Msgs, Query) ->
679+ case lists:keysearch("delete", 1, Query) of
680+ {value, _} ->
681+ PMsgs = lists:filter(
682+ fun(Msg) ->
683+ ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg#msg.timestamp))),
684+ lists:member({"selected", ID}, Query)
685+ end, Msgs),
686+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
687+ gen_server:call(Proc, {delete_messages_by_user_at, PMsgs, Date}, ?CALL_TIMEOUT);
688+ false ->
689+ nothing
690+ end.
691+
692+user_messages_parse_query(User, VHost, Query) ->
693+ Dates = get_dates(VHost),
694+ case lists:keysearch("delete", 1, Query) of
695+ {value, _} ->
696+ PDates = lists:filter(
697+ fun(Date) ->
698+ ID = jlib:encode_base64(binary_to_list(term_to_binary(User++Date))),
699+ lists:member({"selected", ID}, Query)
700+ end, Dates),
701+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
702+ Rez = lists:foldl(
703+ fun(Date, Acc) ->
704+ lists:append(Acc,
705+ [gen_server:call(Proc,
706+ {delete_all_messages_by_user_at, User, Date},
707+ ?CALL_TIMEOUT)])
708+ end, [], PDates),
709+ case lists:member(error, Rez) of
710+ true ->
711+ error;
712+ false ->
713+ nothing
714+ end;
715+ false ->
716+ nothing
717+ end.
718+
719+vhost_messages_parse_query(VHost, Query) ->
720+ Dates = get_dates(VHost),
721+ case lists:keysearch("delete", 1, Query) of
722+ {value, _} ->
723+ PDates = lists:filter(
724+ fun(Date) ->
725+ ID = jlib:encode_base64(binary_to_list(term_to_binary(VHost++Date))),
726+ lists:member({"selected", ID}, Query)
727+ end, Dates),
728+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
729+ Rez = lists:foldl(fun(Date, Acc) ->
730+ lists:append(Acc, [gen_server:call(Proc,
731+ {delete_messages_at, Date},
732+ ?CALL_TIMEOUT)])
733+ end, [], PDates),
734+ case lists:member(error, Rez) of
735+ true ->
736+ error;
737+ false ->
738+ nothing
739+ end;
740+ false ->
741+ nothing
742+ end.
743+
744+vhost_messages_at_parse_query(VHost, Date, Stats, Query) ->
745+ case lists:keysearch("delete", 1, Query) of
746+ {value, _} ->
747+ PStats = lists:filter(
748+ fun({User, _Count}) ->
749+ ID = jlib:encode_base64(binary_to_list(term_to_binary(User++VHost))),
750+ lists:member({"selected", ID}, Query)
751+ end, Stats),
752+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
753+ Rez = lists:foldl(fun({User, _Count}, Acc) ->
754+ lists:append(Acc, [gen_server:call(Proc,
755+ {delete_all_messages_by_user_at,
756+ User, Date},
757+ ?CALL_TIMEOUT)])
758+ end, [], PStats),
759+ case lists:member(error, Rez) of
760+ true ->
761+ error;
762+ false ->
763+ ok
764+ end;
765+ false ->
766+ nothing
767+ end.
768+
769+copy_messages([#state{vhost=VHost}=State, From]) ->
770+ ?INFO_MSG("Going to copy messages from ~p for ~p", [From, VHost]),
771+
772+ {FromDBName, FromDBOpts} =
773+ case lists:keysearch(list_to_atom(From), 1, State#state.dbs) of
774+ {value, {FN, FO}} ->
775+ {FN, FO};
776+ false ->
777+ ?ERROR_MSG("Failed to find record for ~p in dbs", [From]),
778+ throw(error)
779+ end,
780+
781+ FromDBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(FromDBName)),
782+
783+ {ok, _FromPid} = FromDBMod:start(VHost, FromDBOpts),
784+
785+ Dates = FromDBMod:get_dates(VHost),
786+ DatesLength = length(Dates),
787+
788+ lists:foldl(fun(Date, Acc) ->
789+ case copy_messages_int([FromDBMod, State#state.dbmod, VHost, Date]) of
790+ ok ->
791+ ?INFO_MSG("Copied messages at ~p (~p/~p)", [Date, Acc, DatesLength]);
792+ Value ->
793+ ?ERROR_MSG("Failed to copy messages at ~p (~p/~p): ~p", [Date, Acc, DatesLength, Value]),
794+ FromDBMod:stop(VHost),
795+ throw(error)
796+ end,
797+ Acc + 1
798+ end, 1, Dates),
799+ ?INFO_MSG("Copied messages from ~p", [From]),
800+ FromDBMod:stop(VHost);
801+copy_messages([#state{vhost=VHost}=State, From, Date]) ->
802+ {value, {FromDBName, FromDBOpts}} = lists:keysearch(list_to_atom(From), 1, State#state.dbs),
803+ FromDBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(FromDBName)),
804+ {ok, _FromPid} = FromDBMod:start(VHost, FromDBOpts),
805+ case catch copy_messages_int([FromDBMod, State#state.dbmod, VHost, Date]) of
806+ {'exit', Reason} ->
807+ ?ERROR_MSG("Failed to copy messages at ~p: ~p", [Date, Reason]);
808+ ok ->
809+ ?INFO_MSG("Copied messages at ~p", [Date]);
810+ Value ->
811+ ?ERROR_MSG("Failed to copy messages at ~p: ~p", [Date, Value])
812+ end,
813+ FromDBMod:stop(VHost).
814+
815+copy_messages_int([FromDBMod, ToDBMod, VHost, Date]) ->
816+ ets:new(mod_logdb_temp, [named_table, set, public]),
817+ {Time, Value} = timer:tc(?MODULE, copy_messages_int_tc, [[FromDBMod, ToDBMod, VHost, Date]]),
818+ ets:delete_all_objects(mod_logdb_temp),
819+ ets:delete(mod_logdb_temp),
820+ ?INFO_MSG("copy_messages at ~p elapsed ~p sec", [Date, Time/1000000]),
821+ Value.
822+
823+copy_messages_int_tc([FromDBMod, ToDBMod, VHost, Date]) ->
824+ ?INFO_MSG("Going to copy messages from ~p for ~p at ~p", [FromDBMod, VHost, Date]),
825+
826+ ok = FromDBMod:rebuild_stats_at(VHost, Date),
827+ catch mod_logdb:rebuild_stats_at(VHost, Date),
828+ {ok, FromStats} = FromDBMod:get_vhost_stats_at(VHost, Date),
829+ ToStats = case mod_logdb:get_vhost_stats_at(VHost, Date) of
830+ {ok, Stats} -> Stats;
831+ {error, _} -> []
832+ end,
833+
834+ FromStatsS = lists:keysort(1, FromStats),
835+ ToStatsS = lists:keysort(1, ToStats),
836+
837+ StatsLength = length(FromStats),
838+
839+ CopyFun = if
840+ % destination table is empty
841+ FromDBMod /= mod_logdb_mnesia_old, ToStats == [] ->
842+ fun({User, _Count}, Acc) ->
843+ {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date),
844+ MAcc =
845+ lists:foldl(fun(Msg, MFAcc) ->
846+ ok = ToDBMod:log_message(VHost, Msg),
847+ MFAcc + 1
848+ end, 0, Msgs),
849+ NewAcc = Acc + 1,
850+ ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
851+ %timer:sleep(100),
852+ NewAcc
853+ end;
854+ % destination table is not empty
855+ FromDBMod /= mod_logdb_mnesia_old, ToStats /= [] ->
856+ fun({User, _Count}, Acc) ->
857+ {ok, ToMsgs} = ToDBMod:get_user_messages_at(User, VHost, Date),
858+ lists:foreach(fun(#msg{timestamp=Tst}) when length(Tst) == 16 ->
859+ ets:insert(mod_logdb_temp, {Tst});
860+ % mysql, pgsql removes final zeros after decimal point
861+ (#msg{timestamp=Tst}) when length(Tst) < 16 ->
862+ {F, _} = string:to_float(Tst++".0"),
863+ [T] = io_lib:format("~.5f", [F]),
864+ ets:insert(mod_logdb_temp, {T})
865+ end, ToMsgs),
866+ {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date),
867+ MAcc =
868+ lists:foldl(fun(#msg{timestamp=ToTimestamp} = Msg, MFAcc) ->
869+ case ets:member(mod_logdb_temp, ToTimestamp) of
870+ false ->
871+ ok = ToDBMod:log_message(VHost, Msg),
872+ ets:insert(mod_logdb_temp, {ToTimestamp}),
873+ MFAcc + 1;
874+ true ->
875+ MFAcc
876+ end
877+ end, 0, Msgs),
878+ NewAcc = Acc + 1,
879+ ets:delete_all_objects(mod_logdb_temp),
880+ ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
881+ %timer:sleep(100),
882+ NewAcc
883+ end;
884+ % copying from mod_logmnesia
885+ true ->
886+ fun({User, _Count}, Acc) ->
887+ ToStats =
888+ case ToDBMod:get_user_messages_at(User, VHost, Date) of
889+ {ok, []} ->
890+ ok;
891+ {ok, ToMsgs} ->
892+ lists:foreach(fun(#msg{timestamp=Tst}) when length(Tst) == 16 ->
893+ ets:insert(mod_logdb_temp, {Tst});
894+ % mysql, pgsql removes final zeros after decimal point
895+ (#msg{timestamp=Tst}) when length(Tst) < 15 ->
896+ {F, _} = string:to_float(Tst++".0"),
897+ [T] = io_lib:format("~.5f", [F]),
898+ ets:insert(mod_logdb_temp, {T})
899+ end, ToMsgs);
900+ {error, _} ->
901+ ok
902+ end,
903+ {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date),
904+
905+ MAcc =
906+ lists:foldl(
907+ fun({msg, TU, TS, TR, FU, FS, FR, Type, Subj, Body, Timest},
908+ MFAcc) ->
909+ [Timestamp] = if is_float(Timest) == true ->
910+ io_lib:format("~.5f", [Timest]);
911+ % early versions of mod_logmnesia
912+ is_integer(Timest) == true ->
913+ io_lib:format("~.5f", [Timest-719528*86400.0]);
914+ true ->
915+ ?ERROR_MSG("Incorrect timestamp ~p", [Timest]),
916+ throw(error)
917+ end,
918+ case ets:member(mod_logdb_temp, Timestamp) of
919+ false ->
920+ if
921+ % from
922+ TS == VHost ->
923+ TMsg = #msg{timestamp=Timestamp,
924+ owner_name=TU,
925+ peer_name=FU, peer_server=FS, peer_resource=FR,
926+ direction=from,
927+ type=Type,
928+ subject=Subj, body=Body},
929+ ok = ToDBMod:log_message(VHost, TMsg);
930+ true -> ok
931+ end,
932+ if
933+ % to
934+ FS == VHost ->
935+ FMsg = #msg{timestamp=Timestamp,
936+ owner_name=FU,
937+ peer_name=TU, peer_server=TS, peer_resource=TR,
938+ direction=to,
939+ type=Type,
940+ subject=Subj, body=Body},
941+ ok = ToDBMod:log_message(VHost, FMsg);
942+ true -> ok
943+ end,
944+ ets:insert(mod_logdb_temp, {Timestamp}),
945+ MFAcc + 1;
946+ true -> % not ets:member
947+ MFAcc
948+ end % case
949+ end, 0, Msgs), % foldl
950+ NewAcc = Acc + 1,
951+ ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
952+ %timer:sleep(100),
953+ NewAcc
954+ end % fun
955+ end, % if FromDBMod /= mod_logdb_mnesia_old
956+
957+ if
958+ FromStats == [] ->
959+ ?INFO_MSG("No messages were found at ~p", [Date]);
960+ FromStatsS == ToStatsS ->
961+ ?INFO_MSG("Stats are equal at ~p", [Date]);
962+ FromStatsS /= ToStatsS ->
963+ lists:foldl(CopyFun, 0, FromStats),
964+ ok = ToDBMod:rebuild_stats_at(VHost, Date)
965+ %timer:sleep(1000)
966+ end,
967+
968+ ok.
969+
970+list_to_bool(Num) ->
971+ case lists:member(Num, ["t", "true", "y", "yes", "1"]) of
972+ true ->
973+ true;
974+ false ->
975+ case lists:member(Num, ["f", "false", "n", "no", "0"]) of
976+ true ->
977+ false;
978+ false ->
979+ error
980+ end
981+ end.
982+
983+bool_to_list(true) ->
984+ "TRUE";
985+bool_to_list(false) ->
986+ "FALSE".
987+
988+list_to_string([]) ->
989+ "";
990+list_to_string(List) when is_list(List) ->
991+ Str = lists:flatmap(fun(Elm) -> Elm ++ "\n" end, List),
992+ lists:sublist(Str, length(Str)-1).
993+
994+string_to_list(null) ->
995+ [];
996+string_to_list([]) ->
997+ [];
998+string_to_list(String) ->
999+ {ok, List} = regexp:split(String, "\n"),
1000+ List.
1001+
1002+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1003+%
1004+% ad-hoc (copy/pasted from mod_configure.erl)
1005+%
1006+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1007+-define(ITEMS_RESULT(Allow, LNode, Fallback),
1008+ case Allow of
1009+ deny ->
1010+ Fallback;
1011+ allow ->
1012+ case get_local_items(LServer, LNode,
1013+ jlib:jid_to_string(To), Lang) of
1014+ {result, Res} ->
1015+ {result, Res};
1016+ {error, Error} ->
1017+ {error, Error}
1018+ end
1019+ end).
1020+
1021+get_local_items(Acc, From, #jid{lserver = LServer} = To, "", Lang) ->
1022+ case gen_mod:is_loaded(LServer, mod_adhoc) of
1023+ false ->
1024+ Acc;
1025+ _ ->
1026+ Items = case Acc of
1027+ {result, Its} -> Its;
1028+ empty -> []
1029+ end,
1030+ AllowUser = acl:match_rule(LServer, mod_logdb, From),
1031+ AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1032+ if
1033+ AllowUser == allow; AllowAdmin == allow ->
1034+ case get_local_items(LServer, [],
1035+ jlib:jid_to_string(To), Lang) of
1036+ {result, Res} ->
1037+ {result, Items ++ Res};
1038+ {error, _Error} ->
1039+ {result, Items}
1040+ end;
1041+ true ->
1042+ {result, Items}
1043+ end
1044+ end;
1045+get_local_items(Acc, From, #jid{lserver = LServer} = To, Node, Lang) ->
1046+ case gen_mod:is_loaded(LServer, mod_adhoc) of
1047+ false ->
1048+ Acc;
1049+ _ ->
1050+ LNode = string:tokens(Node, "/"),
1051+ AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1052+ case LNode of
1053+ ["mod_logdb"] ->
1054+ ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1055+ ["mod_logdb_users"] ->
1056+ ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1057+ ["mod_logdb_users", [$@ | _]] ->
1058+ ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1059+ ["mod_logdb_users", _User] ->
1060+ ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1061+ ["mod_logdb_settings"] ->
1062+ ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1063+ _ ->
1064+ Acc
1065+ end
1066+ end.
1067+
1068+-define(NODE(Name, Node),
1069+ {xmlelement, "item",
1070+ [{"jid", Server},
1071+ {"name", translate:translate(Lang, Name)},
1072+ {"node", Node}], []}).
1073+
1074+get_local_items(_Host, [], Server, Lang) ->
1075+ {result,
1076+ [?NODE("Messages logging engine", "mod_logdb")]
1077+ };
1078+get_local_items(_Host, ["mod_logdb"], Server, Lang) ->
1079+ {result,
1080+ [?NODE("Messages logging engine users", "mod_logdb_users"),
1081+ ?NODE("Messages logging engine settings", "mod_logdb_settings")]
1082+ };
1083+get_local_items(Host, ["mod_logdb_users"], Server, Lang) ->
1084+ {result, get_all_vh_users(Host, Server, Lang)};
1085+get_local_items(_Host, ["mod_logdb_users", [$@ | Diap]], Server, Lang) ->
1086+ case catch ejabberd_auth:dirty_get_registered_users() of
1087+ {'EXIT', _Reason} ->
1088+ ?ERR_INTERNAL_SERVER_ERROR;
1089+ Users ->
1090+ SUsers = lists:sort([{S, U} || {U, S} <- Users]),
1091+ case catch begin
1092+ {ok, [S1, S2]} = regexp:split(Diap, "-"),
1093+ N1 = list_to_integer(S1),
1094+ N2 = list_to_integer(S2),
1095+ Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
1096+ lists:map(fun({S, U}) ->
1097+ ?NODE(U ++ "@" ++ S, "mod_logdb_users/" ++ U ++ "@" ++ S)
1098+ end, Sub)
1099+ end of
1100+ {'EXIT', _Reason} ->
1101+ ?ERR_NOT_ACCEPTABLE;
1102+ Res ->
1103+ {result, Res}
1104+ end
1105+ end;
1106+get_local_items(_Host, ["mod_logdb_users", _User], _Server, _Lang) ->
1107+ {result, []};
1108+get_local_items(_Host, ["mod_logdb_settings"], _Server, _Lang) ->
1109+ {result, []};
1110+get_local_items(_Host, Item, _Server, _Lang) ->
1111+ ?MYDEBUG("asked for items in ~p", [Item]),
1112+ {error, ?ERR_ITEM_NOT_FOUND}.
1113+
1114+-define(INFO_RESULT(Allow, Feats),
1115+ case Allow of
1116+ deny ->
1117+ {error, ?ERR_FORBIDDEN};
1118+ allow ->
1119+ {result, Feats}
1120+ end).
1121+
1122+get_local_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
1123+ case gen_mod:is_loaded(LServer, mod_adhoc) of
1124+ false ->
1125+ Acc;
1126+ _ ->
1127+ LNode = string:tokens(Node, "/"),
1128+ AllowUser = acl:match_rule(LServer, mod_logdb, From),
1129+ AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1130+ case LNode of
1131+ ["mod_logdb"] when AllowUser == allow; AllowAdmin == allow ->
1132+ ?INFO_RESULT(allow, [?NS_COMMANDS]);
1133+ ["mod_logdb"] ->
1134+ ?INFO_RESULT(deny, [?NS_COMMANDS]);
1135+ ["mod_logdb_users"] ->
1136+ ?INFO_RESULT(AllowAdmin, []);
1137+ ["mod_logdb_users", [$@ | _]] ->
1138+ ?INFO_RESULT(AllowAdmin, []);
1139+ ["mod_logdb_users", _User] ->
1140+ ?INFO_RESULT(AllowAdmin, [?NS_COMMANDS]);
1141+ ["mod_logdb_settings"] ->
1142+ ?INFO_RESULT(AllowAdmin, [?NS_COMMANDS]);
1143+ [] ->
1144+ Acc;
1145+ _ ->
1146+ %?MYDEBUG("asked for ~p features: ~p", [LNode, Allow]),
1147+ Acc
1148+ end
1149+ end.
1150+
1151+-define(INFO_IDENTITY(Category, Type, Name, Lang),
1152+ [{xmlelement, "identity",
1153+ [{"category", Category},
1154+ {"type", Type},
1155+ {"name", translate:translate(Lang, Name)}], []}]).
1156+
1157+-define(INFO_COMMAND(Name, Lang),
1158+ ?INFO_IDENTITY("automation", "command-node", Name, Lang)).
1159+
1160+get_local_identity(Acc, _From, _To, Node, Lang) ->
1161+ LNode = string:tokens(Node, "/"),
1162+ case LNode of
1163+ ["mod_logdb"] ->
1164+ ?INFO_COMMAND("Messages logging engine", Lang);
1165+ ["mod_logdb_users"] ->
1166+ ?INFO_COMMAND("Messages logging engine users", Lang);
1167+ ["mod_logdb_users", [$@ | _]] ->
1168+ Acc;
1169+ ["mod_logdb_users", User] ->
1170+ ?INFO_COMMAND(User, Lang);
1171+ ["mod_logdb_settings"] ->
1172+ ?INFO_COMMAND("Messages logging engine settings", Lang);
1173+ [] ->
1174+ Acc;
1175+ _ ->
1176+ Acc
1177+ end.
1178+
1179+%get_sm_items(Acc, From, To, Node, Lang) ->
1180+% ?MYDEBUG("get_sm_items Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
1181+% Acc.
1182+
1183+%get_sm_features(Acc, From, To, Node, Lang) ->
1184+% ?MYDEBUG("get_sm_features Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
1185+% Acc.
1186+
1187+%get_sm_identity(Acc, From, To, Node, Lang) ->
1188+% ?MYDEBUG("get_sm_identity Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
1189+% Acc.
1190+
1191+adhoc_local_items(Acc, From, #jid{lserver = LServer, server = Server} = To,
1192+ Lang) ->
1193+ Items = case Acc of
1194+ {result, Its} -> Its;
1195+ empty -> []
1196+ end,
1197+ Nodes = recursively_get_local_items(LServer, "", Server, Lang),
1198+ Nodes1 = lists:filter(
1199+ fun(N) ->
1200+ Nd = xml:get_tag_attr_s("node", N),
1201+ F = get_local_features([], From, To, Nd, Lang),
1202+ case F of
1203+ {result, [?NS_COMMANDS]} ->
1204+ true;
1205+ _ ->
1206+ false
1207+ end
1208+ end, Nodes),
1209+ {result, Items ++ Nodes1}.
1210+
1211+recursively_get_local_items(_LServer, "mod_logdb_users", _Server, _Lang) ->
1212+ [];
1213+recursively_get_local_items(LServer, Node, Server, Lang) ->
1214+ LNode = string:tokens(Node, "/"),
1215+ Items = case get_local_items(LServer, LNode, Server, Lang) of
1216+ {result, Res} ->
1217+ Res;
1218+ {error, _Error} ->
1219+ []
1220+ end,
1221+ Nodes = lists:flatten(
1222+ lists:map(
1223+ fun(N) ->
1224+ S = xml:get_tag_attr_s("jid", N),
1225+ Nd = xml:get_tag_attr_s("node", N),
1226+ if (S /= Server) or (Nd == "") ->
1227+ [];
1228+ true ->
1229+ [N, recursively_get_local_items(
1230+ LServer, Nd, Server, Lang)]
1231+ end
1232+ end, Items)),
1233+ Nodes.
1234+
1235+-define(COMMANDS_RESULT(Allow, From, To, Request),
1236+ case Allow of
1237+ deny ->
1238+ {error, ?ERR_FORBIDDEN};
1239+ allow ->
1240+ adhoc_local_commands(From, To, Request)
1241+ end).
1242+
1243+adhoc_local_commands(Acc, From, #jid{lserver = LServer} = To,
1244+ #adhoc_request{node = Node} = Request) ->
1245+ LNode = string:tokens(Node, "/"),
1246+ AllowUser = acl:match_rule(LServer, mod_logdb, From),
1247+ AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1248+ case LNode of
1249+ ["mod_logdb"] when AllowUser == allow; AllowAdmin == allow ->
1250+ ?COMMANDS_RESULT(allow, From, To, Request);
1251+ ["mod_logdb_users", _User] when AllowAdmin == allow ->
1252+ ?COMMANDS_RESULT(allow, From, To, Request);
1253+ ["mod_logdb_settings"] when AllowAdmin == allow ->
1254+ ?COMMANDS_RESULT(allow, From, To, Request);
1255+ _ ->
1256+ Acc
1257+ end.
1258+
1259+adhoc_local_commands(From, #jid{lserver = LServer} = _To,
1260+ #adhoc_request{lang = Lang,
1261+ node = Node,
1262+ sessionid = SessionID,
1263+ action = Action,
1264+ xdata = XData} = Request) ->
1265+ LNode = string:tokens(Node, "/"),
1266+ %% If the "action" attribute is not present, it is
1267+ %% understood as "execute". If there was no <actions/>
1268+ %% element in the first response (which there isn't in our
1269+ %% case), "execute" and "complete" are equivalent.
1270+ ActionIsExecute = lists:member(Action,
1271+ ["", "execute", "complete"]),
1272+ if Action == "cancel" ->
1273+ %% User cancels request
1274+ adhoc:produce_response(
1275+ Request,
1276+ #adhoc_response{status = canceled});
1277+ XData == false, ActionIsExecute ->
1278+ %% User requests form
1279+ case get_form(LServer, LNode, From, Lang) of
1280+ {result, Form} ->
1281+ adhoc:produce_response(
1282+ Request,
1283+ #adhoc_response{status = executing,
1284+ elements = Form});
1285+ {error, Error} ->
1286+ {error, Error}
1287+ end;
1288+ XData /= false, ActionIsExecute ->
1289+ %% User returns form.
1290+ case jlib:parse_xdata_submit(XData) of
1291+ invalid ->
1292+ {error, ?ERR_BAD_REQUEST};
1293+ Fields ->
1294+ case set_form(From, LServer, LNode, Lang, Fields) of
1295+ {result, _Res} ->
1296+ adhoc:produce_response(
1297+ #adhoc_response{lang = Lang,
1298+ node = Node,
1299+ sessionid = SessionID,
1300+ status = completed});
1301+ {error, Error} ->
1302+ {error, Error}
1303+ end
1304+ end;
1305+ true ->
1306+ {error, ?ERR_BAD_REQUEST}
1307+ end.
1308+
1309+-define(LISTLINE(Label, Value),
1310+ {xmlelement, "option", [{"label", Label}],
1311+ [{xmlelement, "value", [], [{xmlcdata, Value}]}]}).
1312+-define(DEFVAL(Value), {xmlelement, "value", [], [{xmlcdata, Value}]}).
1313+
1314+get_user_form(LUser, LServer, Lang) ->
1315+ %From = jlib:jid_to_string(jlib:jid_remove_resource(Jid)),
1316+ #user_settings{dolog_default=DLD,
1317+ dolog_list=DLL,
1318+ donotlog_list=DNLL} = get_user_settings(LUser, LServer),
1319+ {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
1320+ [{xmlelement, "title", [],
1321+ [{xmlcdata,
1322+ translate:translate(
1323+ Lang, "Messages logging engine settings")}]},
1324+ {xmlelement, "instructions", [],
1325+ [{xmlcdata,
1326+ translate:translate(
1327+ Lang, "Set logging preferences")++ ": " ++ LUser ++ "@" ++ LServer}]},
1328+ % default to log
1329+ {xmlelement, "field", [{"type", "list-single"},
1330+ {"label",
1331+ translate:translate(Lang, "Default")},
1332+ {"var", "dolog_default"}],
1333+ [?DEFVAL(atom_to_list(DLD)),
1334+ ?LISTLINE(translate:translate(Lang, "Log Messages"), "true"),
1335+ ?LISTLINE(translate:translate(Lang, "Do Not Log Messages"), "false")
1336+ ]},
1337+ % do log list
1338+ {xmlelement, "field", [{"type", "text-multi"},
1339+ {"label",
1340+ translate:translate(
1341+ Lang, "Log Messages")},
1342+ {"var", "dolog_list"}],
1343+ [{xmlelement, "value", [], [{xmlcdata, list_to_string(DLL)}]}]},
1344+ % do not log list
1345+ {xmlelement, "field", [{"type", "text-multi"},
1346+ {"label",
1347+ translate:translate(
1348+ Lang, "Do Not Log Messages")},
1349+ {"var", "donotlog_list"}],
1350+ [{xmlelement, "value", [], [{xmlcdata, list_to_string(DNLL)}]}]}
1351+ ]}]}.
1352+
1353+get_settings_form(Host, Lang) ->
1354+ #state{dbmod=DBMod,
1355+ dbs=DBs,
1356+ dolog_default=DLD,
1357+ ignore_jids=IgnoreJids,
1358+ groupchat=GroupChat,
1359+ purge_older_days=PurgeDaysT,
1360+ poll_users_settings=PollTime} = mod_logdb:get_module_settings(Host),
1361+
1362+ Backends = lists:map(fun({Backend, _Opts}) ->
1363+ ?LISTLINE(atom_to_list(Backend), atom_to_list(Backend))
1364+ end, DBs),
1365+ DB = lists:sublist(atom_to_list(DBMod), length(atom_to_list(?MODULE)) + 2, length(atom_to_list(DBMod))),
1366+ DBsL = lists:append([?DEFVAL(DB)], Backends),
1367+
1368+ PurgeDays =
1369+ case PurgeDaysT of
1370+ never -> "never";
1371+ Num when is_integer(Num) -> integer_to_list(Num);
1372+ _ -> "unknown"
1373+ end,
1374+ {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
1375+ [{xmlelement, "title", [],
1376+ [{xmlcdata,
1377+ translate:translate(
1378+ Lang, "Messages logging engine settings") ++ " (run-time)"}]},
1379+ {xmlelement, "instructions", [],
1380+ [{xmlcdata,
1381+ translate:translate(
1382+ Lang, "Set run-time settings")}]},
1383+ % backends
1384+ {xmlelement, "field", [{"type", "list-single"},
1385+ {"label",
1386+ translate:translate(Lang, "Backend")},
1387+ {"var", "backend"}],
1388+ DBsL},
1389+ % dbs
1390+ {xmlelement, "field", [{"type", "text-multi"},
1391+ {"label",
1392+ translate:translate(
1393+ Lang, "dbs")},
1394+ {"var", "dbs"}],
1395+ [{xmlelement, "value", [], [{xmlcdata, lists:flatten(io_lib:format("~p.",[DBs]))}]}]},
1396+ % default to log
1397+ {xmlelement, "field", [{"type", "list-single"},
1398+ {"label",
1399+ translate:translate(Lang, "Default")},
1400+ {"var", "dolog_default"}],
1401+ [?DEFVAL(atom_to_list(DLD)),
1402+ ?LISTLINE(translate:translate(Lang, "Log Messages"), "true"),
1403+ ?LISTLINE(translate:translate(Lang, "Do Not Log Messages"), "false")
1404+ ]},
1405+ % groupchat
1406+ {xmlelement, "field", [{"type", "list-single"},
1407+ {"label",
1408+ translate:translate(Lang, "Groupchat messages logging")},
1409+ {"var", "groupchat"}],
1410+ [?DEFVAL(atom_to_list(GroupChat)),
1411+ ?LISTLINE("all", "all"),
1412+ ?LISTLINE("none", "none"),
1413+ ?LISTLINE("send", "send"),
1414+ ?LISTLINE("half", "half")
1415+ ]},
1416+ % ignore_jids
1417+ {xmlelement, "field", [{"type", "text-multi"},
1418+ {"label",
1419+ translate:translate(
1420+ Lang, "Jids/Domains to ignore")},
1421+ {"var", "ignore_list"}],
1422+ [{xmlelement, "value", [], [{xmlcdata, list_to_string(IgnoreJids)}]}]},
1423+ % purge older days
1424+ {xmlelement, "field", [{"type", "text-single"},
1425+ {"label",
1426+ translate:translate(
1427+ Lang, "Purge messages older than (days)")},
1428+ {"var", "purge_older_days"}],
1429+ [{xmlelement, "value", [], [{xmlcdata, PurgeDays}]}]},
1430+ % poll users settings
1431+ {xmlelement, "field", [{"type", "text-single"},
1432+ {"label",
1433+ translate:translate(
1434+ Lang, "Poll users settings (seconds)")},
1435+ {"var", "poll_users_settings"}],
1436+ [{xmlelement, "value", [], [{xmlcdata, integer_to_list(PollTime)}]}]}
1437+ ]}]}.
1438+
1439+get_form(_Host, ["mod_logdb"], #jid{luser = LUser, lserver = LServer} = _Jid, Lang) ->
1440+ get_user_form(LUser, LServer, Lang);
1441+get_form(_Host, ["mod_logdb_users", User], _JidFrom, Lang) ->
1442+ #jid{luser=LUser, lserver=LServer} = jlib:string_to_jid(User),
1443+ get_user_form(LUser, LServer, Lang);
1444+get_form(Host, ["mod_logdb_settings"], _JidFrom, Lang) ->
1445+ get_settings_form(Host, Lang);
1446+get_form(_Host, Command, _, _Lang) ->
1447+ ?MYDEBUG("asked for form ~p", [Command]),
1448+ {error, ?ERR_SERVICE_UNAVAILABLE}.
1449+
1450+check_log_list([Head | Tail]) ->
1451+ case lists:member($@, Head) of
1452+ true -> ok;
1453+ false -> throw(error)
1454+ end,
1455+ % this check for Head to be valid jid
1456+ case jlib:string_to_jid(Head) of
1457+ error ->
1458+ throw(error);
1459+ _ ->
1460+ check_log_list(Tail)
1461+ end;
1462+check_log_list([]) ->
1463+ ok.
1464+
1465+check_ignore_list([Head | Tail]) ->
1466+ case lists:member($@, Head) of
1467+ true -> ok;
1468+ false -> throw(error)
1469+ end,
1470+ % this check for Head to be valid jid
1471+ case jlib:string_to_jid(Head) of
1472+ error ->
1473+ % this check for Head to be valid domain "@domain.org"
1474+ case lists:nth(1, Head) of
1475+ $@ ->
1476+ % TODO: this allows spaces and special characters in Head. May be change to nodeprep?
1477+ case jlib:nameprep(lists:delete($@, Head)) of
1478+ error -> throw(error);
1479+ _ -> check_log_list(Tail)
1480+ end;
1481+ _ -> throw(error)
1482+ end;
1483+ _ ->
1484+ check_ignore_list(Tail)
1485+ end;
1486+check_ignore_list([]) ->
1487+ ok.
1488+
1489+parse_users_settings(XData) ->
1490+ DLD = case lists:keysearch("dolog_default", 1, XData) of
1491+ {value, {_, [String]}} when String == "true"; String == "false" ->
1492+ list_to_bool(String);
1493+ _ ->
1494+ throw(bad_request)
1495+ end,
1496+ DLL = case lists:keysearch("dolog_list", 1, XData) of
1497+ false ->
1498+ throw(bad_request);
1499+ {value, {_, [[]]}} ->
1500+ [];
1501+ {value, {_, List1}} ->
1502+ case catch check_log_list(List1) of
1503+ error ->
1504+ throw(bad_request);
1505+ ok ->
1506+ List1
1507+ end
1508+ end,
1509+ DNLL = case lists:keysearch("donotlog_list", 1, XData) of
1510+ false ->
1511+ throw(bad_request);
1512+ {value, {_, [[]]}} ->
1513+ [];
1514+ {value, {_, List2}} ->
1515+ case catch check_log_list(List2) of
1516+ error ->
1517+ throw(bad_request);
1518+ ok ->
1519+ List2
1520+ end
1521+ end,
1522+ #user_settings{dolog_default=DLD,
1523+ dolog_list=DLL,
1524+ donotlog_list=DNLL}.
1525+
1526+parse_module_settings(XData) ->
1527+ DLD = case lists:keysearch("dolog_default", 1, XData) of
1528+ {value, {_, [Str1]}} when Str1 == "true"; Str1 == "false" ->
1529+ list_to_bool(Str1);
1530+ _ ->
1531+ throw(bad_request)
1532+ end,
1533+ GroupChat = case lists:keysearch("groupchat", 1, XData) of
1534+ {value, {_, [Str2]}} when Str2 == "none";
1535+ Str2 == "all";
1536+ Str2 == "send";
1537+ Str2 == "half" ->
1538+ list_to_atom(Str2);
1539+ _ ->
1540+ throw(bad_request)
1541+ end,
1542+ Ignore = case lists:keysearch("ignore_list", 1, XData) of
1543+ {value, {_, List}} ->
1544+ case catch check_ignore_list(List) of
1545+ ok ->
1546+ List;
1547+ error ->
1548+ throw(bad_request)
1549+ end;
1550+ _ ->
1551+ throw(bad_request)
1552+ end,
1553+ Purge = case lists:keysearch("purge_older_days", 1, XData) of
1554+ {value, {_, ["never"]}} ->
1555+ never;
1556+ {value, {_, [Str3]}} ->
1557+ case catch list_to_integer(Str3) of
1558+ {'EXIT', {badarg, _}} -> throw(bad_request);
1559+ Int1 -> Int1
1560+ end;
1561+ _ ->
1562+ throw(bad_request)
1563+ end,
1564+ Poll = case lists:keysearch("poll_users_settings", 1, XData) of
1565+ {value, {_, [Str4]}} ->
1566+ case catch list_to_integer(Str4) of
1567+ {'EXIT', {badarg, _}} -> throw(bad_request);
1568+ Int2 -> Int2
1569+ end;
1570+ _ ->
1571+ throw(bad_request)
1572+ end,
1573+ #state{dolog_default=DLD,
1574+ groupchat=GroupChat,
1575+ ignore_jids=Ignore,
1576+ purge_older_days=Purge,
1577+ poll_users_settings=Poll}.
1578+
1579+set_form(From, _Host, ["mod_logdb"], _Lang, XData) ->
1580+ #jid{luser=LUser, lserver=LServer} = From,
1581+ case catch parse_users_settings(XData) of
1582+ bad_request ->
1583+ {error, ?ERR_BAD_REQUEST};
1584+ UserSettings ->
1585+ case mod_logdb:set_user_settings(LUser, LServer, UserSettings) of
1586+ ok ->
1587+ {result, []};
1588+ error ->
1589+ {error, ?ERR_INTERNAL_SERVER_ERROR}
1590+ end
1591+ end;
1592+set_form(_From, _Host, ["mod_logdb_users", User], _Lang, XData) ->
1593+ #jid{luser=LUser, lserver=LServer} = jlib:string_to_jid(User),
1594+ case catch parse_users_settings(XData) of
1595+ bad_request -> {error, ?ERR_BAD_REQUEST};
1596+ UserSettings ->
1597+ case mod_logdb:set_user_settings(LUser, LServer, UserSettings) of
1598+ ok ->
1599+ {result, []};
1600+ error ->
1601+ {error, ?ERR_INTERNAL_SERVER_ERROR}
1602+ end
1603+ end;
1604+set_form(_From, Host, ["mod_logdb_settings"], _Lang, XData) ->
1605+ case catch parse_module_settings(XData) of
1606+ bad_request -> {error, ?ERR_BAD_REQUEST};
1607+ Settings ->
1608+ case mod_logdb:set_module_settings(Host, Settings) of
1609+ ok ->
1610+ {result, []};
1611+ error ->
1612+ {error, ?ERR_INTERNAL_SERVER_ERROR}
1613+ end
1614+ end;
1615+set_form(From, _Host, Node, _Lang, XData) ->
1616+ User = jlib:jid_to_string(jlib:jid_remove_resource(From)),
1617+ ?MYDEBUG("set form for ~p at ~p XData=~p", [User, Node, XData]),
1618+ {error, ?ERR_SERVICE_UNAVAILABLE}.
1619+
1620+%adhoc_sm_items(Acc, From, To, Request) ->
1621+% ?MYDEBUG("adhoc_sm_items Acc=~p From=~p To=~p Request=~p", [Acc, From, To, Request]),
1622+% Acc.
1623+
1624+%adhoc_sm_commands(Acc, From, To, Request) ->
1625+% ?MYDEBUG("adhoc_sm_commands Acc=~p From=~p To=~p Request=~p", [Acc, From, To, Request]),
1626+% Acc.
1627+
1628+get_all_vh_users(Host, Server, Lang) ->
1629+ case catch ejabberd_auth:get_vh_registered_users(Host) of
1630+ {'EXIT', _Reason} ->
1631+ [];
1632+ Users ->
1633+ SUsers = lists:sort([{S, U} || {U, S} <- Users]),
1634+ case length(SUsers) of
1635+ N when N =< 100 ->
1636+ lists:map(fun({S, U}) ->
1637+ ?NODE(U ++ "@" ++ S, "mod_logdb_users/" ++ U ++ "@" ++ S)
1638+ end, SUsers);
1639+ N ->
1640+ NParts = trunc(math:sqrt(N * 0.618)) + 1,
1641+ M = trunc(N / NParts) + 1,
1642+ lists:map(fun(K) ->
1643+ L = K + M - 1,
1644+ Node =
1645+ "@" ++ integer_to_list(K) ++
1646+ "-" ++ integer_to_list(L),
1647+ {FS, FU} = lists:nth(K, SUsers),
1648+ {LS, LU} =
1649+ if L < N -> lists:nth(L, SUsers);
1650+ true -> lists:last(SUsers)
1651+ end,
1652+ Name =
1653+ FU ++ "@" ++ FS ++
1654+ " -- " ++
1655+ LU ++ "@" ++ LS,
1656+ ?NODE(Name, "mod_logdb_users/" ++ Node)
1657+ end, lists:seq(1, N, M))
1658+ end
1659+ end.
1660--- src/mod_logdb.hrl.orig Tue Dec 11 14:23:19 2007
1661+++ src/mod_logdb.hrl Tue Aug 7 16:50:32 2007
1662@@ -0,0 +1,29 @@
1663+%%%----------------------------------------------------------------------
1664+%%% File : mod_logdb.hrl
1665+%%% Author : Oleg Palij (mailto:o.palij@gmail.com xmpp://malik@jabber.te.ua)
1666+%%% Purpose :
1667+%%% Version : trunk
1668+%%% Id : $Id$
1669+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
1670+%%%----------------------------------------------------------------------
1671+
1672+-define(logdb_debug, true).
1673+
1674+-ifdef(logdb_debug).
1675+-define(MYDEBUG(Format, Args), io:format("D(~p:~p:~p) : "++Format++"~n",
1676+ [calendar:local_time(),?MODULE,?LINE]++Args)).
1677+-else.
1678+-define(MYDEBUG(_F,_A),[]).
1679+-endif.
1680+
1681+-record(msg, {timestamp,
1682+ owner_name,
1683+ peer_name, peer_server, peer_resource,
1684+ direction,
1685+ type, subject,
1686+ body}).
1687+
1688+-record(user_settings, {owner_name,
1689+ dolog_default,
1690+ dolog_list=[],
1691+ donotlog_list=[]}).
1692--- src/mod_logdb_mnesia.erl.orig Tue Dec 11 14:23:19 2007
1693+++ src/mod_logdb_mnesia.erl Wed Aug 22 22:58:11 2007
1694@@ -0,0 +1,513 @@
1695+%%%----------------------------------------------------------------------
1696+%%% File : mod_logdb_mnesia.erl
1697+%%% Author : Oleg Palij (mailto:o.palij@gmail.com xmpp://malik@jabber.te.ua)
1698+%%% Purpose : mnesia backend for mod_logdb
1699+%%% Version : trunk
1700+%%% Id : $Id$
1701+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
1702+%%%----------------------------------------------------------------------
1703+
1704+-module(mod_logdb_mnesia).
1705+-author('o.palij@gmail.com').
1706+-vsn('$Revision$').
1707+
1708+-include("mod_logdb.hrl").
1709+-include("ejabberd.hrl").
1710+-include("jlib.hrl").
1711+
1712+-behaviour(gen_logdb).
1713+-behaviour(gen_server).
1714+
1715+% gen_server
1716+-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
1717+% gen_mod
1718+-export([start/2, stop/1]).
1719+% gen_logdb
1720+-export([log_message/2,
1721+ rebuild_stats/1,
1722+ rebuild_stats_at/2,
1723+ delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
1724+ get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
1725+ get_dates/1,
1726+ get_users_settings/1, get_user_settings/2, set_user_settings/3]).
1727+
1728+-define(PROCNAME, mod_logdb_mnesia).
1729+-define(CALL_TIMEOUT, 240000).
1730+
1731+-record(state, {vhost}).
1732+
1733+-record(stats, {user, at, count}).
1734+
1735+prefix() ->
1736+ "logdb_".
1737+
1738+suffix(VHost) ->
1739+ "_" ++ VHost.
1740+
1741+stats_table(VHost) ->
1742+ list_to_atom(prefix() ++ "stats" ++ suffix(VHost)).
1743+
1744+table_name(VHost, Date) ->
1745+ list_to_atom(prefix() ++ "messages_" ++ Date ++ suffix(VHost)).
1746+
1747+settings_table(VHost) ->
1748+ list_to_atom(prefix() ++ "settings" ++ suffix(VHost)).
1749+
1750+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1751+%
1752+% gen_mod callbacks
1753+%
1754+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1755+start(VHost, Opts) ->
1756+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1757+ gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
1758+
1759+stop(VHost) ->
1760+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1761+ gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
1762+
1763+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1764+%
1765+% gen_server callbacks
1766+%
1767+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1768+init([VHost, _Opts]) ->
1769+ case mnesia:system_info(is_running) of
1770+ yes ->
1771+ ok = create_stats_table(VHost),
1772+ ok = create_settings_table(VHost),
1773+ {ok, #state{vhost=VHost}};
1774+ no ->
1775+ ?ERROR_MSG("Mnesia not running", []),
1776+ {stop, db_connection_failed};
1777+ Status ->
1778+ ?ERROR_MSG("Mnesia status: ~p", [Status]),
1779+ {stop, db_connection_failed}
1780+ end.
1781+
1782+handle_call({log_message, Msg}, _From, #state{vhost=VHost}=State) ->
1783+ {reply, log_message_int(VHost, Msg), State};
1784+handle_call({rebuild_stats}, _From, #state{vhost=VHost}=State) ->
1785+ {atomic, ok} = delete_nonexistent_stats(VHost),
1786+ Reply =
1787+ lists:foreach(fun(Date) ->
1788+ rebuild_stats_at_int(VHost, Date)
1789+ end, get_dates_int(VHost)),
1790+ {reply, Reply, State};
1791+handle_call({rebuild_stats_at, Date}, _From, #state{vhost=VHost}=State) ->
1792+ Reply = rebuild_stats_at_int(VHost, Date),
1793+ {reply, Reply, State};
1794+handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{vhost=VHost}=State) ->
1795+ Table = table_name(VHost, Date),
1796+ Fun = fun() ->
1797+ lists:foreach(
1798+ fun(Msg) ->
1799+ mnesia:write_lock_table(stats_table(VHost)),
1800+ mnesia:write_lock_table(Table),
1801+ mnesia:delete_object(Table, Msg, write)
1802+ end, Msgs)
1803+ end,
1804+ DRez = case mnesia:transaction(Fun) of
1805+ {aborted, Reason} ->
1806+ ?ERROR_MSG("Failed to delete_messages_by_user_at at ~p for ~p: ~p", [Date, VHost, Reason]),
1807+ error;
1808+ _ ->
1809+ ok
1810+ end,
1811+ Reply =
1812+ case rebuild_stats_at_int(VHost, Date) of
1813+ error ->
1814+ error;
1815+ ok ->
1816+ DRez
1817+ end,
1818+ {reply, Reply, State};
1819+handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{vhost=VHost}=State) ->
1820+ Table = table_name(VHost, Date),
1821+ MsgDelete = fun(#msg{owner_name=Owner} = Msg, _Acc)
1822+ when Owner == User ->
1823+ mnesia:delete_object(Table, Msg, write),
1824+ ok;
1825+ (_Msg, _Acc) -> ok
1826+ end,
1827+ DRez = case mnesia:transaction(fun() ->
1828+ mnesia:foldl(MsgDelete, ok, Table)
1829+ end) of
1830+ {aborted, Reason} ->
1831+ ?ERROR_MSG("Failed to delete_all_messages_by_user_at for ~p@~p at ~p: ~p", [User, VHost, Date, Reason]),
1832+ error;
1833+ _ ->
1834+ ok
1835+ end,
1836+ Reply =
1837+ case rebuild_stats_at_int(VHost, Date) of
1838+ error ->
1839+ error;
1840+ ok ->
1841+ DRez
1842+ end,
1843+ {reply, Reply, State};
1844+handle_call({delete_messages_at, Date}, _From, #state{vhost=VHost}=State) ->
1845+ Reply =
1846+ case mnesia:delete_table(table_name(VHost, Date)) of
1847+ {atomic, ok} ->
1848+ delete_stats_by_vhost_at_int(VHost, Date);
1849+ {aborted, Reason} ->
1850+ ?ERROR_MSG("Failed to delete_messages_at for ~p at ~p", [VHost, Date, Reason]),
1851+ error
1852+ end,
1853+ {reply, Reply, State};
1854+handle_call({get_vhost_stats}, _From, #state{vhost=VHost}=State) ->
1855+ Fun = fun(#stats{at=Date, count=Count}, Stats) ->
1856+ case lists:keysearch(Date, 1, Stats) of
1857+ false ->
1858+ lists:append(Stats, [{Date, Count}]);
1859+ {value, {_, TempCount}} ->
1860+ lists:keyreplace(Date, 1, Stats, {Date, TempCount+Count})
1861+ end
1862+ end,
1863+ Reply =
1864+ case mnesia:transaction(fun() ->
1865+ mnesia:foldl(Fun, [], stats_table(VHost))
1866+ end) of
1867+ {atomic, Result} -> {ok, mod_logdb:sort_stats(Result)};
1868+ {aborted, Reason} -> {error, Reason}
1869+ end,
1870+ {reply, Reply, State};
1871+handle_call({get_vhost_stats_at, Date}, _From, #state{vhost=VHost}=State) ->
1872+ Fun = fun() ->
1873+ Pat = #stats{user='$1', at=Date, count='$2'},
1874+ mnesia:select(stats_table(VHost), [{Pat, [], [['$1', '$2']]}])
1875+ end,
1876+ Reply =
1877+ case mnesia:transaction(Fun) of
1878+ {atomic, Result} ->
1879+ {ok, lists:reverse(lists:keysort(2, [{User, Count} || [User, Count] <- Result]))};
1880+ {aborted, Reason} ->
1881+ {error, Reason}
1882+ end,
1883+ {reply, Reply, State};
1884+handle_call({get_user_stats, User}, _From, #state{vhost=VHost}=State) ->
1885+ Reply =
1886+ case mnesia:transaction(fun() ->
1887+ Pat = #stats{user=User, at='$1', count='$2'},
1888+ mnesia:select(stats_table(VHost), [{Pat, [], [['$1', '$2']]}])
1889+ end) of
1890+ {atomic, Result} ->
1891+ {ok, mod_logdb:sort_stats([{Date, Count} || [Date, Count] <- Result])};
1892+ {aborted, Reason} ->
1893+ {error, Reason}
1894+ end,
1895+ {reply, Reply, State};
1896+handle_call({get_user_messages_at, User, Date}, _From, #state{vhost=VHost}=State) ->
1897+ Reply =
1898+ case mnesia:transaction(fun() ->
1899+ Pat = #msg{owner_name=User, _='_'},
1900+ mnesia:select(table_name(VHost, Date),
1901+ [{Pat, [], ['$_']}])
1902+ end) of
1903+ {atomic, Result} -> {ok, Result};
1904+ {aborted, Reason} ->
1905+ {error, Reason}
1906+ end,
1907+ {reply, Reply, State};
1908+handle_call({get_dates}, _From, #state{vhost=VHost}=State) ->
1909+ {reply, get_dates_int(VHost), State};
1910+handle_call({get_users_settings}, _From, #state{vhost=VHost}=State) ->
1911+ Reply = mnesia:dirty_match_object(settings_table(VHost), #user_settings{_='_'}),
1912+ {reply, {ok, Reply}, State};
1913+handle_call({get_user_settings, User}, _From, #state{vhost=VHost}=State) ->
1914+ Reply =
1915+ case mnesia:dirty_match_object(settings_table(VHost), #user_settings{owner_name=User, _='_'}) of
1916+ [] -> [];
1917+ [Setting] ->
1918+ Setting
1919+ end,
1920+ {reply, Reply, State};
1921+handle_call({set_user_settings, _User, Set}, _From, #state{vhost=VHost}=State) ->
1922+ ?MYDEBUG("~p~n~p", [settings_table(VHost), Set]),
1923+ Reply = mnesia:dirty_write(settings_table(VHost), Set),
1924+ {reply, Reply, State};
1925+handle_call({stop}, _From, State) ->
1926+ {stop, normal, ok, State};
1927+handle_call(Msg, _From, State) ->
1928+ ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
1929+ {noreply, State}.
1930+
1931+handle_cast(Msg, State) ->
1932+ ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
1933+ {noreply, State}.
1934+
1935+handle_info(Info, State) ->
1936+ ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
1937+ {noreply, State}.
1938+
1939+terminate(_Reason, _State) ->
1940+ ok.
1941+
1942+code_change(_OldVsn, State, _Extra) ->
1943+ {ok, State}.
1944+
1945+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1946+%
1947+% gen_logdb callbacks
1948+%
1949+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1950+log_message(VHost, Msg) ->
1951+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1952+ gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
1953+rebuild_stats(VHost) ->
1954+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1955+ gen_server:call(Proc, {rebuild_stats}, ?CALL_TIMEOUT).
1956+rebuild_stats_at(VHost, Date) ->
1957+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1958+ gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
1959+delete_messages_by_user_at(VHost, Msgs, Date) ->
1960+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1961+ gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
1962+delete_all_messages_by_user_at(User, VHost, Date) ->
1963+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1964+ gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
1965+delete_messages_at(VHost, Date) ->
1966+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1967+ gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
1968+get_vhost_stats(VHost) ->
1969+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1970+ gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
1971+get_vhost_stats_at(VHost, Date) ->
1972+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1973+ gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
1974+get_user_stats(User, VHost) ->
1975+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1976+ gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
1977+get_user_messages_at(User, VHost, Date) ->
1978+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1979+ gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
1980+get_dates(VHost) ->
1981+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1982+ gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
1983+get_user_settings(User, VHost) ->
1984+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1985+ gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
1986+get_users_settings(VHost) ->
1987+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1988+ gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
1989+set_user_settings(User, VHost, Set) ->
1990+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1991+ gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
1992+
1993+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1994+%
1995+% internals
1996+%
1997+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1998+log_message_int(VHost, #msg{timestamp=Timestamp}=Msg) ->
1999+ Date = mod_logdb:convert_timestamp_brief(Timestamp),
2000+
2001+ ATable = table_name(VHost, Date),
2002+ Fun = fun() ->
2003+ mnesia:write_lock_table(ATable),
2004+ mnesia:write(ATable, Msg, write)
2005+ end,
2006+ % log message, increment stats for both users
2007+ case mnesia:transaction(Fun) of
2008+ % if table does not exists - create it and try to log message again
2009+ {aborted,{no_exists, _Table}} ->
2010+ case create_msg_table(VHost, Date) of
2011+ {aborted, CReason} ->
2012+ ?ERROR_MSG("Failed to log message: ~p", [CReason]),
2013+ error;
2014+ {atomic, ok} ->
2015+ ?MYDEBUG("Created msg table for ~p at ~p", [VHost, Date]),
2016+ log_message_int(VHost, Msg)
2017+ end;
2018+ {aborted, TReason} ->
2019+ ?ERROR_MSG("Failed to log message: ~p", [TReason]),
2020+ error;
2021+ {atomic, _} ->
2022+ ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
2023+ Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
2024+ increment_user_stats(Msg#msg.owner_name, VHost, Date)
2025+ end.
2026+
2027+increment_user_stats(Owner, VHost, Date) ->
2028+ Fun = fun() ->
2029+ Pat = #stats{user=Owner, at=Date, count='$1'},
2030+ mnesia:write_lock_table(stats_table(VHost)),
2031+ case mnesia:select(stats_table(VHost), [{Pat, [], ['$_']}]) of
2032+ [] ->
2033+ mnesia:write(stats_table(VHost),
2034+ #stats{user=Owner,
2035+ at=Date,
2036+ count=1},
2037+ write);
2038+ [Stats] ->
2039+ mnesia:delete_object(stats_table(VHost),
2040+ #stats{user=Owner,
2041+ at=Date,
2042+ count=Stats#stats.count},
2043+ write),
2044+ New = Stats#stats{count = Stats#stats.count+1},
2045+ if
2046+ New#stats.count > 0 -> mnesia:write(stats_table(VHost),
2047+ New,
2048+ write);
2049+ true -> ok
2050+ end
2051+ end
2052+ end,
2053+ case mnesia:transaction(Fun) of
2054+ {aborted, Reason} ->
2055+ ?ERROR_MSG("Failed to update stats for ~s@~s: ~p", [Owner, VHost, Reason]),
2056+ error;
2057+ {atomic, _} ->
2058+ ?MYDEBUG("Updated stats for ~s@~s", [Owner, VHost]),
2059+ ok
2060+ end.
2061+
2062+get_dates_int(VHost) ->
2063+ Tables = mnesia:system_info(tables),
2064+ lists:foldl(fun(ATable, Dates) ->
2065+ Table = atom_to_list(ATable),
2066+ case regexp:match(Table, VHost++"$") of
2067+ {match, _, _} ->
2068+ case regexp:match(Table,"_[0-9]+-[0-9]+-[0-9]+_") of
2069+ {match, S, E} ->
2070+ lists:append(Dates, [lists:sublist(Table,S+1,E-2)]);
2071+ nomatch ->
2072+ Dates
2073+ end;
2074+ nomatch ->
2075+ Dates
2076+ end
2077+ end, [], Tables).
2078+
2079+rebuild_stats_at_int(VHost, Date) ->
2080+ Table = table_name(VHost, Date),
2081+ STable = stats_table(VHost),
2082+ CFun = fun(Msg, Stats) ->
2083+ Owner = Msg#msg.owner_name,
2084+ case lists:keysearch(Owner, 1, Stats) of
2085+ {value, {_, Count}} ->
2086+ lists:keyreplace(Owner, 1, Stats, {Owner, Count + 1});
2087+ false ->
2088+ lists:append(Stats, [{Owner, 1}])
2089+ end
2090+ end,
2091+ DFun = fun(#stats{at=SDate} = Stat, _Acc)
2092+ when SDate == Date ->
2093+ mnesia:delete_object(stats_table(VHost), Stat, write);
2094+ (_Stat, _Acc) -> ok
2095+ end,
2096+ % TODO: Maybe unregister hooks ?
2097+ case mnesia:transaction(fun() ->
2098+ mnesia:write_lock_table(Table),
2099+ mnesia:write_lock_table(STable),
2100+ % Calc stats for VHost at Date
2101+ case mnesia:foldl(CFun, [], Table) of
2102+ [] -> empty;
2103+ AStats ->
2104+ % Delete all stats for VHost at Date
2105+ mnesia:foldl(DFun, [], STable),
2106+ % Write new calc'ed stats
2107+ lists:foreach(fun({Owner, Count}) ->
2108+ WStat = #stats{user=Owner, at=Date, count=Count},
2109+ mnesia:write(stats_table(VHost), WStat, write)
2110+ end, AStats),
2111+ ok
2112+ end
2113+ end) of
2114+ {aborted, Reason} ->
2115+ ?ERROR_MSG("Failed to rebuild_stats_at for ~p at ~p: ~p", [VHost, Date, Reason]),
2116+ error;
2117+ {atomic, ok} ->
2118+ ok;
2119+ {atomic, empty} ->
2120+ {atomic,ok} = mnesia:delete_table(Table),
2121+ ?MYDEBUG("Dropped table at ~p", [Date]),
2122+ ok
2123+ end.
2124+
2125+delete_nonexistent_stats(VHost) ->
2126+ Dates = get_dates_int(VHost),
2127+ mnesia:transaction(fun() ->
2128+ mnesia:foldl(fun(#stats{at=Date} = Stat, _Acc) ->
2129+ case lists:member(Date, Dates) of
2130+ false -> mnesia:delete_object(Stat);
2131+ true -> ok
2132+ end
2133+ end, ok, stats_table(VHost))
2134+ end).
2135+
2136+delete_stats_by_vhost_at_int(VHost, Date) ->
2137+ StatsDelete = fun(#stats{at=SDate} = Stat, _Acc)
2138+ when SDate == Date ->
2139+ mnesia:delete_object(stats_table(VHost), Stat, write),
2140+ ok;
2141+ (_Msg, _Acc) -> ok
2142+ end,
2143+ case mnesia:transaction(fun() ->
2144+ mnesia:write_lock_table(stats_table(VHost)),
2145+ mnesia:foldl(StatsDelete, ok, stats_table(VHost))
2146+ end) of
2147+ {aborted, Reason} ->
2148+ ?ERROR_MSG("Failed to update stats at ~p for ~p: ~p", [Date, VHost, Reason]),
2149+ rebuild_stats_at_int(VHost, Date);
2150+ _ ->
2151+ ?INFO_MSG("Updated stats at ~p for ~p", [Date, VHost]),
2152+ ok
2153+ end.
2154+
2155+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2156+%
2157+% tables internals
2158+%
2159+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2160+create_stats_table(VHost) ->
2161+ SName = stats_table(VHost),
2162+ case mnesia:create_table(SName,
2163+ [{disc_only_copies, [node()]},
2164+ {type, bag},
2165+ {attributes, record_info(fields, stats)},
2166+ {record_name, stats}
2167+ ]) of
2168+ {atomic, ok} ->
2169+ ?MYDEBUG("Created stats table for ~p", [VHost]),
2170+ lists:foreach(fun(Date) ->
2171+ rebuild_stats_at_int(VHost, Date)
2172+ end, get_dates_int(VHost)),
2173+ ok;
2174+ {aborted, {already_exists, _}} ->
2175+ ?MYDEBUG("Stats table for ~p already exists", [VHost]),
2176+ ok;
2177+ {aborted, Reason} ->
2178+ ?ERROR_MSG("Failed to create stats table: ~p", [Reason]),
2179+ error
2180+ end.
2181+
2182+create_settings_table(VHost) ->
2183+ SName = settings_table(VHost),
2184+ case mnesia:create_table(SName,
2185+ [{disc_copies, [node()]},
2186+ {type, set},
2187+ {attributes, record_info(fields, user_settings)},
2188+ {record_name, user_settings}
2189+ ]) of
2190+ {atomic, ok} ->
2191+ ?MYDEBUG("Created settings table for ~p", [VHost]),
2192+ ok;
2193+ {aborted, {already_exists, _}} ->
2194+ ?MYDEBUG("Settings table for ~p already exists", [VHost]),
2195+ ok;
2196+ {aborted, Reason} ->
2197+ ?ERROR_MSG("Failed to create settings table: ~p", [Reason]),
2198+ error
2199+ end.
2200+
2201+create_msg_table(VHost, Date) ->
2202+ mnesia:create_table(
2203+ table_name(VHost, Date),
2204+ [{disc_only_copies, [node()]},
2205+ {type, bag},
2206+ {attributes, record_info(fields, msg)},
2207+ {record_name, msg}]).
2208--- src/mod_logdb_mysql.erl.orig Tue Dec 11 14:23:19 2007
2209+++ src/mod_logdb_mysql.erl Sun Nov 18 20:53:55 2007
2210@@ -0,0 +1,936 @@
2211+%%%----------------------------------------------------------------------
2212+%%% File : mod_logdb_mysql.erl
2213+%%% Author : Oleg Palij (mailto:o.palij@gmail.com xmpp://malik@jabber.te.ua)
2214+%%% Purpose : MySQL backend for mod_logdb
2215+%%% Version : trunk
2216+%%% Id : $Id$
2217+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
2218+%%%----------------------------------------------------------------------
2219+
2220+-module(mod_logdb_mysql).
2221+-author('o.palij@gmail.com').
2222+-vsn('$Revision$').
2223+
2224+-include("mod_logdb.hrl").
2225+-include("ejabberd.hrl").
2226+-include("jlib.hrl").
2227+
2228+-behaviour(gen_logdb).
2229+-behaviour(gen_server).
2230+
2231+% gen_server
2232+-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
2233+% gen_mod
2234+-export([start/2, stop/1]).
2235+% gen_logdb
2236+-export([log_message/2,
2237+ rebuild_stats/1,
2238+ rebuild_stats_at/2,
2239+ delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
2240+ get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
2241+ get_dates/1,
2242+ get_users_settings/1, get_user_settings/2, set_user_settings/3]).
2243+
2244+% gen_server call timeout
2245+-define(CALL_TIMEOUT, 60000).
2246+-define(TIMEOUT, 60000).
2247+-define(INDEX_SIZE, integer_to_list(170)).
2248+-define(PROCNAME, mod_logdb_mysql).
2249+
2250+-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
2251+ list_to_string/1, string_to_list/1,
2252+ convert_timestamp_brief/1]).
2253+
2254+-record(state, {dbref, vhost}).
2255+
2256+% replace "." with "_"
2257+escape_vhost(VHost) -> lists:map(fun(46) -> 95;
2258+ (A) -> A
2259+ end, VHost).
2260+prefix() ->
2261+ "`logdb_".
2262+
2263+suffix(VHost) ->
2264+ "_" ++ escape_vhost(VHost) ++ "`".
2265+
2266+messages_table(VHost, Date) ->
2267+ prefix() ++ "messages_" ++ Date ++ suffix(VHost).
2268+
2269+stats_table(VHost) ->
2270+ prefix() ++ "stats" ++ suffix(VHost).
2271+
2272+settings_table(VHost) ->
2273+ prefix() ++ "settings" ++ suffix(VHost).
2274+
2275+users_table(VHost) ->
2276+ prefix() ++ "users" ++ suffix(VHost).
2277+servers_table(VHost) ->
2278+ prefix() ++ "servers" ++ suffix(VHost).
2279+resources_table(VHost) ->
2280+ prefix() ++ "resources" ++ suffix(VHost).
2281+
2282+ets_users_table(VHost) -> list_to_atom("logdb_users_" ++ VHost).
2283+ets_servers_table(VHost) -> list_to_atom("logdb_servers_" ++ VHost).
2284+ets_resources_table(VHost) -> list_to_atom("logdb_resources_" ++ VHost).
2285+
2286+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2287+%
2288+% gen_mod callbacks
2289+%
2290+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2291+start(VHost, Opts) ->
2292+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2293+ gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
2294+
2295+stop(VHost) ->
2296+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2297+ gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
2298+
2299+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2300+%
2301+% gen_server callbacks
2302+%
2303+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2304+init([VHost, Opts]) ->
2305+ crypto:start(),
2306+
2307+ Server = gen_mod:get_opt(server, Opts, "localhost"),
2308+ Port = gen_mod:get_opt(port, Opts, 3128),
2309+ DB = gen_mod:get_opt(db, Opts, "logdb"),
2310+ User = gen_mod:get_opt(user, Opts, "root"),
2311+ Password = gen_mod:get_opt(password, Opts, ""),
2312+
2313+ LogFun = fun(debug, Format, Argument) ->
2314+ ?MYDEBUG(Format, Argument);
2315+ (error, Format, Argument) ->
2316+ ?ERROR_MSG(Format, Argument);
2317+ (Level, Format, Argument) ->
2318+ ?MYDEBUG("MySQL (~p)~n", [Level]),
2319+ ?MYDEBUG(Format, Argument)
2320+ end,
2321+ case mysql_conn:start(Server, Port, User, Password, DB, LogFun) of
2322+ {ok, DBRef} ->
2323+ ok = create_stats_table(DBRef, VHost),
2324+ ok = create_settings_table(DBRef, VHost),
2325+ ok = create_users_table(DBRef, VHost),
2326+ % clear ets cache every ...
2327+ timer:send_interval(timer:hours(12), clear_ets_tables),
2328+ ok = create_servers_table(DBRef, VHost),
2329+ ok = create_resources_table(DBRef, VHost),
2330+ erlang:monitor(process, DBRef),
2331+ {ok, #state{dbref=DBRef, vhost=VHost}};
2332+ {error, Reason} ->
2333+ ?ERROR_MSG("MySQL connection failed: ~p~n", [Reason]),
2334+ {stop, db_connection_failed}
2335+ end.
2336+
2337+handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2338+ {reply, log_message_int(DBRef, VHost, Msg), State};
2339+handle_call({rebuild_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2340+ ok = delete_nonexistent_stats(DBRef, VHost),
2341+ Reply =
2342+ lists:foreach(fun(Date) ->
2343+ catch rebuild_stats_at_int(DBRef, VHost, Date)
2344+ end, get_dates_int(DBRef, VHost)),
2345+ {reply, Reply, State};
2346+handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2347+ Reply = rebuild_stats_at_int(DBRef, VHost, Date),
2348+ {reply, Reply, State};
2349+handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
2350+ {reply, error, State};
2351+handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2352+ Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
2353+ ["\"",Timestamp,"\"",","]
2354+ end, Msgs),
2355+
2356+ Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
2357+
2358+ Query = ["DELETE FROM ",messages_table(VHost, Date)," ",
2359+ "WHERE timestamp IN (", Temp1],
2360+
2361+ Reply =
2362+ case sql_query_internal(DBRef, Query) of
2363+ {updated, Aff} ->
2364+ ?MYDEBUG("Aff=~p", [Aff]),
2365+ rebuild_stats_at_int(DBRef, VHost, Date);
2366+ {error, _} ->
2367+ error
2368+ end,
2369+ {reply, Reply, State};
2370+handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2371+ Owner_id = get_user_id(DBRef, VHost, User),
2372+ DQuery = ["DELETE FROM ",messages_table(VHost, Date)," ",
2373+ "WHERE owner_id=\"",Owner_id,"\";"],
2374+ Reply =
2375+ case sql_query_internal(DBRef, DQuery) of
2376+ {updated, _} ->
2377+ rebuild_stats_at_int(DBRef, VHost, Date);
2378+ {error, _} ->
2379+ error
2380+ end,
2381+ {reply, Reply, State};
2382+handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2383+ Reply =
2384+ case sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Date),";"]) of
2385+ {updated, _} ->
2386+ Query = ["DELETE FROM ",stats_table(VHost)," "
2387+ "WHERE at=\"",Date,"\";"],
2388+ case sql_query_internal(DBRef, Query) of
2389+ {updated, _} ->
2390+ ok;
2391+ {error, _} ->
2392+ error
2393+ end;
2394+ {error, _} ->
2395+ error
2396+ end,
2397+ {reply, Reply, State};
2398+handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2399+ SName = stats_table(VHost),
2400+ Query = ["SELECT at, sum(count) ",
2401+ "FROM ",SName," ",
2402+ "GROUP BY at ",
2403+ "ORDER BY DATE(at) DESC;"
2404+ ],
2405+ Reply =
2406+ case sql_query_internal(DBRef, Query) of
2407+ {data, Result} ->
2408+ {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
2409+ {error, Reason} ->
2410+ % TODO: Duplicate error message ?
2411+ {error, Reason}
2412+ end,
2413+ {reply, Reply, State};
2414+handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2415+ SName = stats_table(VHost),
2416+ Query = ["SELECT username, count ",
2417+ "FROM ",SName," ",
2418+ "JOIN ",users_table(VHost)," ON owner_id=user_id "
2419+ "WHERE at=\"",Date,"\";"
2420+ ],
2421+ Reply =
2422+ case sql_query_internal(DBRef, Query) of
2423+ {data, Result} ->
2424+ {ok, lists:reverse(
2425+ lists:keysort(2,
2426+ [ {User, list_to_integer(Count)} || [User, Count] <- Result]))};
2427+ {error, Reason} ->
2428+ % TODO:
2429+ {error, Reason}
2430+ end,
2431+ {reply, Reply, State};
2432+handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2433+ SName = stats_table(VHost),
2434+ Query = ["SELECT at, count ",
2435+ "FROM ",SName," ",
2436+ "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\" ",
2437+ "ORDER BY DATE(at) DESC;"
2438+ ],
2439+ Reply =
2440+ case sql_query_internal(DBRef, Query) of
2441+ {data, Result} ->
2442+ {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result]};
2443+ {error, Result} ->
2444+ {error, Result}
2445+ end,
2446+ {reply, Reply, State};
2447+handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2448+ TName = messages_table(VHost, Date),
2449+ UName = users_table(VHost),
2450+ SName = servers_table(VHost),
2451+ RName = resources_table(VHost),
2452+ Query = ["SELECT users.username,",
2453+ "servers.server,",
2454+ "resources.resource,",
2455+ "messages.direction,"
2456+ "messages.type,"
2457+ "messages.subject,"
2458+ "messages.body,"
2459+ "messages.timestamp "
2460+ "FROM ",TName," AS messages "
2461+ "JOIN ",UName," AS users ON peer_name_id=user_id ",
2462+ "JOIN ",SName," AS servers ON peer_server_id=server_id ",
2463+ "JOIN ",RName," AS resources ON peer_resource_id=resource_id ",
2464+ "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\" ",
2465+ "ORDER BY timestamp ASC;"],
2466+ Reply =
2467+ case sql_query_internal(DBRef, Query) of
2468+ {data, Result} ->
2469+ Fun = fun([Peer_name, Peer_server, Peer_resource,
2470+ Direction,
2471+ Type,
2472+ Subject, Body,
2473+ Timestamp]) ->
2474+ #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
2475+ direction=list_to_atom(Direction),
2476+ type=Type,
2477+ subject=Subject, body=Body,
2478+ timestamp=Timestamp}
2479+ end,
2480+ {ok, lists:map(Fun, Result)};
2481+ {error, Reason} ->
2482+ {error, Reason}
2483+ end,
2484+ {reply, Reply, State};
2485+handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2486+ SName = stats_table(VHost),
2487+ Query = ["SELECT at ",
2488+ "FROM ",SName," ",
2489+ "GROUP BY at ",
2490+ "ORDER BY DATE(at) DESC;"
2491+ ],
2492+ Reply =
2493+ case sql_query_internal(DBRef, Query) of
2494+ {data, Result} ->
2495+ [ Date || [Date] <- Result ];
2496+ {error, Reason} ->
2497+ {error, Reason}
2498+ end,
2499+ {reply, Reply, State};
2500+handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2501+ Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
2502+ "FROM ",settings_table(VHost)," ",
2503+ "JOIN ",users_table(VHost)," ON user_id=owner_id;"],
2504+ Reply =
2505+ case sql_query_internal(DBRef, Query) of
2506+ {data, Result} ->
2507+ {ok, lists:map(fun([Owner, DoLogDef, DoLogL, DoNotLogL]) ->
2508+ #user_settings{owner_name=Owner,
2509+ dolog_default=list_to_bool(DoLogDef),
2510+ dolog_list=string_to_list(DoLogL),
2511+ donotlog_list=string_to_list(DoNotLogL)
2512+ }
2513+ end, Result)};
2514+ {error, _} ->
2515+ error
2516+ end,
2517+ {reply, Reply, State};
2518+handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
2519+ Query = ["SELECT dolog_default,dolog_list,donotlog_list FROM ",settings_table(VHost)," ",
2520+ "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\";"],
2521+ Reply =
2522+ case sql_query_internal(DBRef, Query) of
2523+ {data, []} ->
2524+ {ok, []};
2525+ {data, [[Owner, DoLogDef, DoLogL, DoNotLogL]]} ->
2526+ {ok, #user_settings{owner_name=Owner,
2527+ dolog_default=list_to_bool(DoLogDef),
2528+ dolog_list=string_to_list(DoLogL),
2529+ donotlog_list=string_to_list(DoNotLogL)}};
2530+ {error, _} ->
2531+ error
2532+ end,
2533+ {reply, Reply, State};
2534+handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
2535+ dolog_list=DoLogL,
2536+ donotlog_list=DoNotLogL}},
2537+ _From, #state{dbref=DBRef, vhost=VHost} = State) ->
2538+ User_id = get_user_id(DBRef, VHost, User),
2539+
2540+ Query = ["UPDATE ",settings_table(VHost)," ",
2541+ "SET dolog_default=",bool_to_list(DoLogDef),", ",
2542+ "dolog_list='",list_to_string(DoLogL),"', ",
2543+ "donotlog_list='",list_to_string(DoNotLogL),"' ",
2544+ "WHERE owner_id=\"",User_id,"\";"],
2545+
2546+ Reply =
2547+ case sql_query_internal(DBRef, Query) of
2548+ {updated, 0} ->
2549+ IQuery = ["INSERT INTO ",settings_table(VHost)," ",
2550+ "(owner_id, dolog_default, dolog_list, donotlog_list) ",
2551+ "VALUES ",
2552+ "('",User_id,"', ",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
2553+ case sql_query_internal_silent(DBRef, IQuery) of
2554+ {updated, _} ->
2555+ ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
2556+ ok;
2557+ {error, Reason} ->
2558+ case regexp:match(Reason, "#23000") of
2559+ % Already exists
2560+ {match, _, _} ->
2561+ ok;
2562+ _ ->
2563+ ?ERROR_MSG("Failed setup user ~p@~p: ~p", [User, VHost, Reason]),
2564+ error
2565+ end
2566+ end;
2567+ {updated, 1} ->
2568+ ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
2569+ ok;
2570+ {error, _} ->
2571+ error
2572+ end,
2573+ {reply, Reply, State};
2574+handle_call({stop}, _From, #state{vhost=VHost}=State) ->
2575+ ets:delete(ets_users_table(VHost)),
2576+ ets:delete(ets_servers_table(VHost)),
2577+ ?MYDEBUG("Stoping mysql backend for ~p", [VHost]),
2578+ {stop, normal, ok, State};
2579+handle_call(Msg, _From, State) ->
2580+ ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
2581+ {noreply, State}.
2582+
2583+handle_cast(Msg, State) ->
2584+ ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
2585+ {noreply, State}.
2586+
2587+handle_info(clear_ets_tables, State) ->
2588+ ets:delete_all_objects(ets_users_table(State#state.vhost)),
2589+ ets:delete_all_objects(ets_resources_table(State#state.vhost)),
2590+ {noreply, State};
2591+handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
2592+ {stop, connection_dropped, State};
2593+handle_info(Info, State) ->
2594+ ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
2595+ {noreply, State}.
2596+
2597+terminate(_Reason, _State) ->
2598+ ok.
2599+
2600+code_change(_OldVsn, State, _Extra) ->
2601+ {ok, State}.
2602+
2603+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2604+%
2605+% gen_logdb callbacks
2606+%
2607+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2608+log_message(VHost, Msg) ->
2609+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2610+ gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
2611+rebuild_stats(VHost) ->
2612+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2613+ gen_server:call(Proc, {rebuild_stats}, ?CALL_TIMEOUT).
2614+rebuild_stats_at(VHost, Date) ->
2615+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2616+ gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
2617+delete_messages_by_user_at(VHost, Msgs, Date) ->
2618+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2619+ gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
2620+delete_all_messages_by_user_at(User, VHost, Date) ->
2621+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2622+ gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
2623+delete_messages_at(VHost, Date) ->
2624+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2625+ gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
2626+get_vhost_stats(VHost) ->
2627+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2628+ gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
2629+get_vhost_stats_at(VHost, Date) ->
2630+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2631+ gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
2632+get_user_stats(User, VHost) ->
2633+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2634+ gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
2635+get_user_messages_at(User, VHost, Date) ->
2636+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2637+ gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
2638+get_dates(VHost) ->
2639+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2640+ gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
2641+get_users_settings(VHost) ->
2642+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2643+ gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
2644+get_user_settings(User, VHost) ->
2645+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2646+ gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
2647+set_user_settings(User, VHost, Set) ->
2648+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2649+ gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
2650+
2651+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2652+%
2653+% internals
2654+%
2655+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2656+log_message_int(DBRef, VHost, Msg) ->
2657+ Date = convert_timestamp_brief(Msg#msg.timestamp),
2658+
2659+ Table = messages_table(VHost, Date),
2660+ Owner_id = get_user_id(DBRef, VHost, Msg#msg.owner_name),
2661+ Peer_name_id = get_user_id(DBRef, VHost, Msg#msg.peer_name),
2662+ Peer_server_id = get_server_id(DBRef, VHost, Msg#msg.peer_server),
2663+ Peer_resource_id = get_resource_id(DBRef, VHost, Msg#msg.peer_resource),
2664+
2665+ Query = ["INSERT INTO ",Table," ",
2666+ "(owner_id,",
2667+ "peer_name_id,",
2668+ "peer_server_id,",
2669+ "peer_resource_id,",
2670+ "direction,",
2671+ "type,",
2672+ "subject,",
2673+ "body,",
2674+ "timestamp) ",
2675+ "VALUES ",
2676+ "('", Owner_id, "',",
2677+ "'", Peer_name_id, "',",
2678+ "'", Peer_server_id, "',",
2679+ "'", Peer_resource_id, "',",
2680+ "'", atom_to_list(Msg#msg.direction), "',",
2681+ "'", Msg#msg.type, "',",
2682+ "'", ejabberd_odbc:escape(Msg#msg.subject), "',",
2683+ "'", ejabberd_odbc:escape(Msg#msg.body), "',",
2684+ "'", Msg#msg.timestamp, "');"],
2685+
2686+ case sql_query_internal_silent(DBRef, Query) of
2687+ {updated, _} ->
2688+ ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
2689+ Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
2690+ increment_user_stats(DBRef, Msg#msg.owner_name, Owner_id, VHost, Date);
2691+ {error, Reason} ->
2692+ case regexp:match(Reason, "#42S02") of
2693+ % Table doesn't exist
2694+ {match, _, _} ->
2695+ case create_msg_table(DBRef, VHost, Date) of
2696+ error ->
2697+ error;
2698+ ok ->
2699+ log_message_int(DBRef, VHost, Msg)
2700+ end;
2701+ _ ->
2702+ ?ERROR_MSG("Failed to log message: ~p", [Reason]),
2703+ error
2704+ end
2705+ end.
2706+
2707+increment_user_stats(DBRef, User_name, User_id, VHost, Date) ->
2708+ SName = stats_table(VHost),
2709+ UQuery = ["UPDATE ",SName," ",
2710+ "SET count=count+1 ",
2711+ "WHERE owner_id=\"",User_id,"\" AND at=\"",Date,"\";"],
2712+
2713+ case sql_query_internal(DBRef, UQuery) of
2714+ {updated, 0} ->
2715+ IQuery = ["INSERT INTO ",SName," ",
2716+ "(owner_id, at, count) ",
2717+ "VALUES ",
2718+ "('",User_id,"', '",Date,"', '1');"],
2719+ case sql_query_internal(DBRef, IQuery) of
2720+ {updated, _} ->
2721+ ?MYDEBUG("New stats for ~s@~s at ~s", [User_name, VHost, Date]),
2722+ ok;
2723+ {error, _} ->
2724+ error
2725+ end;
2726+ {updated, _} ->
2727+ ?MYDEBUG("Updated stats for ~s@~s at ~s", [User_name, VHost, Date]),
2728+ ok;
2729+ {error, _} ->
2730+ error
2731+ end.
2732+
2733+get_dates_int(DBRef, VHost) ->
2734+ case sql_query_internal(DBRef, ["SHOW TABLES"]) of
2735+ {data, Tables} ->
2736+ lists:foldl(fun([Table], Dates) ->
2737+ % TODO: check prefix()
2738+ case regexp:match(Table, escape_vhost(VHost)) of
2739+ {match, _, _} ->
2740+ case regexp:match(Table,"[0-9]+-[0-9]+-[0-9]+") of
2741+ {match, S, E} ->
2742+ lists:append(Dates, [lists:sublist(Table,S,E)]);
2743+ nomatch ->
2744+ Dates
2745+ end;
2746+ nomatch ->
2747+ Dates
2748+ end
2749+ end, [], Tables);
2750+ {error, _} ->
2751+ []
2752+ end.
2753+
2754+rebuild_stats_at_int(DBRef, VHost, Date) ->
2755+ Table = messages_table(VHost, Date),
2756+ STable = stats_table(VHost),
2757+
2758+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," WRITE, ",
2759+ STable," WRITE;"]),
2760+ Fun =
2761+ fun() ->
2762+ DQuery = [ "DELETE FROM ",STable," ",
2763+ "WHERE at='",Date,"';"],
2764+
2765+ {updated, _} = sql_query_internal(DBRef, DQuery),
2766+
2767+ SQuery = ["INSERT INTO ",STable," ",
2768+ "(owner_id,at,count) ",
2769+ "SELECT owner_id,\"",Date,"\"",",count(*) ",
2770+ "FROM ",Table," GROUP BY owner_id;"],
2771+
2772+ case sql_query_internal(DBRef, SQuery) of
2773+ {updated, 0} ->
2774+ {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]),
2775+ ok;
2776+ {updated, _} -> ok;
2777+ {error, _} -> error
2778+ end
2779+ end,
2780+
2781+
2782+ Res = case sql_transaction_internal(DBRef, Fun) of
2783+ {atomic, _} ->
2784+ ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
2785+ ok;
2786+ {aborted, _} ->
2787+ error
2788+ end,
2789+ {updated, _} = sql_query_internal(DBRef, ["UNLOCK TABLES;"]),
2790+ Res.
2791+
2792+
2793+delete_nonexistent_stats(DBRef, VHost) ->
2794+ Dates = get_dates_int(DBRef, VHost),
2795+ STable = stats_table(VHost),
2796+
2797+ Temp = lists:flatmap(fun(Date) ->
2798+ ["\"",Date,"\"",","]
2799+ end, Dates),
2800+
2801+ Temp1 = case Temp of
2802+ [] ->
2803+ ["\"\""];
2804+ _ ->
2805+ % replace last "," with ");"
2806+ lists:append([lists:sublist(Temp, length(Temp)-1), ");"])
2807+ end,
2808+
2809+ Query = ["DELETE FROM ",STable," ",
2810+ "WHERE at NOT IN (", Temp1],
2811+
2812+ case sql_query_internal(DBRef, Query) of
2813+ {updated, _} ->
2814+ ok;
2815+ {error, _} ->
2816+ error
2817+ end.
2818+
2819+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2820+%
2821+% tables internals
2822+%
2823+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2824+create_stats_table(DBRef, VHost) ->
2825+ SName = stats_table(VHost),
2826+ Query = ["CREATE TABLE ",SName," (",
2827+ "owner_id MEDIUMINT UNSIGNED, ",
2828+ "at varchar(20), ",
2829+ "count int(11), ",
2830+ "INDEX(owner_id), ",
2831+ "INDEX(at)"
2832+ ") ENGINE=InnoDB CHARACTER SET utf8;"
2833+ ],
2834+ case sql_query_internal_silent(DBRef, Query) of
2835+ {updated, _} ->
2836+ ?MYDEBUG("Created stats table for ~p", [VHost]),
2837+ lists:foreach(fun(Date) ->
2838+ rebuild_stats_at_int(DBRef, VHost, Date)
2839+ end, get_dates_int(DBRef, VHost)),
2840+ ok;
2841+ {error, Reason} ->
2842+ case regexp:match(Reason, "#42S01") of
2843+ {match, _, _} ->
2844+ ?MYDEBUG("Stats table for ~p already exists", [VHost]),
2845+ ok;
2846+ _ ->
2847+ ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
2848+ error
2849+ end
2850+ end.
2851+
2852+create_settings_table(DBRef, VHost) ->
2853+ SName = settings_table(VHost),
2854+ Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
2855+ "owner_id MEDIUMINT UNSIGNED PRIMARY KEY, ",
2856+ "dolog_default TINYINT(1) NOT NULL DEFAULT 1, ",
2857+ "dolog_list TEXT, ",
2858+ "donotlog_list TEXT ",
2859+ ") ENGINE=InnoDB CHARACTER SET utf8;"
2860+ ],
2861+ case sql_query_internal(DBRef, Query) of
2862+ {updated, _} ->
2863+ ?MYDEBUG("Created settings table for ~p", [VHost]),
2864+ ok;
2865+ {error, _} ->
2866+ error
2867+ end.
2868+
2869+create_users_table(DBRef, VHost) ->
2870+ SName = users_table(VHost),
2871+ Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
2872+ "username TEXT NOT NULL, ",
2873+ "user_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
2874+ "UNIQUE INDEX(username(",?INDEX_SIZE,")) ",
2875+ ") ENGINE=InnoDB CHARACTER SET utf8;"
2876+ ],
2877+ case sql_query_internal(DBRef, Query) of
2878+ {updated, _} ->
2879+ ?MYDEBUG("Created users table for ~p", [VHost]),
2880+ ets:new(ets_users_table(VHost), [named_table, set, public]),
2881+ %update_users_from_db(DBRef, VHost),
2882+ ok;
2883+ {error, _} ->
2884+ error
2885+ end.
2886+
2887+create_servers_table(DBRef, VHost) ->
2888+ SName = servers_table(VHost),
2889+ Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
2890+ "server TEXT NOT NULL, ",
2891+ "server_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
2892+ "UNIQUE INDEX(server(",?INDEX_SIZE,")) ",
2893+ ") ENGINE=InnoDB CHARACTER SET utf8;"
2894+ ],
2895+ case sql_query_internal(DBRef, Query) of
2896+ {updated, _} ->
2897+ ?MYDEBUG("Created servers table for ~p", [VHost]),
2898+ ets:new(ets_servers_table(VHost), [named_table, set, public]),
2899+ update_servers_from_db(DBRef, VHost),
2900+ ok;
2901+ {error, _} ->
2902+ error
2903+ end.
2904+
2905+create_resources_table(DBRef, VHost) ->
2906+ RName = resources_table(VHost),
2907+ Query = ["CREATE TABLE IF NOT EXISTS ",RName," (",
2908+ "resource TEXT NOT NULL, ",
2909+ "resource_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
2910+ "UNIQUE INDEX(resource(",?INDEX_SIZE,")) ",
2911+ ") ENGINE=InnoDB CHARACTER SET utf8;"
2912+ ],
2913+ case sql_query_internal(DBRef, Query) of
2914+ {updated, _} ->
2915+ ?MYDEBUG("Created resources table for ~p", [VHost]),
2916+ ets:new(ets_resources_table(VHost), [named_table, set, public]),
2917+ ok;
2918+ {error, _} ->
2919+ error
2920+ end.
2921+
2922+create_msg_table(DBRef, VHost, Date) ->
2923+ TName = messages_table(VHost, Date),
2924+ Query = ["CREATE TABLE ",TName," (",
2925+ "owner_id MEDIUMINT UNSIGNED, ",
2926+ "peer_name_id MEDIUMINT UNSIGNED, ",
2927+ "peer_server_id MEDIUMINT UNSIGNED, ",
2928+ "peer_resource_id MEDIUMINT(8) UNSIGNED, ",
2929+ "direction ENUM('to', 'from'), ",
2930+ "type ENUM('chat','error','groupchat','headline','normal') NOT NULL, ",
2931+ "subject TEXT, ",
2932+ "body TEXT, ",
2933+ "timestamp DOUBLE, ",
2934+ "INDEX owner_i (owner_id), ",
2935+ "INDEX peer_i (peer_name_id, peer_server_id), ",
2936+ "FULLTEXT (body) "
2937+ ") ENGINE=MyISAM CHARACTER SET utf8;"
2938+ ],
2939+ case sql_query_internal(DBRef, Query) of
2940+ {updated, _MySQLRes} ->
2941+ ?MYDEBUG("Created msg table for ~p at ~p", [VHost, Date]),
2942+ ok;
2943+ {error, _} ->
2944+ error
2945+ end.
2946+
2947+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2948+%
2949+% internal ets cache (users, servers, resources)
2950+%
2951+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2952+update_servers_from_db(DBRef, VHost) ->
2953+ ?INFO_MSG("Reading servers from db for ~p", [VHost]),
2954+ SQuery = ["SELECT server, server_id FROM ",servers_table(VHost),";"],
2955+ {data, Result} = sql_query_internal(DBRef, SQuery),
2956+ true = ets:delete_all_objects(ets_servers_table(VHost)),
2957+ true = ets:insert(ets_servers_table(VHost), [ {Server, Server_id} || [Server, Server_id] <- Result]).
2958+
2959+%update_users_from_db(DBRef, VHost) ->
2960+% ?INFO_MSG("Reading users from db for ~p", [VHost]),
2961+% SQuery = ["SELECT username, user_id FROM ",users_table(VHost),";"],
2962+% {data, Result} = sql_query_internal(DBRef, SQuery),
2963+% true = ets:delete_all_objects(ets_users_table(VHost)),
2964+% true = ets:insert(ets_users_table(VHost), [ {Username, User_id} || [Username, User_id] <- Result]).
2965+
2966+%get_user_name(DBRef, VHost, User_id) ->
2967+% case ets:match(ets_users_table(VHost), {'$1', User_id}) of
2968+% [[User]] -> User;
2969+% % this can be in clustered environment
2970+% [] ->
2971+% %update_users_from_db(DBRef, VHost),
2972+% SQuery = ["SELECT username FROM ",users_table(VHost)," ",
2973+% "WHERE user_id=\"",User_id,"\";"],
2974+% {data, [[Name]]} = sql_query_internal(DBRef, SQuery),
2975+% % cache {user, id} pair
2976+% ets:insert(ets_users_table(VHost), {Name, User_id}),
2977+% Name
2978+% end.
2979+
2980+%get_server_name(DBRef, VHost, Server_id) ->
2981+% case ets:match(ets_servers_table(VHost), {'$1', Server_id}) of
2982+% [[Server]] -> Server;
2983+ % this can be in clustered environment
2984+% [] ->
2985+% update_servers_from_db(DBRef, VHost),
2986+% [[Server1]] = ets:match(ets_servers_table(VHost), {'$1', Server_id}),
2987+% Server1
2988+% end.
2989+
2990+get_user_id_from_db(DBRef, VHost, User) ->
2991+ SQuery = ["SELECT user_id FROM ",users_table(VHost)," ",
2992+ "WHERE username=\"",User,"\";"],
2993+ case sql_query_internal(DBRef, SQuery) of
2994+ % no such user in db
2995+ {data, []} ->
2996+ {ok, []};
2997+ {data, [[DBId]]} ->
2998+ % cache {user, id} pair
2999+ ets:insert(ets_users_table(VHost), {User, DBId}),
3000+ {ok, DBId}
3001+ end.
3002+get_user_id(DBRef, VHost, User) ->
3003+ % Look at ets
3004+ case ets:match(ets_users_table(VHost), {User, '$1'}) of
3005+ [] ->
3006+ % Look at db
3007+ case get_user_id_from_db(DBRef, VHost, User) of
3008+ % no such user in db
3009+ {ok, []} ->
3010+ IQuery = ["INSERT INTO ",users_table(VHost)," ",
3011+ "SET username=\"",User,"\";"],
3012+ case sql_query_internal_silent(DBRef, IQuery) of
3013+ {updated, _} ->
3014+ {ok, NewId} = get_user_id_from_db(DBRef, VHost, User),
3015+ NewId;
3016+ {error, Reason} ->
3017+ % this can be in clustered environment
3018+ {match, _, _} = regexp:match(Reason, "#23000"),
3019+ ?ERROR_MSG("Duplicate key name for ~p", [User]),
3020+ {ok, ClID} = get_user_id_from_db(DBRef, VHost, User),
3021+ ClID
3022+ end;
3023+ {ok, DBId} ->
3024+ DBId
3025+ end;
3026+ [[EtsId]] -> EtsId
3027+ end.
3028+
3029+get_server_id(DBRef, VHost, Server) ->
3030+ case ets:match(ets_servers_table(VHost), {Server, '$1'}) of
3031+ [] ->
3032+ IQuery = ["INSERT INTO ",servers_table(VHost)," ",
3033+ "SET server=\"",Server,"\";"],
3034+ case sql_query_internal_silent(DBRef, IQuery) of
3035+ {updated, _} ->
3036+ SQuery = ["SELECT server_id FROM ",servers_table(VHost)," ",
3037+ "WHERE server=\"",Server,"\";"],
3038+ {data, [[Id]]} = sql_query_internal(DBRef, SQuery),
3039+ ets:insert(ets_servers_table(VHost), {Server, Id}),
3040+ Id;
3041+ {error, Reason} ->
3042+ % this can be in clustered environment
3043+ {match, _, _} = regexp:match(Reason, "#23000"),
3044+ ?ERROR_MSG("Duplicate key name for ~p", [Server]),
3045+ update_servers_from_db(DBRef, VHost),
3046+ [[Id1]] = ets:match(ets_servers_table(VHost), {Server, '$1'}),
3047+ Id1
3048+ end;
3049+ [[Id]] -> Id
3050+ end.
3051+
3052+get_resource_id_from_db(DBRef, VHost, Resource) ->
3053+ SQuery = ["SELECT resource_id FROM ",resources_table(VHost)," ",
3054+ "WHERE resource=\"",ejabberd_odbc:escape(Resource),"\";"],
3055+ case sql_query_internal(DBRef, SQuery) of
3056+ % no such resource in db
3057+ {data, []} ->
3058+ {ok, []};
3059+ {data, [[DBId]]} ->
3060+ % cache {resource, id} pair
3061+ ets:insert(ets_resources_table(VHost), {Resource, DBId}),
3062+ {ok, DBId}
3063+ end.
3064+get_resource_id(DBRef, VHost, Resource) ->
3065+ % Look at ets
3066+ case ets:match(ets_resources_table(VHost), {Resource, '$1'}) of
3067+ [] ->
3068+ % Look at db
3069+ case get_resource_id_from_db(DBRef, VHost, Resource) of
3070+ % no such resource in db
3071+ {ok, []} ->
3072+ IQuery = ["INSERT INTO ",resources_table(VHost)," ",
3073+ "SET resource=\"",ejabberd_odbc:escape(Resource),"\";"],
3074+ case sql_query_internal_silent(DBRef, IQuery) of
3075+ {updated, _} ->
3076+ {ok, NewId} = get_resource_id_from_db(DBRef, VHost, Resource),
3077+ NewId;
3078+ {error, Reason} ->
3079+ % this can be in clustered environment
3080+ {match, _, _} = regexp:match(Reason, "#23000"),
3081+ ?ERROR_MSG("Duplicate key name for ~p", [Resource]),
3082+ {ok, ClID} = get_resource_id_from_db(DBRef, VHost, Resource),
3083+ ClID
3084+ end;
3085+ {ok, DBId} ->
3086+ DBId
3087+ end;
3088+ [[EtsId]] -> EtsId
3089+ end.
3090+
3091+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3092+%
3093+% SQL internals
3094+%
3095+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3096+% like do_transaction/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
3097+sql_transaction_internal(DBRef, Fun) ->
3098+ case sql_query_internal(DBRef, ["START TRANSACTION;"]) of
3099+ {updated, _} ->
3100+ case catch Fun() of
3101+ error = Err ->
3102+ rollback_internal(DBRef, Err);
3103+ {error, _} = Err ->
3104+ rollback_internal(DBRef, Err);
3105+ {'EXIT', _} = Err ->
3106+ rollback_internal(DBRef, Err);
3107+ Res ->
3108+ case sql_query_internal(DBRef, ["COMMIT;"]) of
3109+ {error, _} -> rollback_internal(DBRef, {commit_error});
3110+ {updated, _} ->
3111+ case Res of
3112+ {atomic, _} -> Res;
3113+ _ -> {atomic, Res}
3114+ end
3115+ end
3116+ end;
3117+ {error, _} ->
3118+ {aborted, {begin_error}}
3119+ end.
3120+
3121+% like rollback/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
3122+rollback_internal(DBRef, Reason) ->
3123+ Res = sql_query_internal(DBRef, ["ROLLBACK;"]),
3124+ {aborted, {Reason, {rollback_result, Res}}}.
3125+
3126+sql_query_internal(DBRef, Query) ->
3127+ case sql_query_internal_silent(DBRef, Query) of
3128+ {error, Reason} ->
3129+ ?ERROR_MSG("~p while ~p", [Reason, lists:append(Query)]),
3130+ {error, Reason};
3131+ Rez -> Rez
3132+ end.
3133+
3134+sql_query_internal_silent(DBRef, Query) ->
3135+ ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
3136+ get_result(mysql_conn:fetch(DBRef, Query, self(), ?TIMEOUT)).
3137+
3138+get_result({updated, MySQLRes}) ->
3139+ {updated, mysql:get_result_affected_rows(MySQLRes)};
3140+get_result({data, MySQLRes}) ->
3141+ {data, mysql:get_result_rows(MySQLRes)};
3142+get_result({error, "query timed out"}) ->
3143+ {error, "query timed out"};
3144+get_result({error, MySQLRes}) ->
3145+ Reason = mysql:get_result_reason(MySQLRes),
3146+ {error, Reason}.
3147--- src/mod_logdb_mysql5.erl.orig Tue Dec 11 14:23:19 2007
3148+++ src/mod_logdb_mysql5.erl Tue Dec 11 11:58:33 2007
3149@@ -0,0 +1,854 @@
3150+%%%----------------------------------------------------------------------
3151+%%% File : mod_logdb_mysql5.erl
3152+%%% Author : Oleg Palij (mailto:o.palij@gmail.com xmpp://malik@jabber.te.ua)
3153+%%% Purpose : MySQL 5 backend for mod_logdb
3154+%%% Version : trunk
3155+%%% Id : $Id$
3156+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
3157+%%%----------------------------------------------------------------------
3158+
3159+-module(mod_logdb_mysql5).
3160+-author('o.palij@gmail.com').
3161+-vsn('$Revision$').
3162+
3163+-include("mod_logdb.hrl").
3164+-include("ejabberd.hrl").
3165+-include("jlib.hrl").
3166+
3167+-behaviour(gen_logdb).
3168+-behaviour(gen_server).
3169+
3170+% gen_server
3171+-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
3172+% gen_mod
3173+-export([start/2, stop/1]).
3174+% gen_logdb
3175+-export([log_message/2,
3176+ rebuild_stats/1,
3177+ rebuild_stats_at/2,
3178+ delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
3179+ get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
3180+ get_dates/1,
3181+ get_users_settings/1, get_user_settings/2, set_user_settings/3]).
3182+
3183+% gen_server call timeout
3184+-define(CALL_TIMEOUT, 60000).
3185+-define(TIMEOUT, 60000).
3186+-define(INDEX_SIZE, integer_to_list(170)).
3187+-define(PROCNAME, mod_logdb_mysql5).
3188+
3189+-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
3190+ list_to_string/1, string_to_list/1,
3191+ convert_timestamp_brief/1]).
3192+
3193+-record(state, {dbref, vhost}).
3194+
3195+% replace "." with "_"
3196+escape_vhost(VHost) -> lists:map(fun(46) -> 95;
3197+ (A) -> A
3198+ end, VHost).
3199+prefix() ->
3200+ "`logdb_".
3201+
3202+suffix(VHost) ->
3203+ "_" ++ escape_vhost(VHost) ++ "`".
3204+
3205+messages_table(VHost, Date) ->
3206+ prefix() ++ "messages_" ++ Date ++ suffix(VHost).
3207+
3208+% TODO: this needs to be redone to unify view name in stored procedure and in delete_messages_at/2
3209+view_table(VHost, Date) ->
3210+ Table = messages_table(VHost, Date),
3211+ TablewoQ = lists:sublist(Table, 2, length(Table) - 2),
3212+ lists:append(["`v_", TablewoQ, "`"]).
3213+
3214+stats_table(VHost) ->
3215+ prefix() ++ "stats" ++ suffix(VHost).
3216+
3217+settings_table(VHost) ->
3218+ prefix() ++ "settings" ++ suffix(VHost).
3219+
3220+users_table(VHost) ->
3221+ prefix() ++ "users" ++ suffix(VHost).
3222+servers_table(VHost) ->
3223+ prefix() ++ "servers" ++ suffix(VHost).
3224+resources_table(VHost) ->
3225+ prefix() ++ "resources" ++ suffix(VHost).
3226+
3227+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3228+%
3229+% gen_mod callbacks
3230+%
3231+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3232+start(VHost, Opts) ->
3233+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3234+ gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
3235+
3236+stop(VHost) ->
3237+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3238+ gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
3239+
3240+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3241+%
3242+% gen_server callbacks
3243+%
3244+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3245+init([VHost, Opts]) ->
3246+ crypto:start(),
3247+
3248+ Server = gen_mod:get_opt(server, Opts, "localhost"),
3249+ Port = gen_mod:get_opt(port, Opts, 3128),
3250+ DB = gen_mod:get_opt(db, Opts, "logdb"),
3251+ User = gen_mod:get_opt(user, Opts, "root"),
3252+ Password = gen_mod:get_opt(password, Opts, ""),
3253+
3254+ LogFun = fun(debug, Format, Argument) ->
3255+ ?MYDEBUG(Format, Argument);
3256+ (error, Format, Argument) ->
3257+ ?ERROR_MSG(Format, Argument);
3258+ (Level, Format, Argument) ->
3259+ ?MYDEBUG("MySQL (~p)~n", [Level]),
3260+ ?MYDEBUG(Format, Argument)
3261+ end,
3262+ case mysql_conn:start(Server, Port, User, Password, DB, [65536, 131072], LogFun) of
3263+ {ok, DBRef} ->
3264+ ok = create_internals(DBRef, VHost),
3265+ ok = create_stats_table(DBRef, VHost),
3266+ ok = create_settings_table(DBRef, VHost),
3267+ ok = create_users_table(DBRef, VHost),
3268+ ok = create_servers_table(DBRef, VHost),
3269+ ok = create_resources_table(DBRef, VHost),
3270+ erlang:monitor(process, DBRef),
3271+ {ok, #state{dbref=DBRef, vhost=VHost}};
3272+ {error, Reason} ->
3273+ ?ERROR_MSG("MySQL connection failed: ~p~n", [Reason]),
3274+ {stop, db_connection_failed}
3275+ end.
3276+
3277+handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3278+ Date = convert_timestamp_brief(Msg#msg.timestamp),
3279+ TableName = messages_table(VHost, Date),
3280+
3281+ Query = [ "CALL logmessage "
3282+ "('", TableName, "',",
3283+ "'", Date, "',",
3284+ "'", Msg#msg.owner_name, "',",
3285+ "'", Msg#msg.peer_name, "',",
3286+ "'", Msg#msg.peer_server, "',",
3287+ "'", ejabberd_odbc:escape(Msg#msg.peer_resource), "',",
3288+ "'", atom_to_list(Msg#msg.direction), "',",
3289+ "'", Msg#msg.type, "',",
3290+ "'", ejabberd_odbc:escape(Msg#msg.subject), "',",
3291+ "'", ejabberd_odbc:escape(Msg#msg.body), "',",
3292+ "'", Msg#msg.timestamp, "');"],
3293+
3294+ Reply =
3295+ case sql_query_internal(DBRef, Query) of
3296+ {updated, _} ->
3297+ ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
3298+ Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
3299+ ok;
3300+ {error, _Reason} ->
3301+ error
3302+ end,
3303+ {reply, Reply, State};
3304+handle_call({rebuild_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3305+ ok = delete_nonexistent_stats(DBRef, VHost),
3306+ Reply =
3307+ lists:foreach(fun(Date) ->
3308+ catch rebuild_stats_at_int(DBRef, VHost, Date)
3309+ end, get_dates_int(DBRef, VHost)),
3310+ {reply, Reply, State};
3311+handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3312+ Reply = rebuild_stats_at_int(DBRef, VHost, Date),
3313+ {reply, Reply, State};
3314+handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
3315+ {reply, error, State};
3316+handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3317+ Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
3318+ ["\"",Timestamp,"\"",","]
3319+ end, Msgs),
3320+
3321+ Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
3322+
3323+ Query = ["DELETE FROM ",messages_table(VHost, Date)," ",
3324+ "WHERE timestamp IN (", Temp1],
3325+
3326+ Reply =
3327+ case sql_query_internal(DBRef, Query) of
3328+ {updated, Aff} ->
3329+ ?MYDEBUG("Aff=~p", [Aff]),
3330+ rebuild_stats_at_int(DBRef, VHost, Date);
3331+ {error, _} ->
3332+ error
3333+ end,
3334+ {reply, Reply, State};
3335+handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3336+ DQuery = ["DELETE FROM ",messages_table(VHost, Date)," ",
3337+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
3338+ Reply =
3339+ case sql_query_internal(DBRef, DQuery) of
3340+ {updated, _} ->
3341+ rebuild_stats_at_int(DBRef, VHost, Date);
3342+ {error, _} ->
3343+ error
3344+ end,
3345+ {reply, Reply, State};
3346+handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3347+ Fun = fun() ->
3348+ {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Date),";"]),
3349+ TQuery = ["DELETE FROM ",stats_table(VHost)," "
3350+ "WHERE at=\"",Date,"\";"],
3351+ {updated, _} = sql_query_internal(DBRef, TQuery),
3352+ VQuery = ["DROP VIEW IF EXISTS ",view_table(VHost,Date),";"],
3353+ {updated, _} = sql_query_internal(DBRef, VQuery)
3354+ end,
3355+ Reply =
3356+ case sql_transaction_internal(DBRef, Fun) of
3357+ {atomic, _} ->
3358+ ok;
3359+ {aborted, _} ->
3360+ error
3361+ end,
3362+ {reply, Reply, State};
3363+handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3364+ SName = stats_table(VHost),
3365+ Query = ["SELECT at, sum(count) ",
3366+ "FROM ",SName," ",
3367+ "GROUP BY at ",
3368+ "ORDER BY DATE(at) DESC;"
3369+ ],
3370+ Reply =
3371+ case sql_query_internal(DBRef, Query) of
3372+ {data, Result} ->
3373+ {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
3374+ {error, Reason} ->
3375+ % TODO: Duplicate error message ?
3376+ {error, Reason}
3377+ end,
3378+ {reply, Reply, State};
3379+handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3380+ SName = stats_table(VHost),
3381+ Query = ["SELECT username, count ",
3382+ "FROM ",SName," ",
3383+ "JOIN ",users_table(VHost)," ON owner_id=user_id "
3384+ "WHERE at=\"",Date,"\" ",
3385+ "ORDER BY count DESC;"
3386+ ],
3387+ Reply =
3388+ case sql_query_internal(DBRef, Query) of
3389+ {data, Result} ->
3390+ {ok, [ {User, list_to_integer(Count)} || [User, Count] <- Result ]};
3391+ {error, Reason} ->
3392+ {error, Reason}
3393+ end,
3394+ {reply, Reply, State};
3395+handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3396+ SName = stats_table(VHost),
3397+ UName = users_table(VHost),
3398+ Query = ["SELECT stats.at, stats.count ",
3399+ "FROM ",UName," AS users ",
3400+ "JOIN ",SName," AS stats ON owner_id=user_id "
3401+ "WHERE users.username=\"",User,"\" ",
3402+ "ORDER BY DATE(at) DESC;"
3403+ ],
3404+ Reply =
3405+ case sql_query_internal(DBRef, Query) of
3406+ {data, Result} ->
3407+ {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
3408+ {error, Result} ->
3409+ {error, Result}
3410+ end,
3411+ {reply, Reply, State};
3412+handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3413+ Query = ["SELECT peer_name,",
3414+ "peer_server,",
3415+ "peer_resource,",
3416+ "direction,"
3417+ "type,"
3418+ "subject,"
3419+ "body,"
3420+ "timestamp "
3421+ "FROM ",view_table(VHost, Date)," "
3422+ "WHERE owner_name=\"",User,"\";"],
3423+ Reply =
3424+ case sql_query_internal(DBRef, Query) of
3425+ {data, Result} ->
3426+ Fun = fun([Peer_name, Peer_server, Peer_resource,
3427+ Direction,
3428+ Type,
3429+ Subject, Body,
3430+ Timestamp]) ->
3431+ #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
3432+ direction=list_to_atom(Direction),
3433+ type=Type,
3434+ subject=Subject, body=Body,
3435+ timestamp=Timestamp}
3436+ end,
3437+ {ok, lists:map(Fun, Result)};
3438+ {error, Reason} ->
3439+ {error, Reason}
3440+ end,
3441+ {reply, Reply, State};
3442+handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3443+ SName = stats_table(VHost),
3444+ Query = ["SELECT at ",
3445+ "FROM ",SName," ",
3446+ "GROUP BY at ",
3447+ "ORDER BY DATE(at) DESC;"
3448+ ],
3449+ Reply =
3450+ case sql_query_internal(DBRef, Query) of
3451+ {data, Result} ->
3452+ [ Date || [Date] <- Result ];
3453+ {error, Reason} ->
3454+ {error, Reason}
3455+ end,
3456+ {reply, Reply, State};
3457+handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3458+ Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
3459+ "FROM ",settings_table(VHost)," ",
3460+ "JOIN ",users_table(VHost)," ON user_id=owner_id;"],
3461+ Reply =
3462+ case sql_query_internal(DBRef, Query) of
3463+ {data, Result} ->
3464+ {ok, lists:map(fun([Owner, DoLogDef, DoLogL, DoNotLogL]) ->
3465+ #user_settings{owner_name=Owner,
3466+ dolog_default=list_to_bool(DoLogDef),
3467+ dolog_list=string_to_list(DoLogL),
3468+ donotlog_list=string_to_list(DoNotLogL)
3469+ }
3470+ end, Result)};
3471+ {error, _} ->
3472+ error
3473+ end,
3474+ {reply, Reply, State};
3475+handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3476+ Query = ["SELECT dolog_default,dolog_list,donotlog_list FROM ",settings_table(VHost)," ",
3477+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
3478+ Reply =
3479+ case sql_query_internal(DBRef, Query) of
3480+ {data, []} ->
3481+ {ok, []};
3482+ {data, [[Owner, DoLogDef, DoLogL, DoNotLogL]]} ->
3483+ {ok, #user_settings{owner_name=Owner,
3484+ dolog_default=list_to_bool(DoLogDef),
3485+ dolog_list=string_to_list(DoLogL),
3486+ donotlog_list=string_to_list(DoNotLogL)}};
3487+ {error, _} ->
3488+ error
3489+ end,
3490+ {reply, Reply, State};
3491+handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
3492+ dolog_list=DoLogL,
3493+ donotlog_list=DoNotLogL}},
3494+ _From, #state{dbref=DBRef, vhost=VHost} = State) ->
3495+ User_id = get_user_id(DBRef, VHost, User),
3496+ Query = ["UPDATE ",settings_table(VHost)," ",
3497+ "SET dolog_default=",bool_to_list(DoLogDef),", ",
3498+ "dolog_list='",list_to_string(DoLogL),"', ",
3499+ "donotlog_list='",list_to_string(DoNotLogL),"' ",
3500+ "WHERE owner_id=",User_id,";"],
3501+
3502+ Reply =
3503+ case sql_query_internal(DBRef, Query) of
3504+ {updated, 0} ->
3505+ IQuery = ["INSERT INTO ",settings_table(VHost)," ",
3506+ "(owner_id, dolog_default, dolog_list, donotlog_list) ",
3507+ "VALUES ",
3508+ "(",User_id,",",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
3509+ case sql_query_internal_silent(DBRef, IQuery) of
3510+ {updated, _} ->
3511+ ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
3512+ ok;
3513+ {error, Reason} ->
3514+ case regexp:match(Reason, "#23000") of
3515+ % Already exists
3516+ {match, _, _} ->
3517+ ok;
3518+ _ ->
3519+ ?ERROR_MSG("Failed setup user ~p@~p: ~p", [User, VHost, Reason]),
3520+ error
3521+ end
3522+ end;
3523+ {updated, 1} ->
3524+ ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
3525+ ok;
3526+ {error, _} ->
3527+ error
3528+ end,
3529+ {reply, Reply, State};
3530+handle_call({stop}, _From, #state{vhost=VHost}=State) ->
3531+ ?MYDEBUG("Stoping mysql5 backend for ~p", [VHost]),
3532+ {stop, normal, ok, State};
3533+handle_call(Msg, _From, State) ->
3534+ ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
3535+ {noreply, State}.
3536+
3537+handle_cast(Msg, State) ->
3538+ ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
3539+ {noreply, State}.
3540+
3541+handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
3542+ {stop, connection_dropped, State};
3543+handle_info(Info, State) ->
3544+ ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
3545+ {noreply, State}.
3546+
3547+terminate(_Reason, _State) ->
3548+ ok.
3549+
3550+code_change(_OldVsn, State, _Extra) ->
3551+ {ok, State}.
3552+
3553+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3554+%
3555+% gen_logdb callbacks
3556+%
3557+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3558+log_message(VHost, Msg) ->
3559+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3560+ gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
3561+rebuild_stats(VHost) ->
3562+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3563+ gen_server:call(Proc, {rebuild_stats}, ?CALL_TIMEOUT).
3564+rebuild_stats_at(VHost, Date) ->
3565+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3566+ gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
3567+delete_messages_by_user_at(VHost, Msgs, Date) ->
3568+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3569+ gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
3570+delete_all_messages_by_user_at(User, VHost, Date) ->
3571+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3572+ gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
3573+delete_messages_at(VHost, Date) ->
3574+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3575+ gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
3576+get_vhost_stats(VHost) ->
3577+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3578+ gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
3579+get_vhost_stats_at(VHost, Date) ->
3580+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3581+ gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
3582+get_user_stats(User, VHost) ->
3583+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3584+ gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
3585+get_user_messages_at(User, VHost, Date) ->
3586+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3587+ gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
3588+get_dates(VHost) ->
3589+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3590+ gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
3591+get_users_settings(VHost) ->
3592+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3593+ gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
3594+get_user_settings(User, VHost) ->
3595+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3596+ gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
3597+set_user_settings(User, VHost, Set) ->
3598+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3599+ gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
3600+
3601+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3602+%
3603+% internals
3604+%
3605+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3606+get_dates_int(DBRef, VHost) ->
3607+ case sql_query_internal(DBRef, ["SHOW TABLES"]) of
3608+ {data, Tables} ->
3609+ lists:foldl(fun([Table], Dates) ->
3610+ % TODO: check prefix()
3611+ case regexp:match(Table, escape_vhost(VHost)) of
3612+ {match, _, _} ->
3613+ case regexp:match(Table,"[0-9]+-[0-9]+-[0-9]+") of
3614+ {match, S, E} ->
3615+ lists:append(Dates, [lists:sublist(Table,S,E)]);
3616+ nomatch ->
3617+ Dates
3618+ end;
3619+ nomatch ->
3620+ Dates
3621+ end
3622+ end, [], Tables);
3623+ {error, _} ->
3624+ []
3625+ end.
3626+
3627+rebuild_stats_at_int(DBRef, VHost, Date) ->
3628+ Table = messages_table(VHost, Date),
3629+ STable = stats_table(VHost),
3630+
3631+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," WRITE, ",
3632+ STable," WRITE;"]),
3633+ Fun =
3634+ fun() ->
3635+ DQuery = [ "DELETE FROM ",STable," ",
3636+ "WHERE at='",Date,"';"],
3637+
3638+ {updated, _} = sql_query_internal(DBRef, DQuery),
3639+
3640+ SQuery = ["INSERT INTO ",STable," ",
3641+ "(owner_id,at,count) ",
3642+ "SELECT owner_id,\"",Date,"\"",",count(*) ",
3643+ "FROM ",Table," GROUP BY owner_id;"],
3644+
3645+ case sql_query_internal(DBRef, SQuery) of
3646+ {updated, 0} ->
3647+ {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]),
3648+ ok;
3649+ {updated, _} -> ok;
3650+ {error, _} -> error
3651+ end
3652+ end,
3653+
3654+ Res = case sql_transaction_internal(DBRef, Fun) of
3655+ {atomic, _} ->
3656+ ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
3657+ ok;
3658+ {aborted, _} ->
3659+ error
3660+ end,
3661+ {updated, _} = sql_query_internal(DBRef, ["UNLOCK TABLES;"]),
3662+ Res.
3663+
3664+
3665+delete_nonexistent_stats(DBRef, VHost) ->
3666+ Dates = get_dates_int(DBRef, VHost),
3667+ STable = stats_table(VHost),
3668+
3669+ Temp = lists:flatmap(fun(Date) ->
3670+ ["\"",Date,"\"",","]
3671+ end, Dates),
3672+
3673+ Temp1 = case Temp of
3674+ [] ->
3675+ ["\"\""];
3676+ _ ->
3677+ % replace last "," with ");"
3678+ lists:append([lists:sublist(Temp, length(Temp)-1), ");"])
3679+ end,
3680+
3681+ Query = ["DELETE FROM ",STable," ",
3682+ "WHERE at NOT IN (", Temp1],
3683+
3684+ case sql_query_internal(DBRef, Query) of
3685+ {updated, _} ->
3686+ ok;
3687+ {error, _} ->
3688+ error
3689+ end.
3690+
3691+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3692+%
3693+% tables internals
3694+%
3695+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3696+create_stats_table(DBRef, VHost) ->
3697+ SName = stats_table(VHost),
3698+ Query = ["CREATE TABLE ",SName," (",
3699+ "owner_id MEDIUMINT UNSIGNED, ",
3700+ "at VARCHAR(11), ",
3701+ "count INT(11), ",
3702+ "INDEX(owner_id), ",
3703+ "INDEX(at)"
3704+ ") ENGINE=InnoDB CHARACTER SET utf8;"
3705+ ],
3706+ case sql_query_internal_silent(DBRef, Query) of
3707+ {updated, _} ->
3708+ ?MYDEBUG("Created stats table for ~p", [VHost]),
3709+ lists:foreach(fun(Date) ->
3710+ rebuild_stats_at_int(DBRef, VHost, Date)
3711+ end, get_dates_int(DBRef, VHost)),
3712+ ok;
3713+ {error, Reason} ->
3714+ case regexp:match(Reason, "#42S01") of
3715+ {match, _, _} ->
3716+ ?MYDEBUG("Stats table for ~p already exists", [VHost]),
3717+ ok;
3718+ _ ->
3719+ ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
3720+ error
3721+ end
3722+ end.
3723+
3724+create_settings_table(DBRef, VHost) ->
3725+ SName = settings_table(VHost),
3726+ Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
3727+ "owner_id MEDIUMINT UNSIGNED PRIMARY KEY, ",
3728+ "dolog_default TINYINT(1) NOT NULL DEFAULT 1, ",
3729+ "dolog_list TEXT, ",
3730+ "donotlog_list TEXT ",
3731+ ") ENGINE=InnoDB CHARACTER SET utf8;"
3732+ ],
3733+ case sql_query_internal(DBRef, Query) of
3734+ {updated, _} ->
3735+ ?MYDEBUG("Created settings table for ~p", [VHost]),
3736+ ok;
3737+ {error, _} ->
3738+ error
3739+ end.
3740+
3741+create_users_table(DBRef, VHost) ->
3742+ SName = users_table(VHost),
3743+ Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
3744+ "username TEXT NOT NULL, ",
3745+ "user_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
3746+ "UNIQUE INDEX(username(",?INDEX_SIZE,")) ",
3747+ ") ENGINE=InnoDB CHARACTER SET utf8;"
3748+ ],
3749+ case sql_query_internal(DBRef, Query) of
3750+ {updated, _} ->
3751+ ?MYDEBUG("Created users table for ~p", [VHost]),
3752+ ok;
3753+ {error, _} ->
3754+ error
3755+ end.
3756+
3757+create_servers_table(DBRef, VHost) ->
3758+ SName = servers_table(VHost),
3759+ Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
3760+ "server TEXT NOT NULL, ",
3761+ "server_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
3762+ "UNIQUE INDEX(server(",?INDEX_SIZE,")) ",
3763+ ") ENGINE=InnoDB CHARACTER SET utf8;"
3764+ ],
3765+ case sql_query_internal(DBRef, Query) of
3766+ {updated, _} ->
3767+ ?MYDEBUG("Created servers table for ~p", [VHost]),
3768+ ok;
3769+ {error, _} ->
3770+ error
3771+ end.
3772+
3773+create_resources_table(DBRef, VHost) ->
3774+ RName = resources_table(VHost),
3775+ Query = ["CREATE TABLE IF NOT EXISTS ",RName," (",
3776+ "resource TEXT NOT NULL, ",
3777+ "resource_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
3778+ "UNIQUE INDEX(resource(",?INDEX_SIZE,")) ",
3779+ ") ENGINE=InnoDB CHARACTER SET utf8;"
3780+ ],
3781+ case sql_query_internal(DBRef, Query) of
3782+ {updated, _} ->
3783+ ?MYDEBUG("Created resources table for ~p", [VHost]),
3784+ ok;
3785+ {error, _} ->
3786+ error
3787+ end.
3788+
3789+create_internals(DBRef, VHost) ->
3790+ sql_query_internal(DBRef, ["DROP PROCEDURE IF EXISTS `logmessage`;"]),
3791+ case sql_query_internal(DBRef, [get_logmessage(VHost)]) of
3792+ {updated, _} ->
3793+ ?MYDEBUG("Created logmessage for ~p", [VHost]),
3794+ ok;
3795+ {error, _} ->
3796+ error
3797+ end.
3798+
3799+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3800+%
3801+% SQL internals
3802+%
3803+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3804+% like do_transaction/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
3805+sql_transaction_internal(DBRef, Fun) ->
3806+ case sql_query_internal(DBRef, ["START TRANSACTION;"]) of
3807+ {updated, _} ->
3808+ case catch Fun() of
3809+ error = Err ->
3810+ rollback_internal(DBRef, Err);
3811+ {error, _} = Err ->
3812+ rollback_internal(DBRef, Err);
3813+ {'EXIT', _} = Err ->
3814+ rollback_internal(DBRef, Err);
3815+ Res ->
3816+ case sql_query_internal(DBRef, ["COMMIT;"]) of
3817+ {error, _} -> rollback_internal(DBRef, {commit_error});
3818+ {updated, _} ->
3819+ case Res of
3820+ {atomic, _} -> Res;
3821+ _ -> {atomic, Res}
3822+ end
3823+ end
3824+ end;
3825+ {error, _} ->
3826+ {aborted, {begin_error}}
3827+ end.
3828+
3829+% like rollback/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
3830+rollback_internal(DBRef, Reason) ->
3831+ Res = sql_query_internal(DBRef, ["ROLLBACK;"]),
3832+ {aborted, {Reason, {rollback_result, Res}}}.
3833+
3834+sql_query_internal(DBRef, Query) ->
3835+ case sql_query_internal_silent(DBRef, Query) of
3836+ {error, Reason} ->
3837+ ?ERROR_MSG("~p while ~p", [Reason, lists:append(Query)]),
3838+ {error, Reason};
3839+ Rez -> Rez
3840+ end.
3841+
3842+sql_query_internal_silent(DBRef, Query) ->
3843+ ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
3844+ get_result(mysql_conn:fetch(DBRef, Query, self(), ?TIMEOUT)).
3845+
3846+get_result({updated, MySQLRes}) ->
3847+ {updated, mysql:get_result_affected_rows(MySQLRes)};
3848+get_result({data, MySQLRes}) ->
3849+ {data, mysql:get_result_rows(MySQLRes)};
3850+get_result({error, "query timed out"}) ->
3851+ {error, "query timed out"};
3852+get_result({error, MySQLRes}) ->
3853+ Reason = mysql:get_result_reason(MySQLRes),
3854+ {error, Reason}.
3855+
3856+get_user_id(DBRef, VHost, User) ->
3857+ SQuery = ["SELECT user_id FROM ",users_table(VHost)," ",
3858+ "WHERE username=\"",User,"\";"],
3859+ case sql_query_internal(DBRef, SQuery) of
3860+ {data, []} ->
3861+ IQuery = ["INSERT INTO ",users_table(VHost)," ",
3862+ "SET username=\"",User,"\";"],
3863+ case sql_query_internal_silent(DBRef, IQuery) of
3864+ {updated, _} ->
3865+ {data, [[DBIdNew]]} = sql_query_internal(DBRef, SQuery),
3866+ DBIdNew;
3867+ {error, Reason} ->
3868+ % this can be in clustered environment
3869+ {match, _, _} = regexp:match(Reason, "#23000"),
3870+ ?ERROR_MSG("Duplicate key name for ~p", [User]),
3871+ {data, [[ClID]]} = sql_query_internal(DBRef, SQuery),
3872+ ClID
3873+ end;
3874+ {data, [[DBId]]} ->
3875+ DBId
3876+ end.
3877+
3878+get_logmessage(VHost) ->
3879+ UName = users_table(VHost),
3880+ SName = servers_table(VHost),
3881+ RName = resources_table(VHost),
3882+ StName = stats_table(VHost),
3883+ io_lib:format("
3884+CREATE PROCEDURE logmessage(tablename TEXT, atdate TEXT, owner TEXT, peer_name TEXT, peer_server TEXT, peer_resource TEXT, mdirection VARCHAR(4), mtype VARCHAR(10), msubject TEXT, mbody TEXT, mtimestamp DOUBLE)
3885+BEGIN
3886+ DECLARE ownerID MEDIUMINT UNSIGNED;
3887+ DECLARE peer_nameID MEDIUMINT UNSIGNED;
3888+ DECLARE peer_serverID MEDIUMINT UNSIGNED;
3889+ DECLARE peer_resourceID MEDIUMINT UNSIGNED;
3890+ DECLARE Vmtype VARCHAR(10);
3891+ DECLARE Vmtimestamp DOUBLE;
3892+ DECLARE Vmdirection VARCHAR(4);
3893+ DECLARE Vmbody TEXT;
3894+ DECLARE Vmsubject TEXT;
3895+ DECLARE iq TEXT;
3896+ DECLARE cq TEXT;
3897+ DECLARE viewname TEXT;
3898+ DECLARE notable INT;
3899+ DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' SET @notable = 1;
3900+
3901+ SET @notable = 0;
3902+ SET @ownerID = NULL;
3903+ SET @peer_nameID = NULL;
3904+ SET @peer_serverID = NULL;
3905+ SET @peer_resourceID = NULL;
3906+
3907+ SET @Vmtype = mtype;
3908+ SET @Vmtimestamp = mtimestamp;
3909+ SET @Vmdirection = mdirection;
3910+ SET @Vmbody = mbody;
3911+ SET @Vmsubject = msubject;
3912+
3913+ SELECT user_id INTO @ownerID FROM ~s WHERE username=owner;
3914+ IF @ownerID IS NULL THEN
3915+ INSERT INTO ~s SET username=owner;
3916+ SET @ownerID = LAST_INSERT_ID();
3917+ END IF;
3918+
3919+ SELECT user_id INTO @peer_nameID FROM ~s WHERE username=peer_name;
3920+ IF @peer_nameID IS NULL THEN
3921+ INSERT INTO ~s SET username=peer_name;
3922+ SET @peer_nameID = LAST_INSERT_ID();
3923+ END IF;
3924+
3925+ SELECT server_id INTO @peer_serverID FROM ~s WHERE server=peer_server;
3926+ IF @peer_serverID IS NULL THEN
3927+ INSERT INTO ~s SET server=peer_server;
3928+ SET @peer_serverID = LAST_INSERT_ID();
3929+ END IF;
3930+
3931+ SELECT resource_id INTO @peer_resourceID FROM ~s WHERE resource=peer_resource;
3932+ IF @peer_resourceID IS NULL THEN
3933+ INSERT INTO ~s SET resource=peer_resource;
3934+ SET @peer_resourceID = LAST_INSERT_ID();
3935+ END IF;
3936+
3937+ SET @iq = CONCAT(\"INSERT INTO \",tablename,\" (owner_id, peer_name_id, peer_server_id, peer_resource_id, direction, type, subject, body, timestamp) VALUES (@ownerID,@peer_nameID,@peer_serverID,@peer_resourceID,@Vmdirection,@Vmtype,@Vmsubject,@Vmbody,@Vmtimestamp);\");
3938+ PREPARE insertmsg FROM @iq;
3939+
3940+ IF @notable = 1 THEN
3941+ SET @cq = CONCAT(\"CREATE TABLE \",tablename,\" (
3942+ owner_id MEDIUMINT UNSIGNED,
3943+ peer_name_id MEDIUMINT UNSIGNED,
3944+ peer_server_id MEDIUMINT UNSIGNED,
3945+ peer_resource_id MEDIUMINT(8) UNSIGNED,
3946+ direction ENUM('to', 'from'),
3947+ type ENUM('chat','error','groupchat','headline','normal') NOT NULL,
3948+ subject TEXT,
3949+ body TEXT,
3950+ timestamp DOUBLE,
3951+ ext INTEGER DEFAULT NULL,
3952+ INDEX owner_i (owner_id),
3953+ INDEX peer_i (peer_name_id, peer_server_id),
3954+ INDEX ext_i (ext),
3955+ FULLTEXT (body)
3956+ ) ENGINE=MyISAM CHARACTER SET utf8;\");
3957+ PREPARE createtable FROM @cq;
3958+ EXECUTE createtable;
3959+ DEALLOCATE PREPARE createtable;
3960+
3961+ SET @viewname = CONCAT(\"`v_\", TRIM(BOTH '`' FROM tablename), \"`\");
3962+ SET @cq = CONCAT(\"CREATE OR REPLACE VIEW \",@viewname,\" AS
3963+ SELECT owner.username AS owner_name,
3964+ peer.username AS peer_name,
3965+ servers.server AS peer_server,
3966+ resources.resource AS peer_resource,
3967+ messages.direction,
3968+ messages.type,
3969+ messages.subject,
3970+ messages.body,
3971+ messages.timestamp
3972+ FROM
3973+ ~s owner,
3974+ ~s peer,
3975+ ~s servers,
3976+ ~s resources,
3977+ \", tablename,\" messages
3978+ WHERE
3979+ owner.user_id=messages.owner_id and
3980+ peer.user_id=messages.peer_name_id and
3981+ servers.server_id=messages.peer_server_id and
3982+ resources.resource_id=messages.peer_resource_id
3983+ ORDER BY messages.timestamp;\");
3984+ PREPARE createview FROM @cq;
3985+ EXECUTE createview;
3986+ DEALLOCATE PREPARE createview;
3987+
3988+ SET @notable = 0;
3989+ PREPARE insertmsg FROM @iq;
3990+ EXECUTE insertmsg;
3991+ ELSEIF @notable = 0 THEN
3992+ EXECUTE insertmsg;
3993+ END IF;
3994+
3995+ DEALLOCATE PREPARE insertmsg;
3996+
3997+ IF @notable = 0 THEN
3998+ UPDATE ~s SET count=count+1 WHERE owner_id=@ownerID AND at=atdate;
3999+ IF ROW_COUNT() = 0 THEN
4000+ INSERT INTO ~s (owner_id, at, count) VALUES (@ownerID, atdate, 1);
4001+ END IF;
4002+ END IF;
4003+END;", [UName,UName,UName,UName,SName,SName,RName,RName,UName,UName,SName,RName,StName,StName]).
4004--- src/mod_logdb_pgsql.erl.orig Tue Dec 11 14:23:19 2007
4005+++ src/mod_logdb_pgsql.erl Sun Nov 18 20:53:55 2007
4006@@ -0,0 +1,911 @@
4007+%%%----------------------------------------------------------------------
4008+%%% File : mod_logdb_pgsql.erl
4009+%%% Author : Oleg Palij (mailto:o.palij@gmail.com xmpp://malik@jabber.te.ua)
4010+%%% Purpose : Posgresql backend for mod_logdb
4011+%%% Version : trunk
4012+%%% Id : $Id$
4013+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
4014+%%%----------------------------------------------------------------------
4015+
4016+-module(mod_logdb_pgsql).
4017+-author('o.palij@gmail.com').
4018+-vsn('$Revision$').
4019+
4020+-include("mod_logdb.hrl").
4021+-include("ejabberd.hrl").
4022+-include("jlib.hrl").
4023+
4024+-behaviour(gen_logdb).
4025+-behaviour(gen_server).
4026+
4027+% gen_server
4028+-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
4029+% gen_mod
4030+-export([start/2, stop/1]).
4031+% gen_logdb
4032+-export([log_message/2,
4033+ rebuild_stats/1,
4034+ rebuild_stats_at/2,
4035+ delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
4036+ get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
4037+ get_dates/1,
4038+ get_users_settings/1, get_user_settings/2, set_user_settings/3]).
4039+
4040+% gen_server call timeout
4041+-define(CALL_TIMEOUT, 60000).
4042+-define(TIMEOUT, 60000).
4043+-define(PROCNAME, mod_logdb_pgsql).
4044+
4045+-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
4046+ list_to_string/1, string_to_list/1,
4047+ convert_timestamp_brief/1]).
4048+
4049+-record(state, {dbref, vhost, schema}).
4050+
4051+% replace "." with "_"
4052+escape_vhost(VHost) -> lists:map(fun(46) -> 95;
4053+ (A) -> A
4054+ end, VHost).
4055+
4056+prefix(Schema) ->
4057+ Schema ++ ".\"" ++ "logdb_".
4058+
4059+suffix(VHost) ->
4060+ "_" ++ escape_vhost(VHost) ++ "\"".
4061+
4062+messages_table(VHost, Schema, Date) ->
4063+ prefix(Schema) ++ "messages_" ++ Date ++ suffix(VHost).
4064+
4065+% TODO: this needs to be redone to unify view name in stored procedure and in delete_messages_at/2
4066+view_table(VHost, Schema, Date) ->
4067+ Table = messages_table(VHost, Schema, Date),
4068+ TablewoS = lists:sublist(Table, length(Schema) + 3, length(Table) - length(Schema) - 3),
4069+ lists:append([Schema, ".\"v_", TablewoS, "\""]).
4070+
4071+stats_table(VHost, Schema) ->
4072+ prefix(Schema) ++ "stats" ++ suffix(VHost).
4073+
4074+settings_table(VHost, Schema) ->
4075+ prefix(Schema) ++ "settings" ++ suffix(VHost).
4076+
4077+users_table(VHost, Schema) ->
4078+ prefix(Schema) ++ "users" ++ suffix(VHost).
4079+servers_table(VHost, Schema) ->
4080+ prefix(Schema) ++ "servers" ++ suffix(VHost).
4081+resources_table(VHost, Schema) ->
4082+ prefix(Schema) ++ "resources" ++ suffix(VHost).
4083+
4084+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4085+%
4086+% gen_mod callbacks
4087+%
4088+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4089+start(VHost, Opts) ->
4090+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4091+ gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
4092+
4093+stop(VHost) ->
4094+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4095+ gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
4096+
4097+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4098+%
4099+% gen_server callbacks
4100+%
4101+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4102+init([VHost, Opts]) ->
4103+ Server = gen_mod:get_opt(server, Opts, "localhost"),
4104+ DB = gen_mod:get_opt(db, Opts, "ejabberd_logdb"),
4105+ User = gen_mod:get_opt(user, Opts, "root"),
4106+ Port = gen_mod:get_opt(port, Opts, 5432),
4107+ Password = gen_mod:get_opt(password, Opts, ""),
4108+ Schema = gen_mod:get_opt(schema, Opts, "public"),
4109+
4110+ case catch pgsql:connect(Server, DB, User, Password, Port) of
4111+ {ok, DBRef} ->
4112+ {updated, _} = sql_query_internal(DBRef, ["SET SEARCH_PATH TO ",Schema,";"]),
4113+ ok = create_internals(DBRef, VHost, Schema),
4114+ ok = create_stats_table(DBRef, VHost, Schema),
4115+ ok = create_settings_table(DBRef, VHost, Schema),
4116+ ok = create_users_table(DBRef, VHost, Schema),
4117+ ok = create_servers_table(DBRef, VHost, Schema),
4118+ ok = create_resources_table(DBRef, VHost, Schema),
4119+ erlang:monitor(process, DBRef),
4120+ {ok, #state{dbref=DBRef, vhost=VHost, schema=Schema}};
4121+ % this does not work
4122+ {error, Reason} ->
4123+ ?ERROR_MSG("PgSQL connection failed: ~p~n", [Reason]),
4124+ {stop, db_connection_failed};
4125+ % and this too, becouse pgsql_conn do exit() which can not be catched
4126+ {'EXIT', Rez} ->
4127+ ?ERROR_MSG("Rez: ~p~n", [Rez]),
4128+ {stop, db_connection_failed}
4129+ end.
4130+
4131+handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4132+ Date = convert_timestamp_brief(Msg#msg.timestamp),
4133+ TableName = messages_table(VHost, Schema, Date),
4134+
4135+ Query = [ "SELECT logmessage "
4136+ "('", TableName, "',",
4137+ "'", Date, "',",
4138+ "'", Msg#msg.owner_name, "',",
4139+ "'", Msg#msg.peer_name, "',",
4140+ "'", Msg#msg.peer_server, "',",
4141+ "'", ejabberd_odbc:escape(Msg#msg.peer_resource), "',",
4142+ "'", atom_to_list(Msg#msg.direction), "',",
4143+ "'", Msg#msg.type, "',",
4144+ "'", ejabberd_odbc:escape(Msg#msg.subject), "',",
4145+ "'", ejabberd_odbc:escape(Msg#msg.body), "',",
4146+ "'", Msg#msg.timestamp, "');"],
4147+
4148+ Reply =
4149+ case sql_query_internal_silent(DBRef, Query) of
4150+ % TODO: change this
4151+ {data, [{"0"}]} ->
4152+ ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
4153+ Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
4154+ ok;
4155+ {error, _Reason} ->
4156+ error
4157+ end,
4158+ {reply, Reply, State};
4159+handle_call({rebuild_stats}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4160+ ok = delete_nonexistent_stats(DBRef, VHost, Schema),
4161+ Reply =
4162+ lists:foreach(fun(Date) ->
4163+ catch rebuild_stats_at_int(DBRef, VHost, Schema, Date)
4164+ end, get_dates_int(DBRef, VHost)),
4165+ {reply, Reply, State};
4166+handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4167+ Reply = rebuild_stats_at_int(DBRef, VHost, Schema, Date),
4168+ {reply, Reply, State};
4169+handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
4170+ {reply, error, State};
4171+handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4172+ Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
4173+ ["'",Timestamp,"'",","]
4174+ end, Msgs),
4175+
4176+ Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
4177+
4178+ Query = ["DELETE FROM ",messages_table(VHost, Schema, Date)," ",
4179+ "WHERE timestamp IN (", Temp1],
4180+
4181+ Reply =
4182+ case sql_query_internal(DBRef, Query) of
4183+ {updated, _} ->
4184+ rebuild_stats_at_int(DBRef, VHost, Schema, Date);
4185+ {error, _} ->
4186+ error
4187+ end,
4188+ {reply, Reply, State};
4189+handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4190+ DQuery = ["DELETE FROM ",messages_table(VHost, Schema, Date)," ",
4191+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
4192+ Reply =
4193+ case sql_query_internal(DBRef, DQuery) of
4194+ {updated, _} ->
4195+ rebuild_stats_at_int(DBRef, VHost, Schema, Date);
4196+ {error, _} ->
4197+ error
4198+ end,
4199+ {reply, Reply, State};
4200+handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4201+ % TODO
4202+ {updated, _} = sql_query_internal(DBRef, ["DROP VIEW ",view_table(VHost, Schema, Date),";"]),
4203+ Reply =
4204+ case sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Schema, Date),";"]) of
4205+ {updated, _} ->
4206+ Query = ["DELETE FROM ",stats_table(VHost, Schema)," "
4207+ "WHERE at='",Date,"';"],
4208+ case sql_query_internal(DBRef, Query) of
4209+ {updated, _} ->
4210+ ok;
4211+ {error, _} ->
4212+ error
4213+ end;
4214+ {error, _} ->
4215+ error
4216+ end,
4217+ {reply, Reply, State};
4218+handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4219+ SName = stats_table(VHost, Schema),
4220+ Query = ["SELECT at, sum(count) ",
4221+ "FROM ",SName," ",
4222+ "GROUP BY at ",
4223+ "ORDER BY DATE(at) DESC;"
4224+ ],
4225+ Reply =
4226+ case sql_query_internal(DBRef, Query) of
4227+ {data, Recs} ->
4228+ {ok, [ {Date, list_to_integer(Count)} || {Date, Count} <- Recs]};
4229+ {error, Reason} ->
4230+ % TODO: Duplicate error message ?
4231+ {error, Reason}
4232+ end,
4233+ {reply, Reply, State};
4234+handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4235+ SName = stats_table(VHost, Schema),
4236+ Query = ["SELECT username, count ",
4237+ "FROM ",SName," ",
4238+ "JOIN ",users_table(VHost, Schema)," ON owner_id=user_id "
4239+ "WHERE at='",Date,"';"
4240+ ],
4241+ Reply =
4242+ case sql_query_internal(DBRef, Query) of
4243+ {data, Recs} ->
4244+ RFun = fun({User, Count}) ->
4245+ {User, list_to_integer(Count)}
4246+ end,
4247+ {ok, lists:reverse(lists:keysort(2, lists:map(RFun, Recs)))};
4248+ {error, Reason} ->
4249+ % TODO:
4250+ {error, Reason}
4251+ end,
4252+ {reply, Reply, State};
4253+handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4254+ SName = stats_table(VHost, Schema),
4255+ Query = ["SELECT at, count ",
4256+ "FROM ",SName," ",
4257+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"') ",
4258+ "ORDER BY DATE(at) DESC;"
4259+ ],
4260+ Reply =
4261+ case sql_query_internal(DBRef, Query) of
4262+ {data, Recs} ->
4263+ {ok, [ {Date, list_to_integer(Count)} || {Date, Count} <- Recs ]};
4264+ {error, Result} ->
4265+ {error, Result}
4266+ end,
4267+ {reply, Reply, State};
4268+handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4269+ Query = ["SELECT peer_name,",
4270+ "peer_server,",
4271+ "peer_resource,",
4272+ "direction,"
4273+ "type,"
4274+ "subject,"
4275+ "body,"
4276+ "timestamp "
4277+ "FROM ",view_table(VHost, Schema, Date)," "
4278+ "WHERE owner_name='",User,"';"],
4279+ Reply =
4280+ case sql_query_internal(DBRef, Query) of
4281+ {data, Recs} ->
4282+ Fun = fun({Peer_name, Peer_server, Peer_resource,
4283+ Direction,
4284+ Type,
4285+ Subject, Body,
4286+ Timestamp}) ->
4287+ #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
4288+ direction=list_to_atom(Direction),
4289+ type=Type,
4290+ subject=Subject, body=Body,
4291+ timestamp=Timestamp}
4292+ end,
4293+ {ok, lists:map(Fun, Recs)};
4294+ {error, Reason} ->
4295+ {error, Reason}
4296+ end,
4297+ {reply, Reply, State};
4298+handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4299+ SName = stats_table(VHost, Schema),
4300+ Query = ["SELECT at ",
4301+ "FROM ",SName," ",
4302+ "GROUP BY at ",
4303+ "ORDER BY at DESC;"
4304+ ],
4305+ Reply =
4306+ case sql_query_internal(DBRef, Query) of
4307+ {data, Result} ->
4308+ [ Date || {Date} <- Result ];
4309+ {error, Reason} ->
4310+ {error, Reason}
4311+ end,
4312+ {reply, Reply, State};
4313+handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4314+ Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
4315+ "FROM ",settings_table(VHost, Schema)," ",
4316+ "JOIN ",users_table(VHost, Schema)," ON user_id=owner_id;"],
4317+ Reply =
4318+ case sql_query_internal(DBRef, Query) of
4319+ {data, Recs} ->
4320+ {ok, [#user_settings{owner_name=Owner,
4321+ dolog_default=list_to_bool(DoLogDef),
4322+ dolog_list=string_to_list(DoLogL),
4323+ donotlog_list=string_to_list(DoNotLogL)
4324+ } || {Owner, DoLogDef, DoLogL, DoNotLogL} <- Recs]};
4325+ {error, Reason} ->
4326+ {error, Reason}
4327+ end,
4328+ {reply, Reply, State};
4329+handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4330+ Query = ["SELECT dolog_default,dolog_list,donotlog_list ",
4331+ "FROM ",settings_table(VHost, Schema)," ",
4332+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
4333+ Reply =
4334+ case sql_query_internal_silent(DBRef, Query) of
4335+ {data, []} ->
4336+ {ok, []};
4337+ {data, [{DoLogDef, DoLogL, DoNotLogL}]} ->
4338+ {ok, #user_settings{owner_name=User,
4339+ dolog_default=list_to_bool(DoLogDef),
4340+ dolog_list=string_to_list(DoLogL),
4341+ donotlog_list=string_to_list(DoNotLogL)}};
4342+ {error, Reason} ->
4343+ ?ERROR_MSG("Failed to get_user_settings for ~p@~p: ~p", [User, VHost, Reason]),
4344+ error
4345+ end,
4346+ {reply, Reply, State};
4347+handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
4348+ dolog_list=DoLogL,
4349+ donotlog_list=DoNotLogL}},
4350+ _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
4351+ User_id = get_user_id(DBRef, VHost, Schema, User),
4352+ Query = ["UPDATE ",settings_table(VHost, Schema)," ",
4353+ "SET dolog_default=",bool_to_list(DoLogDef),", ",
4354+ "dolog_list='",list_to_string(DoLogL),"', ",
4355+ "donotlog_list='",list_to_string(DoNotLogL),"' ",
4356+ "WHERE owner_id=",User_id,";"],
4357+
4358+ Reply =
4359+ case sql_query_internal(DBRef, Query) of
4360+ {updated, 0} ->
4361+ IQuery = ["INSERT INTO ",settings_table(VHost, Schema)," ",
4362+ "(owner_id, dolog_default, dolog_list, donotlog_list) ",
4363+ "VALUES ",
4364+ "(",User_id,", ",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
4365+ case sql_query_internal(DBRef, IQuery) of
4366+ {updated, 1} ->
4367+ ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
4368+ ok;
4369+ {error, _} ->
4370+ error
4371+ end;
4372+ {updated, 1} ->
4373+ ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
4374+ ok;
4375+ {error, _} ->
4376+ error
4377+ end,
4378+ {reply, Reply, State};
4379+handle_call({stop}, _From, State) ->
4380+ ?MYDEBUG("Stoping pgsql backend for ~p", [State#state.vhost]),
4381+ {stop, normal, ok, State};
4382+handle_call(Msg, _From, State) ->
4383+ ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
4384+ {noreply, State}.
4385+
4386+handle_cast(Msg, State) ->
4387+ ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
4388+ {noreply, State}.
4389+
4390+handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
4391+ {stop, connection_dropped, State};
4392+handle_info(Info, State) ->
4393+ ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
4394+ {noreply, State}.
4395+
4396+terminate(_Reason, _State) ->
4397+ ok.
4398+
4399+code_change(_OldVsn, State, _Extra) ->
4400+ {ok, State}.
4401+
4402+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4403+%
4404+% gen_logdb callbacks
4405+%
4406+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4407+log_message(VHost, Msg) ->
4408+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4409+ gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
4410+rebuild_stats(VHost) ->
4411+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4412+ gen_server:call(Proc, {rebuild_stats}, ?CALL_TIMEOUT).
4413+rebuild_stats_at(VHost, Date) ->
4414+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4415+ gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
4416+delete_messages_by_user_at(VHost, Msgs, Date) ->
4417+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4418+ gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
4419+delete_all_messages_by_user_at(User, VHost, Date) ->
4420+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4421+ gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
4422+delete_messages_at(VHost, Date) ->
4423+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4424+ gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
4425+get_vhost_stats(VHost) ->
4426+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4427+ gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
4428+get_vhost_stats_at(VHost, Date) ->
4429+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4430+ gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
4431+get_user_stats(User, VHost) ->
4432+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4433+ gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
4434+get_user_messages_at(User, VHost, Date) ->
4435+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4436+ gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
4437+get_dates(VHost) ->
4438+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4439+ gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
4440+get_users_settings(VHost) ->
4441+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4442+ gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
4443+get_user_settings(User, VHost) ->
4444+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4445+ gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
4446+set_user_settings(User, VHost, Set) ->
4447+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4448+ gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
4449+
4450+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4451+%
4452+% internals
4453+%
4454+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4455+get_dates_int(DBRef, VHost) ->
4456+ Query = ["SELECT n.nspname as \"Schema\",
4457+ c.relname as \"Name\",
4458+ CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' END as \"Type\",
4459+ r.rolname as \"Owner\"
4460+ FROM pg_catalog.pg_class c
4461+ JOIN pg_catalog.pg_roles r ON r.oid = c.relowner
4462+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
4463+ WHERE c.relkind IN ('r','')
4464+ AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
4465+ AND c.relname ~ '^(.*",escape_vhost(VHost),".*)$'
4466+ AND pg_catalog.pg_table_is_visible(c.oid)
4467+ ORDER BY 1,2;"],
4468+ case sql_query_internal(DBRef, Query) of
4469+ {data, Recs} ->
4470+ lists:foldl(fun({_Schema, Table, _Type, _Owner}, Dates) ->
4471+ case regexp:match(Table,"[0-9]+-[0-9]+-[0-9]+") of
4472+ {match, S, E} ->
4473+ lists:append(Dates, [lists:sublist(Table,S,E)]);
4474+ nomatch ->
4475+ Dates
4476+ end
4477+ end, [], Recs);
4478+ {error, _} ->
4479+ []
4480+ end.
4481+
4482+rebuild_stats_at_int(DBRef, VHost, Schema, Date) ->
4483+ Table = messages_table(VHost, Schema, Date),
4484+ STable = stats_table(VHost, Schema),
4485+
4486+ Fun =
4487+ fun() ->
4488+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," IN ACCESS EXCLUSIVE MODE;"]),
4489+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," IN ACCESS EXCLUSIVE MODE;"]),
4490+
4491+ DQuery = [ "DELETE FROM ",STable," ",
4492+ "WHERE at='",Date,"';"],
4493+
4494+ {updated, _} = sql_query_internal(DBRef, DQuery),
4495+
4496+ SQuery = ["INSERT INTO ",STable," ",
4497+ "(owner_id,at,count) ",
4498+ "SELECT owner_id,'",Date,"'",",count(*) ",
4499+ "FROM ",Table," GROUP BY owner_id;"],
4500+
4501+ case sql_query_internal(DBRef, SQuery) of
4502+ {updated, 0} ->
4503+ {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]),
4504+ ok;
4505+ {updated, _} -> ok;
4506+ {error, _} -> error
4507+ end
4508+ end,
4509+
4510+ case sql_transaction_internal(DBRef, Fun) of
4511+ {atomic, _} ->
4512+ ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
4513+ ok;
4514+ {aborted, _} ->
4515+ error
4516+ end.
4517+
4518+delete_nonexistent_stats(DBRef, VHost, Schema) ->
4519+ Dates = get_dates_int(DBRef, VHost),
4520+ STable = stats_table(VHost, Schema),
4521+
4522+ Temp = lists:flatmap(fun(Date) ->
4523+ ["'",Date,"'",","]
4524+ end, Dates),
4525+
4526+ Temp1 = case Temp of
4527+ [] ->
4528+ ["''"];
4529+ _ ->
4530+ % replace last "," with ");"
4531+ lists:append([lists:sublist(Temp, length(Temp)-1), ");"])
4532+ end,
4533+
4534+ Query = ["DELETE FROM ",STable," ",
4535+ "WHERE at NOT IN (", Temp1],
4536+
4537+ case sql_query_internal(DBRef, Query) of
4538+ {updated, _} ->
4539+ ok;
4540+ {error, _} ->
4541+ error
4542+ end.
4543+
4544+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4545+%
4546+% tables internals
4547+%
4548+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4549+create_stats_table(DBRef, VHost, Schema) ->
4550+ SName = stats_table(VHost, Schema),
4551+
4552+ Fun =
4553+ fun() ->
4554+ Query = ["CREATE TABLE ",SName," (",
4555+ "owner_id INTEGER, ",
4556+ "at VARCHAR(20), ",
4557+ "count integer",
4558+ ");"
4559+ ],
4560+ case sql_query_internal_silent(DBRef, Query) of
4561+ {updated, _} ->
4562+ {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"s_owner_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (owner_id);"]),
4563+ {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"s_at_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (at);"]),
4564+ created;
4565+ {error, Reason} ->
4566+ case lists:keysearch(code, 1, Reason) of
4567+ {value, {code, "42P07"}} ->
4568+ exists;
4569+ _ ->
4570+ ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
4571+ error
4572+ end
4573+ end
4574+ end,
4575+ case sql_transaction_internal(DBRef, Fun) of
4576+ {atomic, created} ->
4577+ ?MYDEBUG("Created stats table for ~p", [VHost]),
4578+ lists:foreach(fun(Date) ->
4579+ rebuild_stats_at_int(DBRef, VHost, Schema, Date)
4580+ end, get_dates_int(DBRef, VHost));
4581+ {atomic, exists} ->
4582+ ?MYDEBUG("Stats table for ~p already exists", [VHost]),
4583+ ok;
4584+ {error, _} -> error
4585+ end.
4586+
4587+create_settings_table(DBRef, VHost, Schema) ->
4588+ SName = settings_table(VHost, Schema),
4589+ Query = ["CREATE TABLE ",SName," (",
4590+ "owner_id INTEGER PRIMARY KEY, ",
4591+ "dolog_default BOOLEAN, ",
4592+ "dolog_list TEXT DEFAULT '', ",
4593+ "donotlog_list TEXT DEFAULT ''",
4594+ ");"
4595+ ],
4596+ case sql_query_internal_silent(DBRef, Query) of
4597+ {updated, _} ->
4598+ ?MYDEBUG("Created settings table for ~p", [VHost]),
4599+ ok;
4600+ {error, Reason} ->
4601+ case lists:keysearch(code, 1, Reason) of
4602+ {value, {code, "42P07"}} ->
4603+ ?MYDEBUG("Settings table for ~p already exists", [VHost]),
4604+ ok;
4605+ _ ->
4606+ ?ERROR_MSG("Failed to create settings table for ~p: ~p", [VHost, Reason]),
4607+ error
4608+ end
4609+ end.
4610+
4611+create_users_table(DBRef, VHost, Schema) ->
4612+ SName = users_table(VHost, Schema),
4613+
4614+ Fun =
4615+ fun() ->
4616+ Query = ["CREATE TABLE ",SName," (",
4617+ "username TEXT UNIQUE, ",
4618+ "user_id SERIAL PRIMARY KEY",
4619+ ");"
4620+ ],
4621+ case sql_query_internal_silent(DBRef, Query) of
4622+ {updated, _} ->
4623+ {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"username_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (username);"]),
4624+ created;
4625+ {error, Reason} ->
4626+ case lists:keysearch(code, 1, Reason) of
4627+ {value, {code, "42P07"}} ->
4628+ exists;
4629+ _ ->
4630+ ?ERROR_MSG("Failed to create users table for ~p: ~p", [VHost, Reason]),
4631+ error
4632+ end
4633+ end
4634+ end,
4635+ case sql_transaction_internal(DBRef, Fun) of
4636+ {atomic, created} ->
4637+ ?MYDEBUG("Created users table for ~p", [VHost]),
4638+ ok;
4639+ {atomic, exists} ->
4640+ ?MYDEBUG("Users table for ~p already exists", [VHost]),
4641+ ok;
4642+ {aborted, _} -> error
4643+ end.
4644+
4645+create_servers_table(DBRef, VHost, Schema) ->
4646+ SName = servers_table(VHost, Schema),
4647+
4648+ Fun =
4649+ fun() ->
4650+ Query = ["CREATE TABLE ",SName," (",
4651+ "server TEXT UNIQUE, ",
4652+ "server_id SERIAL PRIMARY KEY",
4653+ ");"
4654+ ],
4655+ case sql_query_internal_silent(DBRef, Query) of
4656+ {updated, _} ->
4657+ {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"server_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (server);"]),
4658+ created;
4659+ {error, Reason} ->
4660+ case lists:keysearch(code, 1, Reason) of
4661+ {value, {code, "42P07"}} ->
4662+ exists;
4663+ _ ->
4664+ ?ERROR_MSG("Failed to create servers table for ~p: ~p", [VHost, Reason]),
4665+ error
4666+ end
4667+ end
4668+ end,
4669+ case sql_transaction_internal(DBRef, Fun) of
4670+ {atomic, created} ->
4671+ ?MYDEBUG("Created servers table for ~p", [VHost]),
4672+ ok;
4673+ {atomic, exists} ->
4674+ ?MYDEBUG("Servers table for ~p already exists", [VHost]),
4675+ ok;
4676+ {aborted, _} -> error
4677+ end.
4678+
4679+create_resources_table(DBRef, VHost, Schema) ->
4680+ RName = resources_table(VHost, Schema),
4681+ Fun = fun() ->
4682+ Query = ["CREATE TABLE ",RName," (",
4683+ "resource TEXT UNIQUE, ",
4684+ "resource_id SERIAL PRIMARY KEY",
4685+ ");"
4686+ ],
4687+ case sql_query_internal_silent(DBRef, Query) of
4688+ {updated, _} ->
4689+ {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"resource_i_",Schema,"_",escape_vhost(VHost),"\" ON ",RName," (resource);"]),
4690+ created;
4691+ {error, Reason} ->
4692+ case lists:keysearch(code, 1, Reason) of
4693+ {value, {code, "42P07"}} ->
4694+ exists;
4695+ _ ->
4696+ ?ERROR_MSG("Failed to create users table for ~p: ~p", [VHost, Reason]),
4697+ error
4698+ end
4699+ end
4700+ end,
4701+ case sql_transaction_internal(DBRef, Fun) of
4702+ {atomic, created} ->
4703+ ?MYDEBUG("Created resources table for ~p", [VHost]),
4704+ ok;
4705+ {atomic, exists} ->
4706+ ?MYDEBUG("Resources table for ~p already exists", [VHost]),
4707+ ok;
4708+ {aborted, _} -> error
4709+ end.
4710+
4711+create_internals(DBRef, VHost, Schema) ->
4712+ case sql_query_internal(DBRef, [get_logmessage(VHost, Schema)]) of
4713+ {updated, _} ->
4714+ ?MYDEBUG("Created logmessage for ~p", [VHost]),
4715+ ok;
4716+ {error, _} ->
4717+ error
4718+ end.
4719+
4720+get_user_id(DBRef, VHost, Schema, User) ->
4721+ SQuery = ["SELECT user_id FROM ",users_table(VHost, Schema)," ",
4722+ "WHERE username='",User,"';"],
4723+ case sql_query_internal(DBRef, SQuery) of
4724+ {data, []} ->
4725+ IQuery = ["INSERT INTO ",users_table(VHost, Schema)," ",
4726+ "VALUES ('",User,"');"],
4727+ case sql_query_internal_silent(DBRef, IQuery) of
4728+ {updated, _} ->
4729+ {data, [{DBIdNew}]} = sql_query_internal(DBRef, SQuery),
4730+ DBIdNew;
4731+ {error, Reason} ->
4732+ % this can be in clustered environment
4733+ {value, {code, "23505"}} = lists:keysearch(code, 1, Reason),
4734+ ?ERROR_MSG("Duplicate key name for ~p", [User]),
4735+ {data, [{ClID}]} = sql_query_internal(DBRef, SQuery),
4736+ ClID
4737+ end;
4738+ {data, [{DBId}]} ->
4739+ DBId
4740+ end.
4741+
4742+get_logmessage(VHost,Schema) ->
4743+ UName = users_table(VHost,Schema),
4744+ SName = servers_table(VHost,Schema),
4745+ RName = resources_table(VHost,Schema),
4746+ StName = stats_table(VHost,Schema),
4747+ io_lib:format("CREATE OR REPLACE FUNCTION \"logmessage\" (tbname TEXT, atdt TEXT, owner TEXT, peer_name TEXT, peer_server TEXT, peer_resource TEXT, mdirection VARCHAR(4), mtype VARCHAR(9), msubj TEXT, mbody TEXT, mtimestamp DOUBLE PRECISION) RETURNS INTEGER AS $$
4748+DECLARE
4749+ ownerID INTEGER;
4750+ peer_nameID INTEGER;
4751+ peer_serverID INTEGER;
4752+ peer_resourceID INTEGER;
4753+ tablename ALIAS for $1;
4754+ atdate ALIAS for $2;
4755+ viewname TEXT;
4756+BEGIN
4757+ SELECT INTO ownerID user_id FROM ~s WHERE username = owner;
4758+ IF NOT FOUND THEN
4759+ INSERT INTO ~s (username) VALUES (owner);
4760+ ownerID := lastval();
4761+ END IF;
4762+
4763+ SELECT INTO peer_nameID user_id FROM ~s WHERE username = peer_name;
4764+ IF NOT FOUND THEN
4765+ INSERT INTO ~s (username) VALUES (peer_name);
4766+ peer_nameID := lastval();
4767+ END IF;
4768+
4769+ SELECT INTO peer_serverID server_id FROM ~s WHERE server = peer_server;
4770+ IF NOT FOUND THEN
4771+ INSERT INTO ~s (server) VALUES (peer_server);
4772+ peer_serverID := lastval();
4773+ END IF;
4774+
4775+ SELECT INTO peer_resourceID resource_id FROM ~s WHERE resource = peer_resource;
4776+ IF NOT FOUND THEN
4777+ INSERT INTO ~s (resource) VALUES (peer_resource);
4778+ peer_resourceID := lastval();
4779+ END IF;
4780+
4781+ BEGIN
4782+ EXECUTE 'INSERT INTO ' || tablename || ' (owner_id, peer_name_id, peer_server_id, peer_resource_id, direction, type, subject, body, timestamp) VALUES (' || ownerID || ',' || peer_nameID || ',' || peer_serverID || ',' || peer_resourceID || ',''' || mdirection || ''',''' || mtype || ''',''' || msubj || ''',''' || mbody || ''',' || mtimestamp || ')';
4783+ EXCEPTION WHEN undefined_table THEN
4784+ EXECUTE 'CREATE TABLE ' || tablename || ' (' ||
4785+ 'owner_id INTEGER, ' ||
4786+ 'peer_name_id INTEGER, ' ||
4787+ 'peer_server_id INTEGER, ' ||
4788+ 'peer_resource_id INTEGER, ' ||
4789+ 'direction VARCHAR(4) CHECK (direction IN (''to'',''from'')), ' ||
4790+ 'type VARCHAR(9) CHECK (type IN (''chat'',''error'',''groupchat'',''headline'',''normal'')), ' ||
4791+ 'subject TEXT, ' ||
4792+ 'body TEXT, ' ||
4793+ 'timestamp DOUBLE PRECISION)';
4794+ EXECUTE 'CREATE INDEX \"owner_i_' || '~s' || '_' || atdate || '_' || '~s' || '\"' || ' ON ' || tablename || ' (owner_id)';
4795+ EXECUTE 'CREATE INDEX \"peer_i_' || '~s' || '_' || atdate || '_' || '~s' || '\"' || ' ON ' || tablename || ' (peer_server_id, peer_name_id)';
4796+
4797+ viewname := '~s.\"v_' || trim(both '~s.\"' from tablename) || '\"';
4798+
4799+ EXECUTE 'CREATE OR REPLACE VIEW ' || viewname || ' AS ' ||
4800+ 'SELECT owner.username AS owner_name, ' ||
4801+ 'peer.username AS peer_name, ' ||
4802+ 'servers.server AS peer_server, ' ||
4803+ 'resources.resource AS peer_resource, ' ||
4804+ 'messages.direction, ' ||
4805+ 'messages.type, ' ||
4806+ 'messages.subject, ' ||
4807+ 'messages.body, ' ||
4808+ 'messages.timestamp ' ||
4809+ 'FROM ' ||
4810+ '~s owner, ' ||
4811+ '~s peer, ' ||
4812+ '~s servers, ' ||
4813+ '~s resources, ' ||
4814+ tablename || ' messages ' ||
4815+ 'WHERE ' ||
4816+ 'owner.user_id=messages.owner_id and ' ||
4817+ 'peer.user_id=messages.peer_name_id and ' ||
4818+ 'servers.server_id=messages.peer_server_id and ' ||
4819+ 'resources.resource_id=messages.peer_resource_id ' ||
4820+ 'ORDER BY messages.timestamp';
4821+
4822+ EXECUTE 'INSERT INTO ' || tablename || ' (owner_id, peer_name_id, peer_server_id, peer_resource_id, direction, type, subject, body, timestamp) VALUES (' || ownerID || ',' || peer_nameID || ',' || peer_serverID || ',' || peer_resourceID || ',''' || mdirection || ''',''' || mtype || ''',''' || msubj || ''',''' || mbody || ''',' || mtimestamp || ')';
4823+ END;
4824+
4825+ UPDATE ~s SET count=count+1 where at=atdate and owner_id=ownerID;
4826+ IF NOT FOUND THEN
4827+ INSERT INTO ~s (owner_id, at, count) VALUES (ownerID, atdate, 1);
4828+ END IF;
4829+ RETURN 0;
4830+END;
4831+$$ LANGUAGE plpgsql;
4832+", [UName,UName,UName,UName,SName,SName,RName,RName,Schema,escape_vhost(VHost),Schema,escape_vhost(VHost),Schema,Schema,UName,UName,SName,RName,StName,StName]).
4833+
4834+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4835+%
4836+% SQL internals
4837+%
4838+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4839+% like do_transaction/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
4840+sql_transaction_internal(DBRef, Fun) ->
4841+ case sql_query_internal(DBRef, ["BEGIN;"]) of
4842+ {updated, _} ->
4843+ case catch Fun() of
4844+ error = Err ->
4845+ rollback_internal(DBRef, Err);
4846+ {error, _} = Err ->
4847+ rollback_internal(DBRef, Err);
4848+ {'EXIT', _} = Err ->
4849+ rollback_internal(DBRef, Err);
4850+ Res ->
4851+ case sql_query_internal(DBRef, ["COMMIT;"]) of
4852+ {error, _} -> rollback_internal(DBRef, {commit_error});
4853+ {updated, _} ->
4854+ case Res of
4855+ {atomic, _} -> Res;
4856+ _ -> {atomic, Res}
4857+ end
4858+ end
4859+ end;
4860+ {error, _} ->
4861+ {aborted, {begin_error}}
4862+ end.
4863+
4864+% like rollback/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
4865+rollback_internal(DBRef, Reason) ->
4866+ Res = sql_query_internal(DBRef, ["ROLLBACK;"]),
4867+ {aborted, {Reason, {rollback_result, Res}}}.
4868+
4869+sql_query_internal(DBRef, Query) ->
4870+ case sql_query_internal_silent(DBRef, Query) of
4871+ {error, undefined, Rez} ->
4872+ ?ERROR_MSG("Got undefined result: ~p while ~p", [Rez, lists:append(Query)]),
4873+ {error, undefined};
4874+ {error, Error} ->
4875+ ?ERROR_MSG("Failed: ~p while ~p", [Error, lists:append(Query)]),
4876+ {error, Error};
4877+ Rez -> Rez
4878+ end.
4879+
4880+sql_query_internal_silent(DBRef, Query) ->
4881+ ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
4882+ get_result(pgsql:squery(DBRef, Query)).
4883+
4884+get_result({ok, ["CREATE TABLE"]}) ->
4885+ {updated, 1};
4886+get_result({ok, ["DROP TABLE"]}) ->
4887+ {updated, 1};
4888+get_result({ok,["DROP VIEW"]}) ->
4889+ {updated, 1};
4890+get_result({ok, ["CREATE INDEX"]}) ->
4891+ {updated, 1};
4892+get_result({ok, ["CREATE FUNCTION"]}) ->
4893+ {updated, 1};
4894+get_result({ok, [{"SELECT", _Rows, Recs}]}) ->
4895+ {data, [list_to_tuple(Rec) || Rec <- Recs]};
4896+get_result({ok, ["INSERT " ++ OIDN]}) ->
4897+ [_OID, N] = string:tokens(OIDN, " "),
4898+ {updated, list_to_integer(N)};
4899+get_result({ok, ["DELETE " ++ N]}) ->
4900+ {updated, list_to_integer(N)};
4901+get_result({ok, ["UPDATE " ++ N]}) ->
4902+ {updated, list_to_integer(N)};
4903+get_result({ok, ["BEGIN"]}) ->
4904+ {updated, 1};
4905+get_result({ok, ["LOCK TABLE"]}) ->
4906+ {updated, 1};
4907+get_result({ok, ["ROLLBACK"]}) ->
4908+ {updated, 1};
4909+get_result({ok, ["COMMIT"]}) ->
4910+ {updated, 1};
4911+get_result({ok, ["SET"]}) ->
4912+ {updated, 1};
4913+get_result({ok, [{error, Error}]}) ->
4914+ {error, Error};
4915+get_result(Rez) ->
4916+ {error, undefined, Rez}.
4917+
4918--- src/mod_logdb_mnesia_old.erl.orig Tue Dec 11 14:23:19 2007
4919+++ src/mod_logdb_mnesia_old.erl Wed Aug 22 22:58:11 2007
4920@@ -0,0 +1,256 @@
4921+%%%----------------------------------------------------------------------
4922+%%% File : mod_logdb_mnesia_old.erl
4923+%%% Author : Oleg Palij (mailto:o.palij@gmail.com xmpp://malik@jabber.te.ua)
4924+%%% Purpose : mod_logmnesia backend for mod_logdb (should be used only for copy_tables functionality)
4925+%%% Version : trunk
4926+%%% Id : $Id$
4927+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
4928+%%%----------------------------------------------------------------------
4929+
4930+-module(mod_logdb_mnesia_old).
4931+-author('o.palij@gmail.com').
4932+-vsn('$Revision$').
4933+
4934+-include("ejabberd.hrl").
4935+-include("jlib.hrl").
4936+
4937+-behaviour(gen_logdb).
4938+
4939+-export([start/2, stop/1,
4940+ log_message/2,
4941+ rebuild_stats/1,
4942+ rebuild_stats_at/2,
4943+ rebuild_stats_at1/2,
4944+ delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
4945+ get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
4946+ get_dates/1,
4947+ get_users_settings/1, get_user_settings/2, set_user_settings/3]).
4948+
4949+-record(stats, {user, server, table, count}).
4950+-record(msg, {to_user, to_server, to_resource, from_user, from_server, from_resource, id, type, subject, body, timestamp}).
4951+
4952+tables_prefix() -> "messages_".
4953+% stats_table should not start with tables_prefix(VHost) !
4954+% i.e. lists:prefix(tables_prefix(VHost), atom_to_list(stats_table())) must be /= true
4955+stats_table() -> list_to_atom("messages-stats").
4956+% table name as atom from Date
4957+-define(ATABLE(Date), list_to_atom(tables_prefix() ++ Date)).
4958+-define(LTABLE(Date), tables_prefix() ++ Date).
4959+
4960+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4961+%
4962+% gen_logdb callbacks
4963+%
4964+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4965+start(_Opts, _VHost) ->
4966+ case mnesia:system_info(is_running) of
4967+ yes ->
4968+ ok = create_stats_table(),
4969+ {ok, ok};
4970+ no ->
4971+ ?ERROR_MSG("Mnesia not running", []),
4972+ error;
4973+ Status ->
4974+ ?ERROR_MSG("Mnesia status: ~p", [Status]),
4975+ error
4976+ end.
4977+
4978+stop(_VHost) ->
4979+ ok.
4980+
4981+log_message(_VHost, _Msg) ->
4982+ error.
4983+
4984+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4985+%
4986+% gen_logdb callbacks (maintaince)
4987+%
4988+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4989+rebuild_stats(_VHost) ->
4990+ ok.
4991+
4992+rebuild_stats_at(VHost, Date) ->
4993+ Table = ?LTABLE(Date),
4994+ {Time, Value}=timer:tc(?MODULE, rebuild_stats_at1, [VHost, Table]),
4995+ ?INFO_MSG("rebuild_stats_at ~p elapsed ~p sec: ~p~n", [Date, Time/1000000, Value]),
4996+ Value.
4997+rebuild_stats_at1(VHost, Table) ->
4998+ CFun = fun(Msg, Stats) ->
4999+ To = Msg#msg.to_user ++ "@" ++ Msg#msg.to_server,
5000+ Stats_to = if
5001+ Msg#msg.to_server == VHost ->
5002+ case lists:keysearch(To, 1, Stats) of
5003+ {value, {Who_to, Count_to}} ->
5004+ lists:keyreplace(To, 1, Stats, {Who_to, Count_to + 1});
5005+ false ->
5006+ lists:append(Stats, [{To, 1}])
5007+ end;
5008+ true ->
5009+ Stats
5010+ end,
5011+ From = Msg#msg.from_user ++ "@" ++ Msg#msg.from_server,
5012+ Stats_from = if
5013+ Msg#msg.from_server == VHost ->
5014+ case lists:keysearch(From, 1, Stats_to) of
5015+ {value, {Who_from, Count_from}} ->
5016+ lists:keyreplace(From, 1, Stats_to, {Who_from, Count_from + 1});
5017+ false ->
5018+ lists:append(Stats_to, [{From, 1}])
5019+ end;
5020+ true ->
5021+ Stats_to
5022+ end,
5023+ Stats_from
5024+ end,
5025+ DFun = fun(#stats{table=STable, server=Server} = Stat, _Acc)
5026+ when STable == Table, Server == VHost ->
5027+ mnesia:delete_object(stats_table(), Stat, write);
5028+ (_Stat, _Acc) -> ok
5029+ end,
5030+ case mnesia:transaction(fun() ->
5031+ mnesia:write_lock_table(list_to_atom(Table)),
5032+ mnesia:write_lock_table(stats_table()),
5033+ % Calc stats for VHost at Date
5034+ AStats = mnesia:foldl(CFun, [], list_to_atom(Table)),
5035+ % Delete all stats for VHost at Date
5036+ mnesia:foldl(DFun, [], stats_table()),
5037+ % Write new calc'ed stats
5038+ lists:foreach(fun({Who, Count}) ->
5039+ Jid = jlib:string_to_jid(Who),
5040+ JUser = Jid#jid.user,
5041+ WStat = #stats{user=JUser, server=VHost, table=Table, count=Count},
5042+ mnesia:write(stats_table(), WStat, write)
5043+ end, AStats)
5044+ end) of
5045+ {aborted, Reason} ->
5046+ ?ERROR_MSG("Failed to rebuild_stats_at for ~p at ~p: ~p", [VHost, Table, Reason]),
5047+ error;
5048+ {atomic, _} ->
5049+ ok
5050+ end.
5051+
5052+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5053+%
5054+% gen_logdb callbacks (delete)
5055+%
5056+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5057+delete_messages_by_user_at(_VHost, _Msgs, _Date) ->
5058+ error.
5059+
5060+delete_all_messages_by_user_at(_User, _VHost, _Date) ->
5061+ error.
5062+
5063+delete_messages_at(VHost, Date) ->
5064+ Table = list_to_atom(tables_prefix() ++ Date),
5065+
5066+ DFun = fun(#msg{to_server=To_server, from_server=From_server}=Msg, _Acc)
5067+ when To_server == VHost; From_server == VHost ->
5068+ mnesia:delete_object(Table, Msg, write);
5069+ (_Msg, _Acc) -> ok
5070+ end,
5071+
5072+ case mnesia:transaction(fun() ->
5073+ mnesia:foldl(DFun, [], Table)
5074+ end) of
5075+ {aborted, Reason} ->
5076+ ?ERROR_MSG("Failed to delete_messages_at for ~p at ~p: ~p", [VHost, Date, Reason]),
5077+ error;
5078+ {atomic, _} ->
5079+ ok
5080+ end.
5081+
5082+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5083+%
5084+% gen_logdb callbacks (get)
5085+%
5086+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5087+get_vhost_stats(_VHost) ->
5088+ {error, "does not emplemented"}.
5089+
5090+get_vhost_stats_at(VHost, Date) ->
5091+ Fun = fun() ->
5092+ Pat = #stats{user='$1', server=VHost, table=tables_prefix()++Date, count = '$2'},
5093+ mnesia:select(stats_table(), [{Pat, [], [['$1', '$2']]}])
5094+ end,
5095+ case mnesia:transaction(Fun) of
5096+ {atomic, Result} ->
5097+ RFun = fun([User, Count]) ->
5098+ {User, Count}
5099+ end,
5100+ {ok, lists:reverse(lists:keysort(2, lists:map(RFun, Result)))};
5101+ {aborted, Reason} -> {error, Reason}
5102+ end.
5103+
5104+get_user_stats(_User, _VHost) ->
5105+ {error, "does not emplemented"}.
5106+
5107+get_user_messages_at(User, VHost, Date) ->
5108+ Table_name = tables_prefix() ++ Date,
5109+ case mnesia:transaction(fun() ->
5110+ Pat_to = #msg{to_user=User, to_server=VHost, _='_'},
5111+ Pat_from = #msg{from_user=User, from_server=VHost, _='_'},
5112+ mnesia:select(list_to_atom(Table_name),
5113+ [{Pat_to, [], ['$_']},
5114+ {Pat_from, [], ['$_']}])
5115+ end) of
5116+ {atomic, Result} ->
5117+ Msgs = lists:map(fun(#msg{to_user=To_user, to_server=To_server, to_resource=To_res,
5118+ from_user=From_user, from_server=From_server, from_resource=From_res,
5119+ type=Type,
5120+ subject=Subj,
5121+ body=Body, timestamp=Timestamp} = _Msg) ->
5122+ Subject = case Subj of
5123+ "None" -> "";
5124+ _ -> Subj
5125+ end,
5126+ {msg, To_user, To_server, To_res, From_user, From_server, From_res, Type, Subject, Body, Timestamp}
5127+ end, Result),
5128+ {ok, Msgs};
5129+ {aborted, Reason} ->
5130+ {error, Reason}
5131+ end.
5132+
5133+get_dates(_VHost) ->
5134+ Tables = mnesia:system_info(tables),
5135+ MessagesTables =
5136+ lists:filter(fun(Table) ->
5137+ lists:prefix(tables_prefix(), atom_to_list(Table))
5138+ end,
5139+ Tables),
5140+ lists:map(fun(Table) ->
5141+ lists:sublist(atom_to_list(Table),
5142+ length(tables_prefix())+1,
5143+ length(atom_to_list(Table)))
5144+ end,
5145+ MessagesTables).
5146+
5147+get_users_settings(_VHost) ->
5148+ {ok, []}.
5149+get_user_settings(_User, _VHost) ->
5150+ {ok, []}.
5151+set_user_settings(_User, _VHost, _Set) ->
5152+ ok.
5153+
5154+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5155+%
5156+% internal
5157+%
5158+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5159+% called from db_logon/2
5160+create_stats_table() ->
5161+ SName = stats_table(),
5162+ case mnesia:create_table(SName,
5163+ [{disc_only_copies, [node()]},
5164+ {type, bag},
5165+ {attributes, record_info(fields, stats)},
5166+ {record_name, stats}
5167+ ]) of
5168+ {atomic, ok} ->
5169+ ?INFO_MSG("Created stats table", []),
5170+ ok;
5171+ {aborted, {already_exists, _}} ->
5172+ ok;
5173+ {aborted, Reason} ->
5174+ ?ERROR_MSG("Failed to create stats table: ~p", [Reason]),
5175+ error
5176+ end.
5177--- src/gen_logdb.erl.orig Tue Dec 11 14:23:19 2007
5178+++ src/gen_logdb.erl Wed Aug 22 22:58:11 2007
5179@@ -0,0 +1,158 @@
5180+%%%----------------------------------------------------------------------
5181+%%% File : gen_logdb.erl
5182+%%% Author : Oleg Palij (mailto:o.palij@gmail.com xmpp://malik@jabber.te.ua)
5183+%%% Purpose : Describes generic behaviour for mod_logdb backends.
5184+%%% Version : trunk
5185+%%% Id : $Id$
5186+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
5187+%%%----------------------------------------------------------------------
5188+
5189+-module(gen_logdb).
5190+-author('o.palij@gmail.com').
5191+-vsn('$Revision$').
5192+
5193+-export([behaviour_info/1]).
5194+
5195+behaviour_info(callbacks) ->
5196+ [
5197+ % called from handle_info(start, _)
5198+ % it should logon database and return reference to started instance
5199+ % start(VHost, Opts) -> {ok, SPid} | error
5200+ % Options - list of options to connect to db
5201+ % Types: Options = list() -> [] |
5202+ % [{user, "logdb"},
5203+ % {pass, "1234"},
5204+ % {db, "logdb"}] | ...
5205+ % VHost = list() -> "jabber.example.org"
5206+ {start, 2},
5207+
5208+ % called from cleanup/1
5209+ % it should logoff database and do cleanup
5210+ % stop(VHost)
5211+ % Types: VHost = list() -> "jabber.example.org"
5212+ {stop, 1},
5213+
5214+ % called from handle_call({addlog, _}, _, _)
5215+ % it should log messages to database
5216+ % log_message(VHost, Msg) -> ok | error
5217+ % Types:
5218+ % VHost = list() -> "jabber.example.org"
5219+ % Msg = record() -> #msg
5220+ {log_message, 2},
5221+
5222+ % called from ejabberdctl rebuild_stats
5223+ % it should rebuild stats table (if used) for vhost
5224+ % rebuild_stats(VHost)
5225+ % Types:
5226+ % VHost = list() -> "jabber.example.org"
5227+ {rebuild_stats, 1},
5228+
5229+ % it should rebuild stats table (if used) for vhost at Date
5230+ % rebuild_stats_at(VHost, Date)
5231+ % Types:
5232+ % VHost = list() -> "jabber.example.org"
5233+ % Date = list() -> "2007-02-12"
5234+ {rebuild_stats_at, 2},
5235+
5236+ % called from user_messages_at_parse_query/5
5237+ % it should delete selected user messages at date
5238+ % delete_messages_by_user_at(VHost, Msgs, Date) -> ok | error
5239+ % Types:
5240+ % VHost = list() -> "jabber.example.org"
5241+ % Msgs = list() -> [ #msg1, msg2, ... ]
5242+ % Date = list() -> "2007-02-12"
5243+ {delete_messages_by_user_at, 3},
5244+
5245+ % called from user_messages_parse_query/4 | vhost_messages_at_parse_query/4
5246+ % it should delete all user messages at date
5247+ % delete_all_messages_by_user_at(User, VHost, Date) -> ok | error
5248+ % Types:
5249+ % User = list() -> "admin"
5250+ % VHost = list() -> "jabber.example.org"
5251+ % Date = list() -> "2007-02-12"
5252+ {delete_all_messages_by_user_at, 3},
5253+
5254+ % called from vhost_messages_parse_query/3
5255+ % it should delete messages for vhost at date and update stats
5256+ % delete_messages_at(VHost, Date) -> ok | error
5257+ % Types:
5258+ % VHost = list() -> "jabber.example.org"
5259+ % Date = list() -> "2007-02-12"
5260+ {delete_messages_at, 2},
5261+
5262+ % called from ejabberd_web_admin:vhost_messages_stats/3
5263+ % it should return sorted list of count of messages by dates for vhost
5264+ % get_vhost_stats(VHost) -> {ok, [{Date1, Msgs_count1}, {Date2, Msgs_count2}, ... ]} |
5265+ % {error, Reason}
5266+ % Types:
5267+ % VHost = list() -> "jabber.example.org"
5268+ % DateN = list() -> "2007-02-12"
5269+ % Msgs_countN = number() -> 241
5270+ {get_vhost_stats, 1},
5271+
5272+ % called from ejabberd_web_admin:vhost_messages_stats_at/4
5273+ % it should return sorted list of count of messages by users at date for vhost
5274+ % get_vhost_stats_at(VHost, Date) -> {ok, [{User1, Msgs_count1}, {User2, Msgs_count2}, ....]} |
5275+ % {error, Reason}
5276+ % Types:
5277+ % VHost = list() -> "jabber.example.org"
5278+ % Date = list() -> "2007-02-12"
5279+ % UserN = list() -> "admin"
5280+ % Msgs_countN = number() -> 241
5281+ {get_vhost_stats_at, 2},
5282+
5283+ % called from ejabberd_web_admin:user_messages_stats/4
5284+ % it should return sorted list of count of messages by date for user at vhost
5285+ % get_user_stats(User, VHost) -> {ok, [{Date1, Msgs_count1}, {Date2, Msgs_count2}, ...]} |
5286+ % {error, Reason}
5287+ % Types:
5288+ % User = list() -> "admin"
5289+ % VHost = list() -> "jabber.example.org"
5290+ % DateN = list() -> "2007-02-12"
5291+ % Msgs_countN = number() -> 241
5292+ {get_user_stats, 2},
5293+
5294+ % called from ejabberd_web_admin:user_messages_stats_at/5
5295+ % it should return all user messages at date
5296+ % get_user_messages_at(User, VHost, Date) -> {ok, Msgs} | {error, Reason}
5297+ % Types:
5298+ % User = list() -> "admin"
5299+ % VHost = list() -> "jabber.example.org"
5300+ % Date = list() -> "2007-02-12"
5301+ % Msgs = list() -> [ #msg1, msg2, ... ]
5302+ {get_user_messages_at, 3},
5303+
5304+ % called from many places
5305+ % it should return list of dates for vhost
5306+ % get_dates(VHost) -> [Date1, Date2, ... ]
5307+ % Types:
5308+ % VHost = list() -> "jabber.example.org"
5309+ % DateN = list() -> "2007-02-12"
5310+ {get_dates, 1},
5311+
5312+ % called from start
5313+ % it should return list with users settings for VHost in db
5314+ % get_users_settings(VHost) -> [#user_settings1, #user_settings2, ... ] | error
5315+ % Types:
5316+ % VHost = list() -> "jabber.example.org"
5317+ % User = list() -> "admin"
5318+ {get_users_settings, 1},
5319+
5320+ % called from many places
5321+ % it should return User settings at VHost from db
5322+ % get_user_settings(User, VHost) -> error | {ok, #user_settings}
5323+ % Types:
5324+ % User = list() -> "admin"
5325+ % VHost = list() -> "jabber.example.org"
5326+ {get_user_settings, 2},
5327+
5328+ % called from web admin
5329+ % it should set User settings at VHost
5330+ % set_user_settings(User, VHost, #user_settings) -> ok | error
5331+ % Types:
5332+ % User = list() -> "admin"
5333+ % VHost = list() -> "jabber.example.org"
5334+ {set_user_settings, 3}
5335+ ];
5336+behaviour_info(_) ->
5337+ undefined.
5338--- src/web/ejabberd_web_admin-1.1.4.erl Tue Dec 11 13:25:24 2007
5339+++ src/web/ejabberd_web_admin.erl Fri Jul 27 09:19:48 2007
5340@@ -21,6 +21,7 @@
5341 -include("ejabberd.hrl").
5342 -include("jlib.hrl").
5343 -include("ejabberd_http.hrl").
5344+-include("mod_logdb.hrl").
5345
5346 -define(X(Name), {xmlelement, Name, [], []}).
5347 -define(XA(Name, Attrs), {xmlelement, Name, Attrs, []}).
5348@@ -46,6 +47,11 @@
5349 ?XA("input", [{"type", Type},
5350 {"name", Name},
5351 {"value", Value}])).
5352+-define(INPUTC(Type, Name, Value),
5353+ ?XA("input", [{"type", Type},
5354+ {"name", Name},
5355+ {"value", Value},
5356+ {"checked", "true"}])).
5357 -define(INPUTT(Type, Name, Value), ?INPUT(Type, Name, ?T(Value))).
5358 -define(INPUTS(Type, Name, Value, Size),
5359 ?XA("input", [{"type", Type},
5360@@ -137,6 +143,12 @@
5361 [?LI([?ACT(Base ++ "shared-roster/", "Shared Roster")])];
5362 false ->
5363 []
5364+ end ++
5365+ case gen_mod:is_loaded(Host, mod_logdb) of
5366+ true ->
5367+ [?LI([?ACT(Base ++ "messages/", "Users Messages")])];
5368+ false ->
5369+ []
5370 end
5371 )]),
5372 ?XAE("div",
5373@@ -564,6 +576,12 @@
5374 [?LI([?ACT(Base ++ "shared-roster/", "Shared Roster")])];
5375 false ->
5376 []
5377+ end ++
5378+ case gen_mod:is_loaded(Host, mod_logdb) of
5379+ true ->
5380+ [?LI([?ACT(Base ++ "messages/", "Users Messages")])];
5381+ false ->
5382+ []
5383 end
5384 )
5385 ], Host, Lang);
5386@@ -925,6 +943,38 @@
5387 make_xhtml(Res, Host, Lang);
5388
5389 process_admin(Host,
5390+ #request{us = US,
5391+ path = ["messages"],
5392+ q = Query,
5393+ lang = Lang} = Request) when is_list(Host) ->
5394+ Res = vhost_messages_stats(Host, Query, Lang),
5395+ make_xhtml(Res, Host, Lang);
5396+
5397+process_admin(Host,
5398+ #request{us = US,
5399+ path = ["messages", Date],
5400+ q = Query,
5401+ lang = Lang} = Request) when is_list(Host) ->
5402+ Res = vhost_messages_stats_at(Host, Query, Lang, Date),
5403+ make_xhtml(Res, Host, Lang);
5404+
5405+process_admin(Host,
5406+ #request{us = US,
5407+ path = ["user", U, "messages"],
5408+ q = Query,
5409+ lang = Lang} = Request) ->
5410+ Res = user_messages_stats(U, Host, Query, Lang),
5411+ make_xhtml(Res, Host, Lang);
5412+
5413+process_admin(Host,
5414+ #request{us = US,
5415+ path = ["user", U, "messages", Date],
5416+ q = Query,
5417+ lang = Lang} = Request) ->
5418+ Res = user_messages_stats_at(U, Host, Query, Lang, Date),
5419+ make_xhtml(Res, Host, Lang);
5420+
5421+process_admin(Host,
5422 #request{us = US,
5423 path = ["user", U, "roster"],
5424 q = Query,
5425@@ -1442,6 +1492,22 @@
5426 [?XCT("h3", "Password:")] ++ FPassword ++
5427 [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++
5428 [?XE("h3", [?ACT("roster/", "Roster")])] ++
5429+ case gen_mod:is_loaded(Server, mod_logdb) of
5430+ true ->
5431+ Sett = mod_logdb:get_user_settings(User, Server),
5432+ Log =
5433+ case Sett#user_settings.dolog_default of
5434+ false ->
5435+ ?INPUTT("submit", "dolog", "Log Messages");
5436+ true ->
5437+ ?INPUTT("submit", "donotlog", "Do Not Log Messages");
5438+ _ -> []
5439+ end,
5440+ [?XE("h3", [?ACT("messages/", "Messages"), ?C(" "), Log])];
5441+ %[?INPUT("test", "test", "test"), ?C(" "), Log];
5442+ false ->
5443+ []
5444+ end ++
5445 [?BR, ?INPUTT("submit", "removeuser", "Remove User")])].
5446
5447
5448@@ -1462,8 +1528,24 @@
5449 {value, _} ->
5450 ejabberd_auth:remove_user(User, Server),
5451 ok;
5452- false ->
5453- nothing
5454+ _ ->
5455+ case lists:keysearch("dolog", 1, Query) of
5456+ {value, _} ->
5457+ Sett = mod_logdb:get_user_settings(User, Server),
5458+ % TODO: check returned value
5459+ mod_logdb:set_user_settings(User, Server, Sett#user_settings{dolog_default=true}),
5460+ nothing;
5461+ _ ->
5462+ case lists:keysearch("donotlog", 1, Query) of
5463+ {value, _} ->
5464+ Sett = mod_logdb:get_user_settings(User, Server),
5465+ % TODO: check returned value
5466+ mod_logdb:set_user_settings(User, Server, Sett#user_settings{dolog_default=false}),
5467+ nothing;
5468+ false ->
5469+ nothing
5470+ end
5471+ end
5472 end
5473 end.
5474
5475@@ -1574,6 +1656,14 @@
5476 Res = user_roster_parse_query(User, Server, Items1, Query, Admin),
5477 Items = mnesia:dirty_index_read(roster, US, #roster.us),
5478 SItems = lists:sort(Items),
5479+
5480+ Settings = case gen_mod:is_loaded(Server, mod_logdb) of
5481+ true ->
5482+ mod_logdb:get_user_settings(User, Server);
5483+ false ->
5484+ []
5485+ end,
5486+
5487 FItems =
5488 case SItems of
5489 [] ->
5490@@ -1621,7 +1711,33 @@
5491 [?INPUTT("submit",
5492 "remove" ++
5493 term_to_id(R#roster.jid),
5494- "Remove")])])
5495+ "Remove")]),
5496+ case gen_mod:is_loaded(Server, mod_logdb) of
5497+ true ->
5498+ Peer = jlib:jid_to_string(R#roster.jid),
5499+ A = lists:member(Peer, Settings#user_settings.dolog_list),
5500+ B = lists:member(Peer, Settings#user_settings.donotlog_list),
5501+ {Name, Value} =
5502+ if
5503+ A ->
5504+ {"donotlog", "Do Not Log Messages"};
5505+ B ->
5506+ {"dolog", "Log Messages"};
5507+ Settings#user_settings.dolog_default == true ->
5508+ {"donotlog", "Do Not Log Messages"};
5509+ Settings#user_settings.dolog_default == false ->
5510+ {"dolog", "Log Messages"}
5511+ end,
5512+
5513+ ?XAE("td", [{"class", "valign"}],
5514+ [?INPUTT("submit",
5515+ Name ++
5516+ term_to_id(R#roster.jid),
5517+ Value)]);
5518+ false ->
5519+ ?X([])
5520+ end
5521+ ])
5522 end, SItems))])]
5523 end,
5524 [?XC("h1", ?T("Roster of ") ++ us_to_list(US))] ++
5525@@ -1637,6 +1753,288 @@
5526 ?INPUTT("submit", "addjid", "Add Jabber ID")
5527 ])].
5528
5529+vhost_messages_stats(Server, Query, Lang) ->
5530+ Res = case catch mod_logdb:vhost_messages_parse_query(Server, Query) of
5531+ {'EXIT', Reason} ->
5532+ ?ERROR_MSG("~p", [Reason]),
5533+ error;
5534+ VResult -> VResult
5535+ end,
5536+ {Time, Value} = timer:tc(mod_logdb, get_vhost_stats, [Server]),
5537+ ?INFO_MSG("get_vhost_stats(~p) elapsed ~p sec", [Server, Time/1000000]),
5538+ %case mod_logdb:get_vhost_stats(Server) of
5539+ case Value of
5540+ {'EXIT', CReason} ->
5541+ ?ERROR_MSG("Failed to get_vhost_stats: ~p", [CReason]),
5542+ [?XC("h1", ?T("Error occupied while fetching list"))];
5543+ {error, GReason} ->
5544+ ?ERROR_MSG("Failed to get_vhost_stats: ~p", [GReason]),
5545+ [?XC("h1", ?T("Error occupied while fetching list"))];
5546+ {ok, []} ->
5547+ [?XC("h1", ?T("No logged messages for ") ++ Server)];
5548+ {ok, Dates} ->
5549+ Fun = fun({Date, Count}) ->
5550+ ID = jlib:encode_base64(binary_to_list(term_to_binary(Server++Date))),
5551+ ?XE("tr",
5552+ [?XE("td", [?INPUT("checkbox", "selected", ID)]),
5553+ ?XE("td", [?AC(Date, Date)]),
5554+ ?XC("td", integer_to_list(Count))
5555+ ])
5556+ end,
5557+ [?XC("h1", ?T("Logged messages for ") ++ Server)] ++
5558+ case Res of
5559+ ok -> [?CT("Submitted"), ?P];
5560+ error -> [?CT("Bad format"), ?P];
5561+ nothing -> []
5562+ end ++
5563+ [?XAE("form", [{"action", ""}, {"method", "post"}],
5564+ [?XE("table",
5565+ [?XE("thead",
5566+ [?XE("tr",
5567+ [?X("td"),
5568+ ?XCT("td", "Date"),
5569+ ?XCT("td", "Count")
5570+ ])]),
5571+ ?XE("tbody",
5572+ lists:map(Fun, Dates)
5573+ )]),
5574+ ?BR,
5575+ ?INPUTT("submit", "delete", "Delete Selected")
5576+ ])]
5577+ end.
5578+
5579+vhost_messages_stats_at(Server, Query, Lang, Date) ->
5580+ {Time, Value} = timer:tc(mod_logdb, get_vhost_stats_at, [Server, Date]),
5581+ ?INFO_MSG("get_vhost_stats_at(~p,~p) elapsed ~p sec", [Server, Date, Time/1000000]),
5582+ %case mod_logdb:get_vhost_stats_at(Server, Date) of
5583+ case Value of
5584+ {'EXIT', CReason} ->
5585+ ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [CReason]),
5586+ [?XC("h1", ?T("Error occupied while fetching list"))];
5587+ {error, GReason} ->
5588+ ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [GReason]),
5589+ [?XC("h1", ?T("Error occupied while fetching list"))];
5590+ {ok, []} ->
5591+ [?XC("h1", ?T("No logged messages for ") ++ Server ++ ?T(" at ") ++ Date)];
5592+ {ok, Users} ->
5593+ Res = case catch mod_logdb:vhost_messages_at_parse_query(Server, Date, Users, Query) of
5594+ {'EXIT', Reason} ->
5595+ ?ERROR_MSG("~p", [Reason]),
5596+ error;
5597+ VResult -> VResult
5598+ end,
5599+ Fun = fun({User, Count}) ->
5600+ ID = jlib:encode_base64(binary_to_list(term_to_binary(User++Server))),
5601+ ?XE("tr",
5602+ [?XE("td", [?INPUT("checkbox", "selected", ID)]),
5603+ ?XE("td", [?AC("../user/"++User++"/messages/"++Date, User)]),
5604+ ?XC("td", integer_to_list(Count))
5605+ ])
5606+ end,
5607+ [?XC("h1", ?T("Logged messages for ") ++ Server ++ ?T(" at ") ++ Date)] ++
5608+ case Res of
5609+ ok -> [?CT("Submitted"), ?P];
5610+ error -> [?CT("Bad format"), ?P];
5611+ nothing -> []
5612+ end ++
5613+ [?XAE("form", [{"action", ""}, {"method", "post"}],
5614+ [?XE("table",
5615+ [?XE("thead",
5616+ [?XE("tr",
5617+ [?X("td"),
5618+ ?XCT("td", "User"),
5619+ ?XCT("td", "Count")
5620+ ])]),
5621+ ?XE("tbody",
5622+ lists:map(Fun, Users)
5623+ )]),
5624+ ?BR,
5625+ ?INPUTT("submit", "delete", "Delete Selected")
5626+ ])]
5627+ end.
5628+
5629+user_messages_stats(User, Server, Query, Lang) ->
5630+ US = {jlib:nodeprep(User), jlib:nameprep(Server)},
5631+ Jid = us_to_list(US),
5632+
5633+ Res = case catch mod_logdb:user_messages_parse_query(User, Server, Query) of
5634+ {'EXIT', Reason} ->
5635+ ?ERROR_MSG("~p", [Reason]),
5636+ error;
5637+ VResult -> VResult
5638+ end,
5639+
5640+ {Time, Value} = timer:tc(mod_logdb, get_user_stats, [User, Server]),
5641+ ?INFO_MSG("get_user_stats(~p,~p) elapsed ~p sec", [User, Server, Time/1000000]),
5642+
5643+ case Value of
5644+ {'EXIT', CReason} ->
5645+ ?ERROR_MSG("Failed to get_user_stats: ~p", [CReason]),
5646+ [?XC("h1", ?T("Error occupied while fetching days"))];
5647+ {error, GReason} ->
5648+ ?ERROR_MSG("Failed to get_user_stats: ~p", [GReason]),
5649+ [?XC("h1", ?T("Error occupied while fetching days"))];
5650+ {ok, []} ->
5651+ [?XC("h1", ?T("No logged messages for ") ++ Jid)];
5652+ {ok, Dates} ->
5653+ Fun = fun({Date, Count}) ->
5654+ ID = jlib:encode_base64(binary_to_list(term_to_binary(User++Date))),
5655+ ?XE("tr",
5656+ [?XE("td", [?INPUT("checkbox", "selected", ID)]),
5657+ ?XE("td", [?AC(Date, Date)]),
5658+ ?XC("td", integer_to_list(Count))
5659+ ])
5660+ %[?AC(Date, Date ++ " (" ++ integer_to_list(Count) ++ ")"), ?BR]
5661+ end,
5662+ [?XC("h1", ?T("Logged messages for ") ++ Jid)] ++
5663+ case Res of
5664+ ok -> [?CT("Submitted"), ?P];
5665+ error -> [?CT("Bad format"), ?P];
5666+ nothing -> []
5667+ end ++
5668+ [?XAE("form", [{"action", ""}, {"method", "post"}],
5669+ [?XE("table",
5670+ [?XE("thead",
5671+ [?XE("tr",
5672+ [?X("td"),
5673+ ?XCT("td", "Date"),
5674+ ?XCT("td", "Count")
5675+ ])]),
5676+ ?XE("tbody",
5677+ lists:map(Fun, Dates)
5678+ )]),
5679+ ?BR,
5680+ ?INPUTT("submit", "delete", "Delete Selected")
5681+ ])]
5682+ end.
5683+
5684+user_messages_stats_at(User, Server, Query, Lang, Date) ->
5685+ US = {jlib:nodeprep(User), jlib:nameprep(Server)},
5686+ Jid = us_to_list(US),
5687+
5688+ {Time, Value} = timer:tc(mod_logdb, get_user_messages_at, [User, Server, Date]),
5689+ ?INFO_MSG("get_user_messages_at(~p,~p,~p) elapsed ~p sec", [User, Server, Date, Time/1000000]),
5690+ case Value of
5691+ {'EXIT', CReason} ->
5692+ ?ERROR_MSG("Failed to get_user_messages_at: ~p", [CReason]),
5693+ [?XC("h1", ?T("Error occupied while fetching messages"))];
5694+ {error, GReason} ->
5695+ ?ERROR_MSG("Failed to get_user_messages_at: ~p", [GReason]),
5696+ [?XC("h1", ?T("Error occupied while fetching messages"))];
5697+ {ok, []} ->
5698+ [?XC("h1", ?T("No logged messages for ") ++ Jid ++ ?T(" at ") ++ Date)];
5699+ {ok, User_messages} ->
5700+ Res = case catch mod_logdb:user_messages_at_parse_query(Server,
5701+ Date,
5702+ User_messages,
5703+ Query) of
5704+ {'EXIT', Reason} ->
5705+ ?ERROR_MSG("~p", [Reason]),
5706+ error;
5707+ VResult -> VResult
5708+ end,
5709+
5710+ UniqUsers = lists:foldl(fun(#msg{peer_name=PName, peer_server=PServer}, List) ->
5711+ case lists:member(PName++"@"++PServer, List) of
5712+ true -> List;
5713+ false -> lists:append([PName++"@"++PServer], List)
5714+ end
5715+ end, [], User_messages),
5716+
5717+ % Users to filter (sublist of UniqUsers)
5718+ CheckedUsers = case lists:keysearch("filter", 1, Query) of
5719+ {value, _} ->
5720+ lists:filter(fun(UFUser) ->
5721+ ID = jlib:encode_base64(binary_to_list(term_to_binary(UFUser))),
5722+ lists:member({"selected", ID}, Query)
5723+ end, UniqUsers);
5724+ false -> []
5725+ end,
5726+
5727+ % UniqUsers in html (noone selected -> everyone selected)
5728+ Users = lists:map(fun(UHUser) ->
5729+ ID = jlib:encode_base64(binary_to_list(term_to_binary(UHUser))),
5730+ Input = case lists:member(UHUser, CheckedUsers) of
5731+ true -> [?INPUTC("checkbox", "selected", ID)];
5732+ false when CheckedUsers == [] -> [?INPUTC("checkbox", "selected", ID)];
5733+ false -> [?INPUT("checkbox", "selected", ID)]
5734+ end,
5735+ ?XE("tr",
5736+ [?XE("td", Input),
5737+ ?XC("td", UHUser)])
5738+ end, lists:sort(UniqUsers)),
5739+
5740+ % Messages to show (based on Users)
5741+ User_messages_filtered = case CheckedUsers of
5742+ [] -> User_messages;
5743+ _ -> lists:filter(fun(#msg{peer_name=PName, peer_server=PServer}) ->
5744+ lists:member(PName++"@"++PServer, CheckedUsers)
5745+ end, User_messages)
5746+ end,
5747+
5748+ Msgs_Fun = fun(#msg{timestamp=Timestamp,
5749+ subject=Subject,
5750+ direction=Direction,
5751+ peer_name=PName, peer_server=PServer, peer_resource=PRes,
5752+ body=Body}) ->
5753+ TextRaw = case Subject of
5754+ "" -> Body;
5755+ _ -> [?T("Subject"),": ",Subject,"<br>", Body]
5756+ end,
5757+ ID = jlib:encode_base64(binary_to_list(term_to_binary(Timestamp))),
5758+ % replace \n with <br>
5759+ Text = lists:map(fun(10) -> "<br>";
5760+ (A) -> A
5761+ end, TextRaw),
5762+ Resource = case PRes of
5763+ [] -> [];
5764+ undefined -> [];
5765+ R -> "/" ++ R
5766+ end,
5767+ ?XE("tr",
5768+ [?XE("td", [?INPUT("checkbox", "selected", ID)]),
5769+ ?XC("td", mod_logdb:convert_timestamp(Timestamp)),
5770+ ?XC("td", atom_to_list(Direction)++": "++PName++"@"++PServer++Resource),
5771+ ?XC("td", Text)])
5772+ end,
5773+ % Filtered user messages in html
5774+ Msgs = lists:map(Msgs_Fun, lists:sort(User_messages_filtered)),
5775+
5776+ [?XC("h1", ?T("Logged messages for ") ++ Jid ++ ?T(" at ") ++ Date)] ++
5777+ case Res of
5778+ ok -> [?CT("Submitted"), ?P];
5779+ error -> [?CT("Bad format"), ?P];
5780+ nothing -> []
5781+ end ++
5782+ [?XAE("form", [{"action", ""}, {"method", "post"}],
5783+ [?XE("table",
5784+ [?XE("thead",
5785+ [?X("td"),
5786+ ?XCT("td", "User")
5787+ ]
5788+ ),
5789+ ?XE("tbody",
5790+ Users
5791+ )]),
5792+ ?INPUTT("submit", "filter", "Filter Selected")
5793+ ] ++
5794+ [?XE("table",
5795+ [?XE("thead",
5796+ [?XE("tr",
5797+ [?X("td"),
5798+ ?XCT("td", "Date, Time"),
5799+ ?XCT("td", "Direction: Jid"),
5800+ ?XCT("td", "Body")
5801+ ])]),
5802+ ?XE("tbody",
5803+ Msgs
5804+ )]),
5805+ ?INPUTT("submit", "delete", "Delete Selected"),
5806+ ?BR
5807+ ]
5808+ )]
5809+ end.
5810+
5811 user_roster_parse_query(User, Server, Items, Query, Admin) ->
5812 case lists:keysearch("addjid", 1, Query) of
5813 {value, _} ->
5814@@ -1704,10 +2102,41 @@
5815 []}]}}),
5816 throw(submitted);
5817 false ->
5818- ok
5819- end
5820-
5821- end
5822+ case lists:keysearch(
5823+ "donotlog" ++ term_to_id(JID), 1, Query) of
5824+ {value, _} ->
5825+ Peer = jlib:jid_to_string(JID),
5826+ Settings = mod_logdb:get_user_settings(User, Server),
5827+ DNLL = case lists:member(Peer, Settings#user_settings.donotlog_list) of
5828+ false -> lists:append(Settings#user_settings.donotlog_list, [Peer]);
5829+ true -> Settings#user_settings.donotlog_list
5830+ end,
5831+ DLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.dolog_list),
5832+ Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
5833+ % TODO: check returned value
5834+ ok = mod_logdb:set_user_settings(User, Server, Sett),
5835+ throw(nothing);
5836+ false ->
5837+ case lists:keysearch(
5838+ "dolog" ++ term_to_id(JID), 1, Query) of
5839+ {value, _} ->
5840+ Peer = jlib:jid_to_string(JID),
5841+ Settings = mod_logdb:get_user_settings(User, Server),
5842+ DLL = case lists:member(Peer, Settings#user_settings.dolog_list) of
5843+ false -> lists:append(Settings#user_settings.dolog_list, [Peer]);
5844+ true -> Settings#user_settings.dolog_list
5845+ end,
5846+ DNLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.donotlog_list),
5847+ Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
5848+ % TODO: check returned value
5849+ ok = mod_logdb:set_user_settings(User, Server, Sett),
5850+ throw(nothing);
5851+ false ->
5852+ ok
5853+ end % dolog
5854+ end % donotlog
5855+ end % remove
5856+ end % validate
5857 end, Items),
5858 nothing.
5859
5860--- src/mod_muc/mod_muc_room-1.1.4.erl Tue Dec 11 13:26:10 2007
5861+++ src/mod_muc/mod_muc_room.erl Tue Dec 11 14:21:59 2007
5862@@ -652,6 +652,12 @@
5863 false
5864 end,
5865 {reply, Reply, StateName, StateData};
5866+handle_sync_event({get_jid_nick, Jid}, _From, StateName, StateData) ->
5867+ R = case ?DICT:find(jlib:jid_tolower(Jid), StateData#state.users) of
5868+ error -> [];
5869+ {ok, {user, _, Nick, _, _}} -> Nick
5870+ end,
5871+ {reply, R, StateName, StateData};
5872 handle_sync_event(_Event, _From, StateName, StateData) ->
5873 Reply = ok,
5874 {reply, Reply, StateName, StateData}.
5875--- src/msgs/uk-1.1.4.msg Tue Dec 11 14:15:44 2007
5876+++ src/msgs/uk.msg Tue Dec 11 14:23:19 2007
5877@@ -372,6 +372,32 @@
5878 {"ejabberd virtual hosts", "віртуальні хости ejabberd"}.
5879 {"Host", "Хост"}.
5880
5881+% mod_logdb
5882+{"Users Messages", "Повідомлення користувачів"}.
5883+{"Date", "Дата"}.
5884+{"Count", "Кількість"}.
5885+{"Logged messages for ", "Збережені повідомлення для "}.
5886+{" at ", " за "}.
5887+{"No logged messages for ", "Відсутні повідомлення для "}.
5888+{"Date, Time", "Дата, Час"}.
5889+{"Direction: Jid", "Напрямок: Jid"}.
5890+{"Subject", "Тема"}.
5891+{"Body", "Текст"}.
5892+{"Messages", "Повідомлення"}.
5893+{"Filter Selected", "Відфільтрувати виділені"}.
5894+{"Do Not Log Messages", "Не зберігати повідомлення"}.
5895+{"Log Messages", "Зберігати повідомлення"}.
5896+{"Messages logging engine", "Система збереження повідомлень"}.
5897+{"Default", "За замовчуванням"}.
5898+{"Set logging preferences", "Вкажіть налагоджування збереження повідомлень"}.
5899+{"Messages logging engine users", "Користувачі системи збереження повідомлень"}.
5900+{"Messages logging engine settings", "Налагоджування системи збереження повідомлень"}.
5901+{"Set run-time settings", "Вкажіть поточні налагоджування"}.
5902+{"Groupchat messages logging", "Збереження повідомлень типу groupchat"}.
5903+{"Jids/Domains to ignore", "Ігнорувати наступні jids/домени"}.
5904+{"Purge messages older than (days)", "Видаляти повідомлення старіші ніж (дні)"}.
5905+{"Poll users settings (seconds)", "Оновлювати налагоджування користувачів кожні (секунд)"}.
5906+
5907 % Local Variables:
5908 % mode: erlang
5909 % End:
5910--- src/msgs/ru-1.1.4.msg Tue Dec 11 14:15:51 2007
5911+++ src/msgs/ru.msg Tue Dec 11 14:23:19 2007
5912@@ -372,6 +372,32 @@
5913 {"ejabberd virtual hosts", "Виртуальные хосты ejabberd"}.
5914 {"Host", "Хост"}.
5915
5916+% mod_logdb.erl
5917+{"Users Messages", "Сообщения пользователей"}.
5918+{"Date", "Дата"}.
5919+{"Count", "Количество"}.
5920+{"Logged messages for ", "Сохранённые cообщения для "}.
5921+{" at ", " за "}.
5922+{"No logged messages for ", "Отсутствуют сообщения для "}.
5923+{"Date, Time", "Дата, Время"}.
5924+{"Direction: Jid", "Направление: Jid"}.
5925+{"Subject", "Тема"}.
5926+{"Body", "Текст"}.
5927+{"Messages", "Сообщения"}.
5928+{"Filter Selected", "Отфильтровать выделенные"}.
5929+{"Do Not Log Messages", "Не сохранять сообщения"}.
5930+{"Log Messages", "Сохранять сообщения"}.
5931+{"Messages logging engine", "Система логирования сообщений"}.
5932+{"Default", "По умолчанию"}.
5933+{"Set logging preferences", "Задайте настройки логирования"}.
5934+{"Messages logging engine users", "Пользователи системы логирования сообщений"}.
5935+{"Messages logging engine settings", "Настройки системы логирования сообщений"}.
5936+{"Set run-time settings", "Задайте текущие настройки"}.
5937+{"Groupchat messages logging", "Логирование сообщений типа groupchat"}.
5938+{"Jids/Domains to ignore", "Игнорировать следующие jids/домены"}.
5939+{"Purge messages older than (days)", "Удалять сообщения старее чем (дни)"}.
5940+{"Poll users settings (seconds)", "Обновлять настройки пользователей через (секунд)"}.
5941+
5942 % Local Variables:
5943 % mode: erlang
5944 % End:
5945--- src/msgs/nl-1.1.4.msg Tue Dec 11 14:15:58 2007
5946+++ src/msgs/nl.msg Thu Apr 26 16:04:49 2007
5947@@ -331,4 +331,15 @@
5948 {"Members:", "Groepsleden:"}.
5949 {"Displayed Groups:", "Weergegeven groepen:"}.
5950 {"Group ", "Groep "}.
5951+{"Users Messages", "Gebruikersberichten"}.
5952+{"Date", "Datum"}.
5953+{"Count", "Aantal"}.
5954+{"Logged messages for ", "Gelogde berichten van "}.
5955+{" at ", " op "}.
5956+{"No logged messages for ", "Geen gelogde berichten van "}.
5957+{"Date, Time", "Datum en tijd"}.
5958+{"Direction: Jid", "Richting: Jabber ID"}.
5959+{"Subject", "Onderwerp"}.
5960+{"Body", "Berichtveld"}.
5961+{"Messages", "Berichten"}.
5962
5963--- src/msgs/pl-1.1.4.msg Tue Dec 11 14:16:04 2007
5964+++ src/msgs/pl.msg Thu Sep 6 09:52:55 2007
5965@@ -423,3 +423,27 @@
5966 % ./mod_muc/mod_muc.erl
5967 {"ejabberd MUC module\nCopyright (c) 2003-2006 Alexey Shchepin", ""}.
5968
5969+% mod_logdb
5970+{"Users Messages", "Wiadomości użytkownika"}.
5971+{"Date", "Data"}.
5972+{"Count", "Liczba"}.
5973+{"Logged messages for ", "Zapisane wiadomości dla "}.
5974+{" at ", " o "}.
5975+{"No logged messages for ", "Brak zapisanych wiadomości dla "}.
5976+{"Date, Time", "Data, Godzina"}.
5977+{"Direction: Jid", "Kierunek: Jid"}.
5978+{"Subject", "Temat"}.
5979+{"Body", "Treść"}.
5980+{"Messages","Wiadomości"}.
5981+{"Filter Selected", "Odfiltruj zaznaczone"}.
5982+{"Do Not Log Messages", "Nie zapisuj wiadomości"}.
5983+{"Log Messages", "Zapisuj wiadomości"}.
5984+{"Messages logging engine", "System zapisywania historii rozmów"}.
5985+{"Default", "Domyślne"}.
5986+{"Set logging preferences", "Ustaw preferencje zapisywania"}.
5987+{"Messages logging engine settings", "Ustawienia systemu logowania"}.
5988+{"Set run-time settings", "Zapisz ustawienia systemu logowania"}.
5989+{"Groupchat messages logging", "Zapisywanie rozmów z konferencji"}.
5990+{"Jids/Domains to ignore", "JID/Domena która ma być ignorowana"}.
5991+{"Purge messages older than (days)", "Usuń wiadomości starsze niż (w dniach)"}.
5992+{"Poll users settings (seconds)", "Czas aktualizacji preferencji użytkowników (sekundy)"}.
This page took 0.893688 seconds and 4 git commands to generate.