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