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