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