]> git.pld-linux.org Git - packages/ejabberd.git/blame - ejabberd-mod_logdb.patch
- up to 17.04; partial fixes to mod_logdb (doesn't work yet; server hangs at start)
[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 307+++ b/src/mod_logdb.erl
98471d6b 308@@ -0,0 +1,2169 @@
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
98471d6b 327+-export([start/2,stop/1,mod_opt_type/1]).
f7ce3e3a 328+% gen_server
329+-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
330+% hooks
dcddf252 331+-export([send_packet/4, receive_packet/5, 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+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
f7ce3e3a 783+% ejabberd_hooks callbacks
f7ce3e3a 784+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
785+% TODO: change to/from to list as sql stores it as list
dcddf252 786+send_packet(P, _C2SState, Owner, Peer) ->
f7ce3e3a 787+ VHost = Owner#jid.lserver,
788+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
dcddf252
AM
789+ gen_server:cast(Proc, {addlog, to, Owner, Peer, P}),
790+ P.
f7ce3e3a 791+
dcddf252 792+receive_packet(P, _C2SState, _JID, Peer, Owner) ->
f7ce3e3a 793+ VHost = Owner#jid.lserver,
794+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
dcddf252
AM
795+ gen_server:cast(Proc, {addlog, from, Owner, Peer, P}),
796+ P.
f7ce3e3a 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.
98471d6b
AM
2461+mod_opt_type(dbs) ->
2462+ fun (A) -> A end;
2463+mod_opt_type(vhosts) ->
2464+ fun (A) -> A end;
2465+mod_opt_type(poll_users_settings) ->
2466+ fun (I) when is_integer(I), I > 0 -> I end;
2467+mod_opt_type(dolog_default) ->
2468+ fun (A) when is_boolean(A) -> A end;
2469+mod_opt_type(drop_messages_on_user_removal) ->
2470+ fun (A) when is_boolean(A) -> A end;
2471+mod_opt_type(ignore_jids) ->
2472+ fun (A) -> A end;
2473+mod_opt_type(groupchat) ->
2474+ fun (A) -> A end;
2475+mod_opt_type(purge_older_days) ->
2476+ fun (I) when is_integer(I), I > 0 -> I end;
2477+mod_opt_type(_) -> [dbs, vhosts, poll_users_settings, dolog_default, drop_messages_on_user_removal, ignore_jids, groupchat, purge_older_days].
046546ef 2478diff --git a/src/mod_logdb.hrl b/src/mod_logdb.hrl
0d78319d 2479new file mode 100644
046546ef 2480index 0000000..d44f0df
0d78319d 2481--- /dev/null
046546ef 2482+++ b/src/mod_logdb.hrl
234c6b10 2483@@ -0,0 +1,35 @@
2484+%%%----------------------------------------------------------------------
2485+%%% File : mod_logdb.hrl
2486+%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
2487+%%% Purpose :
2488+%%% Version : trunk
0d78319d 2489+%%% Id : $Id: mod_logdb.hrl 1273 2009-02-05 18:12:57Z malik $
234c6b10 2490+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
2491+%%%----------------------------------------------------------------------
2492+
2493+-define(logdb_debug, true).
2494+
2495+-ifdef(logdb_debug).
2496+-define(MYDEBUG(Format, Args), io:format("D(~p:~p:~p) : "++Format++"~n",
2497+ [calendar:local_time(),?MODULE,?LINE]++Args)).
2498+-else.
2499+-define(MYDEBUG(_F,_A),[]).
2500+-endif.
2501+
2502+-record(msg, {timestamp,
2503+ owner_name,
2504+ peer_name, peer_server, peer_resource,
2505+ direction,
2506+ type, subject,
2507+ body}).
2508+
2509+-record(user_settings, {owner_name,
2510+ dolog_default,
2511+ dolog_list=[],
2512+ donotlog_list=[]}).
2513+
2514+-define(INPUTC(Type, Name, Value),
046546ef
AM
2515+ ?XA(<<"input">>, [{<<"type">>, Type},
2516+ {<<"name">>, Name},
2517+ {<<"value">>, Value},
2518+ {<<"checked">>, <<"true">>}])).
2519diff --git a/src/mod_logdb_mnesia.erl b/src/mod_logdb_mnesia.erl
0d78319d 2520new file mode 100644
046546ef 2521index 0000000..a8ae766
0d78319d 2522--- /dev/null
046546ef 2523+++ b/src/mod_logdb_mnesia.erl
98471d6b 2524@@ -0,0 +1,558 @@
234c6b10 2525+%%%----------------------------------------------------------------------
2526+%%% File : mod_logdb_mnesia.erl
2527+%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
2528+%%% Purpose : mnesia backend for mod_logdb
2529+%%% Version : trunk
0d78319d 2530+%%% Id : $Id: mod_logdb_mnesia.erl 1273 2009-02-05 18:12:57Z malik $
234c6b10 2531+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
2532+%%%----------------------------------------------------------------------
2533+
2534+-module(mod_logdb_mnesia).
2535+-author('o.palij@gmail.com').
2536+
2537+-include("mod_logdb.hrl").
2538+-include("ejabberd.hrl").
2539+-include("jlib.hrl").
046546ef 2540+-include("logger.hrl").
234c6b10 2541+
2542+-behaviour(gen_logdb).
2543+-behaviour(gen_server).
0d78319d 2544+
234c6b10 2545+% gen_server
2546+-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
2547+% gen_mod
98471d6b 2548+-export([start/2, stop/1, mod_opt_type/1]).
234c6b10 2549+% gen_logdb
2550+-export([log_message/2,
2551+ rebuild_stats/1,
2552+ rebuild_stats_at/2,
2553+ delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
2554+ get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
2555+ get_dates/1,
2556+ get_users_settings/1, get_user_settings/2, set_user_settings/3,
2557+ drop_user/2]).
0d78319d 2558+
234c6b10 2559+-define(PROCNAME, mod_logdb_mnesia).
2560+-define(CALL_TIMEOUT, 10000).
0d78319d 2561+
234c6b10 2562+-record(state, {vhost}).
2563+
2564+-record(stats, {user, at, count}).
2565+
2566+prefix() ->
2567+ "logdb_".
2568+
2569+suffix(VHost) ->
046546ef 2570+ "_" ++ binary_to_list(VHost).
234c6b10 2571+
2572+stats_table(VHost) ->
2573+ list_to_atom(prefix() ++ "stats" ++ suffix(VHost)).
2574+
2575+table_name(VHost, Date) ->
2576+ list_to_atom(prefix() ++ "messages_" ++ Date ++ suffix(VHost)).
2577+
2578+settings_table(VHost) ->
2579+ list_to_atom(prefix() ++ "settings" ++ suffix(VHost)).
2580+
2581+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2582+%
2583+% gen_mod callbacks
2584+%
2585+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2586+start(VHost, Opts) ->
2587+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2588+ gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
2589+
2590+stop(VHost) ->
2591+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2592+ gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
2593+
2594+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2595+%
2596+% gen_server callbacks
2597+%
2598+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2599+init([VHost, _Opts]) ->
2600+ case mnesia:system_info(is_running) of
2601+ yes ->
2602+ ok = create_stats_table(VHost),
2603+ ok = create_settings_table(VHost),
2604+ {ok, #state{vhost=VHost}};
2605+ no ->
2606+ ?ERROR_MSG("Mnesia not running", []),
2607+ {stop, db_connection_failed};
2608+ Status ->
2609+ ?ERROR_MSG("Mnesia status: ~p", [Status]),
2610+ {stop, db_connection_failed}
2611+ end.
2612+
2613+handle_call({log_message, Msg}, _From, #state{vhost=VHost}=State) ->
2614+ {reply, log_message_int(VHost, Msg), State};
2615+handle_call({rebuild_stats}, _From, #state{vhost=VHost}=State) ->
2616+ {atomic, ok} = delete_nonexistent_stats(VHost),
2617+ Reply =
2618+ lists:foreach(fun(Date) ->
2619+ rebuild_stats_at_int(VHost, Date)
2620+ end, get_dates_int(VHost)),
f7ce3e3a 2621+ {reply, Reply, State};
234c6b10 2622+handle_call({rebuild_stats_at, Date}, _From, #state{vhost=VHost}=State) ->
2623+ Reply = rebuild_stats_at_int(VHost, Date),
0d78319d 2624+ {reply, Reply, State};
234c6b10 2625+handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{vhost=VHost}=State) ->
f7ce3e3a 2626+ Table = table_name(VHost, Date),
234c6b10 2627+ Fun = fun() ->
2628+ lists:foreach(
2629+ fun(Msg) ->
2630+ mnesia:write_lock_table(stats_table(VHost)),
2631+ mnesia:write_lock_table(Table),
2632+ mnesia:delete_object(Table, Msg, write)
2633+ end, Msgs)
2634+ end,
2635+ DRez = case mnesia:transaction(Fun) of
f7ce3e3a 2636+ {aborted, Reason} ->
234c6b10 2637+ ?ERROR_MSG("Failed to delete_messages_by_user_at at ~p for ~p: ~p", [Date, VHost, Reason]),
f7ce3e3a 2638+ error;
2639+ _ ->
2640+ ok
234c6b10 2641+ end,
f7ce3e3a 2642+ Reply =
2643+ case rebuild_stats_at_int(VHost, Date) of
2644+ error ->
2645+ error;
2646+ ok ->
2647+ DRez
2648+ end,
2649+ {reply, Reply, State};
234c6b10 2650+handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{vhost=VHost}=State) ->
2651+ {reply, delete_all_messages_by_user_at_int(User, VHost, Date), State};
f7ce3e3a 2652+handle_call({delete_messages_at, Date}, _From, #state{vhost=VHost}=State) ->
2653+ Reply =
2654+ case mnesia:delete_table(table_name(VHost, Date)) of
2655+ {atomic, ok} ->
2656+ delete_stats_by_vhost_at_int(VHost, Date);
2657+ {aborted, Reason} ->
2658+ ?ERROR_MSG("Failed to delete_messages_at for ~p at ~p", [VHost, Date, Reason]),
2659+ error
2660+ end,
2661+ {reply, Reply, State};
2662+handle_call({get_vhost_stats}, _From, #state{vhost=VHost}=State) ->
2663+ Fun = fun(#stats{at=Date, count=Count}, Stats) ->
2664+ case lists:keysearch(Date, 1, Stats) of
2665+ false ->
2666+ lists:append(Stats, [{Date, Count}]);
2667+ {value, {_, TempCount}} ->
2668+ lists:keyreplace(Date, 1, Stats, {Date, TempCount+Count})
2669+ end
2670+ end,
2671+ Reply =
2672+ case mnesia:transaction(fun() ->
2673+ mnesia:foldl(Fun, [], stats_table(VHost))
2674+ end) of
2675+ {atomic, Result} -> {ok, mod_logdb:sort_stats(Result)};
2676+ {aborted, Reason} -> {error, Reason}
2677+ end,
2678+ {reply, Reply, State};
2679+handle_call({get_vhost_stats_at, Date}, _From, #state{vhost=VHost}=State) ->
2680+ Fun = fun() ->
2681+ Pat = #stats{user='$1', at=Date, count='$2'},
2682+ mnesia:select(stats_table(VHost), [{Pat, [], [['$1', '$2']]}])
2683+ end,
2684+ Reply =
2685+ case mnesia:transaction(Fun) of
2686+ {atomic, Result} ->
2687+ {ok, lists:reverse(lists:keysort(2, [{User, Count} || [User, Count] <- Result]))};
2688+ {aborted, Reason} ->
2689+ {error, Reason}
2690+ end,
2691+ {reply, Reply, State};
2692+handle_call({get_user_stats, User}, _From, #state{vhost=VHost}=State) ->
234c6b10 2693+ {reply, get_user_stats_int(User, VHost), State};
f7ce3e3a 2694+handle_call({get_user_messages_at, User, Date}, _From, #state{vhost=VHost}=State) ->
2695+ Reply =
2696+ case mnesia:transaction(fun() ->
2697+ Pat = #msg{owner_name=User, _='_'},
2698+ mnesia:select(table_name(VHost, Date),
2699+ [{Pat, [], ['$_']}])
2700+ end) of
2701+ {atomic, Result} -> {ok, Result};
2702+ {aborted, Reason} ->
2703+ {error, Reason}
2704+ end,
2705+ {reply, Reply, State};
2706+handle_call({get_dates}, _From, #state{vhost=VHost}=State) ->
2707+ {reply, get_dates_int(VHost), State};
2708+handle_call({get_users_settings}, _From, #state{vhost=VHost}=State) ->
2709+ Reply = mnesia:dirty_match_object(settings_table(VHost), #user_settings{_='_'}),
2710+ {reply, {ok, Reply}, State};
2711+handle_call({get_user_settings, User}, _From, #state{vhost=VHost}=State) ->
2712+ Reply =
2713+ case mnesia:dirty_match_object(settings_table(VHost), #user_settings{owner_name=User, _='_'}) of
2714+ [] -> [];
2715+ [Setting] ->
2716+ Setting
2717+ end,
2718+ {reply, Reply, State};
2719+handle_call({set_user_settings, _User, Set}, _From, #state{vhost=VHost}=State) ->
2720+ ?MYDEBUG("~p~n~p", [settings_table(VHost), Set]),
2721+ Reply = mnesia:dirty_write(settings_table(VHost), Set),
234c6b10 2722+ ?MYDEBUG("~p", [Reply]),
2723+ {reply, Reply, State};
2724+handle_call({drop_user, User}, _From, #state{vhost=VHost}=State) ->
2725+ {ok, Dates} = get_user_stats_int(User, VHost),
2726+ MDResult = lists:map(fun({Date, _}) ->
2727+ delete_all_messages_by_user_at_int(User, VHost, Date)
2728+ end, Dates),
2729+ SDResult = delete_user_settings_int(User, VHost),
2730+ Reply =
2731+ case lists:all(fun(Result) when Result == ok ->
2732+ true;
2733+ (Result) when Result == error ->
2734+ false
2735+ end, lists:append(MDResult, [SDResult])) of
2736+ true ->
2737+ ok;
2738+ false ->
2739+ error
2740+ end,
f7ce3e3a 2741+ {reply, Reply, State};
2742+handle_call({stop}, _From, State) ->
2743+ {stop, normal, ok, State};
2744+handle_call(Msg, _From, State) ->
2745+ ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
2746+ {noreply, State}.
2747+
2748+handle_cast(Msg, State) ->
2749+ ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
2750+ {noreply, State}.
2751+
2752+handle_info(Info, State) ->
2753+ ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
2754+ {noreply, State}.
2755+
2756+terminate(_Reason, _State) ->
2757+ ok.
2758+
2759+code_change(_OldVsn, State, _Extra) ->
2760+ {ok, State}.
2761+
2762+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2763+%
2764+% gen_logdb callbacks
2765+%
2766+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2767+log_message(VHost, Msg) ->
2768+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2769+ gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
2770+rebuild_stats(VHost) ->
2771+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2772+ gen_server:call(Proc, {rebuild_stats}, ?CALL_TIMEOUT).
2773+rebuild_stats_at(VHost, Date) ->
2774+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2775+ gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
2776+delete_messages_by_user_at(VHost, Msgs, Date) ->
2777+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2778+ gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
2779+delete_all_messages_by_user_at(User, VHost, Date) ->
2780+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2781+ gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
2782+delete_messages_at(VHost, Date) ->
2783+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2784+ gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
2785+get_vhost_stats(VHost) ->
2786+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2787+ gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
2788+get_vhost_stats_at(VHost, Date) ->
2789+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2790+ gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
2791+get_user_stats(User, VHost) ->
2792+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2793+ gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
2794+get_user_messages_at(User, VHost, Date) ->
2795+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2796+ gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
2797+get_dates(VHost) ->
2798+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2799+ gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
2800+get_user_settings(User, VHost) ->
2801+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2802+ gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
2803+get_users_settings(VHost) ->
2804+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2805+ gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
2806+set_user_settings(User, VHost, Set) ->
2807+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2808+ gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
234c6b10 2809+drop_user(User, VHost) ->
2810+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2811+ gen_server:call(Proc, {drop_user, User}, ?CALL_TIMEOUT).
f7ce3e3a 2812+
2813+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2814+%
2815+% internals
2816+%
2817+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
046546ef 2818+log_message_int(VHost, #msg{timestamp=Timestamp}=MsgBin) ->
f7ce3e3a 2819+ Date = mod_logdb:convert_timestamp_brief(Timestamp),
2820+
046546ef
AM
2821+ Msg = #msg{timestamp = MsgBin#msg.timestamp,
2822+ owner_name = binary_to_list(MsgBin#msg.owner_name),
2823+ peer_name = binary_to_list(MsgBin#msg.peer_name),
2824+ peer_server = binary_to_list(MsgBin#msg.peer_server),
2825+ peer_resource = binary_to_list(MsgBin#msg.peer_resource),
2826+ direction = MsgBin#msg.direction,
2827+ type = binary_to_list(MsgBin#msg.type),
2828+ subject = binary_to_list(MsgBin#msg.subject),
2829+ body = binary_to_list(MsgBin#msg.body)},
2830+
f7ce3e3a 2831+ ATable = table_name(VHost, Date),
2832+ Fun = fun() ->
2833+ mnesia:write_lock_table(ATable),
2834+ mnesia:write(ATable, Msg, write)
2835+ end,
2836+ % log message, increment stats for both users
2837+ case mnesia:transaction(Fun) of
2838+ % if table does not exists - create it and try to log message again
2839+ {aborted,{no_exists, _Table}} ->
2840+ case create_msg_table(VHost, Date) of
2841+ {aborted, CReason} ->
2842+ ?ERROR_MSG("Failed to log message: ~p", [CReason]),
2843+ error;
2844+ {atomic, ok} ->
046546ef
AM
2845+ ?MYDEBUG("Created msg table for ~s at ~s", [VHost, Date]),
2846+ log_message_int(VHost, MsgBin)
f7ce3e3a 2847+ end;
2848+ {aborted, TReason} ->
2849+ ?ERROR_MSG("Failed to log message: ~p", [TReason]),
2850+ error;
2851+ {atomic, _} ->
046546ef
AM
2852+ ?MYDEBUG("Logged ok for ~s, peer: ~s", [ [Msg#msg.owner_name, <<"@">>, VHost],
2853+ [Msg#msg.peer_name, <<"@">>, Msg#msg.peer_server] ]),
f7ce3e3a 2854+ increment_user_stats(Msg#msg.owner_name, VHost, Date)
2855+ end.
2856+
2857+increment_user_stats(Owner, VHost, Date) ->
2858+ Fun = fun() ->
2859+ Pat = #stats{user=Owner, at=Date, count='$1'},
2860+ mnesia:write_lock_table(stats_table(VHost)),
2861+ case mnesia:select(stats_table(VHost), [{Pat, [], ['$_']}]) of
2862+ [] ->
2863+ mnesia:write(stats_table(VHost),
2864+ #stats{user=Owner,
2865+ at=Date,
2866+ count=1},
2867+ write);
2868+ [Stats] ->
2869+ mnesia:delete_object(stats_table(VHost),
2870+ #stats{user=Owner,
2871+ at=Date,
2872+ count=Stats#stats.count},
2873+ write),
2874+ New = Stats#stats{count = Stats#stats.count+1},
2875+ if
2876+ New#stats.count > 0 -> mnesia:write(stats_table(VHost),
2877+ New,
2878+ write);
2879+ true -> ok
2880+ end
2881+ end
2882+ end,
2883+ case mnesia:transaction(Fun) of
2884+ {aborted, Reason} ->
2885+ ?ERROR_MSG("Failed to update stats for ~s@~s: ~p", [Owner, VHost, Reason]),
2886+ error;
2887+ {atomic, _} ->
2888+ ?MYDEBUG("Updated stats for ~s@~s", [Owner, VHost]),
2889+ ok
2890+ end.
2891+
2892+get_dates_int(VHost) ->
2893+ Tables = mnesia:system_info(tables),
2894+ lists:foldl(fun(ATable, Dates) ->
046546ef
AM
2895+ Table = term_to_binary(ATable),
2896+ case ejabberd_regexp:run( Table, << VHost/binary, <<"$">>/binary >> ) of
0d78319d
AM
2897+ match ->
2898+ case re:run(Table, "_[0-9]+-[0-9]+-[0-9]+_") of
2899+ {match, [{S, E}]} ->
046546ef 2900+ lists:append(Dates, [lists:sublist(binary_to_list(Table), S+2, E-2)]);
f7ce3e3a 2901+ nomatch ->
2902+ Dates
2903+ end;
2904+ nomatch ->
2905+ Dates
2906+ end
2907+ end, [], Tables).
2908+
2909+rebuild_stats_at_int(VHost, Date) ->
2910+ Table = table_name(VHost, Date),
2911+ STable = stats_table(VHost),
2912+ CFun = fun(Msg, Stats) ->
2913+ Owner = Msg#msg.owner_name,
2914+ case lists:keysearch(Owner, 1, Stats) of
2915+ {value, {_, Count}} ->
2916+ lists:keyreplace(Owner, 1, Stats, {Owner, Count + 1});
2917+ false ->
2918+ lists:append(Stats, [{Owner, 1}])
2919+ end
2920+ end,
2921+ DFun = fun(#stats{at=SDate} = Stat, _Acc)
2922+ when SDate == Date ->
2923+ mnesia:delete_object(stats_table(VHost), Stat, write);
2924+ (_Stat, _Acc) -> ok
2925+ end,
2926+ % TODO: Maybe unregister hooks ?
2927+ case mnesia:transaction(fun() ->
2928+ mnesia:write_lock_table(Table),
2929+ mnesia:write_lock_table(STable),
046546ef
AM
2930+ % Delete all stats for VHost at Date
2931+ mnesia:foldl(DFun, [], STable),
f7ce3e3a 2932+ % Calc stats for VHost at Date
2933+ case mnesia:foldl(CFun, [], Table) of
2934+ [] -> empty;
2935+ AStats ->
f7ce3e3a 2936+ % Write new calc'ed stats
2937+ lists:foreach(fun({Owner, Count}) ->
2938+ WStat = #stats{user=Owner, at=Date, count=Count},
2939+ mnesia:write(stats_table(VHost), WStat, write)
2940+ end, AStats),
2941+ ok
2942+ end
2943+ end) of
2944+ {aborted, Reason} ->
2945+ ?ERROR_MSG("Failed to rebuild_stats_at for ~p at ~p: ~p", [VHost, Date, Reason]),
2946+ error;
2947+ {atomic, ok} ->
2948+ ok;
2949+ {atomic, empty} ->
2950+ {atomic,ok} = mnesia:delete_table(Table),
2951+ ?MYDEBUG("Dropped table at ~p", [Date]),
2952+ ok
2953+ end.
2954+
2955+delete_nonexistent_stats(VHost) ->
2956+ Dates = get_dates_int(VHost),
2957+ mnesia:transaction(fun() ->
2958+ mnesia:foldl(fun(#stats{at=Date} = Stat, _Acc) ->
2959+ case lists:member(Date, Dates) of
2960+ false -> mnesia:delete_object(Stat);
2961+ true -> ok
2962+ end
2963+ end, ok, stats_table(VHost))
2964+ end).
2965+
2966+delete_stats_by_vhost_at_int(VHost, Date) ->
2967+ StatsDelete = fun(#stats{at=SDate} = Stat, _Acc)
2968+ when SDate == Date ->
2969+ mnesia:delete_object(stats_table(VHost), Stat, write),
2970+ ok;
2971+ (_Msg, _Acc) -> ok
2972+ end,
2973+ case mnesia:transaction(fun() ->
2974+ mnesia:write_lock_table(stats_table(VHost)),
2975+ mnesia:foldl(StatsDelete, ok, stats_table(VHost))
2976+ end) of
2977+ {aborted, Reason} ->
2978+ ?ERROR_MSG("Failed to update stats at ~p for ~p: ~p", [Date, VHost, Reason]),
2979+ rebuild_stats_at_int(VHost, Date);
2980+ _ ->
2981+ ?INFO_MSG("Updated stats at ~p for ~p", [Date, VHost]),
2982+ ok
2983+ end.
2984+
234c6b10 2985+get_user_stats_int(User, VHost) ->
2986+ case mnesia:transaction(fun() ->
2987+ Pat = #stats{user=User, at='$1', count='$2'},
2988+ mnesia:select(stats_table(VHost), [{Pat, [], [['$1', '$2']]}])
2989+ end) of
2990+ {atomic, Result} ->
2991+ {ok, mod_logdb:sort_stats([{Date, Count} || [Date, Count] <- Result])};
2992+ {aborted, Reason} ->
2993+ {error, Reason}
2994+ end.
2995+
2996+delete_all_messages_by_user_at_int(User, VHost, Date) ->
2997+ Table = table_name(VHost, Date),
2998+ MsgDelete = fun(#msg{owner_name=Owner} = Msg, _Acc)
2999+ when Owner == User ->
3000+ mnesia:delete_object(Table, Msg, write),
3001+ ok;
3002+ (_Msg, _Acc) -> ok
3003+ end,
3004+ DRez = case mnesia:transaction(fun() ->
3005+ mnesia:foldl(MsgDelete, ok, Table)
3006+ end) of
3007+ {aborted, Reason} ->
3008+ ?ERROR_MSG("Failed to delete_all_messages_by_user_at for ~p@~p at ~p: ~p", [User, VHost, Date, Reason]),
3009+ error;
3010+ _ ->
3011+ ok
3012+ end,
3013+ case rebuild_stats_at_int(VHost, Date) of
3014+ error ->
3015+ error;
3016+ ok ->
3017+ DRez
3018+ end.
3019+
3020+delete_user_settings_int(User, VHost) ->
3021+ STable = settings_table(VHost),
3022+ case mnesia:dirty_match_object(STable, #user_settings{owner_name=User, _='_'}) of
3023+ [] ->
3024+ ok;
3025+ [UserSettings] ->
3026+ mnesia:dirty_delete_object(STable, UserSettings)
3027+ end.
3028+
f7ce3e3a 3029+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3030+%
3031+% tables internals
3032+%
3033+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3034+create_stats_table(VHost) ->
3035+ SName = stats_table(VHost),
3036+ case mnesia:create_table(SName,
3037+ [{disc_only_copies, [node()]},
3038+ {type, bag},
3039+ {attributes, record_info(fields, stats)},
3040+ {record_name, stats}
3041+ ]) of
3042+ {atomic, ok} ->
3043+ ?MYDEBUG("Created stats table for ~p", [VHost]),
3044+ lists:foreach(fun(Date) ->
3045+ rebuild_stats_at_int(VHost, Date)
3046+ end, get_dates_int(VHost)),
3047+ ok;
3048+ {aborted, {already_exists, _}} ->
3049+ ?MYDEBUG("Stats table for ~p already exists", [VHost]),
3050+ ok;
3051+ {aborted, Reason} ->
3052+ ?ERROR_MSG("Failed to create stats table: ~p", [Reason]),
3053+ error
3054+ end.
3055+
3056+create_settings_table(VHost) ->
3057+ SName = settings_table(VHost),
3058+ case mnesia:create_table(SName,
3059+ [{disc_copies, [node()]},
3060+ {type, set},
3061+ {attributes, record_info(fields, user_settings)},
3062+ {record_name, user_settings}
3063+ ]) of
3064+ {atomic, ok} ->
3065+ ?MYDEBUG("Created settings table for ~p", [VHost]),
3066+ ok;
3067+ {aborted, {already_exists, _}} ->
3068+ ?MYDEBUG("Settings table for ~p already exists", [VHost]),
3069+ ok;
3070+ {aborted, Reason} ->
3071+ ?ERROR_MSG("Failed to create settings table: ~p", [Reason]),
3072+ error
3073+ end.
3074+
3075+create_msg_table(VHost, Date) ->
3076+ mnesia:create_table(
3077+ table_name(VHost, Date),
3078+ [{disc_only_copies, [node()]},
3079+ {type, bag},
3080+ {attributes, record_info(fields, msg)},
3081+ {record_name, msg}]).
98471d6b 3082+mod_opt_type(_) -> [].
046546ef 3083diff --git a/src/mod_logdb_mnesia_old.erl b/src/mod_logdb_mnesia_old.erl
0d78319d 3084new file mode 100644
046546ef 3085index 0000000..e962d9a
0d78319d 3086--- /dev/null
046546ef 3087+++ b/src/mod_logdb_mnesia_old.erl
98471d6b 3088@@ -0,0 +1,260 @@
f7ce3e3a 3089+%%%----------------------------------------------------------------------
0d78319d 3090+%%% File : mod_logdb_mnesia_old.erl
234c6b10 3091+%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
0d78319d 3092+%%% Purpose : mod_logmnesia backend for mod_logdb (should be used only for copy_tables functionality)
f7ce3e3a 3093+%%% Version : trunk
0d78319d 3094+%%% Id : $Id: mod_logdb_mnesia_old.erl 1273 2009-02-05 18:12:57Z malik $
f7ce3e3a 3095+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
3096+%%%----------------------------------------------------------------------
3097+
0d78319d 3098+-module(mod_logdb_mnesia_old).
f7ce3e3a 3099+-author('o.palij@gmail.com').
f7ce3e3a 3100+
f7ce3e3a 3101+-include("ejabberd.hrl").
3102+-include("jlib.hrl").
046546ef 3103+-include("logger.hrl").
f7ce3e3a 3104+
3105+-behaviour(gen_logdb).
f7ce3e3a 3106+
98471d6b 3107+-export([start/2, stop/1, mod_opt_type/1,
0d78319d 3108+ log_message/2,
f7ce3e3a 3109+ rebuild_stats/1,
3110+ rebuild_stats_at/2,
0d78319d 3111+ rebuild_stats_at1/2,
f7ce3e3a 3112+ delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
3113+ get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
3114+ get_dates/1,
234c6b10 3115+ get_users_settings/1, get_user_settings/2, set_user_settings/3,
3116+ drop_user/2]).
f7ce3e3a 3117+
0d78319d
AM
3118+-record(stats, {user, server, table, count}).
3119+-record(msg, {to_user, to_server, to_resource, from_user, from_server, from_resource, id, type, subject, body, timestamp}).
f7ce3e3a 3120+
0d78319d
AM
3121+tables_prefix() -> "messages_".
3122+% stats_table should not start with tables_prefix(VHost) !
3123+% i.e. lists:prefix(tables_prefix(VHost), atom_to_list(stats_table())) must be /= true
3124+stats_table() -> list_to_atom("messages-stats").
3125+% table name as atom from Date
3126+-define(ATABLE(Date), list_to_atom(tables_prefix() ++ Date)).
3127+-define(LTABLE(Date), tables_prefix() ++ Date).
f7ce3e3a 3128+
3129+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3130+%
0d78319d 3131+% gen_logdb callbacks
f7ce3e3a 3132+%
3133+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
0d78319d
AM
3134+start(_Opts, _VHost) ->
3135+ case mnesia:system_info(is_running) of
3136+ yes ->
3137+ ok = create_stats_table(),
3138+ {ok, ok};
3139+ no ->
3140+ ?ERROR_MSG("Mnesia not running", []),
3141+ error;
3142+ Status ->
3143+ ?ERROR_MSG("Mnesia status: ~p", [Status]),
3144+ error
3145+ end.
3146+
3147+stop(_VHost) ->
3148+ ok.
3149+
3150+log_message(_VHost, _Msg) ->
3151+ error.
3152+
3153+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
f7ce3e3a 3154+%
0d78319d 3155+% gen_logdb callbacks (maintaince)
f7ce3e3a 3156+%
3157+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
0d78319d
AM
3158+rebuild_stats(_VHost) ->
3159+ ok.
f7ce3e3a 3160+
0d78319d
AM
3161+rebuild_stats_at(VHost, Date) ->
3162+ Table = ?LTABLE(Date),
3163+ {Time, Value}=timer:tc(?MODULE, rebuild_stats_at1, [VHost, Table]),
3164+ ?INFO_MSG("rebuild_stats_at ~p elapsed ~p sec: ~p~n", [Date, Time/1000000, Value]),
3165+ Value.
3166+rebuild_stats_at1(VHost, Table) ->
3167+ CFun = fun(Msg, Stats) ->
3168+ To = Msg#msg.to_user ++ "@" ++ Msg#msg.to_server,
3169+ Stats_to = if
3170+ Msg#msg.to_server == VHost ->
3171+ case lists:keysearch(To, 1, Stats) of
3172+ {value, {Who_to, Count_to}} ->
3173+ lists:keyreplace(To, 1, Stats, {Who_to, Count_to + 1});
3174+ false ->
3175+ lists:append(Stats, [{To, 1}])
3176+ end;
3177+ true ->
3178+ Stats
3179+ end,
3180+ From = Msg#msg.from_user ++ "@" ++ Msg#msg.from_server,
3181+ Stats_from = if
3182+ Msg#msg.from_server == VHost ->
3183+ case lists:keysearch(From, 1, Stats_to) of
3184+ {value, {Who_from, Count_from}} ->
3185+ lists:keyreplace(From, 1, Stats_to, {Who_from, Count_from + 1});
3186+ false ->
3187+ lists:append(Stats_to, [{From, 1}])
3188+ end;
3189+ true ->
3190+ Stats_to
3191+ end,
3192+ Stats_from
3193+ end,
3194+ DFun = fun(#stats{table=STable, server=Server} = Stat, _Acc)
3195+ when STable == Table, Server == VHost ->
3196+ mnesia:delete_object(stats_table(), Stat, write);
3197+ (_Stat, _Acc) -> ok
3198+ end,
3199+ case mnesia:transaction(fun() ->
3200+ mnesia:write_lock_table(list_to_atom(Table)),
3201+ mnesia:write_lock_table(stats_table()),
3202+ % Calc stats for VHost at Date
3203+ AStats = mnesia:foldl(CFun, [], list_to_atom(Table)),
3204+ % Delete all stats for VHost at Date
3205+ mnesia:foldl(DFun, [], stats_table()),
3206+ % Write new calc'ed stats
3207+ lists:foreach(fun({Who, Count}) ->
3208+ Jid = jlib:string_to_jid(Who),
3209+ JUser = Jid#jid.user,
3210+ WStat = #stats{user=JUser, server=VHost, table=Table, count=Count},
3211+ mnesia:write(stats_table(), WStat, write)
3212+ end, AStats)
3213+ end) of
3214+ {aborted, Reason} ->
3215+ ?ERROR_MSG("Failed to rebuild_stats_at for ~p at ~p: ~p", [VHost, Table, Reason]),
3216+ error;
3217+ {atomic, _} ->
3218+ ok
3219+ end.
f7ce3e3a 3220+
0d78319d
AM
3221+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3222+%
3223+% gen_logdb callbacks (delete)
3224+%
3225+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3226+delete_messages_by_user_at(_VHost, _Msgs, _Date) ->
3227+ error.
234c6b10 3228+
0d78319d
AM
3229+delete_all_messages_by_user_at(_User, _VHost, _Date) ->
3230+ error.
f7ce3e3a 3231+
0d78319d
AM
3232+delete_messages_at(VHost, Date) ->
3233+ Table = list_to_atom(tables_prefix() ++ Date),
234c6b10 3234+
0d78319d
AM
3235+ DFun = fun(#msg{to_server=To_server, from_server=From_server}=Msg, _Acc)
3236+ when To_server == VHost; From_server == VHost ->
3237+ mnesia:delete_object(Table, Msg, write);
3238+ (_Msg, _Acc) -> ok
3239+ end,
234c6b10 3240+
0d78319d
AM
3241+ case mnesia:transaction(fun() ->
3242+ mnesia:foldl(DFun, [], Table)
3243+ end) of
3244+ {aborted, Reason} ->
3245+ ?ERROR_MSG("Failed to delete_messages_at for ~p at ~p: ~p", [VHost, Date, Reason]),
3246+ error;
3247+ {atomic, _} ->
3248+ ok
3249+ end.
234c6b10 3250+
0d78319d
AM
3251+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3252+%
3253+% gen_logdb callbacks (get)
3254+%
3255+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3256+get_vhost_stats(_VHost) ->
3257+ {error, "does not emplemented"}.
234c6b10 3258+
0d78319d
AM
3259+get_vhost_stats_at(VHost, Date) ->
3260+ Fun = fun() ->
3261+ Pat = #stats{user='$1', server=VHost, table=tables_prefix()++Date, count = '$2'},
3262+ mnesia:select(stats_table(), [{Pat, [], [['$1', '$2']]}])
3263+ end,
3264+ case mnesia:transaction(Fun) of
3265+ {atomic, Result} ->
3266+ RFun = fun([User, Count]) ->
3267+ {User, Count}
3268+ end,
3269+ {ok, lists:reverse(lists:keysort(2, lists:map(RFun, Result)))};
3270+ {aborted, Reason} -> {error, Reason}
3271+ end.
234c6b10 3272+
0d78319d
AM
3273+get_user_stats(_User, _VHost) ->
3274+ {error, "does not emplemented"}.
f7ce3e3a 3275+
0d78319d
AM
3276+get_user_messages_at(User, VHost, Date) ->
3277+ Table_name = tables_prefix() ++ Date,
3278+ case mnesia:transaction(fun() ->
3279+ Pat_to = #msg{to_user=User, to_server=VHost, _='_'},
3280+ Pat_from = #msg{from_user=User, from_server=VHost, _='_'},
3281+ mnesia:select(list_to_atom(Table_name),
3282+ [{Pat_to, [], ['$_']},
3283+ {Pat_from, [], ['$_']}])
3284+ end) of
3285+ {atomic, Result} ->
3286+ Msgs = lists:map(fun(#msg{to_user=To_user, to_server=To_server, to_resource=To_res,
3287+ from_user=From_user, from_server=From_server, from_resource=From_res,
3288+ type=Type,
3289+ subject=Subj,
3290+ body=Body, timestamp=Timestamp} = _Msg) ->
3291+ Subject = case Subj of
3292+ "None" -> "";
3293+ _ -> Subj
3294+ end,
3295+ {msg, To_user, To_server, To_res, From_user, From_server, From_res, Type, Subject, Body, Timestamp}
3296+ end, Result),
3297+ {ok, Msgs};
3298+ {aborted, Reason} ->
3299+ {error, Reason}
3300+ end.
f7ce3e3a 3301+
0d78319d
AM
3302+get_dates(_VHost) ->
3303+ Tables = mnesia:system_info(tables),
3304+ MessagesTables =
3305+ lists:filter(fun(Table) ->
3306+ lists:prefix(tables_prefix(), atom_to_list(Table))
3307+ end,
3308+ Tables),
3309+ lists:map(fun(Table) ->
3310+ lists:sublist(atom_to_list(Table),
3311+ length(tables_prefix())+1,
3312+ length(atom_to_list(Table)))
3313+ end,
3314+ MessagesTables).
3315+
3316+get_users_settings(_VHost) ->
3317+ {ok, []}.
3318+get_user_settings(_User, _VHost) ->
3319+ {ok, []}.
3320+set_user_settings(_User, _VHost, _Set) ->
3321+ ok.
3322+drop_user(_User, _VHost) ->
3323+ ok.
3324+
3325+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3326+%
3327+% internal
3328+%
3329+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3330+% called from db_logon/2
3331+create_stats_table() ->
3332+ SName = stats_table(),
3333+ case mnesia:create_table(SName,
3334+ [{disc_only_copies, [node()]},
3335+ {type, bag},
3336+ {attributes, record_info(fields, stats)},
3337+ {record_name, stats}
3338+ ]) of
3339+ {atomic, ok} ->
3340+ ?INFO_MSG("Created stats table", []),
3341+ ok;
3342+ {aborted, {already_exists, _}} ->
3343+ ok;
3344+ {aborted, Reason} ->
3345+ ?ERROR_MSG("Failed to create stats table: ~p", [Reason]),
3346+ error
3347+ end.
98471d6b 3348+mod_opt_type(_) -> [].
046546ef 3349diff --git a/src/mod_logdb_mysql.erl b/src/mod_logdb_mysql.erl
0d78319d 3350new file mode 100644
046546ef 3351index 0000000..62f437c
0d78319d 3352--- /dev/null
046546ef 3353+++ b/src/mod_logdb_mysql.erl
98471d6b 3354@@ -0,0 +1,1056 @@
0d78319d
AM
3355+%%%----------------------------------------------------------------------
3356+%%% File : mod_logdb_mysql.erl
3357+%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
3358+%%% Purpose : MySQL backend for mod_logdb
3359+%%% Version : trunk
3360+%%% Id : $Id: mod_logdb_mysql.erl 1360 2009-07-30 06:00:14Z malik $
3361+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
3362+%%%----------------------------------------------------------------------
3363+
3364+-module(mod_logdb_mysql).
3365+-author('o.palij@gmail.com').
3366+
3367+-include("mod_logdb.hrl").
3368+-include("ejabberd.hrl").
3369+-include("jlib.hrl").
046546ef 3370+-include("logger.hrl").
0d78319d
AM
3371+
3372+-behaviour(gen_logdb).
3373+-behaviour(gen_server).
3374+
3375+% gen_server
3376+-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
3377+% gen_mod
98471d6b 3378+-export([start/2, stop/1, mod_opt_type/1]).
0d78319d
AM
3379+% gen_logdb
3380+-export([log_message/2,
3381+ rebuild_stats/1,
3382+ rebuild_stats_at/2,
3383+ delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
3384+ get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
3385+ get_dates/1,
3386+ get_users_settings/1, get_user_settings/2, set_user_settings/3,
3387+ drop_user/2]).
3388+
3389+% gen_server call timeout
3390+-define(CALL_TIMEOUT, 30000).
3391+-define(MYSQL_TIMEOUT, 60000).
3392+-define(INDEX_SIZE, integer_to_list(170)).
3393+-define(PROCNAME, mod_logdb_mysql).
3394+
3395+-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
3396+ list_to_string/1, string_to_list/1,
3397+ convert_timestamp_brief/1]).
3398+
3399+-record(state, {dbref, vhost, server, port, db, user, password}).
3400+
3401+% replace "." with "_"
3402+escape_vhost(VHost) -> lists:map(fun(46) -> 95;
3403+ (A) -> A
046546ef 3404+ end, binary_to_list(VHost)).
0d78319d
AM
3405+prefix() ->
3406+ "`logdb_".
3407+
3408+suffix(VHost) ->
3409+ "_" ++ escape_vhost(VHost) ++ "`".
3410+
3411+messages_table(VHost, Date) ->
3412+ prefix() ++ "messages_" ++ Date ++ suffix(VHost).
3413+
3414+stats_table(VHost) ->
3415+ prefix() ++ "stats" ++ suffix(VHost).
3416+
3417+temp_table(VHost) ->
3418+ prefix() ++ "temp" ++ suffix(VHost).
3419+
3420+settings_table(VHost) ->
3421+ prefix() ++ "settings" ++ suffix(VHost).
3422+
3423+users_table(VHost) ->
3424+ prefix() ++ "users" ++ suffix(VHost).
3425+servers_table(VHost) ->
3426+ prefix() ++ "servers" ++ suffix(VHost).
3427+resources_table(VHost) ->
3428+ prefix() ++ "resources" ++ suffix(VHost).
3429+
046546ef
AM
3430+ets_users_table(VHost) -> list_to_atom("logdb_users_" ++ binary_to_list(VHost)).
3431+ets_servers_table(VHost) -> list_to_atom("logdb_servers_" ++ binary_to_list(VHost)).
3432+ets_resources_table(VHost) -> list_to_atom("logdb_resources_" ++ binary_to_list(VHost)).
0d78319d
AM
3433+
3434+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3435+%
3436+% gen_mod callbacks
3437+%
3438+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3439+start(VHost, Opts) ->
3440+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3441+ gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
3442+
3443+stop(VHost) ->
3444+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3445+ gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
3446+
3447+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3448+%
3449+% gen_server callbacks
3450+%
3451+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3452+init([VHost, Opts]) ->
3453+ crypto:start(),
3454+
046546ef
AM
3455+ Server = gen_mod:get_opt(server, Opts, fun(A) -> A end, <<"localhost">>),
3456+ Port = gen_mod:get_opt(port, Opts, fun(A) -> A end, 3306),
3457+ DB = gen_mod:get_opt(db, Opts, fun(A) -> A end, <<"logdb">>),
3458+ User = gen_mod:get_opt(user, Opts, fun(A) -> A end, <<"root">>),
3459+ Password = gen_mod:get_opt(password, Opts, fun(A) -> A end, <<"">>),
0d78319d
AM
3460+
3461+ St = #state{vhost=VHost,
3462+ server=Server, port=Port, db=DB,
3463+ user=User, password=Password},
3464+
3465+ case open_mysql_connection(St) of
3466+ {ok, DBRef} ->
3467+ State = St#state{dbref=DBRef},
3468+ ok = create_stats_table(State),
3469+ ok = create_settings_table(State),
3470+ ok = create_users_table(State),
3471+ % clear ets cache every ...
3472+ timer:send_interval(timer:hours(12), clear_ets_tables),
3473+ ok = create_servers_table(State),
3474+ ok = create_resources_table(State),
3475+ erlang:monitor(process, DBRef),
3476+ {ok, State};
3477+ {error, Reason} ->
3478+ ?ERROR_MSG("MySQL connection failed: ~p~n", [Reason]),
3479+ {stop, db_connection_failed}
3480+ end.
3481+
3482+open_mysql_connection(#state{server=Server, port=Port, db=DB,
3483+ user=DBUser, password=Password} = _State) ->
3484+ LogFun = fun(debug, _Format, _Argument) ->
3485+ %?MYDEBUG(Format, Argument);
3486+ ok;
3487+ (error, Format, Argument) ->
3488+ ?ERROR_MSG(Format, Argument);
3489+ (Level, Format, Argument) ->
3490+ ?MYDEBUG("MySQL (~p)~n", [Level]),
3491+ ?MYDEBUG(Format, Argument)
3492+ end,
3493+ ?INFO_MSG("Opening mysql connection ~s@~s:~p/~s", [DBUser, Server, Port, DB]),
046546ef
AM
3494+ p1_mysql_conn:start(binary_to_list(Server), Port,
3495+ binary_to_list(DBUser), binary_to_list(Password),
3496+ binary_to_list(DB), LogFun).
0d78319d
AM
3497+
3498+close_mysql_connection(DBRef) ->
3499+ ?MYDEBUG("Closing ~p mysql connection", [DBRef]),
046546ef 3500+ catch p1_mysql_conn:stop(DBRef).
0d78319d
AM
3501+
3502+handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3503+ Date = convert_timestamp_brief(Msg#msg.timestamp),
3504+
3505+ Table = messages_table(VHost, Date),
046546ef
AM
3506+ Owner_id = get_user_id(DBRef, VHost, binary_to_list(Msg#msg.owner_name)),
3507+ Peer_name_id = get_user_id(DBRef, VHost, binary_to_list(Msg#msg.peer_name)),
3508+ Peer_server_id = get_server_id(DBRef, VHost, binary_to_list(Msg#msg.peer_server)),
3509+ Peer_resource_id = get_resource_id(DBRef, VHost, binary_to_list(Msg#msg.peer_resource)),
0d78319d
AM
3510+
3511+ Query = ["INSERT INTO ",Table," ",
3512+ "(owner_id,",
3513+ "peer_name_id,",
3514+ "peer_server_id,",
3515+ "peer_resource_id,",
3516+ "direction,",
3517+ "type,",
3518+ "subject,",
3519+ "body,",
3520+ "timestamp) ",
3521+ "VALUES ",
3522+ "('", Owner_id, "',",
3523+ "'", Peer_name_id, "',",
3524+ "'", Peer_server_id, "',",
3525+ "'", Peer_resource_id, "',",
3526+ "'", atom_to_list(Msg#msg.direction), "',",
046546ef
AM
3527+ "'", binary_to_list(Msg#msg.type), "',",
3528+ "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.subject) ), "',",
3529+ "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.body) ), "',",
0d78319d
AM
3530+ "'", Msg#msg.timestamp, "');"],
3531+
3532+ Reply =
3533+ case sql_query_internal_silent(DBRef, Query) of
3534+ {updated, _} ->
046546ef
AM
3535+ ?MYDEBUG("Logged ok for ~s, peer: ~s", [ [Msg#msg.owner_name, <<"@">>, VHost],
3536+ [Msg#msg.peer_name, <<"@">>, Msg#msg.peer_server] ]),
0d78319d
AM
3537+ increment_user_stats(DBRef, Msg#msg.owner_name, Owner_id, VHost, Peer_name_id, Peer_server_id, Date);
3538+ {error, Reason} ->
046546ef 3539+ case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#42S02">>) of
0d78319d
AM
3540+ % Table doesn't exist
3541+ match ->
3542+ case create_msg_table(DBRef, VHost, Date) of
3543+ error ->
3544+ error;
3545+ ok ->
3546+ {updated, _} = sql_query_internal(DBRef, Query),
046546ef 3547+ increment_user_stats(DBRef, binary_to_list(Msg#msg.owner_name), Owner_id, VHost, Peer_name_id, Peer_server_id, Date)
0d78319d
AM
3548+ end;
3549+ _ ->
3550+ ?ERROR_MSG("Failed to log message: ~p", [Reason]),
3551+ error
3552+ end
3553+ end,
3554+ {reply, Reply, State};
3555+handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3556+ Reply = rebuild_stats_at_int(DBRef, VHost, Date),
3557+ {reply, Reply, State};
3558+handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
3559+ {reply, error, State};
3560+handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3561+ Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
3562+ ["\"",Timestamp,"\"",","]
3563+ end, Msgs),
3564+
3565+ Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
3566+
3567+ Query = ["DELETE FROM ",messages_table(VHost, Date)," ",
3568+ "WHERE timestamp IN (", Temp1],
f7ce3e3a 3569+
3570+ Reply =
3571+ case sql_query_internal(DBRef, Query) of
3572+ {updated, Aff} ->
3573+ ?MYDEBUG("Aff=~p", [Aff]),
3574+ rebuild_stats_at_int(DBRef, VHost, Date);
3575+ {error, _} ->
3576+ error
3577+ end,
3578+ {reply, Reply, State};
3579+handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
234c6b10 3580+ ok = delete_all_messages_by_user_at_int(DBRef, User, VHost, Date),
3581+ ok = delete_stats_by_user_at_int(DBRef, User, VHost, Date),
3582+ {reply, ok, State};
f7ce3e3a 3583+handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3584+ Reply =
3585+ case sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Date),";"]) of
3586+ {updated, _} ->
3587+ Query = ["DELETE FROM ",stats_table(VHost)," "
3588+ "WHERE at=\"",Date,"\";"],
3589+ case sql_query_internal(DBRef, Query) of
3590+ {updated, _} ->
3591+ ok;
3592+ {error, _} ->
3593+ error
3594+ end;
3595+ {error, _} ->
3596+ error
3597+ end,
3598+ {reply, Reply, State};
3599+handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3600+ SName = stats_table(VHost),
3601+ Query = ["SELECT at, sum(count) ",
3602+ "FROM ",SName," ",
3603+ "GROUP BY at ",
3604+ "ORDER BY DATE(at) DESC;"
3605+ ],
3606+ Reply =
3607+ case sql_query_internal(DBRef, Query) of
3608+ {data, Result} ->
3609+ {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
3610+ {error, Reason} ->
3611+ % TODO: Duplicate error message ?
3612+ {error, Reason}
3613+ end,
3614+ {reply, Reply, State};
3615+handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3616+ SName = stats_table(VHost),
234c6b10 3617+ Query = ["SELECT username, sum(count) AS allcount ",
f7ce3e3a 3618+ "FROM ",SName," ",
3619+ "JOIN ",users_table(VHost)," ON owner_id=user_id "
234c6b10 3620+ "WHERE at=\"",Date,"\" "
3621+ "GROUP BY username ",
3622+ "ORDER BY allcount DESC;"
f7ce3e3a 3623+ ],
3624+ Reply =
3625+ case sql_query_internal(DBRef, Query) of
3626+ {data, Result} ->
3627+ {ok, lists:reverse(
3628+ lists:keysort(2,
3629+ [ {User, list_to_integer(Count)} || [User, Count] <- Result]))};
3630+ {error, Reason} ->
3631+ % TODO:
3632+ {error, Reason}
3633+ end,
3634+ {reply, Reply, State};
3635+handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
234c6b10 3636+ {reply, get_user_stats_int(DBRef, User, VHost), State};
f7ce3e3a 3637+handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3638+ TName = messages_table(VHost, Date),
3639+ UName = users_table(VHost),
3640+ SName = servers_table(VHost),
3641+ RName = resources_table(VHost),
3642+ Query = ["SELECT users.username,",
3643+ "servers.server,",
3644+ "resources.resource,",
3645+ "messages.direction,"
3646+ "messages.type,"
3647+ "messages.subject,"
3648+ "messages.body,"
3649+ "messages.timestamp "
3650+ "FROM ",TName," AS messages "
3651+ "JOIN ",UName," AS users ON peer_name_id=user_id ",
3652+ "JOIN ",SName," AS servers ON peer_server_id=server_id ",
3653+ "JOIN ",RName," AS resources ON peer_resource_id=resource_id ",
3654+ "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\" ",
3655+ "ORDER BY timestamp ASC;"],
3656+ Reply =
3657+ case sql_query_internal(DBRef, Query) of
3658+ {data, Result} ->
3659+ Fun = fun([Peer_name, Peer_server, Peer_resource,
3660+ Direction,
3661+ Type,
3662+ Subject, Body,
3663+ Timestamp]) ->
3664+ #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
3665+ direction=list_to_atom(Direction),
3666+ type=Type,
3667+ subject=Subject, body=Body,
3668+ timestamp=Timestamp}
3669+ end,
3670+ {ok, lists:map(Fun, Result)};
3671+ {error, Reason} ->
3672+ {error, Reason}
3673+ end,
3674+ {reply, Reply, State};
3675+handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3676+ SName = stats_table(VHost),
3677+ Query = ["SELECT at ",
3678+ "FROM ",SName," ",
3679+ "GROUP BY at ",
3680+ "ORDER BY DATE(at) DESC;"
3681+ ],
3682+ Reply =
3683+ case sql_query_internal(DBRef, Query) of
3684+ {data, Result} ->
3685+ [ Date || [Date] <- Result ];
3686+ {error, Reason} ->
3687+ {error, Reason}
3688+ end,
3689+ {reply, Reply, State};
3690+handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3691+ Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
3692+ "FROM ",settings_table(VHost)," ",
3693+ "JOIN ",users_table(VHost)," ON user_id=owner_id;"],
0d78319d 3694+ Reply =
f7ce3e3a 3695+ case sql_query_internal(DBRef, Query) of
3696+ {data, Result} ->
3697+ {ok, lists:map(fun([Owner, DoLogDef, DoLogL, DoNotLogL]) ->
3698+ #user_settings{owner_name=Owner,
3699+ dolog_default=list_to_bool(DoLogDef),
3700+ dolog_list=string_to_list(DoLogL),
3701+ donotlog_list=string_to_list(DoNotLogL)
3702+ }
3703+ end, Result)};
3704+ {error, _} ->
3705+ error
3706+ end,
3707+ {reply, Reply, State};
3708+handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3709+ Query = ["SELECT dolog_default,dolog_list,donotlog_list FROM ",settings_table(VHost)," ",
3710+ "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\";"],
3711+ Reply =
3712+ case sql_query_internal(DBRef, Query) of
3713+ {data, []} ->
3714+ {ok, []};
3715+ {data, [[Owner, DoLogDef, DoLogL, DoNotLogL]]} ->
3716+ {ok, #user_settings{owner_name=Owner,
3717+ dolog_default=list_to_bool(DoLogDef),
3718+ dolog_list=string_to_list(DoLogL),
3719+ donotlog_list=string_to_list(DoNotLogL)}};
3720+ {error, _} ->
3721+ error
3722+ end,
3723+ {reply, Reply, State};
3724+handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
3725+ dolog_list=DoLogL,
3726+ donotlog_list=DoNotLogL}},
3727+ _From, #state{dbref=DBRef, vhost=VHost} = State) ->
3728+ User_id = get_user_id(DBRef, VHost, User),
3729+
3730+ Query = ["UPDATE ",settings_table(VHost)," ",
3731+ "SET dolog_default=",bool_to_list(DoLogDef),", ",
3732+ "dolog_list='",list_to_string(DoLogL),"', ",
3733+ "donotlog_list='",list_to_string(DoNotLogL),"' ",
3734+ "WHERE owner_id=\"",User_id,"\";"],
3735+
3736+ Reply =
3737+ case sql_query_internal(DBRef, Query) of
3738+ {updated, 0} ->
3739+ IQuery = ["INSERT INTO ",settings_table(VHost)," ",
3740+ "(owner_id, dolog_default, dolog_list, donotlog_list) ",
3741+ "VALUES ",
3742+ "('",User_id,"', ",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
3743+ case sql_query_internal_silent(DBRef, IQuery) of
3744+ {updated, _} ->
3745+ ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
3746+ ok;
3747+ {error, Reason} ->
046546ef 3748+ case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>) of
f7ce3e3a 3749+ % Already exists
0d78319d 3750+ match ->
f7ce3e3a 3751+ ok;
3752+ _ ->
3753+ ?ERROR_MSG("Failed setup user ~p@~p: ~p", [User, VHost, Reason]),
3754+ error
3755+ end
3756+ end;
3757+ {updated, 1} ->
3758+ ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
3759+ ok;
3760+ {error, _} ->
3761+ error
3762+ end,
3763+ {reply, Reply, State};
3764+handle_call({stop}, _From, #state{vhost=VHost}=State) ->
3765+ ets:delete(ets_users_table(VHost)),
3766+ ets:delete(ets_servers_table(VHost)),
3767+ ?MYDEBUG("Stoping mysql backend for ~p", [VHost]),
3768+ {stop, normal, ok, State};
3769+handle_call(Msg, _From, State) ->
3770+ ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
3771+ {noreply, State}.
3772+
234c6b10 3773+handle_cast({rebuild_stats}, State) ->
3774+ rebuild_all_stats_int(State),
3775+ {noreply, State};
3776+handle_cast({drop_user, User}, #state{vhost=VHost} = State) ->
3777+ Fun = fun() ->
3778+ {ok, DBRef} = open_mysql_connection(State),
3779+ {ok, Dates} = get_user_stats_int(DBRef, User, VHost),
3780+ MDResult = lists:map(fun({Date, _}) ->
3781+ delete_all_messages_by_user_at_int(DBRef, User, VHost, Date)
3782+ end, Dates),
3783+ StDResult = delete_all_stats_by_user_int(DBRef, User, VHost),
3784+ SDResult = delete_user_settings_int(DBRef, User, VHost),
3785+ case lists:all(fun(Result) when Result == ok ->
3786+ true;
3787+ (Result) when Result == error ->
3788+ false
3789+ end, lists:append([MDResult, [StDResult], [SDResult]])) of
3790+ true ->
3791+ ?INFO_MSG("Removed ~s@~s", [User, VHost]);
3792+ false ->
3793+ ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost])
3794+ end,
3795+ close_mysql_connection(DBRef)
3796+ end,
3797+ spawn(Fun),
3798+ {noreply, State};
f7ce3e3a 3799+handle_cast(Msg, State) ->
3800+ ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
3801+ {noreply, State}.
3802+
3803+handle_info(clear_ets_tables, State) ->
3804+ ets:delete_all_objects(ets_users_table(State#state.vhost)),
3805+ ets:delete_all_objects(ets_resources_table(State#state.vhost)),
3806+ {noreply, State};
3807+handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
3808+ {stop, connection_dropped, State};
3809+handle_info(Info, State) ->
3810+ ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
3811+ {noreply, State}.
3812+
234c6b10 3813+terminate(_Reason, #state{dbref=DBRef}=_State) ->
3814+ close_mysql_connection(DBRef),
f7ce3e3a 3815+ ok.
3816+
3817+code_change(_OldVsn, State, _Extra) ->
3818+ {ok, State}.
3819+
3820+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3821+%
3822+% gen_logdb callbacks
3823+%
3824+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3825+log_message(VHost, Msg) ->
3826+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3827+ gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
3828+rebuild_stats(VHost) ->
3829+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
234c6b10 3830+ gen_server:cast(Proc, {rebuild_stats}).
f7ce3e3a 3831+rebuild_stats_at(VHost, Date) ->
3832+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3833+ gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
3834+delete_messages_by_user_at(VHost, Msgs, Date) ->
3835+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3836+ gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
3837+delete_all_messages_by_user_at(User, VHost, Date) ->
3838+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3839+ gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
3840+delete_messages_at(VHost, Date) ->
3841+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3842+ gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
3843+get_vhost_stats(VHost) ->
3844+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3845+ gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
3846+get_vhost_stats_at(VHost, Date) ->
3847+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3848+ gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
3849+get_user_stats(User, VHost) ->
3850+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3851+ gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
3852+get_user_messages_at(User, VHost, Date) ->
3853+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3854+ gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
3855+get_dates(VHost) ->
3856+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3857+ gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
3858+get_users_settings(VHost) ->
3859+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3860+ gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
3861+get_user_settings(User, VHost) ->
3862+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3863+ gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
3864+set_user_settings(User, VHost, Set) ->
3865+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3866+ gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
234c6b10 3867+drop_user(User, VHost) ->
3868+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3869+ gen_server:cast(Proc, {drop_user, User}).
f7ce3e3a 3870+
3871+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3872+%
3873+% internals
3874+%
3875+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
234c6b10 3876+increment_user_stats(DBRef, User_name, User_id, VHost, PNameID, PServerID, Date) ->
f7ce3e3a 3877+ SName = stats_table(VHost),
3878+ UQuery = ["UPDATE ",SName," ",
3879+ "SET count=count+1 ",
234c6b10 3880+ "WHERE owner_id=\"",User_id,"\" AND peer_name_id=\"",PNameID,"\" AND peer_server_id=\"",PServerID,"\" AND at=\"",Date,"\";"],
f7ce3e3a 3881+
3882+ case sql_query_internal(DBRef, UQuery) of
3883+ {updated, 0} ->
3884+ IQuery = ["INSERT INTO ",SName," ",
234c6b10 3885+ "(owner_id, peer_name_id, peer_server_id, at, count) ",
f7ce3e3a 3886+ "VALUES ",
234c6b10 3887+ "('",User_id,"', '",PNameID,"', '",PServerID,"', '",Date,"', '1');"],
f7ce3e3a 3888+ case sql_query_internal(DBRef, IQuery) of
3889+ {updated, _} ->
3890+ ?MYDEBUG("New stats for ~s@~s at ~s", [User_name, VHost, Date]),
3891+ ok;
3892+ {error, _} ->
3893+ error
3894+ end;
3895+ {updated, _} ->
3896+ ?MYDEBUG("Updated stats for ~s@~s at ~s", [User_name, VHost, Date]),
3897+ ok;
3898+ {error, _} ->
3899+ error
3900+ end.
3901+
3902+get_dates_int(DBRef, VHost) ->
3903+ case sql_query_internal(DBRef, ["SHOW TABLES"]) of
3904+ {data, Tables} ->
3905+ lists:foldl(fun([Table], Dates) ->
234c6b10 3906+ Reg = lists:sublist(prefix(),2,length(prefix())) ++ ".*" ++ escape_vhost(VHost),
0d78319d
AM
3907+ case re:run(Table, Reg) of
3908+ {match, [{1, _}]} ->
234c6b10 3909+ ?MYDEBUG("matched ~p against ~p", [Table, Reg]),
0d78319d
AM
3910+ case re:run(Table,"[0-9]+-[0-9]+-[0-9]+") of
3911+ {match, [{S, E}]} ->
f7ce3e3a 3912+ lists:append(Dates, [lists:sublist(Table,S,E)]);
3913+ nomatch ->
3914+ Dates
3915+ end;
234c6b10 3916+ _ ->
f7ce3e3a 3917+ Dates
3918+ end
3919+ end, [], Tables);
3920+ {error, _} ->
3921+ []
3922+ end.
3923+
234c6b10 3924+rebuild_all_stats_int(#state{vhost=VHost}=State) ->
3925+ Fun = fun() ->
3926+ {ok, DBRef} = open_mysql_connection(State),
3927+ ok = delete_nonexistent_stats(DBRef, VHost),
3928+ case lists:filter(fun(Date) ->
3929+ case catch rebuild_stats_at_int(DBRef, VHost, Date) of
3930+ ok -> false;
3931+ error -> true;
3932+ {'EXIT', _} -> true
3933+ end
3934+ end, get_dates_int(DBRef, VHost)) of
3935+ [] -> ok;
3936+ FTables ->
3937+ ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]),
3938+ error
3939+ end,
3940+ close_mysql_connection(DBRef)
3941+ end,
3942+ spawn(Fun).
f7ce3e3a 3943+
234c6b10 3944+rebuild_stats_at_int(DBRef, VHost, Date) ->
3945+ TempTable = temp_table(VHost),
3946+ Fun = fun() ->
3947+ Table = messages_table(VHost, Date),
3948+ STable = stats_table(VHost),
f7ce3e3a 3949+
234c6b10 3950+ DQuery = [ "DELETE FROM ",STable," ",
3951+ "WHERE at='",Date,"';"],
f7ce3e3a 3952+
234c6b10 3953+ ok = create_temp_table(DBRef, TempTable),
3954+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," WRITE, ",TempTable," WRITE;"]),
3955+ SQuery = ["INSERT INTO ",TempTable," ",
3956+ "(owner_id,peer_name_id,peer_server_id,at,count) ",
3957+ "SELECT owner_id,peer_name_id,peer_server_id,\"",Date,"\",count(*) ",
3958+ "FROM ",Table," GROUP BY owner_id,peer_name_id,peer_server_id;"],
3959+ case sql_query_internal(DBRef, SQuery) of
3960+ {updated, 0} ->
3961+ Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]),
3962+ case Count of
3963+ {data, [["0"]]} ->
3964+ {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]),
3965+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE;"]),
3966+ {updated, _} = sql_query_internal(DBRef, DQuery),
3967+ ok;
3968+ _ ->
3969+ ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]),
3970+ error
3971+ end;
3972+ {updated, _} ->
3973+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE, ",TempTable," WRITE;"]),
3974+ {updated, _} = sql_query_internal(DBRef, DQuery),
3975+ SQuery1 = ["INSERT INTO ",STable," ",
3976+ "(owner_id,peer_name_id,peer_server_id,at,count) ",
3977+ "SELECT owner_id,peer_name_id,peer_server_id,at,count ",
3978+ "FROM ",TempTable,";"],
3979+ case sql_query_internal(DBRef, SQuery1) of
3980+ {updated, _} -> ok;
3981+ {error, _} -> error
3982+ end;
3983+ {error, _} -> error
3984+ end
3985+ end,
f7ce3e3a 3986+
234c6b10 3987+ case catch apply(Fun, []) of
3988+ ok ->
3989+ ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
3990+ ok;
3991+ error ->
3992+ error;
3993+ {'EXIT', Reason} ->
3994+ ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]),
3995+ error
3996+ end,
3997+ sql_query_internal(DBRef, ["UNLOCK TABLES;"]),
3998+ sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]),
3999+ ok.
f7ce3e3a 4000+
4001+
4002+delete_nonexistent_stats(DBRef, VHost) ->
4003+ Dates = get_dates_int(DBRef, VHost),
4004+ STable = stats_table(VHost),
4005+
4006+ Temp = lists:flatmap(fun(Date) ->
4007+ ["\"",Date,"\"",","]
4008+ end, Dates),
4009+
234c6b10 4010+ case Temp of
4011+ [] ->
4012+ ok;
4013+ _ ->
4014+ % replace last "," with ");"
4015+ Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
4016+ Query = ["DELETE FROM ",STable," ",
4017+ "WHERE at NOT IN (", Temp1],
4018+ case sql_query_internal(DBRef, Query) of
4019+ {updated, _} ->
4020+ ok;
4021+ {error, _} ->
4022+ error
4023+ end
4024+ end.
f7ce3e3a 4025+
234c6b10 4026+get_user_stats_int(DBRef, User, VHost) ->
4027+ SName = stats_table(VHost),
4028+ Query = ["SELECT at, sum(count) as allcount ",
4029+ "FROM ",SName," ",
4030+ "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\" ",
4031+ "GROUP BY at "
4032+ "ORDER BY DATE(at) DESC;"
4033+ ],
f7ce3e3a 4034+ case sql_query_internal(DBRef, Query) of
234c6b10 4035+ {data, Result} ->
4036+ {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result]};
4037+ {error, Result} ->
4038+ {error, Result}
4039+ end.
4040+
4041+delete_all_messages_by_user_at_int(DBRef, User, VHost, Date) ->
4042+ DQuery = ["DELETE FROM ",messages_table(VHost, Date)," ",
4043+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4044+ case sql_query_internal(DBRef, DQuery) of
f7ce3e3a 4045+ {updated, _} ->
234c6b10 4046+ ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]),
f7ce3e3a 4047+ ok;
4048+ {error, _} ->
4049+ error
4050+ end.
4051+
234c6b10 4052+delete_all_stats_by_user_int(DBRef, User, VHost) ->
4053+ SQuery = ["DELETE FROM ",stats_table(VHost)," ",
4054+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4055+ case sql_query_internal(DBRef, SQuery) of
4056+ {updated, _} ->
4057+ ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]),
4058+ ok;
4059+ {error, _} -> error
4060+ end.
4061+
4062+delete_stats_by_user_at_int(DBRef, User, VHost, Date) ->
4063+ SQuery = ["DELETE FROM ",stats_table(VHost)," ",
4064+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\") ",
4065+ "AND at=\"",Date,"\";"],
4066+ case sql_query_internal(DBRef, SQuery) of
4067+ {updated, _} ->
4068+ ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]),
4069+ ok;
4070+ {error, _} -> error
4071+ end.
4072+
4073+delete_user_settings_int(DBRef, User, VHost) ->
4074+ Query = ["DELETE FROM ",settings_table(VHost)," ",
4075+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4076+ case sql_query_internal(DBRef, Query) of
4077+ {updated, _} ->
4078+ ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]),
4079+ ok;
4080+ {error, Reason} ->
4081+ ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]),
4082+ error
4083+ end.
4084+
f7ce3e3a 4085+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4086+%
4087+% tables internals
4088+%
4089+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
234c6b10 4090+create_temp_table(DBRef, Name) ->
4091+ Query = ["CREATE TABLE ",Name," (",
4092+ "owner_id MEDIUMINT UNSIGNED, ",
4093+ "peer_name_id MEDIUMINT UNSIGNED, ",
4094+ "peer_server_id MEDIUMINT UNSIGNED, ",
4095+ "at VARCHAR(11), ",
4096+ "count INT(11) ",
4097+ ") ENGINE=MyISAM CHARACTER SET utf8;"
4098+ ],
4099+ case sql_query_internal(DBRef, Query) of
4100+ {updated, _} -> ok;
4101+ {error, _Reason} -> error
4102+ end.
4103+
4104+create_stats_table(#state{dbref=DBRef, vhost=VHost}=State) ->
f7ce3e3a 4105+ SName = stats_table(VHost),
4106+ Query = ["CREATE TABLE ",SName," (",
4107+ "owner_id MEDIUMINT UNSIGNED, ",
234c6b10 4108+ "peer_name_id MEDIUMINT UNSIGNED, ",
4109+ "peer_server_id MEDIUMINT UNSIGNED, ",
f7ce3e3a 4110+ "at varchar(20), ",
4111+ "count int(11), ",
234c6b10 4112+ "INDEX(owner_id, peer_name_id, peer_server_id), ",
f7ce3e3a 4113+ "INDEX(at)"
4114+ ") ENGINE=InnoDB CHARACTER SET utf8;"
4115+ ],
4116+ case sql_query_internal_silent(DBRef, Query) of
4117+ {updated, _} ->
234c6b10 4118+ ?INFO_MSG("Created stats table for ~p", [VHost]),
4119+ rebuild_all_stats_int(State),
f7ce3e3a 4120+ ok;
4121+ {error, Reason} ->
046546ef 4122+ case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#42S01">>) of
0d78319d 4123+ match ->
f7ce3e3a 4124+ ?MYDEBUG("Stats table for ~p already exists", [VHost]),
234c6b10 4125+ CheckQuery = ["SHOW COLUMNS FROM ",SName," LIKE 'peer_%_id';"],
4126+ case sql_query_internal(DBRef, CheckQuery) of
4127+ {data, Elems} when length(Elems) == 2 ->
4128+ ?MYDEBUG("Stats table structure is ok", []),
4129+ ok;
4130+ _ ->
4131+ ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []),
4132+ case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of
4133+ {updated, _} ->
4134+ ?INFO_MSG("Successfully dropped ~p", [SName]);
4135+ _ ->
4136+ ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName])
4137+ end,
4138+ error
4139+ end;
f7ce3e3a 4140+ _ ->
4141+ ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
4142+ error
4143+ end
4144+ end.
4145+
234c6b10 4146+create_settings_table(#state{dbref=DBRef, vhost=VHost}) ->
f7ce3e3a 4147+ SName = settings_table(VHost),
4148+ Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
4149+ "owner_id MEDIUMINT UNSIGNED PRIMARY KEY, ",
4150+ "dolog_default TINYINT(1) NOT NULL DEFAULT 1, ",
4151+ "dolog_list TEXT, ",
4152+ "donotlog_list TEXT ",
4153+ ") ENGINE=InnoDB CHARACTER SET utf8;"
4154+ ],
4155+ case sql_query_internal(DBRef, Query) of
4156+ {updated, _} ->
4157+ ?MYDEBUG("Created settings table for ~p", [VHost]),
4158+ ok;
4159+ {error, _} ->
4160+ error
4161+ end.
4162+
234c6b10 4163+create_users_table(#state{dbref=DBRef, vhost=VHost}) ->
f7ce3e3a 4164+ SName = users_table(VHost),
4165+ Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
4166+ "username TEXT NOT NULL, ",
4167+ "user_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
4168+ "UNIQUE INDEX(username(",?INDEX_SIZE,")) ",
4169+ ") ENGINE=InnoDB CHARACTER SET utf8;"
4170+ ],
4171+ case sql_query_internal(DBRef, Query) of
4172+ {updated, _} ->
4173+ ?MYDEBUG("Created users table for ~p", [VHost]),
4174+ ets:new(ets_users_table(VHost), [named_table, set, public]),
4175+ %update_users_from_db(DBRef, VHost),
4176+ ok;
4177+ {error, _} ->
4178+ error
4179+ end.
4180+
234c6b10 4181+create_servers_table(#state{dbref=DBRef, vhost=VHost}) ->
f7ce3e3a 4182+ SName = servers_table(VHost),
4183+ Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
4184+ "server TEXT NOT NULL, ",
4185+ "server_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
4186+ "UNIQUE INDEX(server(",?INDEX_SIZE,")) ",
4187+ ") ENGINE=InnoDB CHARACTER SET utf8;"
4188+ ],
4189+ case sql_query_internal(DBRef, Query) of
4190+ {updated, _} ->
4191+ ?MYDEBUG("Created servers table for ~p", [VHost]),
4192+ ets:new(ets_servers_table(VHost), [named_table, set, public]),
4193+ update_servers_from_db(DBRef, VHost),
4194+ ok;
4195+ {error, _} ->
4196+ error
4197+ end.
4198+
234c6b10 4199+create_resources_table(#state{dbref=DBRef, vhost=VHost}) ->
f7ce3e3a 4200+ RName = resources_table(VHost),
4201+ Query = ["CREATE TABLE IF NOT EXISTS ",RName," (",
4202+ "resource TEXT NOT NULL, ",
4203+ "resource_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
4204+ "UNIQUE INDEX(resource(",?INDEX_SIZE,")) ",
4205+ ") ENGINE=InnoDB CHARACTER SET utf8;"
4206+ ],
4207+ case sql_query_internal(DBRef, Query) of
4208+ {updated, _} ->
4209+ ?MYDEBUG("Created resources table for ~p", [VHost]),
4210+ ets:new(ets_resources_table(VHost), [named_table, set, public]),
4211+ ok;
4212+ {error, _} ->
4213+ error
4214+ end.
4215+
4216+create_msg_table(DBRef, VHost, Date) ->
4217+ TName = messages_table(VHost, Date),
4218+ Query = ["CREATE TABLE ",TName," (",
4219+ "owner_id MEDIUMINT UNSIGNED, ",
4220+ "peer_name_id MEDIUMINT UNSIGNED, ",
4221+ "peer_server_id MEDIUMINT UNSIGNED, ",
4222+ "peer_resource_id MEDIUMINT(8) UNSIGNED, ",
4223+ "direction ENUM('to', 'from'), ",
4224+ "type ENUM('chat','error','groupchat','headline','normal') NOT NULL, ",
4225+ "subject TEXT, ",
4226+ "body TEXT, ",
4227+ "timestamp DOUBLE, ",
234c6b10 4228+ "INDEX search_i (owner_id, peer_name_id, peer_server_id, peer_resource_id), ",
f7ce3e3a 4229+ "FULLTEXT (body) "
4230+ ") ENGINE=MyISAM CHARACTER SET utf8;"
4231+ ],
4232+ case sql_query_internal(DBRef, Query) of
4233+ {updated, _MySQLRes} ->
4234+ ?MYDEBUG("Created msg table for ~p at ~p", [VHost, Date]),
4235+ ok;
4236+ {error, _} ->
4237+ error
4238+ end.
4239+
4240+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4241+%
4242+% internal ets cache (users, servers, resources)
4243+%
4244+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4245+update_servers_from_db(DBRef, VHost) ->
4246+ ?INFO_MSG("Reading servers from db for ~p", [VHost]),
4247+ SQuery = ["SELECT server, server_id FROM ",servers_table(VHost),";"],
4248+ {data, Result} = sql_query_internal(DBRef, SQuery),
4249+ true = ets:delete_all_objects(ets_servers_table(VHost)),
4250+ true = ets:insert(ets_servers_table(VHost), [ {Server, Server_id} || [Server, Server_id] <- Result]).
4251+
4252+%update_users_from_db(DBRef, VHost) ->
4253+% ?INFO_MSG("Reading users from db for ~p", [VHost]),
4254+% SQuery = ["SELECT username, user_id FROM ",users_table(VHost),";"],
4255+% {data, Result} = sql_query_internal(DBRef, SQuery),
4256+% true = ets:delete_all_objects(ets_users_table(VHost)),
4257+% true = ets:insert(ets_users_table(VHost), [ {Username, User_id} || [Username, User_id] <- Result]).
4258+
4259+%get_user_name(DBRef, VHost, User_id) ->
4260+% case ets:match(ets_users_table(VHost), {'$1', User_id}) of
4261+% [[User]] -> User;
4262+% % this can be in clustered environment
4263+% [] ->
4264+% %update_users_from_db(DBRef, VHost),
4265+% SQuery = ["SELECT username FROM ",users_table(VHost)," ",
4266+% "WHERE user_id=\"",User_id,"\";"],
4267+% {data, [[Name]]} = sql_query_internal(DBRef, SQuery),
4268+% % cache {user, id} pair
4269+% ets:insert(ets_users_table(VHost), {Name, User_id}),
4270+% Name
4271+% end.
4272+
4273+%get_server_name(DBRef, VHost, Server_id) ->
4274+% case ets:match(ets_servers_table(VHost), {'$1', Server_id}) of
4275+% [[Server]] -> Server;
4276+ % this can be in clustered environment
4277+% [] ->
4278+% update_servers_from_db(DBRef, VHost),
4279+% [[Server1]] = ets:match(ets_servers_table(VHost), {'$1', Server_id}),
4280+% Server1
4281+% end.
4282+
4283+get_user_id_from_db(DBRef, VHost, User) ->
4284+ SQuery = ["SELECT user_id FROM ",users_table(VHost)," ",
4285+ "WHERE username=\"",User,"\";"],
4286+ case sql_query_internal(DBRef, SQuery) of
4287+ % no such user in db
4288+ {data, []} ->
4289+ {ok, []};
4290+ {data, [[DBId]]} ->
4291+ % cache {user, id} pair
4292+ ets:insert(ets_users_table(VHost), {User, DBId}),
4293+ {ok, DBId}
4294+ end.
4295+get_user_id(DBRef, VHost, User) ->
4296+ % Look at ets
4297+ case ets:match(ets_users_table(VHost), {User, '$1'}) of
4298+ [] ->
4299+ % Look at db
4300+ case get_user_id_from_db(DBRef, VHost, User) of
4301+ % no such user in db
4302+ {ok, []} ->
4303+ IQuery = ["INSERT INTO ",users_table(VHost)," ",
4304+ "SET username=\"",User,"\";"],
4305+ case sql_query_internal_silent(DBRef, IQuery) of
4306+ {updated, _} ->
4307+ {ok, NewId} = get_user_id_from_db(DBRef, VHost, User),
4308+ NewId;
4309+ {error, Reason} ->
4310+ % this can be in clustered environment
046546ef 4311+ match = ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>),
f7ce3e3a 4312+ ?ERROR_MSG("Duplicate key name for ~p", [User]),
4313+ {ok, ClID} = get_user_id_from_db(DBRef, VHost, User),
4314+ ClID
4315+ end;
4316+ {ok, DBId} ->
4317+ DBId
4318+ end;
4319+ [[EtsId]] -> EtsId
4320+ end.
4321+
4322+get_server_id(DBRef, VHost, Server) ->
4323+ case ets:match(ets_servers_table(VHost), {Server, '$1'}) of
4324+ [] ->
4325+ IQuery = ["INSERT INTO ",servers_table(VHost)," ",
4326+ "SET server=\"",Server,"\";"],
4327+ case sql_query_internal_silent(DBRef, IQuery) of
4328+ {updated, _} ->
4329+ SQuery = ["SELECT server_id FROM ",servers_table(VHost)," ",
4330+ "WHERE server=\"",Server,"\";"],
4331+ {data, [[Id]]} = sql_query_internal(DBRef, SQuery),
4332+ ets:insert(ets_servers_table(VHost), {Server, Id}),
4333+ Id;
4334+ {error, Reason} ->
4335+ % this can be in clustered environment
046546ef 4336+ match = ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>),
f7ce3e3a 4337+ ?ERROR_MSG("Duplicate key name for ~p", [Server]),
4338+ update_servers_from_db(DBRef, VHost),
4339+ [[Id1]] = ets:match(ets_servers_table(VHost), {Server, '$1'}),
4340+ Id1
4341+ end;
4342+ [[Id]] -> Id
4343+ end.
4344+
4345+get_resource_id_from_db(DBRef, VHost, Resource) ->
4346+ SQuery = ["SELECT resource_id FROM ",resources_table(VHost)," ",
046546ef 4347+ "WHERE resource=\"",binary_to_list(ejabberd_odbc:escape(iolist_to_binary(Resource))),"\";"],
f7ce3e3a 4348+ case sql_query_internal(DBRef, SQuery) of
4349+ % no such resource in db
4350+ {data, []} ->
4351+ {ok, []};
4352+ {data, [[DBId]]} ->
4353+ % cache {resource, id} pair
4354+ ets:insert(ets_resources_table(VHost), {Resource, DBId}),
4355+ {ok, DBId}
4356+ end.
4357+get_resource_id(DBRef, VHost, Resource) ->
4358+ % Look at ets
4359+ case ets:match(ets_resources_table(VHost), {Resource, '$1'}) of
4360+ [] ->
4361+ % Look at db
4362+ case get_resource_id_from_db(DBRef, VHost, Resource) of
4363+ % no such resource in db
4364+ {ok, []} ->
4365+ IQuery = ["INSERT INTO ",resources_table(VHost)," ",
046546ef 4366+ "SET resource=\"",binary_to_list(ejabberd_odbc:escape(iolist_to_binary(Resource))),"\";"],
f7ce3e3a 4367+ case sql_query_internal_silent(DBRef, IQuery) of
4368+ {updated, _} ->
4369+ {ok, NewId} = get_resource_id_from_db(DBRef, VHost, Resource),
4370+ NewId;
4371+ {error, Reason} ->
4372+ % this can be in clustered environment
046546ef
AM
4373+ match = ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>),
4374+ ?ERROR_MSG("Duplicate key name for ~s", [Resource]),
f7ce3e3a 4375+ {ok, ClID} = get_resource_id_from_db(DBRef, VHost, Resource),
4376+ ClID
4377+ end;
4378+ {ok, DBId} ->
4379+ DBId
4380+ end;
4381+ [[EtsId]] -> EtsId
4382+ end.
4383+
4384+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4385+%
0d78319d 4386+% SQL internals
f7ce3e3a 4387+%
4388+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
f7ce3e3a 4389+sql_query_internal(DBRef, Query) ->
4390+ case sql_query_internal_silent(DBRef, Query) of
4391+ {error, Reason} ->
4392+ ?ERROR_MSG("~p while ~p", [Reason, lists:append(Query)]),
4393+ {error, Reason};
4394+ Rez -> Rez
4395+ end.
4396+
4397+sql_query_internal_silent(DBRef, Query) ->
4398+ ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
046546ef 4399+ get_result(p1_mysql_conn:fetch(DBRef, Query, self(), ?MYSQL_TIMEOUT)).
f7ce3e3a 4400+
4401+get_result({updated, MySQLRes}) ->
046546ef 4402+ {updated, p1_mysql:get_result_affected_rows(MySQLRes)};
f7ce3e3a 4403+get_result({data, MySQLRes}) ->
046546ef 4404+ {data, p1_mysql:get_result_rows(MySQLRes)};
f7ce3e3a 4405+get_result({error, "query timed out"}) ->
4406+ {error, "query timed out"};
4407+get_result({error, MySQLRes}) ->
046546ef 4408+ Reason = p1_mysql:get_result_reason(MySQLRes),
f7ce3e3a 4409+ {error, Reason}.
98471d6b 4410+mod_opt_type(_) -> [].
046546ef 4411diff --git a/src/mod_logdb_mysql5.erl b/src/mod_logdb_mysql5.erl
0d78319d 4412new file mode 100644
046546ef 4413index 0000000..d1f399f
0d78319d 4414--- /dev/null
046546ef 4415+++ b/src/mod_logdb_mysql5.erl
98471d6b 4416@@ -0,0 +1,985 @@
f7ce3e3a 4417+%%%----------------------------------------------------------------------
4418+%%% File : mod_logdb_mysql5.erl
234c6b10 4419+%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
f7ce3e3a 4420+%%% Purpose : MySQL 5 backend for mod_logdb
4421+%%% Version : trunk
0d78319d 4422+%%% Id : $Id: mod_logdb_mysql5.erl 1360 2009-07-30 06:00:14Z malik $
f7ce3e3a 4423+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
4424+%%%----------------------------------------------------------------------
4425+
4426+-module(mod_logdb_mysql5).
4427+-author('o.palij@gmail.com').
f7ce3e3a 4428+
4429+-include("mod_logdb.hrl").
4430+-include("ejabberd.hrl").
4431+-include("jlib.hrl").
046546ef 4432+-include("logger.hrl").
f7ce3e3a 4433+
4434+-behaviour(gen_logdb).
4435+-behaviour(gen_server).
4436+
4437+% gen_server
4438+-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
4439+% gen_mod
98471d6b 4440+-export([start/2, stop/1, mod_opt_type/1]).
f7ce3e3a 4441+% gen_logdb
4442+-export([log_message/2,
4443+ rebuild_stats/1,
4444+ rebuild_stats_at/2,
4445+ delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
4446+ get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
4447+ get_dates/1,
234c6b10 4448+ get_users_settings/1, get_user_settings/2, set_user_settings/3,
4449+ drop_user/2]).
f7ce3e3a 4450+
4451+% gen_server call timeout
234c6b10 4452+-define(CALL_TIMEOUT, 30000).
4453+-define(MYSQL_TIMEOUT, 60000).
f7ce3e3a 4454+-define(INDEX_SIZE, integer_to_list(170)).
4455+-define(PROCNAME, mod_logdb_mysql5).
4456+
4457+-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
4458+ list_to_string/1, string_to_list/1,
4459+ convert_timestamp_brief/1]).
4460+
234c6b10 4461+-record(state, {dbref, vhost, server, port, db, user, password}).
f7ce3e3a 4462+
4463+% replace "." with "_"
4464+escape_vhost(VHost) -> lists:map(fun(46) -> 95;
4465+ (A) -> A
046546ef 4466+ end, binary_to_list(VHost)).
f7ce3e3a 4467+prefix() ->
4468+ "`logdb_".
4469+
4470+suffix(VHost) ->
4471+ "_" ++ escape_vhost(VHost) ++ "`".
4472+
4473+messages_table(VHost, Date) ->
4474+ prefix() ++ "messages_" ++ Date ++ suffix(VHost).
4475+
4476+% TODO: this needs to be redone to unify view name in stored procedure and in delete_messages_at/2
4477+view_table(VHost, Date) ->
4478+ Table = messages_table(VHost, Date),
4479+ TablewoQ = lists:sublist(Table, 2, length(Table) - 2),
4480+ lists:append(["`v_", TablewoQ, "`"]).
4481+
4482+stats_table(VHost) ->
4483+ prefix() ++ "stats" ++ suffix(VHost).
4484+
234c6b10 4485+temp_table(VHost) ->
4486+ prefix() ++ "temp" ++ suffix(VHost).
4487+
f7ce3e3a 4488+settings_table(VHost) ->
4489+ prefix() ++ "settings" ++ suffix(VHost).
4490+
4491+users_table(VHost) ->
4492+ prefix() ++ "users" ++ suffix(VHost).
4493+servers_table(VHost) ->
4494+ prefix() ++ "servers" ++ suffix(VHost).
4495+resources_table(VHost) ->
4496+ prefix() ++ "resources" ++ suffix(VHost).
4497+
234c6b10 4498+logmessage_name(VHost) ->
4499+ prefix() ++ "logmessage" ++ suffix(VHost).
4500+
f7ce3e3a 4501+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4502+%
4503+% gen_mod callbacks
4504+%
4505+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4506+start(VHost, Opts) ->
4507+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4508+ gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
4509+
4510+stop(VHost) ->
4511+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4512+ gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
4513+
4514+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4515+%
4516+% gen_server callbacks
4517+%
4518+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4519+init([VHost, Opts]) ->
4520+ crypto:start(),
4521+
046546ef
AM
4522+ Server = gen_mod:get_opt(server, Opts, fun(A) -> A end, <<"localhost">>),
4523+ Port = gen_mod:get_opt(port, Opts, fun(A) -> A end, 3306),
4524+ DB = gen_mod:get_opt(db, Opts, fun(A) -> A end, <<"logdb">>),
4525+ User = gen_mod:get_opt(user, Opts, fun(A) -> A end, <<"root">>),
4526+ Password = gen_mod:get_opt(password, Opts, fun(A) -> A end, <<"">>),
f7ce3e3a 4527+
234c6b10 4528+ St = #state{vhost=VHost,
4529+ server=Server, port=Port, db=DB,
4530+ user=User, password=Password},
4531+
4532+ case open_mysql_connection(St) of
f7ce3e3a 4533+ {ok, DBRef} ->
234c6b10 4534+ State = St#state{dbref=DBRef},
4535+ ok = create_internals(State),
4536+ ok = create_stats_table(State),
4537+ ok = create_settings_table(State),
4538+ ok = create_users_table(State),
4539+ ok = create_servers_table(State),
4540+ ok = create_resources_table(State),
f7ce3e3a 4541+ erlang:monitor(process, DBRef),
234c6b10 4542+ {ok, State};
f7ce3e3a 4543+ {error, Reason} ->
4544+ ?ERROR_MSG("MySQL connection failed: ~p~n", [Reason]),
4545+ {stop, db_connection_failed}
4546+ end.
4547+
234c6b10 4548+open_mysql_connection(#state{server=Server, port=Port, db=DB,
4549+ user=DBUser, password=Password} = _State) ->
4550+ LogFun = fun(debug, _Format, _Argument) ->
4551+ %?MYDEBUG(Format, Argument);
4552+ ok;
4553+ (error, Format, Argument) ->
4554+ ?ERROR_MSG(Format, Argument);
4555+ (Level, Format, Argument) ->
4556+ ?MYDEBUG("MySQL (~p)~n", [Level]),
4557+ ?MYDEBUG(Format, Argument)
4558+ end,
26b6b0c9 4559+ ?INFO_MSG("Opening mysql connection ~s@~s:~p/~s", [DBUser, Server, Port, DB]),
046546ef
AM
4560+ p1_mysql_conn:start(binary_to_list(Server), Port,
4561+ binary_to_list(DBUser), binary_to_list(Password),
4562+ binary_to_list(DB), LogFun).
f7ce3e3a 4563+
234c6b10 4564+close_mysql_connection(DBRef) ->
4565+ ?MYDEBUG("Closing ~p mysql connection", [DBRef]),
046546ef 4566+ catch p1_mysql_conn:stop(DBRef).
f7ce3e3a 4567+
f7ce3e3a 4568+handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4569+ Reply = rebuild_stats_at_int(DBRef, VHost, Date),
4570+ {reply, Reply, State};
4571+handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
4572+ {reply, error, State};
4573+handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4574+ Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
4575+ ["\"",Timestamp,"\"",","]
4576+ end, Msgs),
4577+
4578+ Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
4579+
4580+ Query = ["DELETE FROM ",messages_table(VHost, Date)," ",
4581+ "WHERE timestamp IN (", Temp1],
4582+
4583+ Reply =
4584+ case sql_query_internal(DBRef, Query) of
4585+ {updated, Aff} ->
4586+ ?MYDEBUG("Aff=~p", [Aff]),
4587+ rebuild_stats_at_int(DBRef, VHost, Date);
4588+ {error, _} ->
4589+ error
4590+ end,
4591+ {reply, Reply, State};
4592+handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
234c6b10 4593+ ok = delete_all_messages_by_user_at_int(DBRef, User, VHost, Date),
4594+ ok = delete_stats_by_user_at_int(DBRef, User, VHost, Date),
4595+ {reply, ok, State};
f7ce3e3a 4596+handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4597+ Fun = fun() ->
4598+ {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Date),";"]),
4599+ TQuery = ["DELETE FROM ",stats_table(VHost)," "
4600+ "WHERE at=\"",Date,"\";"],
4601+ {updated, _} = sql_query_internal(DBRef, TQuery),
4602+ VQuery = ["DROP VIEW IF EXISTS ",view_table(VHost,Date),";"],
234c6b10 4603+ {updated, _} = sql_query_internal(DBRef, VQuery),
4604+ ok
f7ce3e3a 4605+ end,
4606+ Reply =
234c6b10 4607+ case catch apply(Fun, []) of
4608+ ok ->
f7ce3e3a 4609+ ok;
234c6b10 4610+ {'EXIT', _} ->
f7ce3e3a 4611+ error
4612+ end,
4613+ {reply, Reply, State};
4614+handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4615+ SName = stats_table(VHost),
4616+ Query = ["SELECT at, sum(count) ",
4617+ "FROM ",SName," ",
4618+ "GROUP BY at ",
4619+ "ORDER BY DATE(at) DESC;"
4620+ ],
4621+ Reply =
4622+ case sql_query_internal(DBRef, Query) of
4623+ {data, Result} ->
4624+ {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
4625+ {error, Reason} ->
4626+ % TODO: Duplicate error message ?
4627+ {error, Reason}
4628+ end,
4629+ {reply, Reply, State};
4630+handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4631+ SName = stats_table(VHost),
234c6b10 4632+ Query = ["SELECT username, sum(count) as allcount ",
f7ce3e3a 4633+ "FROM ",SName," ",
4634+ "JOIN ",users_table(VHost)," ON owner_id=user_id "
4635+ "WHERE at=\"",Date,"\" ",
234c6b10 4636+ "GROUP BY username ",
4637+ "ORDER BY allcount DESC;"
f7ce3e3a 4638+ ],
4639+ Reply =
4640+ case sql_query_internal(DBRef, Query) of
4641+ {data, Result} ->
4642+ {ok, [ {User, list_to_integer(Count)} || [User, Count] <- Result ]};
4643+ {error, Reason} ->
4644+ {error, Reason}
4645+ end,
4646+ {reply, Reply, State};
4647+handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
234c6b10 4648+ {reply, get_user_stats_int(DBRef, User, VHost), State};
f7ce3e3a 4649+handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4650+ Query = ["SELECT peer_name,",
4651+ "peer_server,",
4652+ "peer_resource,",
4653+ "direction,"
4654+ "type,"
4655+ "subject,"
4656+ "body,"
4657+ "timestamp "
4658+ "FROM ",view_table(VHost, Date)," "
4659+ "WHERE owner_name=\"",User,"\";"],
4660+ Reply =
4661+ case sql_query_internal(DBRef, Query) of
4662+ {data, Result} ->
4663+ Fun = fun([Peer_name, Peer_server, Peer_resource,
4664+ Direction,
4665+ Type,
4666+ Subject, Body,
4667+ Timestamp]) ->
4668+ #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
4669+ direction=list_to_atom(Direction),
4670+ type=Type,
4671+ subject=Subject, body=Body,
4672+ timestamp=Timestamp}
4673+ end,
4674+ {ok, lists:map(Fun, Result)};
4675+ {error, Reason} ->
4676+ {error, Reason}
4677+ end,
4678+ {reply, Reply, State};
4679+handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4680+ SName = stats_table(VHost),
4681+ Query = ["SELECT at ",
4682+ "FROM ",SName," ",
4683+ "GROUP BY at ",
4684+ "ORDER BY DATE(at) DESC;"
4685+ ],
4686+ Reply =
4687+ case sql_query_internal(DBRef, Query) of
4688+ {data, Result} ->
4689+ [ Date || [Date] <- Result ];
4690+ {error, Reason} ->
4691+ {error, Reason}
4692+ end,
4693+ {reply, Reply, State};
4694+handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4695+ Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
4696+ "FROM ",settings_table(VHost)," ",
4697+ "JOIN ",users_table(VHost)," ON user_id=owner_id;"],
0d78319d 4698+ Reply =
f7ce3e3a 4699+ case sql_query_internal(DBRef, Query) of
4700+ {data, Result} ->
4701+ {ok, lists:map(fun([Owner, DoLogDef, DoLogL, DoNotLogL]) ->
4702+ #user_settings{owner_name=Owner,
4703+ dolog_default=list_to_bool(DoLogDef),
4704+ dolog_list=string_to_list(DoLogL),
4705+ donotlog_list=string_to_list(DoNotLogL)
4706+ }
4707+ end, Result)};
4708+ {error, _} ->
4709+ error
4710+ end,
4711+ {reply, Reply, State};
4712+handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4713+ Query = ["SELECT dolog_default,dolog_list,donotlog_list FROM ",settings_table(VHost)," ",
4714+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4715+ Reply =
4716+ case sql_query_internal(DBRef, Query) of
4717+ {data, []} ->
4718+ {ok, []};
4719+ {data, [[Owner, DoLogDef, DoLogL, DoNotLogL]]} ->
4720+ {ok, #user_settings{owner_name=Owner,
4721+ dolog_default=list_to_bool(DoLogDef),
4722+ dolog_list=string_to_list(DoLogL),
4723+ donotlog_list=string_to_list(DoNotLogL)}};
4724+ {error, _} ->
4725+ error
4726+ end,
4727+ {reply, Reply, State};
4728+handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
4729+ dolog_list=DoLogL,
4730+ donotlog_list=DoNotLogL}},
4731+ _From, #state{dbref=DBRef, vhost=VHost} = State) ->
4732+ User_id = get_user_id(DBRef, VHost, User),
4733+ Query = ["UPDATE ",settings_table(VHost)," ",
4734+ "SET dolog_default=",bool_to_list(DoLogDef),", ",
4735+ "dolog_list='",list_to_string(DoLogL),"', ",
4736+ "donotlog_list='",list_to_string(DoNotLogL),"' ",
4737+ "WHERE owner_id=",User_id,";"],
4738+
4739+ Reply =
4740+ case sql_query_internal(DBRef, Query) of
4741+ {updated, 0} ->
4742+ IQuery = ["INSERT INTO ",settings_table(VHost)," ",
4743+ "(owner_id, dolog_default, dolog_list, donotlog_list) ",
4744+ "VALUES ",
4745+ "(",User_id,",",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
4746+ case sql_query_internal_silent(DBRef, IQuery) of
4747+ {updated, _} ->
4748+ ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
4749+ ok;
4750+ {error, Reason} ->
046546ef 4751+ case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>) of
f7ce3e3a 4752+ % Already exists
0d78319d 4753+ match ->
f7ce3e3a 4754+ ok;
4755+ _ ->
4756+ ?ERROR_MSG("Failed setup user ~p@~p: ~p", [User, VHost, Reason]),
4757+ error
4758+ end
4759+ end;
4760+ {updated, 1} ->
4761+ ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
4762+ ok;
4763+ {error, _} ->
4764+ error
4765+ end,
4766+ {reply, Reply, State};
4767+handle_call({stop}, _From, #state{vhost=VHost}=State) ->
4768+ ?MYDEBUG("Stoping mysql5 backend for ~p", [VHost]),
4769+ {stop, normal, ok, State};
4770+handle_call(Msg, _From, State) ->
4771+ ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
4772+ {noreply, State}.
4773+
234c6b10 4774+handle_cast({log_message, Msg}, #state{dbref=DBRef, vhost=VHost}=State) ->
4775+ Fun = fun() ->
4776+ Date = convert_timestamp_brief(Msg#msg.timestamp),
4777+ TableName = messages_table(VHost, Date),
4778+
4779+ Query = [ "CALL ",logmessage_name(VHost)," "
4780+ "('", TableName, "',",
4781+ "'", Date, "',",
046546ef
AM
4782+ "'", binary_to_list(Msg#msg.owner_name), "',",
4783+ "'", binary_to_list(Msg#msg.peer_name), "',",
4784+ "'", binary_to_list(Msg#msg.peer_server), "',",
4785+ "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.peer_resource) ), "',",
234c6b10 4786+ "'", atom_to_list(Msg#msg.direction), "',",
046546ef
AM
4787+ "'", binary_to_list(Msg#msg.type), "',",
4788+ "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.subject) ), "',",
4789+ "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.body) ), "',",
234c6b10 4790+ "'", Msg#msg.timestamp, "');"],
4791+
4792+ case sql_query_internal(DBRef, Query) of
4793+ {updated, _} ->
046546ef
AM
4794+ ?MYDEBUG("Logged ok for ~s, peer: ~s", [ [Msg#msg.owner_name, <<"@">>, VHost],
4795+ [Msg#msg.peer_name, <<"@">>, Msg#msg.peer_server] ]),
234c6b10 4796+ ok;
4797+ {error, _Reason} ->
4798+ error
4799+ end
4800+ end,
4801+ spawn(Fun),
4802+ {noreply, State};
4803+handle_cast({rebuild_stats}, State) ->
4804+ rebuild_all_stats_int(State),
4805+ {noreply, State};
4806+handle_cast({drop_user, User}, #state{vhost=VHost} = State) ->
4807+ Fun = fun() ->
4808+ {ok, DBRef} = open_mysql_connection(State),
4809+ {ok, Dates} = get_user_stats_int(DBRef, User, VHost),
4810+ MDResult = lists:map(fun({Date, _}) ->
4811+ delete_all_messages_by_user_at_int(DBRef, User, VHost, Date)
4812+ end, Dates),
4813+ StDResult = delete_all_stats_by_user_int(DBRef, User, VHost),
4814+ SDResult = delete_user_settings_int(DBRef, User, VHost),
4815+ case lists:all(fun(Result) when Result == ok ->
4816+ true;
4817+ (Result) when Result == error ->
4818+ false
4819+ end, lists:append([MDResult, [StDResult], [SDResult]])) of
4820+ true ->
4821+ ?INFO_MSG("Removed ~s@~s", [User, VHost]);
4822+ false ->
4823+ ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost])
4824+ end,
4825+ close_mysql_connection(DBRef)
4826+ end,
4827+ spawn(Fun),
4828+ {noreply, State};
f7ce3e3a 4829+handle_cast(Msg, State) ->
4830+ ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
4831+ {noreply, State}.
4832+
4833+handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
4834+ {stop, connection_dropped, State};
4835+handle_info(Info, State) ->
4836+ ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
4837+ {noreply, State}.
4838+
234c6b10 4839+terminate(_Reason, #state{dbref=DBRef}=_State) ->
4840+ close_mysql_connection(DBRef),
f7ce3e3a 4841+ ok.
4842+
4843+code_change(_OldVsn, State, _Extra) ->
4844+ {ok, State}.
4845+
4846+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4847+%
4848+% gen_logdb callbacks
4849+%
4850+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4851+log_message(VHost, Msg) ->
4852+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
234c6b10 4853+ gen_server:cast(Proc, {log_message, Msg}).
f7ce3e3a 4854+rebuild_stats(VHost) ->
4855+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
234c6b10 4856+ gen_server:cast(Proc, {rebuild_stats}).
f7ce3e3a 4857+rebuild_stats_at(VHost, Date) ->
4858+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4859+ gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
4860+delete_messages_by_user_at(VHost, Msgs, Date) ->
4861+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4862+ gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
4863+delete_all_messages_by_user_at(User, VHost, Date) ->
4864+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4865+ gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
4866+delete_messages_at(VHost, Date) ->
4867+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4868+ gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
4869+get_vhost_stats(VHost) ->
4870+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4871+ gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
4872+get_vhost_stats_at(VHost, Date) ->
4873+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4874+ gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
4875+get_user_stats(User, VHost) ->
4876+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4877+ gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
4878+get_user_messages_at(User, VHost, Date) ->
4879+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4880+ gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
4881+get_dates(VHost) ->
4882+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4883+ gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
4884+get_users_settings(VHost) ->
4885+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4886+ gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
4887+get_user_settings(User, VHost) ->
4888+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4889+ gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
4890+set_user_settings(User, VHost, Set) ->
4891+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4892+ gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
234c6b10 4893+drop_user(User, VHost) ->
4894+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4895+ gen_server:cast(Proc, {drop_user, User}).
f7ce3e3a 4896+
4897+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4898+%
4899+% internals
4900+%
4901+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4902+get_dates_int(DBRef, VHost) ->
4903+ case sql_query_internal(DBRef, ["SHOW TABLES"]) of
4904+ {data, Tables} ->
4905+ lists:foldl(fun([Table], Dates) ->
234c6b10 4906+ Reg = lists:sublist(prefix(),2,length(prefix())) ++ ".*" ++ escape_vhost(VHost),
0d78319d
AM
4907+ case re:run(Table, Reg) of
4908+ {match, [{1, _}]} ->
4909+ case re:run(Table,"[0-9]+-[0-9]+-[0-9]+") of
4910+ {match, [{S, E}]} ->
f7ce3e3a 4911+ lists:append(Dates, [lists:sublist(Table,S,E)]);
4912+ nomatch ->
4913+ Dates
4914+ end;
234c6b10 4915+ _ ->
f7ce3e3a 4916+ Dates
4917+ end
4918+ end, [], Tables);
4919+ {error, _} ->
4920+ []
4921+ end.
4922+
234c6b10 4923+rebuild_all_stats_int(#state{vhost=VHost}=State) ->
4924+ Fun = fun() ->
4925+ {ok, DBRef} = open_mysql_connection(State),
4926+ ok = delete_nonexistent_stats(DBRef, VHost),
4927+ case lists:filter(fun(Date) ->
4928+ case catch rebuild_stats_at_int(DBRef, VHost, Date) of
4929+ ok -> false;
4930+ error -> true;
4931+ {'EXIT', _} -> true
4932+ end
4933+ end, get_dates_int(DBRef, VHost)) of
4934+ [] -> ok;
4935+ FTables ->
4936+ ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]),
4937+ error
4938+ end,
4939+ close_mysql_connection(DBRef)
4940+ end,
4941+ spawn(Fun).
f7ce3e3a 4942+
234c6b10 4943+rebuild_stats_at_int(DBRef, VHost, Date) ->
4944+ TempTable = temp_table(VHost),
4945+ Fun = fun() ->
4946+ Table = messages_table(VHost, Date),
4947+ STable = stats_table(VHost),
f7ce3e3a 4948+
234c6b10 4949+ DQuery = [ "DELETE FROM ",STable," ",
4950+ "WHERE at='",Date,"';"],
f7ce3e3a 4951+
234c6b10 4952+ ok = create_temp_table(DBRef, TempTable),
4953+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," WRITE, ",TempTable," WRITE;"]),
4954+ SQuery = ["INSERT INTO ",TempTable," ",
4955+ "(owner_id,peer_name_id,peer_server_id,at,count) ",
4956+ "SELECT owner_id,peer_name_id,peer_server_id,\"",Date,"\",count(*) ",
4957+ "FROM ",Table," WHERE ext is NULL GROUP BY owner_id,peer_name_id,peer_server_id;"],
4958+ case sql_query_internal(DBRef, SQuery) of
4959+ {updated, 0} ->
4960+ Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]),
4961+ case Count of
4962+ {data, [["0"]]} ->
234c6b10 4963+ {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]),
046546ef
AM
4964+ sql_query_internal(DBRef, ["UNLOCK TABLES;"]),
4965+ {updated, _} = sql_query_internal(DBRef, ["DROP VIEW IF EXISTS ",view_table(VHost,Date),";"]),
4966+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE, ",TempTable," WRITE;"]),
234c6b10 4967+ {updated, _} = sql_query_internal(DBRef, DQuery),
4968+ ok;
4969+ _ ->
4970+ ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]),
4971+ error
4972+ end;
4973+ {updated, _} ->
4974+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE, ",TempTable," WRITE;"]),
4975+ {updated, _} = sql_query_internal(DBRef, DQuery),
4976+ SQuery1 = ["INSERT INTO ",STable," ",
4977+ "(owner_id,peer_name_id,peer_server_id,at,count) ",
4978+ "SELECT owner_id,peer_name_id,peer_server_id,at,count ",
4979+ "FROM ",TempTable,";"],
4980+ case sql_query_internal(DBRef, SQuery1) of
4981+ {updated, _} -> ok;
4982+ {error, _} -> error
4983+ end;
4984+ {error, _} -> error
4985+ end
4986+ end,
f7ce3e3a 4987+
234c6b10 4988+ case catch apply(Fun, []) of
4989+ ok ->
4990+ ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
4991+ ok;
4992+ error ->
4993+ error;
4994+ {'EXIT', Reason} ->
4995+ ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]),
4996+ error
4997+ end,
4998+ sql_query_internal(DBRef, ["UNLOCK TABLES;"]),
4999+ sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]),
5000+ ok.
f7ce3e3a 5001+
5002+delete_nonexistent_stats(DBRef, VHost) ->
5003+ Dates = get_dates_int(DBRef, VHost),
5004+ STable = stats_table(VHost),
5005+
5006+ Temp = lists:flatmap(fun(Date) ->
5007+ ["\"",Date,"\"",","]
5008+ end, Dates),
234c6b10 5009+ case Temp of
5010+ [] ->
5011+ ok;
5012+ _ ->
5013+ % replace last "," with ");"
5014+ Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
5015+ Query = ["DELETE FROM ",STable," ",
5016+ "WHERE at NOT IN (", Temp1],
5017+ case sql_query_internal(DBRef, Query) of
5018+ {updated, _} ->
5019+ ok;
5020+ {error, _} ->
5021+ error
5022+ end
5023+ end.
f7ce3e3a 5024+
234c6b10 5025+get_user_stats_int(DBRef, User, VHost) ->
5026+ SName = stats_table(VHost),
5027+ UName = users_table(VHost),
5028+ Query = ["SELECT stats.at, sum(stats.count) ",
5029+ "FROM ",UName," AS users ",
5030+ "JOIN ",SName," AS stats ON owner_id=user_id "
5031+ "WHERE users.username=\"",User,"\" ",
5032+ "GROUP BY stats.at "
5033+ "ORDER BY DATE(stats.at) DESC;"
5034+ ],
5035+ case sql_query_internal(DBRef, Query) of
5036+ {data, Result} ->
5037+ {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
5038+ {error, Result} ->
5039+ {error, Result}
5040+ end.
5041+
5042+delete_all_messages_by_user_at_int(DBRef, User, VHost, Date) ->
5043+ DQuery = ["DELETE FROM ",messages_table(VHost, Date)," ",
5044+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
5045+ case sql_query_internal(DBRef, DQuery) of
5046+ {updated, _} ->
5047+ ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]),
5048+ ok;
5049+ {error, _} ->
5050+ error
5051+ end.
5052+
5053+delete_all_stats_by_user_int(DBRef, User, VHost) ->
5054+ SQuery = ["DELETE FROM ",stats_table(VHost)," ",
5055+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
5056+ case sql_query_internal(DBRef, SQuery) of
5057+ {updated, _} ->
5058+ ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]),
5059+ ok;
5060+ {error, _} -> error
5061+ end.
f7ce3e3a 5062+
234c6b10 5063+delete_stats_by_user_at_int(DBRef, User, VHost, Date) ->
5064+ SQuery = ["DELETE FROM ",stats_table(VHost)," ",
5065+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\") ",
5066+ "AND at=\"",Date,"\";"],
5067+ case sql_query_internal(DBRef, SQuery) of
5068+ {updated, _} ->
5069+ ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]),
5070+ ok;
5071+ {error, _} -> error
5072+ end.
f7ce3e3a 5073+
234c6b10 5074+delete_user_settings_int(DBRef, User, VHost) ->
5075+ Query = ["DELETE FROM ",settings_table(VHost)," ",
5076+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
f7ce3e3a 5077+ case sql_query_internal(DBRef, Query) of
5078+ {updated, _} ->
234c6b10 5079+ ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]),
f7ce3e3a 5080+ ok;
234c6b10 5081+ {error, Reason} ->
5082+ ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]),
f7ce3e3a 5083+ error
5084+ end.
5085+
5086+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5087+%
5088+% tables internals
5089+%
5090+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
234c6b10 5091+create_temp_table(DBRef, Name) ->
5092+ Query = ["CREATE TABLE ",Name," (",
5093+ "owner_id MEDIUMINT UNSIGNED, ",
5094+ "peer_name_id MEDIUMINT UNSIGNED, ",
5095+ "peer_server_id MEDIUMINT UNSIGNED, ",
5096+ "at VARCHAR(11), ",
5097+ "count INT(11) ",
5098+ ") ENGINE=MyISAM CHARACTER SET utf8;"
5099+ ],
5100+ case sql_query_internal(DBRef, Query) of
5101+ {updated, _} -> ok;
5102+ {error, _Reason} -> error
5103+ end.
5104+
5105+create_stats_table(#state{dbref=DBRef, vhost=VHost}=State) ->
f7ce3e3a 5106+ SName = stats_table(VHost),
5107+ Query = ["CREATE TABLE ",SName," (",
5108+ "owner_id MEDIUMINT UNSIGNED, ",
234c6b10 5109+ "peer_name_id MEDIUMINT UNSIGNED, ",
5110+ "peer_server_id MEDIUMINT UNSIGNED, ",
f7ce3e3a 5111+ "at VARCHAR(11), ",
5112+ "count INT(11), ",
234c6b10 5113+ "ext INTEGER DEFAULT NULL, "
5114+ "INDEX ext_i (ext), "
5115+ "INDEX(owner_id,peer_name_id,peer_server_id), ",
5116+ "INDEX(at) ",
5117+ ") ENGINE=MyISAM CHARACTER SET utf8;"
f7ce3e3a 5118+ ],
5119+ case sql_query_internal_silent(DBRef, Query) of
5120+ {updated, _} ->
5121+ ?MYDEBUG("Created stats table for ~p", [VHost]),
234c6b10 5122+ rebuild_all_stats_int(State),
f7ce3e3a 5123+ ok;
5124+ {error, Reason} ->
046546ef 5125+ case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#42S01">>) of
0d78319d 5126+ match ->
f7ce3e3a 5127+ ?MYDEBUG("Stats table for ~p already exists", [VHost]),
234c6b10 5128+ CheckQuery = ["SHOW COLUMNS FROM ",SName," LIKE 'peer_%_id';"],
5129+ case sql_query_internal(DBRef, CheckQuery) of
5130+ {data, Elems} when length(Elems) == 2 ->
5131+ ?MYDEBUG("Stats table structure is ok", []),
5132+ ok;
5133+ _ ->
5134+ ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []),
5135+ case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of
5136+ {updated, _} ->
5137+ ?INFO_MSG("Successfully dropped ~p", [SName]);
5138+ _ ->
5139+ ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName])
5140+ end,
5141+ error
5142+ end;
f7ce3e3a 5143+ _ ->
5144+ ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
5145+ error
5146+ end
5147+ end.
5148+
234c6b10 5149+create_settings_table(#state{dbref=DBRef, vhost=VHost}) ->
f7ce3e3a 5150+ SName = settings_table(VHost),
5151+ Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
5152+ "owner_id MEDIUMINT UNSIGNED PRIMARY KEY, ",
5153+ "dolog_default TINYINT(1) NOT NULL DEFAULT 1, ",
5154+ "dolog_list TEXT, ",
5155+ "donotlog_list TEXT ",
5156+ ") ENGINE=InnoDB CHARACTER SET utf8;"
5157+ ],
5158+ case sql_query_internal(DBRef, Query) of
5159+ {updated, _} ->
5160+ ?MYDEBUG("Created settings table for ~p", [VHost]),
5161+ ok;
5162+ {error, _} ->
5163+ error
5164+ end.
5165+
234c6b10 5166+create_users_table(#state{dbref=DBRef, vhost=VHost}) ->
f7ce3e3a 5167+ SName = users_table(VHost),
5168+ Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
5169+ "username TEXT NOT NULL, ",
5170+ "user_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
5171+ "UNIQUE INDEX(username(",?INDEX_SIZE,")) ",
5172+ ") ENGINE=InnoDB CHARACTER SET utf8;"
5173+ ],
5174+ case sql_query_internal(DBRef, Query) of
5175+ {updated, _} ->
5176+ ?MYDEBUG("Created users table for ~p", [VHost]),
5177+ ok;
5178+ {error, _} ->
5179+ error
5180+ end.
5181+
234c6b10 5182+create_servers_table(#state{dbref=DBRef, vhost=VHost}) ->
f7ce3e3a 5183+ SName = servers_table(VHost),
5184+ Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
5185+ "server TEXT NOT NULL, ",
5186+ "server_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
5187+ "UNIQUE INDEX(server(",?INDEX_SIZE,")) ",
5188+ ") ENGINE=InnoDB CHARACTER SET utf8;"
5189+ ],
5190+ case sql_query_internal(DBRef, Query) of
5191+ {updated, _} ->
5192+ ?MYDEBUG("Created servers table for ~p", [VHost]),
5193+ ok;
5194+ {error, _} ->
5195+ error
5196+ end.
5197+
234c6b10 5198+create_resources_table(#state{dbref=DBRef, vhost=VHost}) ->
f7ce3e3a 5199+ RName = resources_table(VHost),
5200+ Query = ["CREATE TABLE IF NOT EXISTS ",RName," (",
5201+ "resource TEXT NOT NULL, ",
5202+ "resource_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
5203+ "UNIQUE INDEX(resource(",?INDEX_SIZE,")) ",
5204+ ") ENGINE=InnoDB CHARACTER SET utf8;"
5205+ ],
5206+ case sql_query_internal(DBRef, Query) of
5207+ {updated, _} ->
5208+ ?MYDEBUG("Created resources table for ~p", [VHost]),
5209+ ok;
5210+ {error, _} ->
5211+ error
5212+ end.
5213+
234c6b10 5214+create_internals(#state{dbref=DBRef, vhost=VHost}) ->
5215+ sql_query_internal(DBRef, ["DROP PROCEDURE IF EXISTS ",logmessage_name(VHost),";"]),
f7ce3e3a 5216+ case sql_query_internal(DBRef, [get_logmessage(VHost)]) of
5217+ {updated, _} ->
5218+ ?MYDEBUG("Created logmessage for ~p", [VHost]),
5219+ ok;
5220+ {error, _} ->
5221+ error
5222+ end.
5223+
5224+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5225+%
0d78319d 5226+% SQL internals
f7ce3e3a 5227+%
5228+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
f7ce3e3a 5229+sql_query_internal(DBRef, Query) ->
5230+ case sql_query_internal_silent(DBRef, Query) of
5231+ {error, Reason} ->
5232+ ?ERROR_MSG("~p while ~p", [Reason, lists:append(Query)]),
5233+ {error, Reason};
5234+ Rez -> Rez
5235+ end.
5236+
5237+sql_query_internal_silent(DBRef, Query) ->
5238+ ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
046546ef 5239+ get_result(p1_mysql_conn:fetch(DBRef, Query, self(), ?MYSQL_TIMEOUT)).
f7ce3e3a 5240+
5241+get_result({updated, MySQLRes}) ->
046546ef 5242+ {updated, p1_mysql:get_result_affected_rows(MySQLRes)};
f7ce3e3a 5243+get_result({data, MySQLRes}) ->
046546ef 5244+ {data, p1_mysql:get_result_rows(MySQLRes)};
f7ce3e3a 5245+get_result({error, "query timed out"}) ->
5246+ {error, "query timed out"};
5247+get_result({error, MySQLRes}) ->
046546ef 5248+ Reason = p1_mysql:get_result_reason(MySQLRes),
f7ce3e3a 5249+ {error, Reason}.
5250+
5251+get_user_id(DBRef, VHost, User) ->
5252+ SQuery = ["SELECT user_id FROM ",users_table(VHost)," ",
5253+ "WHERE username=\"",User,"\";"],
5254+ case sql_query_internal(DBRef, SQuery) of
5255+ {data, []} ->
5256+ IQuery = ["INSERT INTO ",users_table(VHost)," ",
5257+ "SET username=\"",User,"\";"],
5258+ case sql_query_internal_silent(DBRef, IQuery) of
5259+ {updated, _} ->
5260+ {data, [[DBIdNew]]} = sql_query_internal(DBRef, SQuery),
5261+ DBIdNew;
5262+ {error, Reason} ->
5263+ % this can be in clustered environment
046546ef 5264+ match = ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>),
f7ce3e3a 5265+ ?ERROR_MSG("Duplicate key name for ~p", [User]),
5266+ {data, [[ClID]]} = sql_query_internal(DBRef, SQuery),
5267+ ClID
5268+ end;
5269+ {data, [[DBId]]} ->
5270+ DBId
5271+ end.
5272+
98471d6b
AM
5273+mod_opt_type(_) -> [].
5274+
f7ce3e3a 5275+get_logmessage(VHost) ->
5276+ UName = users_table(VHost),
5277+ SName = servers_table(VHost),
5278+ RName = resources_table(VHost),
5279+ StName = stats_table(VHost),
5280+ io_lib:format("
234c6b10 5281+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 5282+BEGIN
0d78319d 5283+ DECLARE ownerID MEDIUMINT UNSIGNED;
f7ce3e3a 5284+ DECLARE peer_nameID MEDIUMINT UNSIGNED;
5285+ DECLARE peer_serverID MEDIUMINT UNSIGNED;
5286+ DECLARE peer_resourceID MEDIUMINT UNSIGNED;
5287+ DECLARE Vmtype VARCHAR(10);
5288+ DECLARE Vmtimestamp DOUBLE;
5289+ DECLARE Vmdirection VARCHAR(4);
5290+ DECLARE Vmbody TEXT;
5291+ DECLARE Vmsubject TEXT;
5292+ DECLARE iq TEXT;
5293+ DECLARE cq TEXT;
5294+ DECLARE viewname TEXT;
5295+ DECLARE notable INT;
5296+ DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' SET @notable = 1;
5297+
5298+ SET @notable = 0;
5299+ SET @ownerID = NULL;
5300+ SET @peer_nameID = NULL;
5301+ SET @peer_serverID = NULL;
5302+ SET @peer_resourceID = NULL;
5303+
5304+ SET @Vmtype = mtype;
5305+ SET @Vmtimestamp = mtimestamp;
5306+ SET @Vmdirection = mdirection;
5307+ SET @Vmbody = mbody;
5308+ SET @Vmsubject = msubject;
5309+
5310+ SELECT user_id INTO @ownerID FROM ~s WHERE username=owner;
5311+ IF @ownerID IS NULL THEN
5312+ INSERT INTO ~s SET username=owner;
0d78319d 5313+ SET @ownerID = LAST_INSERT_ID();
f7ce3e3a 5314+ END IF;
5315+
5316+ SELECT user_id INTO @peer_nameID FROM ~s WHERE username=peer_name;
5317+ IF @peer_nameID IS NULL THEN
5318+ INSERT INTO ~s SET username=peer_name;
5319+ SET @peer_nameID = LAST_INSERT_ID();
5320+ END IF;
5321+
5322+ SELECT server_id INTO @peer_serverID FROM ~s WHERE server=peer_server;
5323+ IF @peer_serverID IS NULL THEN
5324+ INSERT INTO ~s SET server=peer_server;
5325+ SET @peer_serverID = LAST_INSERT_ID();
5326+ END IF;
5327+
5328+ SELECT resource_id INTO @peer_resourceID FROM ~s WHERE resource=peer_resource;
5329+ IF @peer_resourceID IS NULL THEN
5330+ INSERT INTO ~s SET resource=peer_resource;
5331+ SET @peer_resourceID = LAST_INSERT_ID();
5332+ END IF;
5333+
5334+ 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);\");
5335+ PREPARE insertmsg FROM @iq;
5336+
5337+ IF @notable = 1 THEN
5338+ SET @cq = CONCAT(\"CREATE TABLE \",tablename,\" (
234c6b10 5339+ owner_id MEDIUMINT UNSIGNED NOT NULL,
5340+ peer_name_id MEDIUMINT UNSIGNED NOT NULL,
5341+ peer_server_id MEDIUMINT UNSIGNED NOT NULL,
5342+ peer_resource_id MEDIUMINT(8) UNSIGNED NOT NULL,
5343+ direction ENUM('to', 'from') NOT NULL,
f7ce3e3a 5344+ type ENUM('chat','error','groupchat','headline','normal') NOT NULL,
5345+ subject TEXT,
5346+ body TEXT,
234c6b10 5347+ timestamp DOUBLE NOT NULL,
f7ce3e3a 5348+ ext INTEGER DEFAULT NULL,
234c6b10 5349+ INDEX search_i (owner_id, peer_name_id, peer_server_id, peer_resource_id),
f7ce3e3a 5350+ INDEX ext_i (ext),
5351+ FULLTEXT (body)
234c6b10 5352+ ) ENGINE=MyISAM
5353+ PACK_KEYS=1
5354+ CHARACTER SET utf8;\");
f7ce3e3a 5355+ PREPARE createtable FROM @cq;
5356+ EXECUTE createtable;
5357+ DEALLOCATE PREPARE createtable;
5358+
5359+ SET @viewname = CONCAT(\"`v_\", TRIM(BOTH '`' FROM tablename), \"`\");
5360+ SET @cq = CONCAT(\"CREATE OR REPLACE VIEW \",@viewname,\" AS
5361+ SELECT owner.username AS owner_name,
5362+ peer.username AS peer_name,
5363+ servers.server AS peer_server,
5364+ resources.resource AS peer_resource,
5365+ messages.direction,
5366+ messages.type,
5367+ messages.subject,
5368+ messages.body,
5369+ messages.timestamp
5370+ FROM
5371+ ~s owner,
5372+ ~s peer,
5373+ ~s servers,
5374+ ~s resources,
5375+ \", tablename,\" messages
5376+ WHERE
5377+ owner.user_id=messages.owner_id and
5378+ peer.user_id=messages.peer_name_id and
5379+ servers.server_id=messages.peer_server_id and
5380+ resources.resource_id=messages.peer_resource_id
5381+ ORDER BY messages.timestamp;\");
5382+ PREPARE createview FROM @cq;
5383+ EXECUTE createview;
5384+ DEALLOCATE PREPARE createview;
5385+
5386+ SET @notable = 0;
5387+ PREPARE insertmsg FROM @iq;
5388+ EXECUTE insertmsg;
5389+ ELSEIF @notable = 0 THEN
5390+ EXECUTE insertmsg;
5391+ END IF;
5392+
5393+ DEALLOCATE PREPARE insertmsg;
5394+
5395+ IF @notable = 0 THEN
234c6b10 5396+ 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 5397+ IF ROW_COUNT() = 0 THEN
234c6b10 5398+ INSERT INTO ~s (owner_id, peer_name_id, peer_server_id, at, count) VALUES (@ownerID, @peer_nameID, @peer_serverID, atdate, 1);
f7ce3e3a 5399+ END IF;
5400+ END IF;
234c6b10 5401+END;", [logmessage_name(VHost),UName,UName,UName,UName,SName,SName,RName,RName,UName,UName,SName,RName,StName,StName]).
046546ef 5402diff --git a/src/mod_logdb_pgsql.erl b/src/mod_logdb_pgsql.erl
0d78319d 5403new file mode 100644
046546ef 5404index 0000000..3c2ae95
0d78319d 5405--- /dev/null
046546ef
AM
5406+++ b/src/mod_logdb_pgsql.erl
5407@@ -0,0 +1,1108 @@
5408+% {ok, DBRef} = pgsql:connect([{host, "127.0.0.1"}, {database, "logdb"}, {user, "logdb"}, {password, "logdb"}, {port, 5432}, {as_binary, true}]).
5409+% Schema = "test".
5410+% 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 5411+%%%----------------------------------------------------------------------
5412+%%% File : mod_logdb_pgsql.erl
234c6b10 5413+%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
f7ce3e3a 5414+%%% Purpose : Posgresql backend for mod_logdb
5415+%%% Version : trunk
0d78319d 5416+%%% Id : $Id: mod_logdb_pgsql.erl 1360 2009-07-30 06:00:14Z malik $
f7ce3e3a 5417+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
5418+%%%----------------------------------------------------------------------
5419+
5420+-module(mod_logdb_pgsql).
5421+-author('o.palij@gmail.com').
f7ce3e3a 5422+
5423+-include("mod_logdb.hrl").
5424+-include("ejabberd.hrl").
5425+-include("jlib.hrl").
046546ef 5426+-include("logger.hrl").
f7ce3e3a 5427+
5428+-behaviour(gen_logdb).
5429+-behaviour(gen_server).
5430+
5431+% gen_server
5432+-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
5433+% gen_mod
98471d6b 5434+-export([start/2, stop/1, mod_opt_type/1]).
f7ce3e3a 5435+% gen_logdb
5436+-export([log_message/2,
5437+ rebuild_stats/1,
5438+ rebuild_stats_at/2,
5439+ delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
5440+ get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
5441+ get_dates/1,
234c6b10 5442+ get_users_settings/1, get_user_settings/2, set_user_settings/3,
5443+ drop_user/2]).
5444+
5445+-export([view_table/3]).
f7ce3e3a 5446+
5447+% gen_server call timeout
234c6b10 5448+-define(CALL_TIMEOUT, 30000).
5449+-define(PGSQL_TIMEOUT, 60000).
f7ce3e3a 5450+-define(PROCNAME, mod_logdb_pgsql).
5451+
5452+-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
5453+ list_to_string/1, string_to_list/1,
5454+ convert_timestamp_brief/1]).
5455+
234c6b10 5456+-record(state, {dbref, vhost, server, port, db, user, password, schema}).
f7ce3e3a 5457+
5458+% replace "." with "_"
5459+escape_vhost(VHost) -> lists:map(fun(46) -> 95;
5460+ (A) -> A
046546ef 5461+ end, binary_to_list(VHost)).
f7ce3e3a 5462+
5463+prefix(Schema) ->
5464+ Schema ++ ".\"" ++ "logdb_".
5465+
5466+suffix(VHost) ->
5467+ "_" ++ escape_vhost(VHost) ++ "\"".
5468+
5469+messages_table(VHost, Schema, Date) ->
5470+ prefix(Schema) ++ "messages_" ++ Date ++ suffix(VHost).
5471+
f7ce3e3a 5472+view_table(VHost, Schema, Date) ->
5473+ Table = messages_table(VHost, Schema, Date),
5474+ TablewoS = lists:sublist(Table, length(Schema) + 3, length(Table) - length(Schema) - 3),
5475+ lists:append([Schema, ".\"v_", TablewoS, "\""]).
5476+
5477+stats_table(VHost, Schema) ->
5478+ prefix(Schema) ++ "stats" ++ suffix(VHost).
5479+
234c6b10 5480+temp_table(VHost, Schema) ->
5481+ prefix(Schema) ++ "temp" ++ suffix(VHost).
5482+
f7ce3e3a 5483+settings_table(VHost, Schema) ->
5484+ prefix(Schema) ++ "settings" ++ suffix(VHost).
5485+
5486+users_table(VHost, Schema) ->
5487+ prefix(Schema) ++ "users" ++ suffix(VHost).
5488+servers_table(VHost, Schema) ->
5489+ prefix(Schema) ++ "servers" ++ suffix(VHost).
5490+resources_table(VHost, Schema) ->
5491+ prefix(Schema) ++ "resources" ++ suffix(VHost).
5492+
234c6b10 5493+logmessage_name(VHost, Schema) ->
5494+ prefix(Schema) ++ "logmessage" ++ suffix(VHost).
5495+
f7ce3e3a 5496+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5497+%
5498+% gen_mod callbacks
5499+%
5500+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5501+start(VHost, Opts) ->
5502+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5503+ gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
5504+
5505+stop(VHost) ->
5506+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5507+ gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
5508+
5509+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5510+%
5511+% gen_server callbacks
5512+%
5513+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5514+init([VHost, Opts]) ->
046546ef
AM
5515+ Server = gen_mod:get_opt(server, Opts, fun(A) -> A end, <<"localhost">>),
5516+ DB = gen_mod:get_opt(db, Opts, fun(A) -> A end, <<"ejabberd_logdb">>),
5517+ User = gen_mod:get_opt(user, Opts, fun(A) -> A end, <<"root">>),
5518+ Port = gen_mod:get_opt(port, Opts, fun(A) -> A end, 5432),
5519+ Password = gen_mod:get_opt(password, Opts, fun(A) -> A end, <<"">>),
5520+ Schema = binary_to_list(gen_mod:get_opt(schema, Opts, fun(A) -> A end, <<"public">>)),
f7ce3e3a 5521+
046546ef 5522+ ?MYDEBUG("Starting pgsql backend for ~s", [VHost]),
234c6b10 5523+
5524+ St = #state{vhost=VHost,
5525+ server=Server, port=Port, db=DB,
5526+ user=User, password=Password,
5527+ schema=Schema},
5528+
5529+ case open_pgsql_connection(St) of
f7ce3e3a 5530+ {ok, DBRef} ->
234c6b10 5531+ State = St#state{dbref=DBRef},
5532+ ok = create_internals(State),
5533+ ok = create_stats_table(State),
5534+ ok = create_settings_table(State),
5535+ ok = create_users_table(State),
5536+ ok = create_servers_table(State),
5537+ ok = create_resources_table(State),
f7ce3e3a 5538+ erlang:monitor(process, DBRef),
234c6b10 5539+ {ok, State};
f7ce3e3a 5540+ % this does not work
5541+ {error, Reason} ->
5542+ ?ERROR_MSG("PgSQL connection failed: ~p~n", [Reason]),
5543+ {stop, db_connection_failed};
5544+ % and this too, becouse pgsql_conn do exit() which can not be catched
5545+ {'EXIT', Rez} ->
5546+ ?ERROR_MSG("Rez: ~p~n", [Rez]),
5547+ {stop, db_connection_failed}
5548+ end.
5549+
234c6b10 5550+open_pgsql_connection(#state{server=Server, port=Port, db=DB, schema=Schema,
5551+ user=User, password=Password} = _State) ->
26b6b0c9 5552+ ?INFO_MSG("Opening pgsql connection ~s@~s:~p/~s", [User, Server, Port, DB]),
234c6b10 5553+ {ok, DBRef} = pgsql:connect(Server, DB, User, Password, Port),
5554+ {updated, _} = sql_query_internal(DBRef, ["SET SEARCH_PATH TO ",Schema,";"]),
5555+ {ok, DBRef}.
5556+
5557+close_pgsql_connection(DBRef) ->
5558+ ?MYDEBUG("Closing ~p pgsql connection", [DBRef]),
5559+ pgsql:terminate(DBRef).
5560+
f7ce3e3a 5561+handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5562+ Date = convert_timestamp_brief(Msg#msg.timestamp),
5563+ TableName = messages_table(VHost, Schema, Date),
234c6b10 5564+ ViewName = view_table(VHost, Schema, Date),
f7ce3e3a 5565+
234c6b10 5566+ Query = [ "SELECT ", logmessage_name(VHost, Schema)," "
f7ce3e3a 5567+ "('", TableName, "',",
234c6b10 5568+ "'", ViewName, "',",
f7ce3e3a 5569+ "'", Date, "',",
046546ef
AM
5570+ "'", binary_to_list(Msg#msg.owner_name), "',",
5571+ "'", binary_to_list(Msg#msg.peer_name), "',",
5572+ "'", binary_to_list(Msg#msg.peer_server), "',",
5573+ "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.peer_resource) ), "',",
234c6b10 5574+ "'", atom_to_list(Msg#msg.direction), "',",
046546ef
AM
5575+ "'", binary_to_list(Msg#msg.type), "',",
5576+ "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.subject) ), "',",
5577+ "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.body) ), "',",
234c6b10 5578+ "'", Msg#msg.timestamp, "');"],
5579+
5580+ case sql_query_internal_silent(DBRef, Query) of
5581+ % TODO: change this
5582+ {data, [{"0"}]} ->
046546ef
AM
5583+ ?MYDEBUG("Logged ok for ~s, peer: ~s", [ [Msg#msg.owner_name, <<"@">>, VHost],
5584+ [Msg#msg.peer_name, <<"@">>, Msg#msg.peer_server] ]),
234c6b10 5585+ ok;
5586+ {error, _Reason} ->
5587+ error
5588+ end,
5589+ {reply, ok, State};
f7ce3e3a 5590+handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5591+ Reply = rebuild_stats_at_int(DBRef, VHost, Schema, Date),
5592+ {reply, Reply, State};
5593+handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
5594+ {reply, error, State};
5595+handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5596+ Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
5597+ ["'",Timestamp,"'",","]
5598+ end, Msgs),
5599+
5600+ Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
5601+
5602+ Query = ["DELETE FROM ",messages_table(VHost, Schema, Date)," ",
5603+ "WHERE timestamp IN (", Temp1],
5604+
5605+ Reply =
5606+ case sql_query_internal(DBRef, Query) of
5607+ {updated, _} ->
5608+ rebuild_stats_at_int(DBRef, VHost, Schema, Date);
5609+ {error, _} ->
5610+ error
5611+ end,
5612+ {reply, Reply, State};
5613+handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
234c6b10 5614+ ok = delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date),
5615+ ok = delete_stats_by_user_at_int(DBRef, Schema, User, VHost, Date),
5616+ {reply, ok, State};
f7ce3e3a 5617+handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
f7ce3e3a 5618+ {updated, _} = sql_query_internal(DBRef, ["DROP VIEW ",view_table(VHost, Schema, Date),";"]),
5619+ Reply =
234c6b10 5620+ case sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Schema, Date)," CASCADE;"]) of
f7ce3e3a 5621+ {updated, _} ->
5622+ Query = ["DELETE FROM ",stats_table(VHost, Schema)," "
5623+ "WHERE at='",Date,"';"],
5624+ case sql_query_internal(DBRef, Query) of
5625+ {updated, _} ->
5626+ ok;
5627+ {error, _} ->
5628+ error
5629+ end;
5630+ {error, _} ->
5631+ error
5632+ end,
5633+ {reply, Reply, State};
5634+handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5635+ SName = stats_table(VHost, Schema),
5636+ Query = ["SELECT at, sum(count) ",
5637+ "FROM ",SName," ",
5638+ "GROUP BY at ",
5639+ "ORDER BY DATE(at) DESC;"
5640+ ],
5641+ Reply =
5642+ case sql_query_internal(DBRef, Query) of
5643+ {data, Recs} ->
5644+ {ok, [ {Date, list_to_integer(Count)} || {Date, Count} <- Recs]};
5645+ {error, Reason} ->
5646+ % TODO: Duplicate error message ?
5647+ {error, Reason}
5648+ end,
5649+ {reply, Reply, State};
5650+handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5651+ SName = stats_table(VHost, Schema),
234c6b10 5652+ Query = ["SELECT username, sum(count) AS allcount ",
f7ce3e3a 5653+ "FROM ",SName," ",
234c6b10 5654+ "JOIN ",users_table(VHost, Schema)," ON owner_id=user_id ",
5655+ "WHERE at='",Date,"' ",
5656+ "GROUP BY username ",
5657+ "ORDER BY allcount DESC;"
f7ce3e3a 5658+ ],
5659+ Reply =
5660+ case sql_query_internal(DBRef, Query) of
5661+ {data, Recs} ->
5662+ RFun = fun({User, Count}) ->
5663+ {User, list_to_integer(Count)}
5664+ end,
5665+ {ok, lists:reverse(lists:keysort(2, lists:map(RFun, Recs)))};
5666+ {error, Reason} ->
5667+ % TODO:
5668+ {error, Reason}
5669+ end,
5670+ {reply, Reply, State};
5671+handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
234c6b10 5672+ {reply, get_user_stats_int(DBRef, Schema, User, VHost), State};
f7ce3e3a 5673+handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5674+ Query = ["SELECT peer_name,",
5675+ "peer_server,",
5676+ "peer_resource,",
5677+ "direction,"
5678+ "type,"
5679+ "subject,"
5680+ "body,"
5681+ "timestamp "
5682+ "FROM ",view_table(VHost, Schema, Date)," "
5683+ "WHERE owner_name='",User,"';"],
5684+ Reply =
5685+ case sql_query_internal(DBRef, Query) of
5686+ {data, Recs} ->
5687+ Fun = fun({Peer_name, Peer_server, Peer_resource,
5688+ Direction,
5689+ Type,
5690+ Subject, Body,
5691+ Timestamp}) ->
5692+ #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
5693+ direction=list_to_atom(Direction),
5694+ type=Type,
5695+ subject=Subject, body=Body,
5696+ timestamp=Timestamp}
5697+ end,
5698+ {ok, lists:map(Fun, Recs)};
5699+ {error, Reason} ->
5700+ {error, Reason}
5701+ end,
5702+ {reply, Reply, State};
5703+handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5704+ SName = stats_table(VHost, Schema),
5705+ Query = ["SELECT at ",
5706+ "FROM ",SName," ",
5707+ "GROUP BY at ",
5708+ "ORDER BY at DESC;"
5709+ ],
5710+ Reply =
5711+ case sql_query_internal(DBRef, Query) of
5712+ {data, Result} ->
5713+ [ Date || {Date} <- Result ];
5714+ {error, Reason} ->
5715+ {error, Reason}
5716+ end,
5717+ {reply, Reply, State};
5718+handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5719+ Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
5720+ "FROM ",settings_table(VHost, Schema)," ",
5721+ "JOIN ",users_table(VHost, Schema)," ON user_id=owner_id;"],
5722+ Reply =
5723+ case sql_query_internal(DBRef, Query) of
5724+ {data, Recs} ->
5725+ {ok, [#user_settings{owner_name=Owner,
5726+ dolog_default=list_to_bool(DoLogDef),
5727+ dolog_list=string_to_list(DoLogL),
5728+ donotlog_list=string_to_list(DoNotLogL)
5729+ } || {Owner, DoLogDef, DoLogL, DoNotLogL} <- Recs]};
5730+ {error, Reason} ->
5731+ {error, Reason}
5732+ end,
5733+ {reply, Reply, State};
5734+handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5735+ Query = ["SELECT dolog_default,dolog_list,donotlog_list ",
5736+ "FROM ",settings_table(VHost, Schema)," ",
5737+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
5738+ Reply =
5739+ case sql_query_internal_silent(DBRef, Query) of
5740+ {data, []} ->
5741+ {ok, []};
5742+ {data, [{DoLogDef, DoLogL, DoNotLogL}]} ->
5743+ {ok, #user_settings{owner_name=User,
5744+ dolog_default=list_to_bool(DoLogDef),
5745+ dolog_list=string_to_list(DoLogL),
5746+ donotlog_list=string_to_list(DoNotLogL)}};
5747+ {error, Reason} ->
046546ef 5748+ ?ERROR_MSG("Failed to get_user_settings for ~s@~s: ~p", [User, VHost, Reason]),
f7ce3e3a 5749+ error
5750+ end,
5751+ {reply, Reply, State};
5752+handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
5753+ dolog_list=DoLogL,
5754+ donotlog_list=DoNotLogL}},
5755+ _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5756+ User_id = get_user_id(DBRef, VHost, Schema, User),
5757+ Query = ["UPDATE ",settings_table(VHost, Schema)," ",
5758+ "SET dolog_default=",bool_to_list(DoLogDef),", ",
5759+ "dolog_list='",list_to_string(DoLogL),"', ",
5760+ "donotlog_list='",list_to_string(DoNotLogL),"' ",
5761+ "WHERE owner_id=",User_id,";"],
5762+
5763+ Reply =
5764+ case sql_query_internal(DBRef, Query) of
5765+ {updated, 0} ->
5766+ IQuery = ["INSERT INTO ",settings_table(VHost, Schema)," ",
5767+ "(owner_id, dolog_default, dolog_list, donotlog_list) ",
5768+ "VALUES ",
5769+ "(",User_id,", ",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
5770+ case sql_query_internal(DBRef, IQuery) of
5771+ {updated, 1} ->
5772+ ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
5773+ ok;
5774+ {error, _} ->
5775+ error
5776+ end;
5777+ {updated, 1} ->
5778+ ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
5779+ ok;
5780+ {error, _} ->
5781+ error
5782+ end,
5783+ {reply, Reply, State};
5784+handle_call({stop}, _From, State) ->
5785+ ?MYDEBUG("Stoping pgsql backend for ~p", [State#state.vhost]),
5786+ {stop, normal, ok, State};
5787+handle_call(Msg, _From, State) ->
5788+ ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
5789+ {noreply, State}.
5790+
234c6b10 5791+
5792+handle_cast({rebuild_stats}, State) ->
5793+ rebuild_all_stats_int(State),
5794+ {noreply, State};
5795+handle_cast({drop_user, User}, #state{vhost=VHost, schema=Schema}=State) ->
5796+ Fun = fun() ->
5797+ {ok, DBRef} = open_pgsql_connection(State),
5798+ {ok, Dates} = get_user_stats_int(DBRef, Schema, User, VHost),
5799+ MDResult = lists:map(fun({Date, _}) ->
5800+ delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date)
5801+ end, Dates),
5802+ StDResult = delete_all_stats_by_user_int(DBRef, Schema, User, VHost),
5803+ SDResult = delete_user_settings_int(DBRef, Schema, User, VHost),
5804+ case lists:all(fun(Result) when Result == ok ->
5805+ true;
5806+ (Result) when Result == error ->
5807+ false
5808+ end, lists:append([MDResult, [StDResult], [SDResult]])) of
5809+ true ->
5810+ ?INFO_MSG("Removed ~s@~s", [User, VHost]);
5811+ false ->
5812+ ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost])
5813+ end,
5814+ close_pgsql_connection(DBRef)
5815+ end,
5816+ spawn(Fun),
5817+ {noreply, State};
f7ce3e3a 5818+handle_cast(Msg, State) ->
5819+ ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
5820+ {noreply, State}.
5821+
5822+handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
5823+ {stop, connection_dropped, State};
5824+handle_info(Info, State) ->
5825+ ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
5826+ {noreply, State}.
5827+
234c6b10 5828+terminate(_Reason, #state{dbref=DBRef}=_State) ->
5829+ close_pgsql_connection(DBRef),
f7ce3e3a 5830+ ok.
5831+
5832+code_change(_OldVsn, State, _Extra) ->
5833+ {ok, State}.
5834+
5835+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5836+%
5837+% gen_logdb callbacks
5838+%
5839+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5840+log_message(VHost, Msg) ->
5841+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5842+ gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
5843+rebuild_stats(VHost) ->
5844+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
234c6b10 5845+ gen_server:cast(Proc, {rebuild_stats}).
f7ce3e3a 5846+rebuild_stats_at(VHost, Date) ->
5847+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5848+ gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
5849+delete_messages_by_user_at(VHost, Msgs, Date) ->
5850+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5851+ gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
5852+delete_all_messages_by_user_at(User, VHost, Date) ->
5853+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5854+ gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
5855+delete_messages_at(VHost, Date) ->
5856+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5857+ gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
5858+get_vhost_stats(VHost) ->
5859+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5860+ gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
5861+get_vhost_stats_at(VHost, Date) ->
5862+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5863+ gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
5864+get_user_stats(User, VHost) ->
5865+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5866+ gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
5867+get_user_messages_at(User, VHost, Date) ->
5868+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5869+ gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
5870+get_dates(VHost) ->
5871+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5872+ gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
5873+get_users_settings(VHost) ->
5874+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5875+ gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
5876+get_user_settings(User, VHost) ->
5877+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5878+ gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
5879+set_user_settings(User, VHost, Set) ->
5880+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5881+ gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
234c6b10 5882+drop_user(User, VHost) ->
5883+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5884+ gen_server:cast(Proc, {drop_user, User}).
f7ce3e3a 5885+
5886+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5887+%
5888+% internals
5889+%
5890+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5891+get_dates_int(DBRef, VHost) ->
5892+ Query = ["SELECT n.nspname as \"Schema\",
5893+ c.relname as \"Name\",
5894+ 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\",
5895+ r.rolname as \"Owner\"
5896+ FROM pg_catalog.pg_class c
5897+ JOIN pg_catalog.pg_roles r ON r.oid = c.relowner
5898+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
5899+ WHERE c.relkind IN ('r','')
5900+ AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
5901+ AND c.relname ~ '^(.*",escape_vhost(VHost),".*)$'
5902+ AND pg_catalog.pg_table_is_visible(c.oid)
5903+ ORDER BY 1,2;"],
5904+ case sql_query_internal(DBRef, Query) of
5905+ {data, Recs} ->
5906+ lists:foldl(fun({_Schema, Table, _Type, _Owner}, Dates) ->
0d78319d
AM
5907+ case re:run(Table,"[0-9]+-[0-9]+-[0-9]+") of
5908+ {match, [{S, E}]} ->
f7ce3e3a 5909+ lists:append(Dates, [lists:sublist(Table,S,E)]);
5910+ nomatch ->
5911+ Dates
5912+ end
5913+ end, [], Recs);
5914+ {error, _} ->
5915+ []
5916+ end.
5917+
234c6b10 5918+rebuild_all_stats_int(#state{vhost=VHost, schema=Schema}=State) ->
5919+ Fun = fun() ->
5920+ {ok, DBRef} = open_pgsql_connection(State),
5921+ ok = delete_nonexistent_stats(DBRef, Schema, VHost),
5922+ case lists:filter(fun(Date) ->
5923+ case catch rebuild_stats_at_int(DBRef, VHost, Schema, Date) of
5924+ ok -> false;
5925+ error -> true;
5926+ {'EXIT', _} -> true
5927+ end
5928+ end, get_dates_int(DBRef, VHost)) of
5929+ [] -> ok;
5930+ FTables ->
5931+ ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]),
5932+ error
5933+ end,
5934+ close_pgsql_connection(DBRef)
5935+ end,
5936+ spawn(Fun).
f7ce3e3a 5937+
234c6b10 5938+rebuild_stats_at_int(DBRef, VHost, Schema, Date) ->
5939+ TempTable = temp_table(VHost, Schema),
f7ce3e3a 5940+ Fun =
5941+ fun() ->
234c6b10 5942+ Table = messages_table(VHost, Schema, Date),
5943+ STable = stats_table(VHost, Schema),
f7ce3e3a 5944+
5945+ DQuery = [ "DELETE FROM ",STable," ",
5946+ "WHERE at='",Date,"';"],
5947+
234c6b10 5948+ ok = create_temp_table(DBRef, VHost, Schema),
5949+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," IN ACCESS EXCLUSIVE MODE;"]),
5950+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",TempTable," IN ACCESS EXCLUSIVE MODE;"]),
5951+ SQuery = ["INSERT INTO ",TempTable," ",
5952+ "(owner_id,peer_name_id,peer_server_id,at,count) ",
5953+ "SELECT owner_id,peer_name_id,peer_server_id,'",Date,"'",",count(*) ",
5954+ "FROM ",Table," GROUP BY owner_id,peer_name_id,peer_server_id;"],
f7ce3e3a 5955+ case sql_query_internal(DBRef, SQuery) of
5956+ {updated, 0} ->
234c6b10 5957+ Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]),
5958+ case Count of
5959+ {data, [{"0"}]} ->
5960+ {updated, _} = sql_query_internal(DBRef, ["DROP VIEW ",view_table(VHost, Schema, Date),";"]),
5961+ {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table," CASCADE;"]),
5962+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," IN ACCESS EXCLUSIVE MODE;"]),
5963+ {updated, _} = sql_query_internal(DBRef, DQuery),
5964+ ok;
5965+ _ ->
5966+ ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]),
5967+ error
5968+ end;
5969+ {updated, _} ->
5970+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," IN ACCESS EXCLUSIVE MODE;"]),
5971+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",TempTable," IN ACCESS EXCLUSIVE MODE;"]),
5972+ {updated, _} = sql_query_internal(DBRef, DQuery),
5973+ SQuery1 = ["INSERT INTO ",STable," ",
5974+ "(owner_id,peer_name_id,peer_server_id,at,count) ",
5975+ "SELECT owner_id,peer_name_id,peer_server_id,at,count ",
5976+ "FROM ",TempTable,";"],
5977+ case sql_query_internal(DBRef, SQuery1) of
5978+ {updated, _} -> ok;
5979+ {error, _} -> error
5980+ end;
f7ce3e3a 5981+ {error, _} -> error
5982+ end
234c6b10 5983+ end, % fun
f7ce3e3a 5984+
5985+ case sql_transaction_internal(DBRef, Fun) of
5986+ {atomic, _} ->
046546ef 5987+ ?INFO_MSG("Rebuilded stats for ~s at ~s", [VHost, Date]),
234c6b10 5988+ ok;
5989+ {aborted, Reason} ->
5990+ ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]),
5991+ error
5992+ end,
5993+ sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]),
5994+ ok.
f7ce3e3a 5995+
234c6b10 5996+delete_nonexistent_stats(DBRef, Schema, VHost) ->
f7ce3e3a 5997+ Dates = get_dates_int(DBRef, VHost),
5998+ STable = stats_table(VHost, Schema),
5999+
6000+ Temp = lists:flatmap(fun(Date) ->
6001+ ["'",Date,"'",","]
6002+ end, Dates),
6003+
234c6b10 6004+ case Temp of
6005+ [] ->
6006+ ok;
6007+ _ ->
6008+ % replace last "," with ");"
6009+ Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
6010+ Query = ["DELETE FROM ",STable," ",
6011+ "WHERE at NOT IN (", Temp1],
6012+ case sql_query_internal(DBRef, Query) of
6013+ {updated, _} ->
6014+ ok;
6015+ {error, _} ->
6016+ error
6017+ end
6018+ end.
f7ce3e3a 6019+
234c6b10 6020+get_user_stats_int(DBRef, Schema, User, VHost) ->
6021+ SName = stats_table(VHost, Schema),
6022+ UName = users_table(VHost, Schema),
6023+ Query = ["SELECT stats.at, sum(stats.count) ",
6024+ "FROM ",UName," AS users ",
6025+ "JOIN ",SName," AS stats ON owner_id=user_id "
6026+ "WHERE users.username='",User,"' ",
6027+ "GROUP BY stats.at "
6028+ "ORDER BY DATE(at) DESC;"
6029+ ],
f7ce3e3a 6030+ case sql_query_internal(DBRef, Query) of
234c6b10 6031+ {data, Recs} ->
6032+ {ok, [ {Date, list_to_integer(Count)} || {Date, Count} <- Recs ]};
6033+ {error, Result} ->
6034+ {error, Result}
6035+ end.
6036+
6037+delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date) ->
6038+ DQuery = ["DELETE FROM ",messages_table(VHost, Schema, Date)," ",
6039+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
6040+ case sql_query_internal(DBRef, DQuery) of
f7ce3e3a 6041+ {updated, _} ->
234c6b10 6042+ ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]),
f7ce3e3a 6043+ ok;
6044+ {error, _} ->
6045+ error
6046+ end.
6047+
234c6b10 6048+delete_all_stats_by_user_int(DBRef, Schema, User, VHost) ->
6049+ SQuery = ["DELETE FROM ",stats_table(VHost, Schema)," ",
6050+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
6051+ case sql_query_internal(DBRef, SQuery) of
6052+ {updated, _} ->
6053+ ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]),
6054+ ok;
6055+ {error, _} -> error
6056+ end.
6057+
6058+delete_stats_by_user_at_int(DBRef, Schema, User, VHost, Date) ->
6059+ SQuery = ["DELETE FROM ",stats_table(VHost, Schema)," ",
6060+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"') ",
6061+ "AND at='",Date,"';"],
6062+ case sql_query_internal(DBRef, SQuery) of
6063+ {updated, _} ->
6064+ ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]),
6065+ ok;
6066+ {error, _} -> error
6067+ end.
6068+
6069+delete_user_settings_int(DBRef, Schema, User, VHost) ->
6070+ Query = ["DELETE FROM ",settings_table(VHost, Schema)," ",
6071+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
6072+ case sql_query_internal(DBRef, Query) of
6073+ {updated, _} ->
6074+ ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]),
6075+ ok;
6076+ {error, Reason} ->
6077+ ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]),
6078+ error
6079+ end.
6080+
f7ce3e3a 6081+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6082+%
6083+% tables internals
6084+%
6085+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
234c6b10 6086+create_temp_table(DBRef, VHost, Schema) ->
6087+ TName = temp_table(VHost, Schema),
6088+ Query = ["CREATE TABLE ",TName," (",
6089+ "owner_id INTEGER, ",
6090+ "peer_name_id INTEGER, ",
6091+ "peer_server_id INTEGER, ",
6092+ "at VARCHAR(20), ",
6093+ "count INTEGER ",
6094+ ");"
6095+ ],
6096+ case sql_query_internal(DBRef, Query) of
6097+ {updated, _} -> ok;
6098+ {error, _Reason} -> error
6099+ end.
6100+
6101+create_stats_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
f7ce3e3a 6102+ SName = stats_table(VHost, Schema),
6103+
6104+ Fun =
6105+ fun() ->
6106+ Query = ["CREATE TABLE ",SName," (",
6107+ "owner_id INTEGER, ",
234c6b10 6108+ "peer_name_id INTEGER, ",
6109+ "peer_server_id INTEGER, ",
f7ce3e3a 6110+ "at VARCHAR(20), ",
6111+ "count integer",
6112+ ");"
6113+ ],
6114+ case sql_query_internal_silent(DBRef, Query) of
6115+ {updated, _} ->
234c6b10 6116+ {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 6117+ {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"s_at_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (at);"]),
6118+ created;
6119+ {error, Reason} ->
6120+ case lists:keysearch(code, 1, Reason) of
6121+ {value, {code, "42P07"}} ->
6122+ exists;
6123+ _ ->
046546ef 6124+ ?ERROR_MSG("Failed to create stats table for ~s: ~p", [VHost, Reason]),
f7ce3e3a 6125+ error
6126+ end
6127+ end
6128+ end,
6129+ case sql_transaction_internal(DBRef, Fun) of
6130+ {atomic, created} ->
046546ef 6131+ ?MYDEBUG("Created stats table for ~s", [VHost]),
234c6b10 6132+ rebuild_all_stats_int(State),
6133+ ok;
f7ce3e3a 6134+ {atomic, exists} ->
046546ef 6135+ ?MYDEBUG("Stats table for ~s already exists", [VHost]),
0d78319d 6136+ {match, [{F, L}]} = re:run(SName, "\".*\""),
046546ef 6137+ QTable = lists:sublist(SName, F+2, L-2),
234c6b10 6138+ 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);"],
6139+ {data,[{OID}]} = sql_query_internal(DBRef, OIDQuery),
6140+ 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$';"],
6141+ case sql_query_internal(DBRef, CheckQuery) of
6142+ {data, Elems} when length(Elems) == 2 ->
6143+ ?MYDEBUG("Stats table structure is ok", []),
6144+ ok;
6145+ _ ->
6146+ ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []),
6147+ case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of
6148+ {updated, _} ->
6149+ ?INFO_MSG("Successfully dropped ~p", [SName]);
6150+ _ ->
6151+ ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName])
6152+ end,
6153+ error
6154+ end;
f7ce3e3a 6155+ {error, _} -> error
6156+ end.
6157+
234c6b10 6158+create_settings_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
f7ce3e3a 6159+ SName = settings_table(VHost, Schema),
6160+ Query = ["CREATE TABLE ",SName," (",
6161+ "owner_id INTEGER PRIMARY KEY, ",
6162+ "dolog_default BOOLEAN, ",
6163+ "dolog_list TEXT DEFAULT '', ",
6164+ "donotlog_list TEXT DEFAULT ''",
6165+ ");"
6166+ ],
6167+ case sql_query_internal_silent(DBRef, Query) of
6168+ {updated, _} ->
046546ef 6169+ ?MYDEBUG("Created settings table for ~s", [VHost]),
f7ce3e3a 6170+ ok;
6171+ {error, Reason} ->
6172+ case lists:keysearch(code, 1, Reason) of
6173+ {value, {code, "42P07"}} ->
046546ef 6174+ ?MYDEBUG("Settings table for ~s already exists", [VHost]),
f7ce3e3a 6175+ ok;
6176+ _ ->
046546ef 6177+ ?ERROR_MSG("Failed to create settings table for ~s: ~p", [VHost, Reason]),
f7ce3e3a 6178+ error
6179+ end
6180+ end.
6181+
234c6b10 6182+create_users_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
f7ce3e3a 6183+ SName = users_table(VHost, Schema),
6184+
6185+ Fun =
6186+ fun() ->
6187+ Query = ["CREATE TABLE ",SName," (",
6188+ "username TEXT UNIQUE, ",
6189+ "user_id SERIAL PRIMARY KEY",
6190+ ");"
6191+ ],
6192+ case sql_query_internal_silent(DBRef, Query) of
6193+ {updated, _} ->
6194+ {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"username_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (username);"]),
6195+ created;
6196+ {error, Reason} ->
6197+ case lists:keysearch(code, 1, Reason) of
6198+ {value, {code, "42P07"}} ->
6199+ exists;
6200+ _ ->
046546ef 6201+ ?ERROR_MSG("Failed to create users table for ~s: ~p", [VHost, Reason]),
f7ce3e3a 6202+ error
6203+ end
6204+ end
6205+ end,
6206+ case sql_transaction_internal(DBRef, Fun) of
6207+ {atomic, created} ->
046546ef 6208+ ?MYDEBUG("Created users table for ~s", [VHost]),
f7ce3e3a 6209+ ok;
0d78319d 6210+ {atomic, exists} ->
046546ef 6211+ ?MYDEBUG("Users table for ~s already exists", [VHost]),
f7ce3e3a 6212+ ok;
6213+ {aborted, _} -> error
6214+ end.
6215+
234c6b10 6216+create_servers_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
f7ce3e3a 6217+ SName = servers_table(VHost, Schema),
f7ce3e3a 6218+ Fun =
6219+ fun() ->
6220+ Query = ["CREATE TABLE ",SName," (",
6221+ "server TEXT UNIQUE, ",
6222+ "server_id SERIAL PRIMARY KEY",
6223+ ");"
6224+ ],
6225+ case sql_query_internal_silent(DBRef, Query) of
6226+ {updated, _} ->
6227+ {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"server_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (server);"]),
6228+ created;
6229+ {error, Reason} ->
6230+ case lists:keysearch(code, 1, Reason) of
6231+ {value, {code, "42P07"}} ->
6232+ exists;
6233+ _ ->
046546ef 6234+ ?ERROR_MSG("Failed to create servers table for ~s: ~p", [VHost, Reason]),
f7ce3e3a 6235+ error
6236+ end
6237+ end
6238+ end,
6239+ case sql_transaction_internal(DBRef, Fun) of
6240+ {atomic, created} ->
046546ef 6241+ ?MYDEBUG("Created servers table for ~s", [VHost]),
f7ce3e3a 6242+ ok;
6243+ {atomic, exists} ->
046546ef 6244+ ?MYDEBUG("Servers table for ~s already exists", [VHost]),
f7ce3e3a 6245+ ok;
6246+ {aborted, _} -> error
6247+ end.
6248+
234c6b10 6249+create_resources_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
f7ce3e3a 6250+ RName = resources_table(VHost, Schema),
6251+ Fun = fun() ->
6252+ Query = ["CREATE TABLE ",RName," (",
6253+ "resource TEXT UNIQUE, ",
6254+ "resource_id SERIAL PRIMARY KEY",
6255+ ");"
6256+ ],
6257+ case sql_query_internal_silent(DBRef, Query) of
6258+ {updated, _} ->
6259+ {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"resource_i_",Schema,"_",escape_vhost(VHost),"\" ON ",RName," (resource);"]),
6260+ created;
6261+ {error, Reason} ->
6262+ case lists:keysearch(code, 1, Reason) of
6263+ {value, {code, "42P07"}} ->
6264+ exists;
6265+ _ ->
046546ef 6266+ ?ERROR_MSG("Failed to create users table for ~s: ~p", [VHost, Reason]),
f7ce3e3a 6267+ error
6268+ end
6269+ end
6270+ end,
6271+ case sql_transaction_internal(DBRef, Fun) of
6272+ {atomic, created} ->
046546ef 6273+ ?MYDEBUG("Created resources table for ~s", [VHost]),
f7ce3e3a 6274+ ok;
6275+ {atomic, exists} ->
046546ef 6276+ ?MYDEBUG("Resources table for ~s already exists", [VHost]),
f7ce3e3a 6277+ ok;
6278+ {aborted, _} -> error
6279+ end.
6280+
26b6b0c9 6281+create_internals(#state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
234c6b10 6282+ 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 6283+ case sql_query_internal(DBRef, [get_logmessage(VHost, Schema)]) of
6284+ {updated, _} ->
6285+ ?MYDEBUG("Created logmessage for ~p", [VHost]),
6286+ ok;
26b6b0c9
AM
6287+ {error, Reason} ->
6288+ case lists:keysearch(code, 1, Reason) of
6289+ {value, {code, "42704"}} ->
6290+ ?ERROR_MSG("plpgsql language must be installed into database '~s'. Use CREATE LANGUAGE...", [State#state.db]),
6291+ error;
6292+ _ ->
6293+ error
6294+ end
f7ce3e3a 6295+ end.
6296+
6297+get_user_id(DBRef, VHost, Schema, User) ->
6298+ SQuery = ["SELECT user_id FROM ",users_table(VHost, Schema)," ",
6299+ "WHERE username='",User,"';"],
6300+ case sql_query_internal(DBRef, SQuery) of
6301+ {data, []} ->
6302+ IQuery = ["INSERT INTO ",users_table(VHost, Schema)," ",
6303+ "VALUES ('",User,"');"],
6304+ case sql_query_internal_silent(DBRef, IQuery) of
6305+ {updated, _} ->
6306+ {data, [{DBIdNew}]} = sql_query_internal(DBRef, SQuery),
6307+ DBIdNew;
6308+ {error, Reason} ->
6309+ % this can be in clustered environment
6310+ {value, {code, "23505"}} = lists:keysearch(code, 1, Reason),
6311+ ?ERROR_MSG("Duplicate key name for ~p", [User]),
6312+ {data, [{ClID}]} = sql_query_internal(DBRef, SQuery),
6313+ ClID
6314+ end;
6315+ {data, [{DBId}]} ->
6316+ DBId
6317+ end.
6318+
6319+get_logmessage(VHost,Schema) ->
6320+ UName = users_table(VHost,Schema),
6321+ SName = servers_table(VHost,Schema),
6322+ RName = resources_table(VHost,Schema),
6323+ StName = stats_table(VHost,Schema),
234c6b10 6324+ 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 6325+DECLARE
6326+ ownerID INTEGER;
6327+ peer_nameID INTEGER;
6328+ peer_serverID INTEGER;
6329+ peer_resourceID INTEGER;
6330+ tablename ALIAS for $1;
234c6b10 6331+ viewname ALIAS for $2;
6332+ atdate ALIAS for $3;
f7ce3e3a 6333+BEGIN
6334+ SELECT INTO ownerID user_id FROM ~s WHERE username = owner;
6335+ IF NOT FOUND THEN
6336+ INSERT INTO ~s (username) VALUES (owner);
6337+ ownerID := lastval();
6338+ END IF;
6339+
6340+ SELECT INTO peer_nameID user_id FROM ~s WHERE username = peer_name;
6341+ IF NOT FOUND THEN
6342+ INSERT INTO ~s (username) VALUES (peer_name);
6343+ peer_nameID := lastval();
6344+ END IF;
6345+
6346+ SELECT INTO peer_serverID server_id FROM ~s WHERE server = peer_server;
6347+ IF NOT FOUND THEN
6348+ INSERT INTO ~s (server) VALUES (peer_server);
6349+ peer_serverID := lastval();
6350+ END IF;
6351+
6352+ SELECT INTO peer_resourceID resource_id FROM ~s WHERE resource = peer_resource;
6353+ IF NOT FOUND THEN
6354+ INSERT INTO ~s (resource) VALUES (peer_resource);
6355+ peer_resourceID := lastval();
6356+ END IF;
6357+
6358+ BEGIN
234c6b10 6359+ 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 6360+ EXCEPTION WHEN undefined_table THEN
6361+ EXECUTE 'CREATE TABLE ' || tablename || ' (' ||
6362+ 'owner_id INTEGER, ' ||
6363+ 'peer_name_id INTEGER, ' ||
6364+ 'peer_server_id INTEGER, ' ||
6365+ 'peer_resource_id INTEGER, ' ||
6366+ 'direction VARCHAR(4) CHECK (direction IN (''to'',''from'')), ' ||
6367+ 'type VARCHAR(9) CHECK (type IN (''chat'',''error'',''groupchat'',''headline'',''normal'')), ' ||
6368+ 'subject TEXT, ' ||
6369+ 'body TEXT, ' ||
6370+ 'timestamp DOUBLE PRECISION)';
234c6b10 6371+ EXECUTE 'CREATE INDEX \"search_i_' || '~s' || '_' || atdate || '_' || '~s' || '\"' || ' ON ' || tablename || ' (owner_id, peer_name_id, peer_server_id, peer_resource_id)';
f7ce3e3a 6372+
6373+ EXECUTE 'CREATE OR REPLACE VIEW ' || viewname || ' AS ' ||
6374+ 'SELECT owner.username AS owner_name, ' ||
6375+ 'peer.username AS peer_name, ' ||
6376+ 'servers.server AS peer_server, ' ||
6377+ 'resources.resource AS peer_resource, ' ||
6378+ 'messages.direction, ' ||
6379+ 'messages.type, ' ||
6380+ 'messages.subject, ' ||
6381+ 'messages.body, ' ||
6382+ 'messages.timestamp ' ||
6383+ 'FROM ' ||
6384+ '~s owner, ' ||
6385+ '~s peer, ' ||
6386+ '~s servers, ' ||
6387+ '~s resources, ' ||
6388+ tablename || ' messages ' ||
6389+ 'WHERE ' ||
6390+ 'owner.user_id=messages.owner_id and ' ||
6391+ 'peer.user_id=messages.peer_name_id and ' ||
6392+ 'servers.server_id=messages.peer_server_id and ' ||
6393+ 'resources.resource_id=messages.peer_resource_id ' ||
6394+ 'ORDER BY messages.timestamp';
6395+
234c6b10 6396+ 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 6397+ END;
6398+
234c6b10 6399+ 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 6400+ IF NOT FOUND THEN
234c6b10 6401+ INSERT INTO ~s (owner_id, peer_name_id, peer_server_id, at, count) VALUES (ownerID, peer_nameID, peer_serverID, atdate, 1);
f7ce3e3a 6402+ END IF;
6403+ RETURN 0;
6404+END;
6405+$$ LANGUAGE plpgsql;
234c6b10 6406+", [logmessage_name(VHost,Schema),UName,UName,UName,UName,SName,SName,RName,RName,Schema,escape_vhost(VHost),UName,UName,SName,RName,StName,StName]).
f7ce3e3a 6407+
6408+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6409+%
0d78319d 6410+% SQL internals
f7ce3e3a 6411+%
6412+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6413+% like do_transaction/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
6414+sql_transaction_internal(DBRef, Fun) ->
6415+ case sql_query_internal(DBRef, ["BEGIN;"]) of
6416+ {updated, _} ->
6417+ case catch Fun() of
6418+ error = Err ->
6419+ rollback_internal(DBRef, Err);
6420+ {error, _} = Err ->
6421+ rollback_internal(DBRef, Err);
6422+ {'EXIT', _} = Err ->
6423+ rollback_internal(DBRef, Err);
6424+ Res ->
6425+ case sql_query_internal(DBRef, ["COMMIT;"]) of
6426+ {error, _} -> rollback_internal(DBRef, {commit_error});
6427+ {updated, _} ->
6428+ case Res of
6429+ {atomic, _} -> Res;
6430+ _ -> {atomic, Res}
6431+ end
6432+ end
6433+ end;
6434+ {error, _} ->
6435+ {aborted, {begin_error}}
6436+ end.
6437+
6438+% like rollback/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
6439+rollback_internal(DBRef, Reason) ->
6440+ Res = sql_query_internal(DBRef, ["ROLLBACK;"]),
6441+ {aborted, {Reason, {rollback_result, Res}}}.
6442+
6443+sql_query_internal(DBRef, Query) ->
6444+ case sql_query_internal_silent(DBRef, Query) of
6445+ {error, undefined, Rez} ->
6446+ ?ERROR_MSG("Got undefined result: ~p while ~p", [Rez, lists:append(Query)]),
6447+ {error, undefined};
6448+ {error, Error} ->
6449+ ?ERROR_MSG("Failed: ~p while ~p", [Error, lists:append(Query)]),
6450+ {error, Error};
0d78319d
AM
6451+ Rez -> Rez
6452+ end.
234c6b10 6453+
0d78319d
AM
6454+sql_query_internal_silent(DBRef, Query) ->
6455+ ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
6456+ % TODO: use pquery?
6457+ get_result(pgsql:squery(DBRef, Query)).
234c6b10 6458+
0d78319d
AM
6459+get_result({ok, ["CREATE TABLE"]}) ->
6460+ {updated, 1};
6461+get_result({ok, ["DROP TABLE"]}) ->
6462+ {updated, 1};
6463+get_result({ok, ["ALTER TABLE"]}) ->
6464+ {updated, 1};
6465+get_result({ok,["DROP VIEW"]}) ->
6466+ {updated, 1};
6467+get_result({ok,["DROP FUNCTION"]}) ->
6468+ {updated, 1};
6469+get_result({ok, ["CREATE INDEX"]}) ->
6470+ {updated, 1};
6471+get_result({ok, ["CREATE FUNCTION"]}) ->
6472+ {updated, 1};
046546ef 6473+get_result({ok, [{[$S, $E, $L, $E, $C, $T, $ | _Rest], _Rows, Recs}]}) ->
0d78319d
AM
6474+ Fun = fun(Rec) ->
6475+ list_to_tuple(
6476+ lists:map(fun(Elem) when is_binary(Elem) ->
6477+ binary_to_list(Elem);
6478+ (Elem) when is_list(Elem) ->
6479+ Elem;
6480+ (Elem) when is_integer(Elem) ->
6481+ integer_to_list(Elem);
6482+ (Elem) when is_float(Elem) ->
6483+ float_to_list(Elem);
6484+ (Elem) when is_boolean(Elem) ->
6485+ atom_to_list(Elem);
6486+ (Elem) ->
6487+ ?ERROR_MSG("Unknown element type ~p", [Elem]),
6488+ Elem
6489+ end, Rec))
6490+ end,
6491+ Res = lists:map(Fun, Recs),
6492+ %{data, [list_to_tuple(Rec) || Rec <- Recs]};
6493+ {data, Res};
6494+get_result({ok, ["INSERT " ++ OIDN]}) ->
6495+ [_OID, N] = string:tokens(OIDN, " "),
6496+ {updated, list_to_integer(N)};
6497+get_result({ok, ["DELETE " ++ N]}) ->
6498+ {updated, list_to_integer(N)};
6499+get_result({ok, ["UPDATE " ++ N]}) ->
6500+ {updated, list_to_integer(N)};
6501+get_result({ok, ["BEGIN"]}) ->
6502+ {updated, 1};
6503+get_result({ok, ["LOCK TABLE"]}) ->
6504+ {updated, 1};
6505+get_result({ok, ["ROLLBACK"]}) ->
6506+ {updated, 1};
6507+get_result({ok, ["COMMIT"]}) ->
6508+ {updated, 1};
6509+get_result({ok, ["SET"]}) ->
6510+ {updated, 1};
6511+get_result({ok, [{error, Error}]}) ->
6512+ {error, Error};
6513+get_result(Rez) ->
6514+ {error, undefined, Rez}.
98471d6b 6515+mod_opt_type(_) -> [].
046546ef 6516diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl
e2b4c35d 6517index df06bce..b460f46 100644
046546ef
AM
6518--- a/src/mod_muc_room.erl
6519+++ b/src/mod_muc_room.erl
4852a36b
AM
6520@@ -545,6 +545,12 @@ handle_sync_event({process_item_change,
6521 NSD ->
6522 {reply, {ok, NSD}, StateName, NSD}
6523 end;
0d78319d 6524+handle_sync_event({get_jid_nick, Jid}, _From, StateName, StateData) ->
4852a36b
AM
6525+ R = case (?DICT):find(jlib:jid_tolower(Jid), StateData#state.users) of
6526+ error -> [];
6527+ {ok, {user, _, Nick, _, _}} -> Nick
6528+ end,
6529+ {reply, R, StateName, StateData};
6530 handle_sync_event(get_subscribers, _From, StateName, StateData) ->
6531 JIDs = lists:map(fun jid:make/1,
6532 ?DICT:fetch_keys(StateData#state.subscribers)),
046546ef 6533diff --git a/src/mod_roster.erl b/src/mod_roster.erl
e2b4c35d 6534index 31fbeb1..764a628 100644
046546ef
AM
6535--- a/src/mod_roster.erl
6536+++ b/src/mod_roster.erl
e2b4c35d 6537@@ -63,6 +63,8 @@
046546ef
AM
6538
6539 -include("ejabberd_web_admin.hrl").
0d78319d 6540
234c6b10 6541+-include("mod_logdb.hrl").
0d78319d 6542+
046546ef 6543 -export_type([subscription/0]).
234c6b10 6544
6545 start(Host, Opts) ->
e2b4c35d 6546@@ -1426,6 +1428,14 @@ user_roster(User, Server, Query, Lang) ->
046546ef 6547 Query),
234c6b10 6548 Items = get_roster(LUser, LServer),
6549 SItems = lists:sort(Items),
6550+
6551+ Settings = case gen_mod:is_loaded(Server, mod_logdb) of
6552+ true ->
6553+ mod_logdb:get_user_settings(User, Server);
6554+ false ->
6555+ []
6556+ end,
6557+
046546ef
AM
6558 FItems = case SItems of
6559 [] -> [?CT(<<"None">>)];
6560 _ ->
e2b4c35d 6561@@ -1483,7 +1493,33 @@ user_roster(User, Server, Query, Lang) ->
046546ef
AM
6562 [?INPUTT(<<"submit">>,
6563 <<"remove",
6564 (ejabberd_web_admin:term_to_id(R#roster.jid))/binary>>,
6565- <<"Remove">>)])])
6566+ <<"Remove">>)]),
6567+ case gen_mod:is_loaded(Server, mod_logdb) of
6568+ true ->
6569+ Peer = jlib:jid_to_string(R#roster.jid),
6570+ A = lists:member(Peer, Settings#user_settings.dolog_list),
6571+ B = lists:member(Peer, Settings#user_settings.donotlog_list),
6572+ {Name, Value} =
6573+ if
6574+ A ->
6575+ {<<"donotlog">>, <<"Do Not Log Messages">>};
6576+ B ->
6577+ {<<"dolog">>, <<"Log Messages">>};
6578+ Settings#user_settings.dolog_default == true ->
6579+ {<<"donotlog">>, <<"Do Not Log Messages">>};
6580+ Settings#user_settings.dolog_default == false ->
6581+ {<<"dolog">>, <<"Log Messages">>}
6582+ end,
6583+
6584+ ?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
6585+ [?INPUTT(<<"submit">>,
6586+ <<Name,
6587+ (ejabberd_web_admin:term_to_id(R#roster.jid))/binary>>,
6588+ Value)]);
6589+ false ->
6590+ ?X([])
6591+ end
6592+ ])
6593 end,
6594 SItems)))])]
6595 end,
e2b4c35d 6596@@ -1608,9 +1644,42 @@ user_roster_item_parse_query(User, Server, Items,
046546ef
AM
6597 =
6598 []}]}}),
6599 throw(submitted);
6600- false -> ok
6601- end
6602- end
6603+ false ->
6604+ case lists:keysearch(
6605+ <<"donotlog">>, (ejabberd_web_admin:term_to_id(JID))/binary, 1, Query) of
6606+ {value, _} ->
6607+ Peer = jlib:jid_to_string(JID),
6608+ Settings = mod_logdb:get_user_settings(User, Server),
6609+ DNLL = case lists:member(Peer, Settings#user_settings.donotlog_list) of
6610+ false -> lists:append(Settings#user_settings.donotlog_list, [Peer]);
6611+ true -> Settings#user_settings.donotlog_list
6612+ end,
6613+ DLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.dolog_list),
6614+ Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
6615+ % TODO: check returned value
6616+ ok = mod_logdb:set_user_settings(User, Server, Sett),
6617+ throw(nothing);
6618+ false ->
6619+ case lists:keysearch(
6620+ <<"dolog">>, (ejabberd_web_admin:term_to_id(JID))/binary, 1, Query) of
6621+ {value, _} ->
6622+ Peer = jlib:jid_to_string(JID),
6623+ Settings = mod_logdb:get_user_settings(User, Server),
6624+ DLL = case lists:member(Peer, Settings#user_settings.dolog_list) of
6625+ false -> lists:append(Settings#user_settings.dolog_list, [Peer]);
6626+ true -> Settings#user_settings.dolog_list
6627+ end,
6628+ DNLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.donotlog_list),
6629+ Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
6630+ % TODO: check returned value
6631+ ok = mod_logdb:set_user_settings(User, Server, Sett),
6632+ throw(nothing);
6633+ false ->
6634+ ok
6635+ end % dolog
6636+ end % donotlog
6637+ end % remove
234c6b10 6638+ end % validate
046546ef
AM
6639 end,
6640 Items),
234c6b10 6641 nothing.
This page took 1.427272 seconds and 4 git commands to generate.