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