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