1 diff --git a/priv/msgs/nl.msg b/priv/msgs/nl.msg
2 index 70e739f..76df8cf 100644
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"}.
10 +{"Users Messages", "Gebruikersberichten"}.
13 +{"Logged messages for ~s", "Gelogde berichten van ~s"}.
14 +{"Logged messages for ~s at ~s", "Gelogde berichten van ~s op ~s"}.
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"}.
23 diff --git a/priv/msgs/pl.msg b/priv/msgs/pl.msg
24 index 4bc2063..7540386 100644
25 --- a/priv/msgs/pl.msg
26 +++ b/priv/msgs/pl.msg
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"}.
32 +{"Users Messages", "Wiadomości użytkownika"}.
35 +{"Logged messages for ~s", "Zapisane wiadomości dla ~s"}.
36 +{"Logged messages for ~s at", "Zapisane wiadomości dla ~s o ~s"}.
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"}.
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)"}.
57 diff --git a/priv/msgs/ru.msg b/priv/msgs/ru.msg
58 index ece7348..b159f98 100644
59 --- a/priv/msgs/ru.msg
60 +++ b/priv/msgs/ru.msg
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"}.
66 +{"Users Messages", "Сообщения пользователей"}.
68 +{"Count", "Количество"}.
69 +{"Logged messages for ~s", "Сохранённые cообщения для ~s"}.
70 +{"Logged messages for ~s at ~s", "Сохранённые cообщения для ~s за ~s"}.
72 +{"No logged messages for ~s", "Отсутствуют сообщения для ~s"}.
73 +{"No logged messages for ~s at ~s", "Отсутствуют сообщения для ~s за ~s"}.
74 +{"Date, Time", "Дата, Время"}.
75 +{"Direction: Jid", "Направление: Jid"}.
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)", "Обновлять настройки пользователей через (секунд)"}.
93 +{"Do not drop", "Не удалять"}.
94 +{"Drop messages on user removal", "Удалять сообщения при удалении пользователя"}.
95 diff --git a/priv/msgs/uk.msg b/priv/msgs/uk.msg
96 index 6e21c90..9838624 100644
97 --- a/priv/msgs/uk.msg
98 +++ b/priv/msgs/uk.msg
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"}.
104 +{"Users Messages", "Повідомлення користувачів"}.
106 +{"Count", "Кількість"}.
107 +{"Logged messages for ~s", "Збережені повідомлення для ~s"}.
108 +{"Logged messages for ~s at ~s", "Збережені повідомлення для ~s за ~s"}.
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", "Тема"}.
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", "Видаляти повідомлення під час видалення користувача"}.
133 diff --git a/rebar.config.script b/rebar.config.script
134 index f342a6c..2ad3f1f 100644
135 --- a/rebar.config.script
136 +++ b/rebar.config.script
137 @@ -81,9 +81,9 @@ PostHooks = [ConfigureCmd("p1_tls", ""),
139 CfgDeps = lists:flatmap(
140 fun({mysql, true}) ->
141 - [{p1_mysql, ".*", {git, "git://github.com/processone/mysql"}}];
142 + [{p1_mysql, ".*", {git, "git://github.com/paleg/mysql", {branch, "multi"}}}];
144 - [{p1_pgsql, ".*", {git, "git://github.com/processone/pgsql"}}];
145 + [{p1_pgsql, ".*", {git, "git://github.com/paleg/pgsql", {branch, "errordescr_fix"}}}];
147 [{p1_pam, ".*", {git, "git://github.com/processone/epam"}}];
149 diff --git a/src/gen_logdb.erl b/src/gen_logdb.erl
151 index 0000000..06a894b
153 +++ b/src/gen_logdb.erl
155 +%%%----------------------------------------------------------------------
156 +%%% File : gen_logdb.erl
157 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
158 +%%% Purpose : Describes generic behaviour for mod_logdb backends.
160 +%%% Id : $Id: gen_logdb.erl 1273 2009-02-05 18:12:57Z malik $
161 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
162 +%%%----------------------------------------------------------------------
165 +-author('o.palij@gmail.com').
167 +-export([behaviour_info/1]).
169 +behaviour_info(callbacks) ->
171 + % called from handle_info(start, _)
172 + % it should logon database and return reference to started instance
173 + % start(VHost, Opts) -> {ok, SPid} | error
174 + % Options - list of options to connect to db
175 + % Types: Options = list() -> [] |
176 + % [{user, "logdb"},
178 + % {db, "logdb"}] | ...
179 + % VHost = list() -> "jabber.example.org"
182 + % called from cleanup/1
183 + % it should logoff database and do cleanup
185 + % Types: VHost = list() -> "jabber.example.org"
188 + % called from handle_call({addlog, _}, _, _)
189 + % it should log messages to database
190 + % log_message(VHost, Msg) -> ok | error
192 + % VHost = list() -> "jabber.example.org"
193 + % Msg = record() -> #msg
196 + % called from ejabberdctl rebuild_stats
197 + % it should rebuild stats table (if used) for vhost
198 + % rebuild_stats(VHost)
200 + % VHost = list() -> "jabber.example.org"
201 + {rebuild_stats, 1},
203 + % it should rebuild stats table (if used) for vhost at Date
204 + % rebuild_stats_at(VHost, Date)
206 + % VHost = list() -> "jabber.example.org"
207 + % Date = list() -> "2007-02-12"
208 + {rebuild_stats_at, 2},
210 + % called from user_messages_at_parse_query/5
211 + % it should delete selected user messages at date
212 + % delete_messages_by_user_at(VHost, Msgs, Date) -> ok | error
214 + % VHost = list() -> "jabber.example.org"
215 + % Msgs = list() -> [ #msg1, msg2, ... ]
216 + % Date = list() -> "2007-02-12"
217 + {delete_messages_by_user_at, 3},
219 + % called from user_messages_parse_query/4 | vhost_messages_at_parse_query/4
220 + % it should delete all user messages at date
221 + % delete_all_messages_by_user_at(User, VHost, Date) -> ok | error
223 + % User = list() -> "admin"
224 + % VHost = list() -> "jabber.example.org"
225 + % Date = list() -> "2007-02-12"
226 + {delete_all_messages_by_user_at, 3},
228 + % called from vhost_messages_parse_query/3
229 + % it should delete messages for vhost at date and update stats
230 + % delete_messages_at(VHost, Date) -> ok | error
232 + % VHost = list() -> "jabber.example.org"
233 + % Date = list() -> "2007-02-12"
234 + {delete_messages_at, 2},
236 + % called from ejabberd_web_admin:vhost_messages_stats/3
237 + % it should return sorted list of count of messages by dates for vhost
238 + % get_vhost_stats(VHost) -> {ok, [{Date1, Msgs_count1}, {Date2, Msgs_count2}, ... ]} |
241 + % VHost = list() -> "jabber.example.org"
242 + % DateN = list() -> "2007-02-12"
243 + % Msgs_countN = number() -> 241
244 + {get_vhost_stats, 1},
246 + % called from ejabberd_web_admin:vhost_messages_stats_at/4
247 + % it should return sorted list of count of messages by users at date for vhost
248 + % get_vhost_stats_at(VHost, Date) -> {ok, [{User1, Msgs_count1}, {User2, Msgs_count2}, ....]} |
251 + % VHost = list() -> "jabber.example.org"
252 + % Date = list() -> "2007-02-12"
253 + % UserN = list() -> "admin"
254 + % Msgs_countN = number() -> 241
255 + {get_vhost_stats_at, 2},
257 + % called from ejabberd_web_admin:user_messages_stats/4
258 + % it should return sorted list of count of messages by date for user at vhost
259 + % get_user_stats(User, VHost) -> {ok, [{Date1, Msgs_count1}, {Date2, Msgs_count2}, ...]} |
262 + % User = list() -> "admin"
263 + % VHost = list() -> "jabber.example.org"
264 + % DateN = list() -> "2007-02-12"
265 + % Msgs_countN = number() -> 241
266 + {get_user_stats, 2},
268 + % called from ejabberd_web_admin:user_messages_stats_at/5
269 + % it should return all user messages at date
270 + % get_user_messages_at(User, VHost, Date) -> {ok, Msgs} | {error, Reason}
272 + % User = list() -> "admin"
273 + % VHost = list() -> "jabber.example.org"
274 + % Date = list() -> "2007-02-12"
275 + % Msgs = list() -> [ #msg1, msg2, ... ]
276 + {get_user_messages_at, 3},
278 + % called from many places
279 + % it should return list of dates for vhost
280 + % get_dates(VHost) -> [Date1, Date2, ... ]
282 + % VHost = list() -> "jabber.example.org"
283 + % DateN = list() -> "2007-02-12"
286 + % called from start
287 + % it should return list with users settings for VHost in db
288 + % get_users_settings(VHost) -> [#user_settings1, #user_settings2, ... ] | error
290 + % VHost = list() -> "jabber.example.org"
291 + {get_users_settings, 1},
293 + % called from many places
294 + % it should return User settings at VHost from db
295 + % get_user_settings(User, VHost) -> error | {ok, #user_settings}
297 + % User = list() -> "admin"
298 + % VHost = list() -> "jabber.example.org"
299 + {get_user_settings, 2},
301 + % called from web admin
302 + % it should set User settings at VHost
303 + % set_user_settings(User, VHost, #user_settings) -> ok | error
305 + % User = list() -> "admin"
306 + % VHost = list() -> "jabber.example.org"
307 + {set_user_settings, 3},
309 + % called from remove_user (ejabberd hook)
310 + % it should remove user messages and settings at VHost
311 + % drop_user(User, VHost) -> ok | error
313 + % User = list() -> "admin"
314 + % VHost = list() -> "jabber.example.org"
317 +behaviour_info(_) ->
319 diff --git a/src/mod_logdb.erl b/src/mod_logdb.erl
321 index 0000000..72f1982
323 +++ b/src/mod_logdb.erl
325 +%%%----------------------------------------------------------------------
326 +%%% File : mod_logdb.erl
327 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
328 +%%% Purpose : Frontend for log user messages to db
330 +%%% Id : $Id: mod_logdb.erl 1360 2009-07-30 06:00:14Z malik $
331 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
332 +%%%----------------------------------------------------------------------
335 +-author('o.palij@gmail.com').
337 +-behaviour(gen_server).
338 +-behaviour(gen_mod).
341 +-export([start_link/2]).
343 +-export([start/2,stop/1]).
345 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
347 +-export([send_packet/3, receive_packet/4, remove_user/2]).
348 +-export([get_local_identity/5,
349 + get_local_features/5,
351 + adhoc_local_items/4,
352 + adhoc_local_commands/4
353 +% get_sm_identity/5,
354 +% get_sm_features/5,
357 +% adhoc_sm_commands/4]).
360 +-export([rebuild_stats/3,
361 + copy_messages/1, copy_messages_ctl/3, copy_messages_int_tc/1]).
363 +-export([get_vhost_stats/1, get_vhost_stats_at/2,
364 + get_user_stats/2, get_user_messages_at/3,
367 + convert_timestamp/1, convert_timestamp_brief/1,
368 + get_user_settings/2, set_user_settings/3,
369 + user_messages_at_parse_query/4, user_messages_parse_query/3,
370 + vhost_messages_parse_query/2, vhost_messages_at_parse_query/4,
371 + list_to_bool/1, bool_to_list/1,
372 + list_to_string/1, string_to_list/1,
373 + get_module_settings/1, set_module_settings/2,
374 + purge_old_records/2]).
376 +-export([webadmin_menu/3,
379 + user_parse_query/5]).
381 +-export([vhost_messages_stats/3,
382 + vhost_messages_stats_at/4,
383 + user_messages_stats/4,
384 + user_messages_stats_at/5]).
386 +-include("mod_logdb.hrl").
387 +-include("ejabberd.hrl").
388 +-include("mod_roster.hrl").
389 +-include("jlib.hrl").
390 +-include("ejabberd_ctl.hrl").
391 +-include("adhoc.hrl").
392 +-include("ejabberd_web_admin.hrl").
393 +-include("ejabberd_http.hrl").
394 +-include("logger.hrl").
396 +-define(PROCNAME, ejabberd_mod_logdb).
397 +% gen_server call timeout
398 +-define(CALL_TIMEOUT, 10000).
400 +-record(state, {vhost, dbmod, backendPid, monref, purgeRef, pollRef, dbopts, dbs, dolog_default, ignore_jids, groupchat, purge_older_days, poll_users_settings, drop_messages_on_user_removal}).
402 +ets_settings_table(VHost) -> list_to_atom("ets_logdb_settings_" ++ binary_to_list(VHost)).
404 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
406 +% gen_mod/gen_server callbacks
408 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
409 +% ejabberd starts module
410 +start(VHost, Opts) ->
412 + {gen_mod:get_module_proc(VHost, ?PROCNAME),
413 + {?MODULE, start_link, [VHost, Opts]},
418 + % add child to ejabberd_sup
419 + supervisor:start_child(ejabberd_sup, ChildSpec).
421 +% supervisor starts gen_server
422 +start_link(VHost, Opts) ->
423 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
424 + {ok, Pid} = gen_server:start_link({local, Proc}, ?MODULE, [VHost, Opts], []),
428 +init([VHost, Opts]) ->
429 + process_flag(trap_exit, true),
430 + DBsRaw = gen_mod:get_opt(dbs, Opts, fun(A) -> A end, [{mnesia, []}]),
431 + DBs = case lists:keysearch(mnesia, 1, DBsRaw) of
432 + false -> lists:append(DBsRaw, [{mnesia,[]}]);
433 + {value, _} -> DBsRaw
435 + VHostDB = gen_mod:get_opt(vhosts, Opts, fun(A) -> A end, [{VHost, mnesia}]),
436 + % 10 is default becouse of using in clustered environment
437 + PollUsersSettings = gen_mod:get_opt(poll_users_settings, Opts, fun(A) -> A end, 10),
440 + case lists:keysearch(VHost, 1, VHostDB) of
442 + ?WARNING_MSG("There is no logging backend defined for '~s', switching to mnesia", [VHost]),
444 + {value,{_, DBNameResult}} ->
445 + case lists:keysearch(DBNameResult, 1, DBs) of
447 + ?WARNING_MSG("There is no such logging backend '~s' defined for '~s', switching to mnesia", [DBNameResult, VHost]),
449 + {value, {_, DBOptsResult}} ->
450 + {DBNameResult, DBOptsResult}
454 + ?MYDEBUG("Starting mod_logdb for '~s' with '~s' backend", [VHost, DBName]),
456 + DBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(DBName)),
458 + {ok, #state{vhost=VHost,
461 + % dbs used for convert messages from one backend to other
463 + dolog_default=gen_mod:get_opt(dolog_default, Opts, fun(A) -> A end, true),
464 + drop_messages_on_user_removal=gen_mod:get_opt(drop_messages_on_user_removal, Opts, fun(A) -> A end, true),
465 + ignore_jids=gen_mod:get_opt(ignore_jids, Opts, fun(A) -> A end, []),
466 + groupchat=gen_mod:get_opt(groupchat, Opts, fun(A) -> A end, none),
467 + purge_older_days=gen_mod:get_opt(purge_older_days, Opts, fun(A) -> A end, never),
468 + poll_users_settings=PollUsersSettings}}.
470 +cleanup(#state{vhost=VHost} = _State) ->
471 + ?MYDEBUG("Stopping ~s for ~p", [?MODULE, VHost]),
473 + %ets:delete(ets_settings_table(VHost)),
475 + ejabberd_hooks:delete(remove_user, VHost, ?MODULE, remove_user, 90),
476 + ejabberd_hooks:delete(user_send_packet, VHost, ?MODULE, send_packet, 90),
477 + ejabberd_hooks:delete(user_receive_packet, VHost, ?MODULE, receive_packet, 90),
478 + %ejabberd_hooks:delete(adhoc_sm_commands, VHost, ?MODULE, adhoc_sm_commands, 110),
479 + %ejabberd_hooks:delete(adhoc_sm_items, VHost, ?MODULE, adhoc_sm_items, 110),
480 + ejabberd_hooks:delete(adhoc_local_commands, VHost, ?MODULE, adhoc_local_commands, 50),
481 + ejabberd_hooks:delete(adhoc_local_items, VHost, ?MODULE, adhoc_local_items, 50),
482 + %ejabberd_hooks:delete(disco_sm_identity, VHost, ?MODULE, get_sm_identity, 50),
483 + %ejabberd_hooks:delete(disco_sm_features, VHost, ?MODULE, get_sm_features, 50),
484 + %ejabberd_hooks:delete(disco_sm_items, VHost, ?MODULE, get_sm_items, 50),
485 + ejabberd_hooks:delete(disco_local_identity, VHost, ?MODULE, get_local_identity, 50),
486 + ejabberd_hooks:delete(disco_local_features, VHost, ?MODULE, get_local_features, 50),
487 + ejabberd_hooks:delete(disco_local_items, VHost, ?MODULE, get_local_items, 50),
489 + ejabberd_hooks:delete(webadmin_menu_host, VHost, ?MODULE, webadmin_menu, 70),
490 + ejabberd_hooks:delete(webadmin_user, VHost, ?MODULE, webadmin_user, 50),
491 + ejabberd_hooks:delete(webadmin_page_host, VHost, ?MODULE, webadmin_page, 50),
492 + ejabberd_hooks:delete(webadmin_user_parse_query, VHost, ?MODULE, user_parse_query, 50),
494 + ?MYDEBUG("Removed hooks for ~p", [VHost]),
496 + %ejabberd_ctl:unregister_commands(VHost, [{"rebuild_stats", "rebuild mod_logdb module stats for vhost"}], ?MODULE, rebuild_stats),
497 + %Supported_backends = lists:flatmap(fun({Backend, _Opts}) ->
498 + % [atom_to_list(Backend), " "]
499 + % end, State#state.dbs),
500 + %ejabberd_ctl:unregister_commands(
502 + % [{"copy_messages backend", "copy messages from backend to current backend. backends could be: " ++ Supported_backends }],
503 + % ?MODULE, copy_messages_ctl),
504 + ?MYDEBUG("Unregistered commands for ~p", [VHost]).
507 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
508 + %gen_server:call(Proc, {cleanup}),
509 + %?MYDEBUG("Cleanup in stop finished!!!!", []),
510 + %timer:sleep(10000),
511 + ok = supervisor:terminate_child(ejabberd_sup, Proc),
512 + ok = supervisor:delete_child(ejabberd_sup, Proc).
514 +handle_call({cleanup}, _From, State) ->
516 + ?MYDEBUG("Cleanup finished!!!!!", []),
517 + {reply, ok, State};
518 +handle_call({get_dates}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
519 + Reply = DBMod:get_dates(VHost),
520 + {reply, Reply, State};
521 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
522 +% ejabberd_web_admin callbacks
523 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
524 +handle_call({delete_messages_by_user_at, PMsgs, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
525 + Reply = DBMod:delete_messages_by_user_at(VHost, PMsgs, binary_to_list(Date)),
526 + {reply, Reply, State};
527 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
528 + Reply = DBMod:delete_all_messages_by_user_at(binary_to_list(User), VHost, binary_to_list(Date)),
529 + {reply, Reply, State};
530 +handle_call({delete_messages_at, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
531 + Reply = DBMod:delete_messages_at(VHost, Date),
532 + {reply, Reply, State};
533 +handle_call({get_vhost_stats}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
534 + Reply = DBMod:get_vhost_stats(VHost),
535 + {reply, Reply, State};
536 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
537 + Reply = DBMod:get_vhost_stats_at(VHost, binary_to_list(Date)),
538 + {reply, Reply, State};
539 +handle_call({get_user_stats, User}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
540 + Reply = DBMod:get_user_stats(binary_to_list(User), VHost),
541 + {reply, Reply, State};
542 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
543 + Reply = DBMod:get_user_messages_at(binary_to_list(User), VHost, binary_to_list(Date)),
544 + {reply, Reply, State};
545 +handle_call({get_user_settings, User}, _From, #state{dbmod=_DBMod, vhost=VHost}=State) ->
546 + Reply = case ets:match_object(ets_settings_table(VHost),
547 + #user_settings{owner_name=User, _='_'}) of
549 + _ -> #user_settings{owner_name=User,
550 + dolog_default=State#state.dolog_default,
554 + {reply, Reply, State};
555 +% TODO: remove User ??
556 +handle_call({set_user_settings, User, GSet}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
557 + Set = GSet#user_settings{owner_name=User},
559 + case ets:match_object(ets_settings_table(VHost),
560 + #user_settings{owner_name=User, _='_'}) of
564 + case DBMod:set_user_settings(binary_to_list(User), VHost, Set) of
568 + true = ets:insert(ets_settings_table(VHost), Set),
572 + {reply, Reply, State};
573 +handle_call({get_module_settings}, _From, State) ->
574 + {reply, State, State};
575 +handle_call({set_module_settings, #state{purge_older_days=PurgeDays,
576 + poll_users_settings=PollSec} = Settings},
578 + #state{purgeRef=PurgeRefOld,
579 + pollRef=PollRefOld,
580 + purge_older_days=PurgeDaysOld,
581 + poll_users_settings=PollSecOld} = State) ->
583 + PurgeDays == never, PurgeDaysOld /= never ->
584 + {ok, cancel} = timer:cancel(PurgeRefOld),
586 + is_integer(PurgeDays), PurgeDaysOld == never ->
587 + set_purge_timer(PurgeDays);
593 + PollSec == PollSecOld ->
595 + PollSec == 0, PollSecOld /= 0 ->
596 + {ok, cancel} = timer:cancel(PollRefOld),
598 + is_integer(PollSec), PollSecOld == 0 ->
599 + set_poll_timer(PollSec);
600 + is_integer(PollSec), PollSecOld /= 0 ->
601 + {ok, cancel} = timer:cancel(PollRefOld),
602 + set_poll_timer(PollSec)
605 + NewState = State#state{dolog_default=Settings#state.dolog_default,
606 + ignore_jids=Settings#state.ignore_jids,
607 + groupchat=Settings#state.groupchat,
608 + drop_messages_on_user_removal=Settings#state.drop_messages_on_user_removal,
609 + purge_older_days=PurgeDays,
610 + poll_users_settings=PollSec,
613 + {reply, ok, NewState};
614 +handle_call(Msg, _From, State) ->
615 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
617 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
618 +% end ejabberd_web_admin callbacks
619 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
621 +% ejabberd_hooks call
622 +handle_cast({addlog, Direction, Owner, Peer, Packet}, #state{dbmod=DBMod, vhost=VHost}=State) ->
623 + case filter(Owner, Peer, State) of
625 + case catch packet_parse(Owner, Peer, Packet, Direction, State) of
628 + {'EXIT', Reason} ->
629 + ?ERROR_MSG("Failed to parse: ~p", [Reason]);
631 + DBMod:log_message(VHost, Msg)
637 +handle_cast({remove_user, User}, #state{dbmod=DBMod, vhost=VHost}=State) ->
638 + case State#state.drop_messages_on_user_removal of
640 + DBMod:drop_user(binary_to_list(User), VHost),
641 + ?INFO_MSG("Launched ~s@~s removal", [User, VHost]);
643 + ?INFO_MSG("Message removing is disabled. Keeping messages for ~s@~s", [User, VHost])
646 +% ejabberdctl rebuild_stats/3
647 +handle_cast({rebuild_stats}, #state{dbmod=DBMod, vhost=VHost}=State) ->
648 + DBMod:rebuild_stats(VHost),
650 +handle_cast({copy_messages, Backend}, State) ->
651 + spawn(?MODULE, copy_messages, [[State, Backend]]),
653 +handle_cast({copy_messages, Backend, Date}, State) ->
654 + spawn(?MODULE, copy_messages, [[State, Backend, Date]]),
656 +handle_cast(Msg, State) ->
657 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
660 +% return: disabled | timer reference
661 +set_purge_timer(PurgeDays) ->
664 + Days when is_integer(Days) ->
665 + {ok, Ref1} = timer:send_interval(timer:hours(24), scheduled_purging),
669 +% return: disabled | timer reference
670 +set_poll_timer(PollSec) ->
673 + {ok, Ref2} = timer:send_interval(timer:seconds(PollSec), poll_users_settings),
675 + % db polling disabled
679 + {ok, Ref3} = timer:send_interval(timer:seconds(10), poll_users_settings),
683 +% actual starting of logging
684 +% from timer:send_after (in init)
685 +handle_info(start, #state{dbmod=DBMod, vhost=VHost}=State) ->
686 + case DBMod:start(VHost, State#state.dbopts) of
687 + {error,{already_started,_}} ->
688 + ?MYDEBUG("backend module already started - trying to stop it", []),
690 + {stop, already_started, State};
692 + timer:sleep(30000),
693 + ?ERROR_MSG("Failed to start: ~p", [Reason]),
694 + {stop, db_connection_failed, State};
696 + ?INFO_MSG("~p connection established", [DBMod]),
698 + MonRef = erlang:monitor(process, SPid),
700 + ets:new(ets_settings_table(VHost), [named_table,public,set,{keypos, #user_settings.owner_name}]),
701 + DoLog = case DBMod:get_users_settings(VHost) of
702 + {ok, Settings} -> [Sett#user_settings{owner_name = iolist_to_binary(Sett#user_settings.owner_name)} || Sett <- Settings];
703 + {error, _Reason} -> []
705 + ets:insert(ets_settings_table(VHost), DoLog),
707 + TrefPurge = set_purge_timer(State#state.purge_older_days),
708 + TrefPoll = set_poll_timer(State#state.poll_users_settings),
710 + ejabberd_hooks:add(remove_user, VHost, ?MODULE, remove_user, 90),
711 + ejabberd_hooks:add(user_send_packet, VHost, ?MODULE, send_packet, 90),
712 + ejabberd_hooks:add(user_receive_packet, VHost, ?MODULE, receive_packet, 90),
714 + ejabberd_hooks:add(disco_local_items, VHost, ?MODULE, get_local_items, 50),
715 + ejabberd_hooks:add(disco_local_features, VHost, ?MODULE, get_local_features, 50),
716 + ejabberd_hooks:add(disco_local_identity, VHost, ?MODULE, get_local_identity, 50),
717 + %ejabberd_hooks:add(disco_sm_items, VHost, ?MODULE, get_sm_items, 50),
718 + %ejabberd_hooks:add(disco_sm_features, VHost, ?MODULE, get_sm_features, 50),
719 + %ejabberd_hooks:add(disco_sm_identity, VHost, ?MODULE, get_sm_identity, 50),
720 + ejabberd_hooks:add(adhoc_local_items, VHost, ?MODULE, adhoc_local_items, 50),
721 + ejabberd_hooks:add(adhoc_local_commands, VHost, ?MODULE, adhoc_local_commands, 50),
722 + %ejabberd_hooks:add(adhoc_sm_items, VHost, ?MODULE, adhoc_sm_items, 50),
723 + %ejabberd_hooks:add(adhoc_sm_commands, VHost, ?MODULE, adhoc_sm_commands, 50),
725 + ejabberd_hooks:add(webadmin_menu_host, VHost, ?MODULE, webadmin_menu, 70),
726 + ejabberd_hooks:add(webadmin_user, VHost, ?MODULE, webadmin_user, 50),
727 + ejabberd_hooks:add(webadmin_page_host, VHost, ?MODULE, webadmin_page, 50),
728 + ejabberd_hooks:add(webadmin_user_parse_query, VHost, ?MODULE, user_parse_query, 50),
730 + ?MYDEBUG("Added hooks for ~p", [VHost]),
732 + %ejabberd_ctl:register_commands(
734 + % [{"rebuild_stats", "rebuild mod_logdb module stats for vhost"}],
735 + % ?MODULE, rebuild_stats),
736 + %Supported_backends = lists:flatmap(fun({Backend, _Opts}) ->
737 + % [atom_to_list(Backend), " "]
738 + % end, State#state.dbs),
739 + %ejabberd_ctl:register_commands(
741 + % [{"copy_messages backend", "copy messages from backend to current backend. backends could be: " ++ Supported_backends }],
742 + % ?MODULE, copy_messages_ctl),
743 + ?MYDEBUG("Registered commands for ~p", [VHost]),
745 + NewState=State#state{monref = MonRef, backendPid=SPid, purgeRef=TrefPurge, pollRef=TrefPoll},
746 + {noreply, NewState};
748 + ?ERROR_MSG("Rez=~p", [Rez]),
749 + timer:sleep(30000),
750 + {stop, db_connection_failed, State}
752 +% from timer:send_interval/2 (in start handle_info)
753 +handle_info(scheduled_purging, #state{vhost=VHost, purge_older_days=Days} = State) ->
754 + ?MYDEBUG("Starting scheduled purging of old records for ~p", [VHost]),
755 + spawn(?MODULE, purge_old_records, [VHost, integer_to_list(Days)]),
757 +% from timer:send_interval/2 (in start handle_info)
758 +handle_info(poll_users_settings, #state{dbmod=DBMod, vhost=VHost}=State) ->
759 + {ok, DoLog} = DBMod:get_users_settings(VHost),
760 + ?MYDEBUG("DoLog=~p", [DoLog]),
761 + true = ets:delete_all_objects(ets_settings_table(VHost)),
762 + ets:insert(ets_settings_table(VHost), DoLog),
764 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
765 + {stop, db_connection_dropped, State};
766 +handle_info({fetch_result, _, _}, State) ->
767 + ?MYDEBUG("Got timed out mysql fetch result", []),
769 +handle_info(Info, State) ->
770 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
773 +terminate(db_connection_failed, _State) ->
775 +terminate(db_connection_dropped, State) ->
776 + ?MYDEBUG("Got terminate with db_connection_dropped", []),
779 +terminate(Reason, #state{monref=undefined} = State) ->
780 + ?MYDEBUG("Got terminate with undefined monref.~nReason: ~p", [Reason]),
783 +terminate(Reason, #state{dbmod=DBMod, vhost=VHost, monref=MonRef, backendPid=Pid} = State) ->
784 + ?INFO_MSG("Reason: ~p", [Reason]),
785 + case erlang:is_process_alive(Pid) of
787 + erlang:demonitor(MonRef, [flush]),
795 +code_change(_OldVsn, State, _Extra) ->
798 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
800 +% ejabberd_hooks callbacks
802 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
803 +% TODO: change to/from to list as sql stores it as list
804 +send_packet(Owner, Peer, P) ->
805 + VHost = Owner#jid.lserver,
806 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
807 + gen_server:cast(Proc, {addlog, to, Owner, Peer, P}).
809 +receive_packet(_JID, Peer, Owner, P) ->
810 + VHost = Owner#jid.lserver,
811 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
812 + gen_server:cast(Proc, {addlog, from, Owner, Peer, P}).
814 +remove_user(User, Server) ->
815 + LUser = jlib:nodeprep(User),
816 + LServer = jlib:nameprep(Server),
817 + Proc = gen_mod:get_module_proc(LServer, ?PROCNAME),
818 + gen_server:cast(Proc, {remove_user, LUser}).
820 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
824 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
825 +rebuild_stats(_Val, VHost, ["rebuild_stats"]) ->
826 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
827 + gen_server:cast(Proc, {rebuild_stats}),
828 + {stop, ?STATUS_SUCCESS};
829 +rebuild_stats(Val, _VHost, _Args) ->
832 +copy_messages_ctl(_Val, VHost, ["copy_messages", Backend]) ->
833 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
834 + gen_server:cast(Proc, {copy_messages, Backend}),
835 + {stop, ?STATUS_SUCCESS};
836 +copy_messages_ctl(_Val, VHost, ["copy_messages", Backend, Date]) ->
837 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
838 + gen_server:cast(Proc, {copy_messages, Backend, Date}),
839 + {stop, ?STATUS_SUCCESS};
840 +copy_messages_ctl(Val, _VHost, _Args) ->
842 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
846 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
848 +% handle_cast({addlog, E}, _)
849 +% raw packet -> #msg
850 +packet_parse(Owner, Peer, Packet, Direction, State) ->
851 + case xml:get_subtag(Packet, <<"body">>) of
856 + case xml:get_tag_attr_s(<<"type">>, Packet) of
857 + <<"error">> -> throw(ignore);
858 + [] -> <<"normal">>;
862 + case Message_type of
863 + <<"groupchat">> when State#state.groupchat == send, Direction == to ->
865 + <<"groupchat">> when State#state.groupchat == send, Direction == from ->
867 + <<"groupchat">> when State#state.groupchat == half ->
868 + Rooms = ets:match(muc_online_room, '$1'),
869 + Ni=lists:foldl(fun([{muc_online_room, {GName, GHost}, Pid}], Names) ->
870 + case gen_fsm:sync_send_all_state_event(Pid, {get_jid_nick,Owner}) of
873 + lists:append(Names, [jlib:jid_to_string({GName, GHost, Nick})])
876 + case lists:member(jlib:jid_to_string(Peer), Ni) of
877 + true when Direction == from ->
882 + <<"groupchat">> when State#state.groupchat == none ->
888 + Message_body = xml:get_tag_cdata(Body_xml),
890 + case xml:get_subtag(Packet, <<"subject">>) of
894 + xml:get_tag_cdata(Subject_xml)
897 + OwnerName = stringprep:tolower(Owner#jid.user),
898 + PName = stringprep:tolower(Peer#jid.user),
899 + PServer = stringprep:tolower(Peer#jid.server),
900 + PResource = Peer#jid.resource,
902 + #msg{timestamp = get_timestamp(),
903 + owner_name = OwnerName,
905 + peer_server = PServer,
906 + peer_resource = PResource,
907 + direction = Direction,
908 + type = Message_type,
909 + subject = Message_subject,
910 + body = Message_body}
913 +% called from handle_cast({addlog, _}, _) -> true (log messages) | false (do not log messages)
914 +filter(Owner, Peer, State) ->
915 + OwnerBin = << (Owner#jid.luser)/binary, "@", (Owner#jid.lserver)/binary >>,
916 + OwnerServ = << "@", (Owner#jid.lserver)/binary >>,
917 + PeerBin = << (Peer#jid.luser)/binary, "@", (Peer#jid.lserver)/binary >>,
918 + PeerServ = << "@", (Peer#jid.lserver)/binary >>,
920 + LogTo = case ets:match_object(ets_settings_table(State#state.vhost),
921 + #user_settings{owner_name=Owner#jid.luser, _='_'}) of
922 + [#user_settings{dolog_default=Default,
924 + donotlog_list=DNLL}] ->
926 + A = lists:member(PeerBin, DLL),
927 + B = lists:member(PeerBin, DNLL),
931 + Default == true -> true;
932 + Default == false -> false;
933 + true -> State#state.dolog_default
935 + _ -> State#state.dolog_default
937 + lists:all(fun(O) -> O end,
938 + [not lists:member(OwnerBin, State#state.ignore_jids),
939 + not lists:member(PeerBin, State#state.ignore_jids),
940 + not lists:member(OwnerServ, State#state.ignore_jids),
941 + not lists:member(PeerServ, State#state.ignore_jids),
944 +purge_old_records(VHost, Days) ->
945 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
947 + Dates = ?MODULE:get_dates(VHost),
948 + DateNow = calendar:datetime_to_gregorian_seconds({date(), {0,0,1}}),
949 + DateDiff = list_to_integer(Days)*24*60*60,
950 + ?MYDEBUG("Purging tables older than ~s days", [Days]),
951 + lists:foreach(fun(Date) ->
952 + [Year, Month, Day] = ejabberd_regexp:split(iolist_to_binary(Date), <<"[^0-9]+">>),
953 + DateInSec = calendar:datetime_to_gregorian_seconds({{binary_to_integer(Year), binary_to_integer(Month), binary_to_integer(Day)}, {0,0,1}}),
955 + (DateNow - DateInSec) > DateDiff ->
956 + gen_server:call(Proc, {delete_messages_at, Date});
958 + ?MYDEBUG("Skipping messages at ~p", [Date])
962 +% called from get_vhost_stats/2, get_user_stats/3
963 +sort_stats(Stats) ->
964 + % Stats = [{"2003-4-15",1}, {"2006-8-18",1}, ... ]
965 + CFun = fun({TableName, Count}) ->
966 + [Year, Month, Day] = ejabberd_regexp:split(iolist_to_binary(TableName), <<"[^0-9]+">>),
967 + { calendar:datetime_to_gregorian_seconds({{binary_to_integer(Year), binary_to_integer(Month), binary_to_integer(Day)}, {0,0,1}}), Count }
969 + % convert to [{63364377601,1}, {63360662401,1}, ... ]
970 + CStats = lists:map(CFun, Stats),
972 + SortedStats = lists:reverse(lists:keysort(1, CStats)),
973 + % convert to [{"2007-12-9",1}, {"2007-10-27",1}, ... ] sorted list
974 + [{mod_logdb:convert_timestamp_brief(TableSec), Count} || {TableSec, Count} <- SortedStats].
976 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
978 +% Date/Time operations
980 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
981 +% return float seconds elapsed from "zero hour" as list
983 + {MegaSec, Sec, MicroSec} = now(),
984 + [List] = io_lib:format("~.5f", [MegaSec*1000000 + Sec + MicroSec/1000000]),
987 +% convert float seconds elapsed from "zero hour" to local time "%Y-%m-%d %H:%M:%S" string
988 +convert_timestamp(Seconds) when is_list(Seconds) ->
989 + case string:to_float(Seconds++".0") of
990 + {F,_} when is_float(F) -> convert_timestamp(F);
991 + _ -> erlang:error(badarg, [Seconds])
993 +convert_timestamp(Seconds) when is_float(Seconds) ->
994 + GregSec = trunc(Seconds + 719528*86400),
995 + UnivDT = calendar:gregorian_seconds_to_datetime(GregSec),
996 + {{Year, Month, Day},{Hour, Minute, Sec}} = calendar:universal_time_to_local_time(UnivDT),
997 + integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day) ++ " " ++ integer_to_list(Hour) ++ ":" ++ integer_to_list(Minute) ++ ":" ++ integer_to_list(Sec).
999 +% convert float seconds elapsed from "zero hour" to local time "%Y-%m-%d" string
1000 +convert_timestamp_brief(Seconds) when is_list(Seconds) ->
1001 + convert_timestamp_brief(list_to_float(Seconds));
1002 +convert_timestamp_brief(Seconds) when is_float(Seconds) ->
1003 + GregSec = trunc(Seconds + 719528*86400),
1004 + UnivDT = calendar:gregorian_seconds_to_datetime(GregSec),
1005 + {{Year, Month, Day},{_Hour, _Minute, _Sec}} = calendar:universal_time_to_local_time(UnivDT),
1006 + integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day);
1007 +convert_timestamp_brief(Seconds) when is_integer(Seconds) ->
1008 + {{Year, Month, Day},{_Hour, _Minute, _Sec}} = calendar:gregorian_seconds_to_datetime(Seconds),
1009 + integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day).
1011 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1013 +% DB operations (get)
1015 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1016 +get_vhost_stats(VHost) ->
1017 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1018 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
1020 +get_vhost_stats_at(VHost, Date) ->
1021 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1022 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
1024 +get_user_stats(User, VHost) ->
1025 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1026 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
1028 +get_user_messages_at(User, VHost, Date) ->
1029 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1030 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
1032 +get_dates(VHost) ->
1033 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1034 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
1036 +get_user_settings(User, VHost) ->
1037 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1038 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
1040 +set_user_settings(User, VHost, Set) ->
1041 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1042 + gen_server:call(Proc, {set_user_settings, User, Set}).
1044 +get_module_settings(VHost) ->
1045 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1046 + gen_server:call(Proc, {get_module_settings}).
1048 +set_module_settings(VHost, Settings) ->
1049 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1050 + gen_server:call(Proc, {set_module_settings, Settings}).
1052 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1054 +% Web admin callbacks (delete)
1056 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1057 +user_messages_at_parse_query(VHost, Date, Msgs, Query) ->
1058 + case lists:keysearch(<<"delete">>, 1, Query) of
1060 + PMsgs = lists:filter(
1062 + ID = jlib:encode_base64(term_to_binary(Msg#msg.timestamp)),
1063 + lists:member({<<"selected">>, ID}, Query)
1065 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1066 + gen_server:call(Proc, {delete_messages_by_user_at, PMsgs, Date}, ?CALL_TIMEOUT);
1071 +user_messages_parse_query(User, VHost, Query) ->
1072 + case lists:keysearch(<<"delete">>, 1, Query) of
1074 + Dates = get_dates(VHost),
1075 + PDates = lists:filter(
1077 + ID = jlib:encode_base64( << User/binary, (iolist_to_binary(Date))/binary >> ),
1078 + lists:member({<<"selected">>, ID}, Query)
1080 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1081 + Rez = lists:foldl(
1084 + [gen_server:call(Proc,
1085 + {delete_all_messages_by_user_at, User, iolist_to_binary(Date)},
1088 + case lists:member(error, Rez) of
1098 +vhost_messages_parse_query(VHost, Query) ->
1099 + case lists:keysearch(<<"delete">>, 1, Query) of
1101 + Dates = get_dates(VHost),
1102 + PDates = lists:filter(
1104 + ID = jlib:encode_base64( << VHost/binary, (iolist_to_binary(Date))/binary >> ),
1105 + lists:member({<<"selected">>, ID}, Query)
1107 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1108 + Rez = lists:foldl(fun(Date, Acc) ->
1109 + lists:append(Acc, [gen_server:call(Proc,
1110 + {delete_messages_at, Date},
1113 + case lists:member(error, Rez) of
1123 +vhost_messages_at_parse_query(VHost, Date, Stats, Query) ->
1124 + case lists:keysearch(<<"delete">>, 1, Query) of
1126 + PStats = lists:filter(
1127 + fun({User, _Count}) ->
1128 + ID = jlib:encode_base64( << (iolist_to_binary(User))/binary, VHost/binary >> ),
1129 + lists:member({<<"selected">>, ID}, Query)
1131 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1132 + Rez = lists:foldl(fun({User, _Count}, Acc) ->
1133 + lists:append(Acc, [gen_server:call(Proc,
1134 + {delete_all_messages_by_user_at,
1135 + iolist_to_binary(User), iolist_to_binary(Date)},
1138 + case lists:member(error, Rez) of
1148 +copy_messages([#state{vhost=VHost}=State, From]) ->
1149 + ?INFO_MSG("Going to copy messages from ~p for ~p", [From, VHost]),
1151 + {FromDBName, FromDBOpts} =
1152 + case lists:keysearch(list_to_atom(From), 1, State#state.dbs) of
1153 + {value, {FN, FO}} ->
1156 + ?ERROR_MSG("Failed to find record for ~p in dbs", [From]),
1160 + FromDBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(FromDBName)),
1162 + {ok, _FromPid} = FromDBMod:start(VHost, FromDBOpts),
1164 + Dates = FromDBMod:get_dates(VHost),
1165 + DatesLength = length(Dates),
1167 + lists:foldl(fun(Date, Acc) ->
1168 + case copy_messages_int([FromDBMod, State#state.dbmod, VHost, Date]) of
1170 + ?INFO_MSG("Copied messages at ~p (~p/~p)", [Date, Acc, DatesLength]);
1172 + ?ERROR_MSG("Failed to copy messages at ~p (~p/~p): ~p", [Date, Acc, DatesLength, Value]),
1173 + FromDBMod:stop(VHost),
1178 + ?INFO_MSG("Copied messages from ~p", [From]),
1179 + FromDBMod:stop(VHost);
1180 +copy_messages([#state{vhost=VHost}=State, From, Date]) ->
1181 + {value, {FromDBName, FromDBOpts}} = lists:keysearch(list_to_atom(From), 1, State#state.dbs),
1182 + FromDBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(FromDBName)),
1183 + {ok, _FromPid} = FromDBMod:start(VHost, FromDBOpts),
1184 + case catch copy_messages_int([FromDBMod, State#state.dbmod, VHost, Date]) of
1185 + {'exit', Reason} ->
1186 + ?ERROR_MSG("Failed to copy messages at ~p: ~p", [Date, Reason]);
1188 + ?INFO_MSG("Copied messages at ~p", [Date]);
1190 + ?ERROR_MSG("Failed to copy messages at ~p: ~p", [Date, Value])
1192 + FromDBMod:stop(VHost).
1194 +copy_messages_int([FromDBMod, ToDBMod, VHost, Date]) ->
1195 + ets:new(mod_logdb_temp, [named_table, set, public]),
1196 + {Time, Value} = timer:tc(?MODULE, copy_messages_int_tc, [[FromDBMod, ToDBMod, VHost, Date]]),
1197 + ets:delete_all_objects(mod_logdb_temp),
1198 + ets:delete(mod_logdb_temp),
1199 + ?INFO_MSG("copy_messages at ~p elapsed ~p sec", [Date, Time/1000000]),
1202 +copy_messages_int_tc([FromDBMod, ToDBMod, VHost, Date]) ->
1203 + ?INFO_MSG("Going to copy messages from ~p for ~p at ~p", [FromDBMod, VHost, Date]),
1205 + ok = FromDBMod:rebuild_stats_at(VHost, binary_to_list(Date)),
1206 + catch mod_logdb:rebuild_stats_at(VHost, Date),
1207 + {ok, FromStats} = FromDBMod:get_vhost_stats_at(VHost, binary_to_list(Date)),
1208 + ToStats = case mod_logdb:get_vhost_stats_at(VHost, Date) of
1209 + {ok, Stats} -> Stats;
1213 + FromStatsS = lists:keysort(1, FromStats),
1214 + ToStatsS = lists:keysort(1, ToStats),
1216 + StatsLength = length(FromStats),
1219 + % destination table is empty
1220 + FromDBMod /= mod_logdb_mnesia_old, ToStats == [] ->
1221 + fun({User, _Count}, Acc) ->
1222 + {ok, Msgs} = FromDBMod:get_user_messages_at(binary_to_list(User), VHost, binary_to_list(Date)),
1224 + lists:foldl(fun(Msg, MFAcc) ->
1225 + ok = ToDBMod:log_message(VHost, Msg),
1229 + ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
1230 + %timer:sleep(100),
1233 + % destination table is not empty
1234 + FromDBMod /= mod_logdb_mnesia_old, ToStats /= [] ->
1235 + fun({User, _Count}, Acc) ->
1236 + {ok, ToMsgs} = ToDBMod:get_user_messages_at(binary_to_list(User), VHost, binary_to_list(Date)),
1237 + lists:foreach(fun(#msg{timestamp=Tst}) when length(Tst) == 16 ->
1238 + ets:insert(mod_logdb_temp, {Tst});
1239 + % mysql, pgsql removes final zeros after decimal point
1240 + (#msg{timestamp=Tst}) when length(Tst) < 16 ->
1241 + {F, _} = string:to_float(Tst++".0"),
1242 + [T] = io_lib:format("~.5f", [F]),
1243 + ets:insert(mod_logdb_temp, {T})
1245 + {ok, Msgs} = FromDBMod:get_user_messages_at(binary_to_list(User), VHost, binary_to_list(Date)),
1247 + lists:foldl(fun(#msg{timestamp=ToTimestamp} = Msg, MFAcc) ->
1248 + case ets:member(mod_logdb_temp, ToTimestamp) of
1250 + ok = ToDBMod:log_message(VHost, Msg),
1251 + ets:insert(mod_logdb_temp, {ToTimestamp}),
1258 + ets:delete_all_objects(mod_logdb_temp),
1259 + ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
1260 + %timer:sleep(100),
1263 + % copying from mod_logmnesia
1265 + fun({User, _Count}, Acc) ->
1267 + case ToDBMod:get_user_messages_at(binary_to_list(User), VHost, binary_to_list(Date)) of
1271 + lists:foreach(fun(#msg{timestamp=Tst}) when length(Tst) == 16 ->
1272 + ets:insert(mod_logdb_temp, {Tst});
1273 + % mysql, pgsql removes final zeros after decimal point
1274 + (#msg{timestamp=Tst}) when length(Tst) < 15 ->
1275 + {F, _} = string:to_float(Tst++".0"),
1276 + [T] = io_lib:format("~.5f", [F]),
1277 + ets:insert(mod_logdb_temp, {T})
1282 + {ok, Msgs} = FromDBMod:get_user_messages_at(binary_to_list(User), VHost, binary_to_list(Date)),
1286 + fun({msg, TU, TS, TR, FU, FS, FR, Type, Subj, Body, Timest},
1288 + [Timestamp] = if is_float(Timest) == true ->
1289 + io_lib:format("~.5f", [Timest]);
1290 + % early versions of mod_logmnesia
1291 + is_integer(Timest) == true ->
1292 + io_lib:format("~.5f", [Timest-719528*86400.0]);
1294 + ?ERROR_MSG("Incorrect timestamp ~p", [Timest]),
1297 + case ets:member(mod_logdb_temp, Timestamp) of
1302 + TMsg = #msg{timestamp=Timestamp,
1304 + peer_name=FU, peer_server=FS, peer_resource=FR,
1307 + subject=Subj, body=Body},
1308 + ok = ToDBMod:log_message(VHost, TMsg);
1314 + FMsg = #msg{timestamp=Timestamp,
1316 + peer_name=TU, peer_server=TS, peer_resource=TR,
1319 + subject=Subj, body=Body},
1320 + ok = ToDBMod:log_message(VHost, FMsg);
1323 + ets:insert(mod_logdb_temp, {Timestamp}),
1325 + true -> % not ets:member
1328 + end, 0, Msgs), % foldl
1330 + ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
1331 + %timer:sleep(100),
1334 + end, % if FromDBMod /= mod_logdb_mnesia_old
1337 + FromStats == [] ->
1338 + ?INFO_MSG("No messages were found at ~p", [Date]);
1339 + FromStatsS == ToStatsS ->
1340 + ?INFO_MSG("Stats are equal at ~p", [Date]);
1341 + FromStatsS /= ToStatsS ->
1342 + lists:foldl(CopyFun, 0, FromStats),
1343 + ok = ToDBMod:rebuild_stats_at(VHost, binary_to_list(Date))
1344 + %timer:sleep(1000)
1349 +list_to_bool(Num) when is_binary(Num) ->
1350 + list_to_bool(binary_to_list(Num));
1351 +list_to_bool(Num) when is_list(Num) ->
1352 + case lists:member(Num, ["t", "true", "y", "yes", "1"]) of
1356 + case lists:member(Num, ["f", "false", "n", "no", "0"]) of
1364 +bool_to_list(true) ->
1366 +bool_to_list(false) ->
1369 +list_to_string([]) ->
1371 +list_to_string(List) when is_list(List) ->
1372 + Str = lists:flatmap(fun(Elm) when is_binary(Elm) ->
1373 + binary_to_list(Elm) ++ "\n";
1374 + (Elm) when is_list(Elm) ->
1377 + lists:sublist(Str, length(Str)-1).
1379 +string_to_list(null) ->
1381 +string_to_list([]) ->
1383 +string_to_list(String) ->
1384 + ejabberd_regexp:split(iolist_to_binary(String), <<"\n">>).
1386 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1388 +% ad-hoc (copy/pasted from mod_configure.erl)
1390 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1391 +-define(ITEMS_RESULT(Allow, LNode, Fallback),
1396 + case get_local_items(LServer, LNode,
1397 + jlib:jid_to_string(To), Lang) of
1405 +get_local_items(Acc, From, #jid{lserver = LServer} = To, <<"">>, Lang) ->
1406 + case gen_mod:is_loaded(LServer, mod_adhoc) of
1410 + Items = case Acc of
1411 + {result, Its} -> Its;
1414 + AllowUser = acl:match_rule(LServer, mod_logdb, From),
1415 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1417 + AllowUser == allow; AllowAdmin == allow ->
1418 + case get_local_items(LServer, [],
1419 + jlib:jid_to_string(To), Lang) of
1421 + {result, Items ++ Res};
1422 + {error, _Error} ->
1429 +get_local_items(Acc, From, #jid{lserver = LServer} = To, Node, Lang) ->
1430 + case gen_mod:is_loaded(LServer, mod_adhoc) of
1434 + LNode = str:tokens(Node, <<"/">>),
1435 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1437 + [<<"mod_logdb">>] ->
1438 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1439 + [<<"mod_logdb_users">>] ->
1440 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1441 + [<<"mod_logdb_users">>, <<$@, _/binary>>] ->
1442 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1443 + [<<"mod_logdb_users">>, _User] ->
1444 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1445 + [<<"mod_logdb_settings">>] ->
1446 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1452 +-define(T(Lang, Text), translate:translate(Lang, Text)).
1454 +-define(NODE(Name, Node),
1455 + #xmlel{name = <<"item">>,
1457 + [{<<"jid">>, Server}, {<<"name">>, ?T(Lang, Name)},
1458 + {<<"node">>, Node}],
1461 +get_local_items(_Host, [], Server, Lang) ->
1463 + [?NODE(<<"Messages logging engine">>, <<"mod_logdb">>)]
1465 +get_local_items(_Host, [<<"mod_logdb">>], Server, Lang) ->
1467 + [?NODE(<<"Messages logging engine users">>, <<"mod_logdb_users">>),
1468 + ?NODE(<<"Messages logging engine settings">>, <<"mod_logdb_settings">>)]
1470 +get_local_items(Host, [<<"mod_logdb_users">>], Server, Lang) ->
1471 + {result, get_all_vh_users(Host, Server, Lang)};
1472 +get_local_items(Host, [<<"mod_logdb_users">>, <<$@, Diap/binary>>], Server, Lang) ->
1473 + case catch ejabberd_auth:get_vh_registered_users(Host) of
1474 + {'EXIT', _Reason} ->
1475 + ?ERR_INTERNAL_SERVER_ERROR;
1477 + SUsers = lists:sort([{S, U} || {U, S} <- Users]),
1479 + [S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
1480 + N1 = jlib:binary_to_integer(S1),
1481 + N2 = jlib:binary_to_integer(S2),
1482 + Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
1483 + lists:map(fun({S, U}) ->
1484 + ?NODE(<< U/binary, "@", S/binary >>,
1485 + << (iolist_to_binary("mod_logdb_users/"))/binary, U/binary, "@", S/binary >>)
1488 + {'EXIT', _Reason} ->
1489 + ?ERR_NOT_ACCEPTABLE;
1494 +get_local_items(_Host, [<<"mod_logdb_users">>, _User], _Server, _Lang) ->
1496 +get_local_items(_Host, [<<"mod_logdb_settings">>], _Server, _Lang) ->
1498 +get_local_items(_Host, Item, _Server, _Lang) ->
1499 + ?MYDEBUG("asked for items in ~p", [Item]),
1500 + {error, ?ERR_ITEM_NOT_FOUND}.
1502 +-define(INFO_RESULT(Allow, Feats),
1504 + deny -> {error, ?ERR_FORBIDDEN};
1505 + allow -> {result, Feats}
1508 +get_local_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
1509 + case gen_mod:is_loaded(LServer, mod_adhoc) of
1513 + LNode = str:tokens(Node, <<"/">>),
1514 + AllowUser = acl:match_rule(LServer, mod_logdb, From),
1515 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1517 + [<<"mod_logdb">>] when AllowUser == allow; AllowAdmin == allow ->
1518 + ?INFO_RESULT(allow, [?NS_COMMANDS]);
1519 + [<<"mod_logdb">>] ->
1520 + ?INFO_RESULT(deny, [?NS_COMMANDS]);
1521 + [<<"mod_logdb_users">>] ->
1522 + ?INFO_RESULT(AllowAdmin, []);
1523 + [<<"mod_logdb_users">>, [$@ | _]] ->
1524 + ?INFO_RESULT(AllowAdmin, []);
1525 + [<<"mod_logdb_users">>, _User] ->
1526 + ?INFO_RESULT(AllowAdmin, [?NS_COMMANDS]);
1527 + [<<"mod_logdb_settings">>] ->
1528 + ?INFO_RESULT(AllowAdmin, [?NS_COMMANDS]);
1536 +-define(INFO_IDENTITY(Category, Type, Name, Lang),
1537 + [#xmlel{name = <<"identity">>,
1539 + [{<<"category">>, Category}, {<<"type">>, Type},
1540 + {<<"name">>, ?T(Lang, Name)}],
1543 +-define(INFO_COMMAND(Name, Lang),
1544 + ?INFO_IDENTITY(<<"automation">>, <<"command-node">>,
1547 +get_local_identity(Acc, _From, _To, Node, Lang) ->
1548 + LNode = str:tokens(Node, <<"/">>),
1550 + [<<"mod_logdb">>] ->
1551 + ?INFO_COMMAND(<<"Messages logging engine">>, Lang);
1552 + [<<"mod_logdb_users">>] ->
1553 + ?INFO_COMMAND(<<"Messages logging engine users">>, Lang);
1554 + [<<"mod_logdb_users">>, [$@ | _]] ->
1556 + [<<"mod_logdb_users">>, User] ->
1557 + ?INFO_COMMAND(User, Lang);
1558 + [<<"mod_logdb_settings">>] ->
1559 + ?INFO_COMMAND(<<"Messages logging engine settings">>, Lang);
1566 +%get_sm_items(Acc, From, To, Node, Lang) ->
1567 +% ?MYDEBUG("get_sm_items Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
1570 +%get_sm_features(Acc, From, To, Node, Lang) ->
1571 +% ?MYDEBUG("get_sm_features Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
1574 +%get_sm_identity(Acc, From, To, Node, Lang) ->
1575 +% ?MYDEBUG("get_sm_identity Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
1578 +adhoc_local_items(Acc, From, #jid{lserver = LServer, server = Server} = To,
1580 + Items = case Acc of
1581 + {result, Its} -> Its;
1584 + Nodes = recursively_get_local_items(LServer, "", Server, Lang),
1585 + Nodes1 = lists:filter(
1587 + Nd = xml:get_tag_attr_s("node", N),
1588 + F = get_local_features([], From, To, Nd, Lang),
1590 + {result, [?NS_COMMANDS]} ->
1596 + {result, Items ++ Nodes1}.
1598 +recursively_get_local_items(_LServer, <<"mod_logdb_users">>, _Server, _Lang) ->
1600 +recursively_get_local_items(LServer, Node, Server, Lang) ->
1601 + LNode = str:tokens(Node, <<"/">>),
1602 + Items = case get_local_items(LServer, LNode, Server, Lang) of
1605 + {error, _Error} ->
1608 + Nodes = lists:flatten(
1611 + S = xml:get_tag_attr_s("jid", N),
1612 + Nd = xml:get_tag_attr_s("node", N),
1613 + if (S /= Server) or (Nd == "") ->
1616 + [N, recursively_get_local_items(
1617 + LServer, Nd, Server, Lang)]
1622 +-define(COMMANDS_RESULT(Allow, From, To, Request),
1625 + {error, ?ERR_FORBIDDEN};
1627 + adhoc_local_commands(From, To, Request)
1630 +adhoc_local_commands(Acc, From, #jid{lserver = LServer} = To,
1631 + #adhoc_request{node = Node} = Request) ->
1632 + LNode = str:tokens(Node, <<"/">>),
1633 + AllowUser = acl:match_rule(LServer, mod_logdb, From),
1634 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1636 + [<<"mod_logdb">>] when AllowUser == allow; AllowAdmin == allow ->
1637 + ?COMMANDS_RESULT(allow, From, To, Request);
1638 + [<<"mod_logdb_users">>, _User] when AllowAdmin == allow ->
1639 + ?COMMANDS_RESULT(allow, From, To, Request);
1640 + [<<"mod_logdb_settings">>] when AllowAdmin == allow ->
1641 + ?COMMANDS_RESULT(allow, From, To, Request);
1646 +adhoc_local_commands(From, #jid{lserver = LServer} = _To,
1647 + #adhoc_request{lang = Lang,
1649 + sessionid = SessionID,
1651 + xdata = XData} = Request) ->
1652 + LNode = str:tokens(Node, <<"/">>),
1653 + %% If the "action" attribute is not present, it is
1654 + %% understood as "execute". If there was no <actions/>
1655 + %% element in the first response (which there isn't in our
1656 + %% case), "execute" and "complete" are equivalent.
1657 + ActionIsExecute = lists:member(Action,
1658 + [<<"">>, <<"execute">>, <<"complete">>]),
1659 + if Action == <<"cancel">> ->
1660 + %% User cancels request
1661 + adhoc:produce_response(
1663 + #adhoc_response{status = canceled});
1664 + XData == false, ActionIsExecute ->
1665 + %% User requests form
1666 + case get_form(LServer, LNode, From, Lang) of
1668 + adhoc:produce_response(
1670 + #adhoc_response{status = executing,
1671 + elements = Form});
1675 + XData /= false, ActionIsExecute ->
1676 + %% User returns form.
1677 + case jlib:parse_xdata_submit(XData) of
1679 + {error, ?ERR_BAD_REQUEST};
1681 + case catch set_form(From, LServer, LNode, Lang, Fields) of
1683 + adhoc:produce_response(
1684 + #adhoc_response{lang = Lang,
1686 + sessionid = SessionID,
1687 + status = completed});
1688 + {'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
1689 + {error, Error} -> {error, Error}
1693 + {error, ?ERR_BAD_REQUEST}
1696 +-define(LISTLINE(Label, Value),
1697 + #xmlel{name = <<"option">>,
1698 + attrs = [{<<"label">>, ?T(Lang, Label)}],
1699 + children = [#xmlel{name = <<"value">>, attrs = [],
1700 + children = [{xmlcdata, Value}]
1702 +-define(DEFVAL(Value), #xmlel{name = <<"value">>, attrs = [],
1703 + children = [{xmlcdata, Value}]}).
1705 +get_user_form(LUser, LServer, Lang) ->
1706 + %From = jlib:jid_to_string(jlib:jid_remove_resource(Jid)),
1707 + #user_settings{dolog_default=DLD,
1709 + donotlog_list=DNLL} = get_user_settings(LUser, LServer),
1711 + [#xmlel{name = <<"x">>,
1712 + attrs = [{<<"xmlns">>, ?NS_XDATA}],
1714 + #xmlel{name = <<"title">>, attrs = [],
1717 + ?T(Lang, <<"Messages logging engine settings">>)}]},
1718 + #xmlel{name = <<"instructions">>, attrs = [],
1721 + << (?T(Lang, <<"Set logging preferences">>))/binary, (iolist_to_binary(": "))/binary,
1722 + LUser/binary, "@", LServer/binary >> }]},
1723 + #xmlel{name = <<"field">>,
1724 + attrs = [{<<"type">>, <<"list-single">>},
1725 + {<<"label">>, ?T(Lang, <<"Default">>)},
1726 + {<<"var">>, <<"dolog_default">>}],
1728 + [?DEFVAL(jlib:atom_to_binary(DLD)),
1729 + ?LISTLINE(<<"Log Messages">>, <<"true">>),
1730 + ?LISTLINE(<<"Do Not Log Messages">>, <<"false">>)
1732 + #xmlel{name = <<"field">>,
1733 + attrs = [{<<"type">>, <<"text-multi">>},
1734 + {<<"label">>, ?T(Lang, <<"Log Messages">>)},
1735 + {<<"var">>, <<"dolog_list">>}],
1736 + children = [#xmlel{name = <<"value">>, attrs = [],
1737 + children = [{xmlcdata, iolist_to_binary(list_to_string(DLL))}]}
1740 + #xmlel{name = <<"field">>,
1741 + attrs = [{<<"type">>, <<"text-multi">>},
1742 + {<<"label">>, ?T(Lang, <<"Do Not Log Messages">>)},
1743 + {<<"var">>, <<"donotlog_list">>}],
1744 + children = [#xmlel{name = <<"value">>, attrs = [],
1745 + children = [{xmlcdata, iolist_to_binary(list_to_string(DNLL))}]}
1750 +get_settings_form(Host, Lang) ->
1751 + #state{dbmod=_DBMod,
1753 + dolog_default=DLD,
1754 + ignore_jids=IgnoreJids,
1755 + groupchat=GroupChat,
1756 + purge_older_days=PurgeDaysT,
1757 + drop_messages_on_user_removal=MRemoval,
1758 + poll_users_settings=PollTime} = mod_logdb:get_module_settings(Host),
1760 + %Backends = lists:map(fun({Backend, _Opts}) ->
1761 + % ?LISTLINE(jlib:atom_to_binary(Backend), jlib:atom_to_binary(Backend))
1763 + %DB = iolist_to_binary(lists:sublist(atom_to_list(DBMod), length(atom_to_list(?MODULE)) + 2, length(atom_to_list(DBMod)))),
1764 + %DBsL = lists:append([?DEFVAL(DB)], Backends),
1767 + case PurgeDaysT of
1768 + never -> <<"never">>;
1769 + Num when is_integer(Num) -> integer_to_binary(Num);
1770 + _ -> <<"unknown">>
1773 + [#xmlel{name = <<"x">>,
1774 + attrs = [{<<"xmlns">>, ?NS_XDATA}],
1775 + children = [#xmlel{name = <<"title">>, attrs = [],
1778 + <<(?T(Lang, <<"Messages logging engine settings">>))/binary,
1779 + (iolist_to_binary(" (run-time)"))/binary >>}]},
1780 + #xmlel{name = <<"instructions">>, attrs = [],
1782 + [{xmlcdata, ?T(Lang, <<"Set run-time settings">>)}]},
1783 +% #xmlel{name = <<"field">>,
1784 +% attrs = [{<<"type">>, <<"list-single">>},
1785 +% {<<"label">>, ?T(Lang, <<"Backend">>)},
1786 +% {<<"var">>, <<"backend">>}],
1787 +% children = DBsL},
1788 +% #xmlel{name = <<"field">>,
1789 +% attrs = [{<<"type">>, <<"text-multi">>},
1790 +% {<<"label">>, ?T(Lang, <<"dbs">>)},
1791 +% {<<"var">>, <<"dbs">>}],
1792 +% children = [#xmlel{name = <<"value">>, attrs = [],
1793 +% children = [{xmlcdata, iolist_to_binary(lists:flatten(io_lib:format("~p.",[DBs])))}]}
1796 + #xmlel{name = <<"field">>,
1797 + attrs = [{<<"type">>, <<"list-single">>},
1798 + {<<"label">>, ?T(Lang, <<"Default">>)},
1799 + {<<"var">>, <<"dolog_default">>}],
1801 + [?DEFVAL(jlib:atom_to_binary(DLD)),
1802 + ?LISTLINE(<<"Log Messages">>, <<"true">>),
1803 + ?LISTLINE(<<"Do Not Log Messages">>, <<"false">>)
1805 + #xmlel{name = <<"field">>,
1806 + attrs = [{<<"type">>, <<"list-single">>},
1807 + {<<"label">>, ?T(Lang, <<"Drop messages on user removal">>)},
1808 + {<<"var">>, <<"drop_messages_on_user_removal">>}],
1810 + [?DEFVAL(jlib:atom_to_binary(MRemoval)),
1811 + ?LISTLINE(<<"Drop">>, <<"true">>),
1812 + ?LISTLINE(<<"Do not drop">>, <<"false">>)
1814 + #xmlel{name = <<"field">>,
1815 + attrs = [{<<"type">>, <<"list-single">>},
1816 + {<<"label">>, ?T(Lang, <<"Groupchat messages logging">>)},
1817 + {<<"var">>, <<"groupchat">>}],
1819 + [?DEFVAL(jlib:atom_to_binary(GroupChat)),
1820 + ?LISTLINE(<<"all">>, <<"all">>),
1821 + ?LISTLINE(<<"none">>, <<"none">>),
1822 + ?LISTLINE(<<"send">>, <<"send">>),
1823 + ?LISTLINE(<<"half">>, <<"half">>)
1825 + #xmlel{name = <<"field">>,
1826 + attrs = [{<<"type">>, <<"text-multi">>},
1827 + {<<"label">>, ?T(Lang, <<"Jids/Domains to ignore">>)},
1828 + {<<"var">>, <<"ignore_list">>}],
1829 + children = [#xmlel{name = <<"value">>, attrs = [],
1830 + children = [{xmlcdata, iolist_to_binary(list_to_string(IgnoreJids))}]}
1833 + #xmlel{name = <<"field">>,
1834 + attrs = [{<<"type">>, <<"text-single">>},
1835 + {<<"label">>, ?T(Lang, <<"Purge messages older than (days)">>)},
1836 + {<<"var">>, <<"purge_older_days">>}],
1837 + children = [#xmlel{name = <<"value">>, attrs = [],
1838 + children = [{xmlcdata, iolist_to_binary(PurgeDays)}]}
1841 + #xmlel{name = <<"field">>,
1842 + attrs = [{<<"type">>, <<"text-single">>},
1843 + {<<"label">>, ?T(Lang, <<"Poll users settings (seconds)">>)},
1844 + {<<"var">>, <<"poll_users_settings">>}],
1845 + children = [#xmlel{name = <<"value">>, attrs = [],
1846 + children = [{xmlcdata, integer_to_binary(PollTime)}]}
1852 +%get_form(_Host, [<<"mod_logdb">>], #jid{luser = LUser, lserver = LServer} = _Jid, Lang) ->
1853 +% get_user_form(LUser, LServer, Lang);
1854 +get_form(_Host, [<<"mod_logdb_users">>, User], _JidFrom, Lang) ->
1855 + #jid{luser=LUser, lserver=LServer} = jlib:string_to_jid(User),
1856 + get_user_form(LUser, LServer, Lang);
1857 +get_form(Host, [<<"mod_logdb_settings">>], _JidFrom, Lang) ->
1858 + get_settings_form(Host, Lang);
1859 +get_form(_Host, Command, _, _Lang) ->
1860 + ?MYDEBUG("asked for form ~p", [Command]),
1861 + {error, ?ERR_SERVICE_UNAVAILABLE}.
1863 +check_log_list([]) ->
1865 +check_log_list([<<>>]) ->
1867 +check_log_list([Head | Tail]) ->
1868 + case binary:match(Head, <<$@>>) of
1869 + nomatch -> throw(error);
1872 + % this check for Head to be valid jid
1873 + case jlib:string_to_jid(Head) of
1877 + check_log_list(Tail)
1880 +check_ignore_list([]) ->
1882 +check_ignore_list([<<>>]) ->
1884 +check_ignore_list([<<>> | Tail]) ->
1885 + check_ignore_list(Tail);
1886 +check_ignore_list([Head | Tail]) ->
1887 + case binary:match(Head, <<$@>>) of
1889 + nomatch -> throw(error)
1891 + % this check for Head to be valid jid
1892 + case jlib:string_to_jid(Head) of
1894 + % this check for Head to be valid domain "@domain.org"
1896 + << $@, Rest/binary >> ->
1897 + % TODO: this allows spaces and special characters in Head. May be change to nodeprep?
1898 + case jlib:nameprep(Rest) of
1899 + error -> throw(error);
1900 + _ -> check_ignore_list(Tail)
1905 + check_ignore_list(Tail)
1908 +parse_users_settings(XData) ->
1909 + DLD = case lists:keysearch(<<"dolog_default">>, 1, XData) of
1910 + {value, {_, [String]}} when String == <<"true">>; String == <<"false">> ->
1911 + list_to_bool(String);
1913 + throw(bad_request)
1915 + DLL = case lists:keysearch(<<"dolog_list">>, 1, XData) of
1917 + throw(bad_request);
1918 + {value, {_, [[]]}} ->
1920 + {value, {_, List1}} ->
1921 + case catch check_log_list(List1) of
1923 + throw(bad_request);
1928 + DNLL = case lists:keysearch(<<"donotlog_list">>, 1, XData) of
1930 + throw(bad_request);
1931 + {value, {_, [[]]}} ->
1933 + {value, {_, List2}} ->
1934 + case catch check_log_list(List2) of
1936 + throw(bad_request);
1941 + #user_settings{dolog_default=DLD,
1943 + donotlog_list=DNLL}.
1945 +parse_module_settings(XData) ->
1946 + DLD = case lists:keysearch(<<"dolog_default">>, 1, XData) of
1947 + {value, {_, [Str1]}} when Str1 == <<"true">>; Str1 == <<"false">> ->
1948 + list_to_bool(Str1);
1950 + throw(bad_request)
1952 + MRemoval = case lists:keysearch(<<"drop_messages_on_user_removal">>, 1, XData) of
1953 + {value, {_, [Str5]}} when Str5 == <<"true">>; Str5 == <<"false">> ->
1954 + list_to_bool(Str5);
1956 + throw(bad_request)
1958 + GroupChat = case lists:keysearch(<<"groupchat">>, 1, XData) of
1959 + {value, {_, [Str2]}} when Str2 == <<"none">>;
1960 + Str2 == <<"all">>;
1961 + Str2 == <<"send">>;
1962 + Str2 == <<"half">> ->
1963 + jlib:binary_to_atom(Str2);
1965 + throw(bad_request)
1967 + Ignore = case lists:keysearch(<<"ignore_list">>, 1, XData) of
1968 + {value, {_, List}} ->
1969 + case catch check_ignore_list(List) of
1973 + throw(bad_request)
1976 + throw(bad_request)
1978 + Purge = case lists:keysearch(<<"purge_older_days">>, 1, XData) of
1979 + {value, {_, [<<"never">>]}} ->
1981 + {value, {_, [Str3]}} ->
1982 + case catch binary_to_integer(Str3) of
1983 + {'EXIT', {badarg, _}} -> throw(bad_request);
1987 + throw(bad_request)
1989 + Poll = case lists:keysearch(<<"poll_users_settings">>, 1, XData) of
1990 + {value, {_, [Str4]}} ->
1991 + case catch binary_to_integer(Str4) of
1992 + {'EXIT', {badarg, _}} -> throw(bad_request);
1996 + throw(bad_request)
1998 + #state{dolog_default=DLD,
1999 + groupchat=GroupChat,
2000 + ignore_jids=Ignore,
2001 + purge_older_days=Purge,
2002 + drop_messages_on_user_removal=MRemoval,
2003 + poll_users_settings=Poll}.
2005 +set_form(From, _Host, [<<"mod_logdb">>], _Lang, XData) ->
2006 + #jid{luser=LUser, lserver=LServer} = From,
2007 + case catch parse_users_settings(XData) of
2009 + {error, ?ERR_BAD_REQUEST};
2010 + {'EXIT', Reason} ->
2011 + ?ERROR_MSG("Failed to set form ~p", [Reason]),
2012 + {error, ?ERR_BAD_REQUEST};
2014 + case mod_logdb:set_user_settings(LUser, LServer, UserSettings) of
2018 + {error, ?ERR_INTERNAL_SERVER_ERROR}
2021 +set_form(_From, _Host, [<<"mod_logdb_users">>, User], _Lang, XData) ->
2022 + #jid{luser=LUser, lserver=LServer} = jlib:string_to_jid(User),
2023 + case catch parse_users_settings(XData) of
2024 + bad_request -> {error, ?ERR_BAD_REQUEST};
2025 + {'EXIT', Reason} ->
2026 + ?ERROR_MSG("Failed to set form ~p", [Reason]),
2027 + {error, ?ERR_BAD_REQUEST};
2029 + case mod_logdb:set_user_settings(LUser, LServer, UserSettings) of
2033 + {error, ?ERR_INTERNAL_SERVER_ERROR}
2036 +set_form(_From, Host, [<<"mod_logdb_settings">>], _Lang, XData) ->
2037 + case catch parse_module_settings(XData) of
2038 + bad_request -> {error, ?ERR_BAD_REQUEST};
2039 + {'EXIT', Reason} ->
2040 + ?ERROR_MSG("Failed to set form ~p", [Reason]),
2041 + {error, ?ERR_BAD_REQUEST};
2043 + case mod_logdb:set_module_settings(Host, Settings) of
2047 + {error, ?ERR_INTERNAL_SERVER_ERROR}
2050 +set_form(From, _Host, Node, _Lang, XData) ->
2051 + User = jlib:jid_to_string(jlib:jid_remove_resource(From)),
2052 + ?MYDEBUG("set form for ~p at ~p XData=~p", [User, Node, XData]),
2053 + {error, ?ERR_SERVICE_UNAVAILABLE}.
2055 +%adhoc_sm_items(Acc, From, To, Request) ->
2056 +% ?MYDEBUG("adhoc_sm_items Acc=~p From=~p To=~p Request=~p", [Acc, From, To, Request]),
2059 +%adhoc_sm_commands(Acc, From, To, Request) ->
2060 +% ?MYDEBUG("adhoc_sm_commands Acc=~p From=~p To=~p Request=~p", [Acc, From, To, Request]),
2063 +get_all_vh_users(Host, Server, Lang) ->
2064 + case catch ejabberd_auth:get_vh_registered_users(Host) of
2065 + {'EXIT', _Reason} ->
2068 + SUsers = lists:sort([{S, U} || {U, S} <- Users]),
2069 + case length(SUsers) of
2070 + N when N =< 100 ->
2071 + lists:map(fun({S, U}) ->
2072 + ?NODE(<< U/binary, "@", S/binary >>,
2073 + << (iolist_to_binary("mod_logdb_users/"))/binary, U/binary, "@", S/binary >>)
2077 + NParts = trunc(math:sqrt(N * 0.618)) + 1,
2078 + M = trunc(N / NParts) + 1,
2079 + lists:map(fun(K) ->
2082 + (iolist_to_binary(integer_to_list(K)))/binary,
2084 + (iolist_to_binary(integer_to_list(L)))/binary
2086 + {FS, FU} = lists:nth(K, SUsers),
2088 + if L < N -> lists:nth(L, SUsers);
2089 + true -> lists:last(SUsers)
2092 + <<FU/binary, "@", FS/binary,
2094 + LU/binary, "@", LS/binary>>,
2095 + ?NODE(Name, << (iolist_to_binary("mod_logdb_users/"))/binary, Node/binary >>)
2096 + end, lists:seq(1, N, M))
2100 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2104 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2105 +webadmin_menu(Acc, _Host, Lang) ->
2106 + [{<<"messages">>, ?T(<<"Users Messages">>)} | Acc].
2108 +webadmin_user(Acc, User, Server, Lang) ->
2109 + Sett = get_user_settings(User, Server),
2111 + case Sett#user_settings.dolog_default of
2113 + ?INPUTT(<<"submit">>, <<"dolog">>, <<"Log Messages">>);
2115 + ?INPUTT(<<"submit">>, <<"donotlog">>, <<"Do Not Log Messages">>);
2118 + Acc ++ [?XE(<<"h3">>, [?ACT(<<"messages/">>, <<"Messages">>), ?C(<<" ">>), Log])].
2120 +webadmin_page(_, Host,
2121 + #request{path = [<<"messages">>],
2124 + Res = vhost_messages_stats(Host, Query, Lang),
2126 +webadmin_page(_, Host,
2127 + #request{path = [<<"messages">>, Date],
2130 + Res = vhost_messages_stats_at(Host, Query, Lang, Date),
2132 +webadmin_page(_, Host,
2133 + #request{path = [<<"user">>, U, <<"messages">>],
2136 + Res = user_messages_stats(U, Host, Query, Lang),
2138 +webadmin_page(_, Host,
2139 + #request{path = [<<"user">>, U, <<"messages">>, Date],
2142 + Res = mod_logdb:user_messages_stats_at(U, Host, Query, Lang, Date),
2144 +webadmin_page(Acc, _Host, _R) -> Acc.
2146 +user_parse_query(_, <<"dolog">>, User, Server, _Query) ->
2147 + Sett = get_user_settings(User, Server),
2148 + % TODO: check returned value
2149 + set_user_settings(User, Server, Sett#user_settings{dolog_default=true}),
2151 +user_parse_query(_, <<"donotlog">>, User, Server, _Query) ->
2152 + Sett = get_user_settings(User, Server),
2153 + % TODO: check returned value
2154 + set_user_settings(User, Server, Sett#user_settings{dolog_default=false}),
2156 +user_parse_query(Acc, _Action, _User, _Server, _Query) ->
2159 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2163 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2164 +vhost_messages_stats(Server, Query, Lang) ->
2165 + Res = case catch vhost_messages_parse_query(Server, Query) of
2166 + {'EXIT', Reason} ->
2167 + ?ERROR_MSG("~p", [Reason]),
2169 + VResult -> VResult
2171 + {Time, Value} = timer:tc(mod_logdb, get_vhost_stats, [Server]),
2172 + ?INFO_MSG("get_vhost_stats(~p) elapsed ~p sec", [Server, Time/1000000]),
2173 + %case get_vhost_stats(Server) of
2175 + {'EXIT', CReason} ->
2176 + ?ERROR_MSG("Failed to get_vhost_stats: ~p", [CReason]),
2177 + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))];
2178 + {error, GReason} ->
2179 + ?ERROR_MSG("Failed to get_vhost_stats: ~p", [GReason]),
2180 + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))];
2182 + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s">>), [Server])))];
2184 + Fun = fun({Date, Count}) ->
2185 + DateBin = iolist_to_binary(Date),
2186 + ID = jlib:encode_base64( << Server/binary, DateBin/binary >> ),
2188 + [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
2189 + [?INPUT(<<"checkbox">>, <<"selected">>, ID)]),
2190 + ?XE(<<"td">>, [?AC(DateBin, DateBin)]),
2191 + ?XC(<<"td">>, integer_to_binary(Count))
2195 + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Logged messages for ~s">>), [Server])))] ++
2197 + ok -> [?CT(<<"Submitted">>), ?P];
2198 + error -> [?CT(<<"Bad format">>), ?P];
2201 + [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
2206 + ?XCT(<<"td">>, <<"Date">>),
2207 + ?XCT(<<"td">>, <<"Count">>)
2210 + lists:map(Fun, Dates)
2213 + ?INPUTT(<<"submit">>, <<"delete">>, <<"Delete Selected">>)
2217 +vhost_messages_stats_at(Server, Query, Lang, Date) ->
2218 + {Time, Value} = timer:tc(mod_logdb, get_vhost_stats_at, [Server, Date]),
2219 + ?INFO_MSG("get_vhost_stats_at(~p,~p) elapsed ~p sec", [Server, Date, Time/1000000]),
2220 + %case get_vhost_stats_at(Server, Date) of
2222 + {'EXIT', CReason} ->
2223 + ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [CReason]),
2224 + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))];
2225 + {error, GReason} ->
2226 + ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [GReason]),
2227 + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))];
2229 + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s at ~s">>), [Server, Date])))];
2231 + Res = case catch vhost_messages_at_parse_query(Server, Date, Stats, Query) of
2232 + {'EXIT', Reason} ->
2233 + ?ERROR_MSG("~p", [Reason]),
2235 + VResult -> VResult
2237 + Fun = fun({User, Count}) ->
2238 + UserBin = iolist_to_binary(User),
2239 + ID = jlib:encode_base64( << UserBin/binary, Server/binary >> ),
2241 + [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
2242 + [?INPUT(<<"checkbox">>, <<"selected">>, ID)]),
2243 + ?XE(<<"td">>, [?AC(<< <<"../user/">>/binary, UserBin/binary, <<"/messages/">>/binary, Date/binary >>, UserBin)]),
2244 + ?XC(<<"td">>, integer_to_binary(Count))
2247 + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Logged messages for ~s at ~s">>), [Server, Date])))] ++
2249 + ok -> [?CT(<<"Submitted">>), ?P];
2250 + error -> [?CT(<<"Bad format">>), ?P];
2253 + [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
2258 + ?XCT(<<"td">>, <<"User">>),
2259 + ?XCT(<<"td">>, <<"Count">>)
2262 + lists:map(Fun, Stats)
2265 + ?INPUTT(<<"submit">>, <<"delete">>, <<"Delete Selected">>)
2269 +user_messages_stats(User, Server, Query, Lang) ->
2270 + Jid = jlib:jid_to_string({User, Server, ""}),
2272 + Res = case catch user_messages_parse_query(User, Server, Query) of
2273 + {'EXIT', Reason} ->
2274 + ?ERROR_MSG("~p", [Reason]),
2276 + VResult -> VResult
2279 + {Time, Value} = timer:tc(mod_logdb, get_user_stats, [User, Server]),
2280 + ?INFO_MSG("get_user_stats(~p,~p) elapsed ~p sec", [User, Server, Time/1000000]),
2283 + {'EXIT', CReason} ->
2284 + ?ERROR_MSG("Failed to get_user_stats: ~p", [CReason]),
2285 + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching days">>))];
2286 + {error, GReason} ->
2287 + ?ERROR_MSG("Failed to get_user_stats: ~p", [GReason]),
2288 + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching days">>))];
2290 + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s">>), [Jid])))];
2292 + Fun = fun({Date, Count}) ->
2293 + DateBin = iolist_to_binary(Date),
2294 + ID = jlib:encode_base64( << User/binary, DateBin/binary >> ),
2296 + [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
2297 + [?INPUT(<<"checkbox">>, <<"selected">>, ID)]),
2298 + ?XE(<<"td">>, [?AC(DateBin, DateBin)]),
2299 + ?XC(<<"td">>, iolist_to_binary(integer_to_list(Count)))
2302 + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T("Logged messages for ~s"), [Jid])))] ++
2304 + ok -> [?CT(<<"Submitted">>), ?P];
2305 + error -> [?CT(<<"Bad format">>), ?P];
2308 + [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
2313 + ?XCT(<<"td">>, <<"Date">>),
2314 + ?XCT(<<"td">>, <<"Count">>)
2317 + lists:map(Fun, Dates)
2320 + ?INPUTT(<<"submit">>, <<"delete">>, <<"Delete Selected">>)
2324 +search_user_nick(User, List) ->
2325 + case lists:keysearch(User, 1, List) of
2326 + {value,{User, []}} ->
2328 + {value,{User, Nick}} ->
2334 +user_messages_stats_at(User, Server, Query, Lang, Date) ->
2335 + Jid = jlib:jid_to_string({User, Server, ""}),
2337 + {Time, Value} = timer:tc(mod_logdb, get_user_messages_at, [User, Server, Date]),
2338 + ?INFO_MSG("get_user_messages_at(~p,~p,~p) elapsed ~p sec", [User, Server, Date, Time/1000000]),
2340 + {'EXIT', CReason} ->
2341 + ?ERROR_MSG("Failed to get_user_messages_at: ~p", [CReason]),
2342 + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching messages">>))];
2343 + {error, GReason} ->
2344 + ?ERROR_MSG("Failed to get_user_messages_at: ~p", [GReason]),
2345 + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching messages">>))];
2347 + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s at ~s">>), [Jid, Date])))];
2348 + {ok, User_messages} ->
2349 + Res = case catch user_messages_at_parse_query(Server,
2353 + {'EXIT', Reason} ->
2354 + ?ERROR_MSG("~p", [Reason]),
2356 + VResult -> VResult
2359 + UR = ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]),
2361 + lists:map(fun(Item) ->
2362 + {jlib:jid_to_string(Item#roster.jid), Item#roster.name}
2365 + UniqUsers = lists:foldl(fun(#msg{peer_name=PName, peer_server=PServer}, List) ->
2366 + ToAdd = PName++"@"++PServer,
2367 + case lists:member(ToAdd, List) of
2369 + false -> lists:append([ToAdd], List)
2371 + end, [], User_messages),
2373 + % Users to filter (sublist of UniqUsers)
2374 + CheckedUsers = case lists:keysearch(<<"filter">>, 1, Query) of
2376 + lists:filter(fun(UFUser) ->
2377 + ID = jlib:encode_base64(term_to_binary(UFUser)),
2378 + lists:member({<<"selected">>, ID}, Query)
2383 + % UniqUsers in html (noone selected -> everyone selected)
2384 + Users = lists:map(fun(UHUser) ->
2385 + ID = jlib:encode_base64(term_to_binary(UHUser)),
2386 + Input = case lists:member(UHUser, CheckedUsers) of
2387 + true -> [?INPUTC(<<"checkbox">>, <<"selected">>, ID)];
2388 + false when CheckedUsers == [] -> [?INPUTC(<<"checkbox">>, <<"selected">>, ID)];
2389 + false -> [?INPUT(<<"checkbox">>, <<"selected">>, ID)]
2392 + case search_user_nick(UHUser, UserRoster) of
2393 + nothing -> <<"">>;
2394 + N -> iolist_to_binary( " ("++ N ++")" )
2397 + [?XE(<<"td">>, Input),
2398 + ?XC(<<"td">>, iolist_to_binary(UHUser++Nick))])
2399 + end, lists:sort(UniqUsers)),
2400 + % Messages to show (based on Users)
2401 + User_messages_filtered = case CheckedUsers of
2402 + [] -> User_messages;
2403 + _ -> lists:filter(fun(#msg{peer_name=PName, peer_server=PServer}) ->
2404 + lists:member(PName++"@"++PServer, CheckedUsers)
2405 + end, User_messages)
2408 + Msgs_Fun = fun(#msg{timestamp=Timestamp,
2410 + direction=Direction,
2411 + peer_name=PName, peer_server=PServer, peer_resource=PRes,
2414 + Text = case Subject of
2415 + "" -> iolist_to_binary(Body);
2416 + _ -> iolist_to_binary([binary_to_list(?T(<<"Subject">>)) ++ ": " ++ Subject ++ "\n" ++ Body])
2418 + Resource = case PRes of
2424 + case search_user_nick(PName++"@"++PServer, UserRoster) of
2425 + nothing when PServer == Server ->
2427 + nothing when Type == "groupchat", Direction == from ->
2428 + PName++"@"++PServer++Resource;
2430 + PName++"@"++PServer;
2433 + ID = jlib:encode_base64(term_to_binary(Timestamp)),
2435 + [?XE(<<"td">>, [?INPUT(<<"checkbox">>, <<"selected">>, ID)]),
2436 + ?XC(<<"td">>, iolist_to_binary(convert_timestamp(Timestamp))),
2437 + ?XC(<<"td">>, iolist_to_binary(atom_to_list(Direction)++": "++UserNick)),
2438 + ?XE(<<"td">>, [?XC(<<"pre">>, Text)])])
2440 + % Filtered user messages in html
2441 + Msgs = lists:map(Msgs_Fun, lists:sort(User_messages_filtered)),
2443 + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Logged messages for ~s at ~s">>), [Jid, Date])))] ++
2445 + ok -> [?CT(<<"Submitted">>), ?P];
2446 + error -> [?CT(<<"Bad format">>), ?P];
2449 + [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
2453 + ?XCT(<<"td">>, <<"User">>)
2459 + ?INPUTT(<<"submit">>, <<"filter">>, <<"Filter Selected">>)
2465 + ?XCT(<<"td">>, <<"Date, Time">>),
2466 + ?XCT(<<"td">>, <<"Direction: Jid">>),
2467 + ?XCT(<<"td">>, <<"Body">>)
2472 + ?INPUTT(<<"submit">>, <<"delete">>, <<"Delete Selected">>),
2477 diff --git a/src/mod_logdb.hrl b/src/mod_logdb.hrl
2478 new file mode 100644
2479 index 0000000..d44f0df
2481 +++ b/src/mod_logdb.hrl
2483 +%%%----------------------------------------------------------------------
2484 +%%% File : mod_logdb.hrl
2485 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
2487 +%%% Version : trunk
2488 +%%% Id : $Id: mod_logdb.hrl 1273 2009-02-05 18:12:57Z malik $
2489 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
2490 +%%%----------------------------------------------------------------------
2492 +-define(logdb_debug, true).
2494 +-ifdef(logdb_debug).
2495 +-define(MYDEBUG(Format, Args), io:format("D(~p:~p:~p) : "++Format++"~n",
2496 + [calendar:local_time(),?MODULE,?LINE]++Args)).
2498 +-define(MYDEBUG(_F,_A),[]).
2501 +-record(msg, {timestamp,
2503 + peer_name, peer_server, peer_resource,
2508 +-record(user_settings, {owner_name,
2511 + donotlog_list=[]}).
2513 +-define(INPUTC(Type, Name, Value),
2514 + ?XA(<<"input">>, [{<<"type">>, Type},
2515 + {<<"name">>, Name},
2516 + {<<"value">>, Value},
2517 + {<<"checked">>, <<"true">>}])).
2518 diff --git a/src/mod_logdb_mnesia.erl b/src/mod_logdb_mnesia.erl
2519 new file mode 100644
2520 index 0000000..a8ae766
2522 +++ b/src/mod_logdb_mnesia.erl
2524 +%%%----------------------------------------------------------------------
2525 +%%% File : mod_logdb_mnesia.erl
2526 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
2527 +%%% Purpose : mnesia backend for mod_logdb
2528 +%%% Version : trunk
2529 +%%% Id : $Id: mod_logdb_mnesia.erl 1273 2009-02-05 18:12:57Z malik $
2530 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
2531 +%%%----------------------------------------------------------------------
2533 +-module(mod_logdb_mnesia).
2534 +-author('o.palij@gmail.com').
2536 +-include("mod_logdb.hrl").
2537 +-include("ejabberd.hrl").
2538 +-include("jlib.hrl").
2539 +-include("logger.hrl").
2541 +-behaviour(gen_logdb).
2542 +-behaviour(gen_server).
2545 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
2547 +-export([start/2, stop/1]).
2549 +-export([log_message/2,
2551 + rebuild_stats_at/2,
2552 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
2553 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
2555 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
2558 +-define(PROCNAME, mod_logdb_mnesia).
2559 +-define(CALL_TIMEOUT, 10000).
2561 +-record(state, {vhost}).
2563 +-record(stats, {user, at, count}).
2569 + "_" ++ binary_to_list(VHost).
2571 +stats_table(VHost) ->
2572 + list_to_atom(prefix() ++ "stats" ++ suffix(VHost)).
2574 +table_name(VHost, Date) ->
2575 + list_to_atom(prefix() ++ "messages_" ++ Date ++ suffix(VHost)).
2577 +settings_table(VHost) ->
2578 + list_to_atom(prefix() ++ "settings" ++ suffix(VHost)).
2580 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2582 +% gen_mod callbacks
2584 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2585 +start(VHost, Opts) ->
2586 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2587 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
2590 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2591 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
2593 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2595 +% gen_server callbacks
2597 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2598 +init([VHost, _Opts]) ->
2599 + case mnesia:system_info(is_running) of
2601 + ok = create_stats_table(VHost),
2602 + ok = create_settings_table(VHost),
2603 + {ok, #state{vhost=VHost}};
2605 + ?ERROR_MSG("Mnesia not running", []),
2606 + {stop, db_connection_failed};
2608 + ?ERROR_MSG("Mnesia status: ~p", [Status]),
2609 + {stop, db_connection_failed}
2612 +handle_call({log_message, Msg}, _From, #state{vhost=VHost}=State) ->
2613 + {reply, log_message_int(VHost, Msg), State};
2614 +handle_call({rebuild_stats}, _From, #state{vhost=VHost}=State) ->
2615 + {atomic, ok} = delete_nonexistent_stats(VHost),
2617 + lists:foreach(fun(Date) ->
2618 + rebuild_stats_at_int(VHost, Date)
2619 + end, get_dates_int(VHost)),
2620 + {reply, Reply, State};
2621 +handle_call({rebuild_stats_at, Date}, _From, #state{vhost=VHost}=State) ->
2622 + Reply = rebuild_stats_at_int(VHost, Date),
2623 + {reply, Reply, State};
2624 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{vhost=VHost}=State) ->
2625 + Table = table_name(VHost, Date),
2629 + mnesia:write_lock_table(stats_table(VHost)),
2630 + mnesia:write_lock_table(Table),
2631 + mnesia:delete_object(Table, Msg, write)
2634 + DRez = case mnesia:transaction(Fun) of
2635 + {aborted, Reason} ->
2636 + ?ERROR_MSG("Failed to delete_messages_by_user_at at ~p for ~p: ~p", [Date, VHost, Reason]),
2642 + case rebuild_stats_at_int(VHost, Date) of
2648 + {reply, Reply, State};
2649 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{vhost=VHost}=State) ->
2650 + {reply, delete_all_messages_by_user_at_int(User, VHost, Date), State};
2651 +handle_call({delete_messages_at, Date}, _From, #state{vhost=VHost}=State) ->
2653 + case mnesia:delete_table(table_name(VHost, Date)) of
2655 + delete_stats_by_vhost_at_int(VHost, Date);
2656 + {aborted, Reason} ->
2657 + ?ERROR_MSG("Failed to delete_messages_at for ~p at ~p", [VHost, Date, Reason]),
2660 + {reply, Reply, State};
2661 +handle_call({get_vhost_stats}, _From, #state{vhost=VHost}=State) ->
2662 + Fun = fun(#stats{at=Date, count=Count}, Stats) ->
2663 + case lists:keysearch(Date, 1, Stats) of
2665 + lists:append(Stats, [{Date, Count}]);
2666 + {value, {_, TempCount}} ->
2667 + lists:keyreplace(Date, 1, Stats, {Date, TempCount+Count})
2671 + case mnesia:transaction(fun() ->
2672 + mnesia:foldl(Fun, [], stats_table(VHost))
2674 + {atomic, Result} -> {ok, mod_logdb:sort_stats(Result)};
2675 + {aborted, Reason} -> {error, Reason}
2677 + {reply, Reply, State};
2678 +handle_call({get_vhost_stats_at, Date}, _From, #state{vhost=VHost}=State) ->
2680 + Pat = #stats{user='$1', at=Date, count='$2'},
2681 + mnesia:select(stats_table(VHost), [{Pat, [], [['$1', '$2']]}])
2684 + case mnesia:transaction(Fun) of
2685 + {atomic, Result} ->
2686 + {ok, lists:reverse(lists:keysort(2, [{User, Count} || [User, Count] <- Result]))};
2687 + {aborted, Reason} ->
2690 + {reply, Reply, State};
2691 +handle_call({get_user_stats, User}, _From, #state{vhost=VHost}=State) ->
2692 + {reply, get_user_stats_int(User, VHost), State};
2693 +handle_call({get_user_messages_at, User, Date}, _From, #state{vhost=VHost}=State) ->
2695 + case mnesia:transaction(fun() ->
2696 + Pat = #msg{owner_name=User, _='_'},
2697 + mnesia:select(table_name(VHost, Date),
2698 + [{Pat, [], ['$_']}])
2700 + {atomic, Result} -> {ok, Result};
2701 + {aborted, Reason} ->
2704 + {reply, Reply, State};
2705 +handle_call({get_dates}, _From, #state{vhost=VHost}=State) ->
2706 + {reply, get_dates_int(VHost), State};
2707 +handle_call({get_users_settings}, _From, #state{vhost=VHost}=State) ->
2708 + Reply = mnesia:dirty_match_object(settings_table(VHost), #user_settings{_='_'}),
2709 + {reply, {ok, Reply}, State};
2710 +handle_call({get_user_settings, User}, _From, #state{vhost=VHost}=State) ->
2712 + case mnesia:dirty_match_object(settings_table(VHost), #user_settings{owner_name=User, _='_'}) of
2717 + {reply, Reply, State};
2718 +handle_call({set_user_settings, _User, Set}, _From, #state{vhost=VHost}=State) ->
2719 + ?MYDEBUG("~p~n~p", [settings_table(VHost), Set]),
2720 + Reply = mnesia:dirty_write(settings_table(VHost), Set),
2721 + ?MYDEBUG("~p", [Reply]),
2722 + {reply, Reply, State};
2723 +handle_call({drop_user, User}, _From, #state{vhost=VHost}=State) ->
2724 + {ok, Dates} = get_user_stats_int(User, VHost),
2725 + MDResult = lists:map(fun({Date, _}) ->
2726 + delete_all_messages_by_user_at_int(User, VHost, Date)
2728 + SDResult = delete_user_settings_int(User, VHost),
2730 + case lists:all(fun(Result) when Result == ok ->
2732 + (Result) when Result == error ->
2734 + end, lists:append(MDResult, [SDResult])) of
2740 + {reply, Reply, State};
2741 +handle_call({stop}, _From, State) ->
2742 + {stop, normal, ok, State};
2743 +handle_call(Msg, _From, State) ->
2744 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
2747 +handle_cast(Msg, State) ->
2748 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
2751 +handle_info(Info, State) ->
2752 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
2755 +terminate(_Reason, _State) ->
2758 +code_change(_OldVsn, State, _Extra) ->
2761 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2763 +% gen_logdb callbacks
2765 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2766 +log_message(VHost, Msg) ->
2767 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2768 + gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
2769 +rebuild_stats(VHost) ->
2770 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2771 + gen_server:call(Proc, {rebuild_stats}, ?CALL_TIMEOUT).
2772 +rebuild_stats_at(VHost, Date) ->
2773 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2774 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
2775 +delete_messages_by_user_at(VHost, Msgs, Date) ->
2776 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2777 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
2778 +delete_all_messages_by_user_at(User, VHost, Date) ->
2779 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2780 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
2781 +delete_messages_at(VHost, Date) ->
2782 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2783 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
2784 +get_vhost_stats(VHost) ->
2785 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2786 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
2787 +get_vhost_stats_at(VHost, Date) ->
2788 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2789 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
2790 +get_user_stats(User, VHost) ->
2791 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2792 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
2793 +get_user_messages_at(User, VHost, Date) ->
2794 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2795 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
2796 +get_dates(VHost) ->
2797 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2798 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
2799 +get_user_settings(User, VHost) ->
2800 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2801 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
2802 +get_users_settings(VHost) ->
2803 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2804 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
2805 +set_user_settings(User, VHost, Set) ->
2806 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2807 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
2808 +drop_user(User, VHost) ->
2809 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2810 + gen_server:call(Proc, {drop_user, User}, ?CALL_TIMEOUT).
2812 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2816 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2817 +log_message_int(VHost, #msg{timestamp=Timestamp}=MsgBin) ->
2818 + Date = mod_logdb:convert_timestamp_brief(Timestamp),
2820 + Msg = #msg{timestamp = MsgBin#msg.timestamp,
2821 + owner_name = binary_to_list(MsgBin#msg.owner_name),
2822 + peer_name = binary_to_list(MsgBin#msg.peer_name),
2823 + peer_server = binary_to_list(MsgBin#msg.peer_server),
2824 + peer_resource = binary_to_list(MsgBin#msg.peer_resource),
2825 + direction = MsgBin#msg.direction,
2826 + type = binary_to_list(MsgBin#msg.type),
2827 + subject = binary_to_list(MsgBin#msg.subject),
2828 + body = binary_to_list(MsgBin#msg.body)},
2830 + ATable = table_name(VHost, Date),
2832 + mnesia:write_lock_table(ATable),
2833 + mnesia:write(ATable, Msg, write)
2835 + % log message, increment stats for both users
2836 + case mnesia:transaction(Fun) of
2837 + % if table does not exists - create it and try to log message again
2838 + {aborted,{no_exists, _Table}} ->
2839 + case create_msg_table(VHost, Date) of
2840 + {aborted, CReason} ->
2841 + ?ERROR_MSG("Failed to log message: ~p", [CReason]),
2844 + ?MYDEBUG("Created msg table for ~s at ~s", [VHost, Date]),
2845 + log_message_int(VHost, MsgBin)
2847 + {aborted, TReason} ->
2848 + ?ERROR_MSG("Failed to log message: ~p", [TReason]),
2851 + ?MYDEBUG("Logged ok for ~s, peer: ~s", [ [Msg#msg.owner_name, <<"@">>, VHost],
2852 + [Msg#msg.peer_name, <<"@">>, Msg#msg.peer_server] ]),
2853 + increment_user_stats(Msg#msg.owner_name, VHost, Date)
2856 +increment_user_stats(Owner, VHost, Date) ->
2858 + Pat = #stats{user=Owner, at=Date, count='$1'},
2859 + mnesia:write_lock_table(stats_table(VHost)),
2860 + case mnesia:select(stats_table(VHost), [{Pat, [], ['$_']}]) of
2862 + mnesia:write(stats_table(VHost),
2863 + #stats{user=Owner,
2868 + mnesia:delete_object(stats_table(VHost),
2869 + #stats{user=Owner,
2871 + count=Stats#stats.count},
2873 + New = Stats#stats{count = Stats#stats.count+1},
2875 + New#stats.count > 0 -> mnesia:write(stats_table(VHost),
2882 + case mnesia:transaction(Fun) of
2883 + {aborted, Reason} ->
2884 + ?ERROR_MSG("Failed to update stats for ~s@~s: ~p", [Owner, VHost, Reason]),
2887 + ?MYDEBUG("Updated stats for ~s@~s", [Owner, VHost]),
2891 +get_dates_int(VHost) ->
2892 + Tables = mnesia:system_info(tables),
2893 + lists:foldl(fun(ATable, Dates) ->
2894 + Table = term_to_binary(ATable),
2895 + case ejabberd_regexp:run( Table, << VHost/binary, <<"$">>/binary >> ) of
2897 + case re:run(Table, "_[0-9]+-[0-9]+-[0-9]+_") of
2898 + {match, [{S, E}]} ->
2899 + lists:append(Dates, [lists:sublist(binary_to_list(Table), S+2, E-2)]);
2908 +rebuild_stats_at_int(VHost, Date) ->
2909 + Table = table_name(VHost, Date),
2910 + STable = stats_table(VHost),
2911 + CFun = fun(Msg, Stats) ->
2912 + Owner = Msg#msg.owner_name,
2913 + case lists:keysearch(Owner, 1, Stats) of
2914 + {value, {_, Count}} ->
2915 + lists:keyreplace(Owner, 1, Stats, {Owner, Count + 1});
2917 + lists:append(Stats, [{Owner, 1}])
2920 + DFun = fun(#stats{at=SDate} = Stat, _Acc)
2921 + when SDate == Date ->
2922 + mnesia:delete_object(stats_table(VHost), Stat, write);
2923 + (_Stat, _Acc) -> ok
2925 + % TODO: Maybe unregister hooks ?
2926 + case mnesia:transaction(fun() ->
2927 + mnesia:write_lock_table(Table),
2928 + mnesia:write_lock_table(STable),
2929 + % Delete all stats for VHost at Date
2930 + mnesia:foldl(DFun, [], STable),
2931 + % Calc stats for VHost at Date
2932 + case mnesia:foldl(CFun, [], Table) of
2935 + % Write new calc'ed stats
2936 + lists:foreach(fun({Owner, Count}) ->
2937 + WStat = #stats{user=Owner, at=Date, count=Count},
2938 + mnesia:write(stats_table(VHost), WStat, write)
2943 + {aborted, Reason} ->
2944 + ?ERROR_MSG("Failed to rebuild_stats_at for ~p at ~p: ~p", [VHost, Date, Reason]),
2948 + {atomic, empty} ->
2949 + {atomic,ok} = mnesia:delete_table(Table),
2950 + ?MYDEBUG("Dropped table at ~p", [Date]),
2954 +delete_nonexistent_stats(VHost) ->
2955 + Dates = get_dates_int(VHost),
2956 + mnesia:transaction(fun() ->
2957 + mnesia:foldl(fun(#stats{at=Date} = Stat, _Acc) ->
2958 + case lists:member(Date, Dates) of
2959 + false -> mnesia:delete_object(Stat);
2962 + end, ok, stats_table(VHost))
2965 +delete_stats_by_vhost_at_int(VHost, Date) ->
2966 + StatsDelete = fun(#stats{at=SDate} = Stat, _Acc)
2967 + when SDate == Date ->
2968 + mnesia:delete_object(stats_table(VHost), Stat, write),
2970 + (_Msg, _Acc) -> ok
2972 + case mnesia:transaction(fun() ->
2973 + mnesia:write_lock_table(stats_table(VHost)),
2974 + mnesia:foldl(StatsDelete, ok, stats_table(VHost))
2976 + {aborted, Reason} ->
2977 + ?ERROR_MSG("Failed to update stats at ~p for ~p: ~p", [Date, VHost, Reason]),
2978 + rebuild_stats_at_int(VHost, Date);
2980 + ?INFO_MSG("Updated stats at ~p for ~p", [Date, VHost]),
2984 +get_user_stats_int(User, VHost) ->
2985 + case mnesia:transaction(fun() ->
2986 + Pat = #stats{user=User, at='$1', count='$2'},
2987 + mnesia:select(stats_table(VHost), [{Pat, [], [['$1', '$2']]}])
2989 + {atomic, Result} ->
2990 + {ok, mod_logdb:sort_stats([{Date, Count} || [Date, Count] <- Result])};
2991 + {aborted, Reason} ->
2995 +delete_all_messages_by_user_at_int(User, VHost, Date) ->
2996 + Table = table_name(VHost, Date),
2997 + MsgDelete = fun(#msg{owner_name=Owner} = Msg, _Acc)
2998 + when Owner == User ->
2999 + mnesia:delete_object(Table, Msg, write),
3001 + (_Msg, _Acc) -> ok
3003 + DRez = case mnesia:transaction(fun() ->
3004 + mnesia:foldl(MsgDelete, ok, Table)
3006 + {aborted, Reason} ->
3007 + ?ERROR_MSG("Failed to delete_all_messages_by_user_at for ~p@~p at ~p: ~p", [User, VHost, Date, Reason]),
3012 + case rebuild_stats_at_int(VHost, Date) of
3019 +delete_user_settings_int(User, VHost) ->
3020 + STable = settings_table(VHost),
3021 + case mnesia:dirty_match_object(STable, #user_settings{owner_name=User, _='_'}) of
3025 + mnesia:dirty_delete_object(STable, UserSettings)
3028 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3032 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3033 +create_stats_table(VHost) ->
3034 + SName = stats_table(VHost),
3035 + case mnesia:create_table(SName,
3036 + [{disc_only_copies, [node()]},
3038 + {attributes, record_info(fields, stats)},
3039 + {record_name, stats}
3042 + ?MYDEBUG("Created stats table for ~p", [VHost]),
3043 + lists:foreach(fun(Date) ->
3044 + rebuild_stats_at_int(VHost, Date)
3045 + end, get_dates_int(VHost)),
3047 + {aborted, {already_exists, _}} ->
3048 + ?MYDEBUG("Stats table for ~p already exists", [VHost]),
3050 + {aborted, Reason} ->
3051 + ?ERROR_MSG("Failed to create stats table: ~p", [Reason]),
3055 +create_settings_table(VHost) ->
3056 + SName = settings_table(VHost),
3057 + case mnesia:create_table(SName,
3058 + [{disc_copies, [node()]},
3060 + {attributes, record_info(fields, user_settings)},
3061 + {record_name, user_settings}
3064 + ?MYDEBUG("Created settings table for ~p", [VHost]),
3066 + {aborted, {already_exists, _}} ->
3067 + ?MYDEBUG("Settings table for ~p already exists", [VHost]),
3069 + {aborted, Reason} ->
3070 + ?ERROR_MSG("Failed to create settings table: ~p", [Reason]),
3074 +create_msg_table(VHost, Date) ->
3075 + mnesia:create_table(
3076 + table_name(VHost, Date),
3077 + [{disc_only_copies, [node()]},
3079 + {attributes, record_info(fields, msg)},
3080 + {record_name, msg}]).
3081 diff --git a/src/mod_logdb_mnesia_old.erl b/src/mod_logdb_mnesia_old.erl
3082 new file mode 100644
3083 index 0000000..e962d9a
3085 +++ b/src/mod_logdb_mnesia_old.erl
3087 +%%%----------------------------------------------------------------------
3088 +%%% File : mod_logdb_mnesia_old.erl
3089 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
3090 +%%% Purpose : mod_logmnesia backend for mod_logdb (should be used only for copy_tables functionality)
3091 +%%% Version : trunk
3092 +%%% Id : $Id: mod_logdb_mnesia_old.erl 1273 2009-02-05 18:12:57Z malik $
3093 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
3094 +%%%----------------------------------------------------------------------
3096 +-module(mod_logdb_mnesia_old).
3097 +-author('o.palij@gmail.com').
3099 +-include("ejabberd.hrl").
3100 +-include("jlib.hrl").
3101 +-include("logger.hrl").
3103 +-behaviour(gen_logdb).
3105 +-export([start/2, stop/1,
3108 + rebuild_stats_at/2,
3109 + rebuild_stats_at1/2,
3110 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
3111 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
3113 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
3116 +-record(stats, {user, server, table, count}).
3117 +-record(msg, {to_user, to_server, to_resource, from_user, from_server, from_resource, id, type, subject, body, timestamp}).
3119 +tables_prefix() -> "messages_".
3120 +% stats_table should not start with tables_prefix(VHost) !
3121 +% i.e. lists:prefix(tables_prefix(VHost), atom_to_list(stats_table())) must be /= true
3122 +stats_table() -> list_to_atom("messages-stats").
3123 +% table name as atom from Date
3124 +-define(ATABLE(Date), list_to_atom(tables_prefix() ++ Date)).
3125 +-define(LTABLE(Date), tables_prefix() ++ Date).
3127 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3129 +% gen_logdb callbacks
3131 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3132 +start(_Opts, _VHost) ->
3133 + case mnesia:system_info(is_running) of
3135 + ok = create_stats_table(),
3138 + ?ERROR_MSG("Mnesia not running", []),
3141 + ?ERROR_MSG("Mnesia status: ~p", [Status]),
3148 +log_message(_VHost, _Msg) ->
3151 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3153 +% gen_logdb callbacks (maintaince)
3155 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3156 +rebuild_stats(_VHost) ->
3159 +rebuild_stats_at(VHost, Date) ->
3160 + Table = ?LTABLE(Date),
3161 + {Time, Value}=timer:tc(?MODULE, rebuild_stats_at1, [VHost, Table]),
3162 + ?INFO_MSG("rebuild_stats_at ~p elapsed ~p sec: ~p~n", [Date, Time/1000000, Value]),
3164 +rebuild_stats_at1(VHost, Table) ->
3165 + CFun = fun(Msg, Stats) ->
3166 + To = Msg#msg.to_user ++ "@" ++ Msg#msg.to_server,
3168 + Msg#msg.to_server == VHost ->
3169 + case lists:keysearch(To, 1, Stats) of
3170 + {value, {Who_to, Count_to}} ->
3171 + lists:keyreplace(To, 1, Stats, {Who_to, Count_to + 1});
3173 + lists:append(Stats, [{To, 1}])
3178 + From = Msg#msg.from_user ++ "@" ++ Msg#msg.from_server,
3180 + Msg#msg.from_server == VHost ->
3181 + case lists:keysearch(From, 1, Stats_to) of
3182 + {value, {Who_from, Count_from}} ->
3183 + lists:keyreplace(From, 1, Stats_to, {Who_from, Count_from + 1});
3185 + lists:append(Stats_to, [{From, 1}])
3192 + DFun = fun(#stats{table=STable, server=Server} = Stat, _Acc)
3193 + when STable == Table, Server == VHost ->
3194 + mnesia:delete_object(stats_table(), Stat, write);
3195 + (_Stat, _Acc) -> ok
3197 + case mnesia:transaction(fun() ->
3198 + mnesia:write_lock_table(list_to_atom(Table)),
3199 + mnesia:write_lock_table(stats_table()),
3200 + % Calc stats for VHost at Date
3201 + AStats = mnesia:foldl(CFun, [], list_to_atom(Table)),
3202 + % Delete all stats for VHost at Date
3203 + mnesia:foldl(DFun, [], stats_table()),
3204 + % Write new calc'ed stats
3205 + lists:foreach(fun({Who, Count}) ->
3206 + Jid = jlib:string_to_jid(Who),
3207 + JUser = Jid#jid.user,
3208 + WStat = #stats{user=JUser, server=VHost, table=Table, count=Count},
3209 + mnesia:write(stats_table(), WStat, write)
3212 + {aborted, Reason} ->
3213 + ?ERROR_MSG("Failed to rebuild_stats_at for ~p at ~p: ~p", [VHost, Table, Reason]),
3219 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3221 +% gen_logdb callbacks (delete)
3223 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3224 +delete_messages_by_user_at(_VHost, _Msgs, _Date) ->
3227 +delete_all_messages_by_user_at(_User, _VHost, _Date) ->
3230 +delete_messages_at(VHost, Date) ->
3231 + Table = list_to_atom(tables_prefix() ++ Date),
3233 + DFun = fun(#msg{to_server=To_server, from_server=From_server}=Msg, _Acc)
3234 + when To_server == VHost; From_server == VHost ->
3235 + mnesia:delete_object(Table, Msg, write);
3236 + (_Msg, _Acc) -> ok
3239 + case mnesia:transaction(fun() ->
3240 + mnesia:foldl(DFun, [], Table)
3242 + {aborted, Reason} ->
3243 + ?ERROR_MSG("Failed to delete_messages_at for ~p at ~p: ~p", [VHost, Date, Reason]),
3249 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3251 +% gen_logdb callbacks (get)
3253 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3254 +get_vhost_stats(_VHost) ->
3255 + {error, "does not emplemented"}.
3257 +get_vhost_stats_at(VHost, Date) ->
3259 + Pat = #stats{user='$1', server=VHost, table=tables_prefix()++Date, count = '$2'},
3260 + mnesia:select(stats_table(), [{Pat, [], [['$1', '$2']]}])
3262 + case mnesia:transaction(Fun) of
3263 + {atomic, Result} ->
3264 + RFun = fun([User, Count]) ->
3267 + {ok, lists:reverse(lists:keysort(2, lists:map(RFun, Result)))};
3268 + {aborted, Reason} -> {error, Reason}
3271 +get_user_stats(_User, _VHost) ->
3272 + {error, "does not emplemented"}.
3274 +get_user_messages_at(User, VHost, Date) ->
3275 + Table_name = tables_prefix() ++ Date,
3276 + case mnesia:transaction(fun() ->
3277 + Pat_to = #msg{to_user=User, to_server=VHost, _='_'},
3278 + Pat_from = #msg{from_user=User, from_server=VHost, _='_'},
3279 + mnesia:select(list_to_atom(Table_name),
3280 + [{Pat_to, [], ['$_']},
3281 + {Pat_from, [], ['$_']}])
3283 + {atomic, Result} ->
3284 + Msgs = lists:map(fun(#msg{to_user=To_user, to_server=To_server, to_resource=To_res,
3285 + from_user=From_user, from_server=From_server, from_resource=From_res,
3288 + body=Body, timestamp=Timestamp} = _Msg) ->
3289 + Subject = case Subj of
3293 + {msg, To_user, To_server, To_res, From_user, From_server, From_res, Type, Subject, Body, Timestamp}
3296 + {aborted, Reason} ->
3300 +get_dates(_VHost) ->
3301 + Tables = mnesia:system_info(tables),
3303 + lists:filter(fun(Table) ->
3304 + lists:prefix(tables_prefix(), atom_to_list(Table))
3307 + lists:map(fun(Table) ->
3308 + lists:sublist(atom_to_list(Table),
3309 + length(tables_prefix())+1,
3310 + length(atom_to_list(Table)))
3314 +get_users_settings(_VHost) ->
3316 +get_user_settings(_User, _VHost) ->
3318 +set_user_settings(_User, _VHost, _Set) ->
3320 +drop_user(_User, _VHost) ->
3323 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3327 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3328 +% called from db_logon/2
3329 +create_stats_table() ->
3330 + SName = stats_table(),
3331 + case mnesia:create_table(SName,
3332 + [{disc_only_copies, [node()]},
3334 + {attributes, record_info(fields, stats)},
3335 + {record_name, stats}
3338 + ?INFO_MSG("Created stats table", []),
3340 + {aborted, {already_exists, _}} ->
3342 + {aborted, Reason} ->
3343 + ?ERROR_MSG("Failed to create stats table: ~p", [Reason]),
3346 diff --git a/src/mod_logdb_mysql.erl b/src/mod_logdb_mysql.erl
3347 new file mode 100644
3348 index 0000000..62f437c
3350 +++ b/src/mod_logdb_mysql.erl
3352 +%%%----------------------------------------------------------------------
3353 +%%% File : mod_logdb_mysql.erl
3354 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
3355 +%%% Purpose : MySQL backend for mod_logdb
3356 +%%% Version : trunk
3357 +%%% Id : $Id: mod_logdb_mysql.erl 1360 2009-07-30 06:00:14Z malik $
3358 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
3359 +%%%----------------------------------------------------------------------
3361 +-module(mod_logdb_mysql).
3362 +-author('o.palij@gmail.com').
3364 +-include("mod_logdb.hrl").
3365 +-include("ejabberd.hrl").
3366 +-include("jlib.hrl").
3367 +-include("logger.hrl").
3369 +-behaviour(gen_logdb).
3370 +-behaviour(gen_server).
3373 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
3375 +-export([start/2, stop/1]).
3377 +-export([log_message/2,
3379 + rebuild_stats_at/2,
3380 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
3381 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
3383 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
3386 +% gen_server call timeout
3387 +-define(CALL_TIMEOUT, 30000).
3388 +-define(MYSQL_TIMEOUT, 60000).
3389 +-define(INDEX_SIZE, integer_to_list(170)).
3390 +-define(PROCNAME, mod_logdb_mysql).
3392 +-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
3393 + list_to_string/1, string_to_list/1,
3394 + convert_timestamp_brief/1]).
3396 +-record(state, {dbref, vhost, server, port, db, user, password}).
3398 +% replace "." with "_"
3399 +escape_vhost(VHost) -> lists:map(fun(46) -> 95;
3401 + end, binary_to_list(VHost)).
3406 + "_" ++ escape_vhost(VHost) ++ "`".
3408 +messages_table(VHost, Date) ->
3409 + prefix() ++ "messages_" ++ Date ++ suffix(VHost).
3411 +stats_table(VHost) ->
3412 + prefix() ++ "stats" ++ suffix(VHost).
3414 +temp_table(VHost) ->
3415 + prefix() ++ "temp" ++ suffix(VHost).
3417 +settings_table(VHost) ->
3418 + prefix() ++ "settings" ++ suffix(VHost).
3420 +users_table(VHost) ->
3421 + prefix() ++ "users" ++ suffix(VHost).
3422 +servers_table(VHost) ->
3423 + prefix() ++ "servers" ++ suffix(VHost).
3424 +resources_table(VHost) ->
3425 + prefix() ++ "resources" ++ suffix(VHost).
3427 +ets_users_table(VHost) -> list_to_atom("logdb_users_" ++ binary_to_list(VHost)).
3428 +ets_servers_table(VHost) -> list_to_atom("logdb_servers_" ++ binary_to_list(VHost)).
3429 +ets_resources_table(VHost) -> list_to_atom("logdb_resources_" ++ binary_to_list(VHost)).
3431 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3433 +% gen_mod callbacks
3435 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3436 +start(VHost, Opts) ->
3437 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3438 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
3441 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3442 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
3444 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3446 +% gen_server callbacks
3448 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3449 +init([VHost, Opts]) ->
3452 + Server = gen_mod:get_opt(server, Opts, fun(A) -> A end, <<"localhost">>),
3453 + Port = gen_mod:get_opt(port, Opts, fun(A) -> A end, 3306),
3454 + DB = gen_mod:get_opt(db, Opts, fun(A) -> A end, <<"logdb">>),
3455 + User = gen_mod:get_opt(user, Opts, fun(A) -> A end, <<"root">>),
3456 + Password = gen_mod:get_opt(password, Opts, fun(A) -> A end, <<"">>),
3458 + St = #state{vhost=VHost,
3459 + server=Server, port=Port, db=DB,
3460 + user=User, password=Password},
3462 + case open_mysql_connection(St) of
3464 + State = St#state{dbref=DBRef},
3465 + ok = create_stats_table(State),
3466 + ok = create_settings_table(State),
3467 + ok = create_users_table(State),
3468 + % clear ets cache every ...
3469 + timer:send_interval(timer:hours(12), clear_ets_tables),
3470 + ok = create_servers_table(State),
3471 + ok = create_resources_table(State),
3472 + erlang:monitor(process, DBRef),
3474 + {error, Reason} ->
3475 + ?ERROR_MSG("MySQL connection failed: ~p~n", [Reason]),
3476 + {stop, db_connection_failed}
3479 +open_mysql_connection(#state{server=Server, port=Port, db=DB,
3480 + user=DBUser, password=Password} = _State) ->
3481 + LogFun = fun(debug, _Format, _Argument) ->
3482 + %?MYDEBUG(Format, Argument);
3484 + (error, Format, Argument) ->
3485 + ?ERROR_MSG(Format, Argument);
3486 + (Level, Format, Argument) ->
3487 + ?MYDEBUG("MySQL (~p)~n", [Level]),
3488 + ?MYDEBUG(Format, Argument)
3490 + ?INFO_MSG("Opening mysql connection ~s@~s:~p/~s", [DBUser, Server, Port, DB]),
3491 + p1_mysql_conn:start(binary_to_list(Server), Port,
3492 + binary_to_list(DBUser), binary_to_list(Password),
3493 + binary_to_list(DB), LogFun).
3495 +close_mysql_connection(DBRef) ->
3496 + ?MYDEBUG("Closing ~p mysql connection", [DBRef]),
3497 + catch p1_mysql_conn:stop(DBRef).
3499 +handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3500 + Date = convert_timestamp_brief(Msg#msg.timestamp),
3502 + Table = messages_table(VHost, Date),
3503 + Owner_id = get_user_id(DBRef, VHost, binary_to_list(Msg#msg.owner_name)),
3504 + Peer_name_id = get_user_id(DBRef, VHost, binary_to_list(Msg#msg.peer_name)),
3505 + Peer_server_id = get_server_id(DBRef, VHost, binary_to_list(Msg#msg.peer_server)),
3506 + Peer_resource_id = get_resource_id(DBRef, VHost, binary_to_list(Msg#msg.peer_resource)),
3508 + Query = ["INSERT INTO ",Table," ",
3511 + "peer_server_id,",
3512 + "peer_resource_id,",
3519 + "('", Owner_id, "',",
3520 + "'", Peer_name_id, "',",
3521 + "'", Peer_server_id, "',",
3522 + "'", Peer_resource_id, "',",
3523 + "'", atom_to_list(Msg#msg.direction), "',",
3524 + "'", binary_to_list(Msg#msg.type), "',",
3525 + "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.subject) ), "',",
3526 + "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.body) ), "',",
3527 + "'", Msg#msg.timestamp, "');"],
3530 + case sql_query_internal_silent(DBRef, Query) of
3532 + ?MYDEBUG("Logged ok for ~s, peer: ~s", [ [Msg#msg.owner_name, <<"@">>, VHost],
3533 + [Msg#msg.peer_name, <<"@">>, Msg#msg.peer_server] ]),
3534 + increment_user_stats(DBRef, Msg#msg.owner_name, Owner_id, VHost, Peer_name_id, Peer_server_id, Date);
3535 + {error, Reason} ->
3536 + case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#42S02">>) of
3537 + % Table doesn't exist
3539 + case create_msg_table(DBRef, VHost, Date) of
3543 + {updated, _} = sql_query_internal(DBRef, Query),
3544 + increment_user_stats(DBRef, binary_to_list(Msg#msg.owner_name), Owner_id, VHost, Peer_name_id, Peer_server_id, Date)
3547 + ?ERROR_MSG("Failed to log message: ~p", [Reason]),
3551 + {reply, Reply, State};
3552 +handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3553 + Reply = rebuild_stats_at_int(DBRef, VHost, Date),
3554 + {reply, Reply, State};
3555 +handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
3556 + {reply, error, State};
3557 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3558 + Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
3559 + ["\"",Timestamp,"\"",","]
3562 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
3564 + Query = ["DELETE FROM ",messages_table(VHost, Date)," ",
3565 + "WHERE timestamp IN (", Temp1],
3568 + case sql_query_internal(DBRef, Query) of
3570 + ?MYDEBUG("Aff=~p", [Aff]),
3571 + rebuild_stats_at_int(DBRef, VHost, Date);
3575 + {reply, Reply, State};
3576 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3577 + ok = delete_all_messages_by_user_at_int(DBRef, User, VHost, Date),
3578 + ok = delete_stats_by_user_at_int(DBRef, User, VHost, Date),
3579 + {reply, ok, State};
3580 +handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3582 + case sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Date),";"]) of
3584 + Query = ["DELETE FROM ",stats_table(VHost)," "
3585 + "WHERE at=\"",Date,"\";"],
3586 + case sql_query_internal(DBRef, Query) of
3595 + {reply, Reply, State};
3596 +handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3597 + SName = stats_table(VHost),
3598 + Query = ["SELECT at, sum(count) ",
3599 + "FROM ",SName," ",
3601 + "ORDER BY DATE(at) DESC;"
3604 + case sql_query_internal(DBRef, Query) of
3606 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
3607 + {error, Reason} ->
3608 + % TODO: Duplicate error message ?
3611 + {reply, Reply, State};
3612 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3613 + SName = stats_table(VHost),
3614 + Query = ["SELECT username, sum(count) AS allcount ",
3615 + "FROM ",SName," ",
3616 + "JOIN ",users_table(VHost)," ON owner_id=user_id "
3617 + "WHERE at=\"",Date,"\" "
3618 + "GROUP BY username ",
3619 + "ORDER BY allcount DESC;"
3622 + case sql_query_internal(DBRef, Query) of
3624 + {ok, lists:reverse(
3626 + [ {User, list_to_integer(Count)} || [User, Count] <- Result]))};
3627 + {error, Reason} ->
3631 + {reply, Reply, State};
3632 +handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3633 + {reply, get_user_stats_int(DBRef, User, VHost), State};
3634 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3635 + TName = messages_table(VHost, Date),
3636 + UName = users_table(VHost),
3637 + SName = servers_table(VHost),
3638 + RName = resources_table(VHost),
3639 + Query = ["SELECT users.username,",
3640 + "servers.server,",
3641 + "resources.resource,",
3642 + "messages.direction,"
3644 + "messages.subject,"
3646 + "messages.timestamp "
3647 + "FROM ",TName," AS messages "
3648 + "JOIN ",UName," AS users ON peer_name_id=user_id ",
3649 + "JOIN ",SName," AS servers ON peer_server_id=server_id ",
3650 + "JOIN ",RName," AS resources ON peer_resource_id=resource_id ",
3651 + "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\" ",
3652 + "ORDER BY timestamp ASC;"],
3654 + case sql_query_internal(DBRef, Query) of
3656 + Fun = fun([Peer_name, Peer_server, Peer_resource,
3661 + #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
3662 + direction=list_to_atom(Direction),
3664 + subject=Subject, body=Body,
3665 + timestamp=Timestamp}
3667 + {ok, lists:map(Fun, Result)};
3668 + {error, Reason} ->
3671 + {reply, Reply, State};
3672 +handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3673 + SName = stats_table(VHost),
3674 + Query = ["SELECT at ",
3675 + "FROM ",SName," ",
3677 + "ORDER BY DATE(at) DESC;"
3680 + case sql_query_internal(DBRef, Query) of
3682 + [ Date || [Date] <- Result ];
3683 + {error, Reason} ->
3686 + {reply, Reply, State};
3687 +handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3688 + Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
3689 + "FROM ",settings_table(VHost)," ",
3690 + "JOIN ",users_table(VHost)," ON user_id=owner_id;"],
3692 + case sql_query_internal(DBRef, Query) of
3694 + {ok, lists:map(fun([Owner, DoLogDef, DoLogL, DoNotLogL]) ->
3695 + #user_settings{owner_name=Owner,
3696 + dolog_default=list_to_bool(DoLogDef),
3697 + dolog_list=string_to_list(DoLogL),
3698 + donotlog_list=string_to_list(DoNotLogL)
3704 + {reply, Reply, State};
3705 +handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3706 + Query = ["SELECT dolog_default,dolog_list,donotlog_list FROM ",settings_table(VHost)," ",
3707 + "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\";"],
3709 + case sql_query_internal(DBRef, Query) of
3712 + {data, [[Owner, DoLogDef, DoLogL, DoNotLogL]]} ->
3713 + {ok, #user_settings{owner_name=Owner,
3714 + dolog_default=list_to_bool(DoLogDef),
3715 + dolog_list=string_to_list(DoLogL),
3716 + donotlog_list=string_to_list(DoNotLogL)}};
3720 + {reply, Reply, State};
3721 +handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
3722 + dolog_list=DoLogL,
3723 + donotlog_list=DoNotLogL}},
3724 + _From, #state{dbref=DBRef, vhost=VHost} = State) ->
3725 + User_id = get_user_id(DBRef, VHost, User),
3727 + Query = ["UPDATE ",settings_table(VHost)," ",
3728 + "SET dolog_default=",bool_to_list(DoLogDef),", ",
3729 + "dolog_list='",list_to_string(DoLogL),"', ",
3730 + "donotlog_list='",list_to_string(DoNotLogL),"' ",
3731 + "WHERE owner_id=\"",User_id,"\";"],
3734 + case sql_query_internal(DBRef, Query) of
3736 + IQuery = ["INSERT INTO ",settings_table(VHost)," ",
3737 + "(owner_id, dolog_default, dolog_list, donotlog_list) ",
3739 + "('",User_id,"', ",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
3740 + case sql_query_internal_silent(DBRef, IQuery) of
3742 + ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
3744 + {error, Reason} ->
3745 + case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>) of
3750 + ?ERROR_MSG("Failed setup user ~p@~p: ~p", [User, VHost, Reason]),
3755 + ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
3760 + {reply, Reply, State};
3761 +handle_call({stop}, _From, #state{vhost=VHost}=State) ->
3762 + ets:delete(ets_users_table(VHost)),
3763 + ets:delete(ets_servers_table(VHost)),
3764 + ?MYDEBUG("Stoping mysql backend for ~p", [VHost]),
3765 + {stop, normal, ok, State};
3766 +handle_call(Msg, _From, State) ->
3767 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
3770 +handle_cast({rebuild_stats}, State) ->
3771 + rebuild_all_stats_int(State),
3773 +handle_cast({drop_user, User}, #state{vhost=VHost} = State) ->
3775 + {ok, DBRef} = open_mysql_connection(State),
3776 + {ok, Dates} = get_user_stats_int(DBRef, User, VHost),
3777 + MDResult = lists:map(fun({Date, _}) ->
3778 + delete_all_messages_by_user_at_int(DBRef, User, VHost, Date)
3780 + StDResult = delete_all_stats_by_user_int(DBRef, User, VHost),
3781 + SDResult = delete_user_settings_int(DBRef, User, VHost),
3782 + case lists:all(fun(Result) when Result == ok ->
3784 + (Result) when Result == error ->
3786 + end, lists:append([MDResult, [StDResult], [SDResult]])) of
3788 + ?INFO_MSG("Removed ~s@~s", [User, VHost]);
3790 + ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost])
3792 + close_mysql_connection(DBRef)
3796 +handle_cast(Msg, State) ->
3797 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
3800 +handle_info(clear_ets_tables, State) ->
3801 + ets:delete_all_objects(ets_users_table(State#state.vhost)),
3802 + ets:delete_all_objects(ets_resources_table(State#state.vhost)),
3804 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
3805 + {stop, connection_dropped, State};
3806 +handle_info(Info, State) ->
3807 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
3810 +terminate(_Reason, #state{dbref=DBRef}=_State) ->
3811 + close_mysql_connection(DBRef),
3814 +code_change(_OldVsn, State, _Extra) ->
3817 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3819 +% gen_logdb callbacks
3821 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3822 +log_message(VHost, Msg) ->
3823 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3824 + gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
3825 +rebuild_stats(VHost) ->
3826 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3827 + gen_server:cast(Proc, {rebuild_stats}).
3828 +rebuild_stats_at(VHost, Date) ->
3829 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3830 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
3831 +delete_messages_by_user_at(VHost, Msgs, Date) ->
3832 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3833 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
3834 +delete_all_messages_by_user_at(User, VHost, Date) ->
3835 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3836 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
3837 +delete_messages_at(VHost, Date) ->
3838 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3839 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
3840 +get_vhost_stats(VHost) ->
3841 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3842 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
3843 +get_vhost_stats_at(VHost, Date) ->
3844 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3845 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
3846 +get_user_stats(User, VHost) ->
3847 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3848 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
3849 +get_user_messages_at(User, VHost, Date) ->
3850 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3851 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
3852 +get_dates(VHost) ->
3853 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3854 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
3855 +get_users_settings(VHost) ->
3856 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3857 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
3858 +get_user_settings(User, VHost) ->
3859 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3860 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
3861 +set_user_settings(User, VHost, Set) ->
3862 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3863 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
3864 +drop_user(User, VHost) ->
3865 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3866 + gen_server:cast(Proc, {drop_user, User}).
3868 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3872 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3873 +increment_user_stats(DBRef, User_name, User_id, VHost, PNameID, PServerID, Date) ->
3874 + SName = stats_table(VHost),
3875 + UQuery = ["UPDATE ",SName," ",
3876 + "SET count=count+1 ",
3877 + "WHERE owner_id=\"",User_id,"\" AND peer_name_id=\"",PNameID,"\" AND peer_server_id=\"",PServerID,"\" AND at=\"",Date,"\";"],
3879 + case sql_query_internal(DBRef, UQuery) of
3881 + IQuery = ["INSERT INTO ",SName," ",
3882 + "(owner_id, peer_name_id, peer_server_id, at, count) ",
3884 + "('",User_id,"', '",PNameID,"', '",PServerID,"', '",Date,"', '1');"],
3885 + case sql_query_internal(DBRef, IQuery) of
3887 + ?MYDEBUG("New stats for ~s@~s at ~s", [User_name, VHost, Date]),
3893 + ?MYDEBUG("Updated stats for ~s@~s at ~s", [User_name, VHost, Date]),
3899 +get_dates_int(DBRef, VHost) ->
3900 + case sql_query_internal(DBRef, ["SHOW TABLES"]) of
3902 + lists:foldl(fun([Table], Dates) ->
3903 + Reg = lists:sublist(prefix(),2,length(prefix())) ++ ".*" ++ escape_vhost(VHost),
3904 + case re:run(Table, Reg) of
3905 + {match, [{1, _}]} ->
3906 + ?MYDEBUG("matched ~p against ~p", [Table, Reg]),
3907 + case re:run(Table,"[0-9]+-[0-9]+-[0-9]+") of
3908 + {match, [{S, E}]} ->
3909 + lists:append(Dates, [lists:sublist(Table,S,E)]);
3921 +rebuild_all_stats_int(#state{vhost=VHost}=State) ->
3923 + {ok, DBRef} = open_mysql_connection(State),
3924 + ok = delete_nonexistent_stats(DBRef, VHost),
3925 + case lists:filter(fun(Date) ->
3926 + case catch rebuild_stats_at_int(DBRef, VHost, Date) of
3929 + {'EXIT', _} -> true
3931 + end, get_dates_int(DBRef, VHost)) of
3934 + ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]),
3937 + close_mysql_connection(DBRef)
3941 +rebuild_stats_at_int(DBRef, VHost, Date) ->
3942 + TempTable = temp_table(VHost),
3944 + Table = messages_table(VHost, Date),
3945 + STable = stats_table(VHost),
3947 + DQuery = [ "DELETE FROM ",STable," ",
3948 + "WHERE at='",Date,"';"],
3950 + ok = create_temp_table(DBRef, TempTable),
3951 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," WRITE, ",TempTable," WRITE;"]),
3952 + SQuery = ["INSERT INTO ",TempTable," ",
3953 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
3954 + "SELECT owner_id,peer_name_id,peer_server_id,\"",Date,"\",count(*) ",
3955 + "FROM ",Table," GROUP BY owner_id,peer_name_id,peer_server_id;"],
3956 + case sql_query_internal(DBRef, SQuery) of
3958 + Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]),
3960 + {data, [["0"]]} ->
3961 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]),
3962 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE;"]),
3963 + {updated, _} = sql_query_internal(DBRef, DQuery),
3966 + ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]),
3970 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE, ",TempTable," WRITE;"]),
3971 + {updated, _} = sql_query_internal(DBRef, DQuery),
3972 + SQuery1 = ["INSERT INTO ",STable," ",
3973 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
3974 + "SELECT owner_id,peer_name_id,peer_server_id,at,count ",
3975 + "FROM ",TempTable,";"],
3976 + case sql_query_internal(DBRef, SQuery1) of
3977 + {updated, _} -> ok;
3978 + {error, _} -> error
3980 + {error, _} -> error
3984 + case catch apply(Fun, []) of
3986 + ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
3990 + {'EXIT', Reason} ->
3991 + ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]),
3994 + sql_query_internal(DBRef, ["UNLOCK TABLES;"]),
3995 + sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]),
3999 +delete_nonexistent_stats(DBRef, VHost) ->
4000 + Dates = get_dates_int(DBRef, VHost),
4001 + STable = stats_table(VHost),
4003 + Temp = lists:flatmap(fun(Date) ->
4004 + ["\"",Date,"\"",","]
4011 + % replace last "," with ");"
4012 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
4013 + Query = ["DELETE FROM ",STable," ",
4014 + "WHERE at NOT IN (", Temp1],
4015 + case sql_query_internal(DBRef, Query) of
4023 +get_user_stats_int(DBRef, User, VHost) ->
4024 + SName = stats_table(VHost),
4025 + Query = ["SELECT at, sum(count) as allcount ",
4026 + "FROM ",SName," ",
4027 + "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\" ",
4029 + "ORDER BY DATE(at) DESC;"
4031 + case sql_query_internal(DBRef, Query) of
4033 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result]};
4034 + {error, Result} ->
4038 +delete_all_messages_by_user_at_int(DBRef, User, VHost, Date) ->
4039 + DQuery = ["DELETE FROM ",messages_table(VHost, Date)," ",
4040 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4041 + case sql_query_internal(DBRef, DQuery) of
4043 + ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]),
4049 +delete_all_stats_by_user_int(DBRef, User, VHost) ->
4050 + SQuery = ["DELETE FROM ",stats_table(VHost)," ",
4051 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4052 + case sql_query_internal(DBRef, SQuery) of
4054 + ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]),
4056 + {error, _} -> error
4059 +delete_stats_by_user_at_int(DBRef, User, VHost, Date) ->
4060 + SQuery = ["DELETE FROM ",stats_table(VHost)," ",
4061 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\") ",
4062 + "AND at=\"",Date,"\";"],
4063 + case sql_query_internal(DBRef, SQuery) of
4065 + ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]),
4067 + {error, _} -> error
4070 +delete_user_settings_int(DBRef, User, VHost) ->
4071 + Query = ["DELETE FROM ",settings_table(VHost)," ",
4072 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4073 + case sql_query_internal(DBRef, Query) of
4075 + ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]),
4077 + {error, Reason} ->
4078 + ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]),
4082 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4086 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4087 +create_temp_table(DBRef, Name) ->
4088 + Query = ["CREATE TABLE ",Name," (",
4089 + "owner_id MEDIUMINT UNSIGNED, ",
4090 + "peer_name_id MEDIUMINT UNSIGNED, ",
4091 + "peer_server_id MEDIUMINT UNSIGNED, ",
4092 + "at VARCHAR(11), ",
4094 + ") ENGINE=MyISAM CHARACTER SET utf8;"
4096 + case sql_query_internal(DBRef, Query) of
4097 + {updated, _} -> ok;
4098 + {error, _Reason} -> error
4101 +create_stats_table(#state{dbref=DBRef, vhost=VHost}=State) ->
4102 + SName = stats_table(VHost),
4103 + Query = ["CREATE TABLE ",SName," (",
4104 + "owner_id MEDIUMINT UNSIGNED, ",
4105 + "peer_name_id MEDIUMINT UNSIGNED, ",
4106 + "peer_server_id MEDIUMINT UNSIGNED, ",
4107 + "at varchar(20), ",
4108 + "count int(11), ",
4109 + "INDEX(owner_id, peer_name_id, peer_server_id), ",
4111 + ") ENGINE=InnoDB CHARACTER SET utf8;"
4113 + case sql_query_internal_silent(DBRef, Query) of
4115 + ?INFO_MSG("Created stats table for ~p", [VHost]),
4116 + rebuild_all_stats_int(State),
4118 + {error, Reason} ->
4119 + case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#42S01">>) of
4121 + ?MYDEBUG("Stats table for ~p already exists", [VHost]),
4122 + CheckQuery = ["SHOW COLUMNS FROM ",SName," LIKE 'peer_%_id';"],
4123 + case sql_query_internal(DBRef, CheckQuery) of
4124 + {data, Elems} when length(Elems) == 2 ->
4125 + ?MYDEBUG("Stats table structure is ok", []),
4128 + ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []),
4129 + case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of
4131 + ?INFO_MSG("Successfully dropped ~p", [SName]);
4133 + ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName])
4138 + ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
4143 +create_settings_table(#state{dbref=DBRef, vhost=VHost}) ->
4144 + SName = settings_table(VHost),
4145 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
4146 + "owner_id MEDIUMINT UNSIGNED PRIMARY KEY, ",
4147 + "dolog_default TINYINT(1) NOT NULL DEFAULT 1, ",
4148 + "dolog_list TEXT, ",
4149 + "donotlog_list TEXT ",
4150 + ") ENGINE=InnoDB CHARACTER SET utf8;"
4152 + case sql_query_internal(DBRef, Query) of
4154 + ?MYDEBUG("Created settings table for ~p", [VHost]),
4160 +create_users_table(#state{dbref=DBRef, vhost=VHost}) ->
4161 + SName = users_table(VHost),
4162 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
4163 + "username TEXT NOT NULL, ",
4164 + "user_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
4165 + "UNIQUE INDEX(username(",?INDEX_SIZE,")) ",
4166 + ") ENGINE=InnoDB CHARACTER SET utf8;"
4168 + case sql_query_internal(DBRef, Query) of
4170 + ?MYDEBUG("Created users table for ~p", [VHost]),
4171 + ets:new(ets_users_table(VHost), [named_table, set, public]),
4172 + %update_users_from_db(DBRef, VHost),
4178 +create_servers_table(#state{dbref=DBRef, vhost=VHost}) ->
4179 + SName = servers_table(VHost),
4180 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
4181 + "server TEXT NOT NULL, ",
4182 + "server_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
4183 + "UNIQUE INDEX(server(",?INDEX_SIZE,")) ",
4184 + ") ENGINE=InnoDB CHARACTER SET utf8;"
4186 + case sql_query_internal(DBRef, Query) of
4188 + ?MYDEBUG("Created servers table for ~p", [VHost]),
4189 + ets:new(ets_servers_table(VHost), [named_table, set, public]),
4190 + update_servers_from_db(DBRef, VHost),
4196 +create_resources_table(#state{dbref=DBRef, vhost=VHost}) ->
4197 + RName = resources_table(VHost),
4198 + Query = ["CREATE TABLE IF NOT EXISTS ",RName," (",
4199 + "resource TEXT NOT NULL, ",
4200 + "resource_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
4201 + "UNIQUE INDEX(resource(",?INDEX_SIZE,")) ",
4202 + ") ENGINE=InnoDB CHARACTER SET utf8;"
4204 + case sql_query_internal(DBRef, Query) of
4206 + ?MYDEBUG("Created resources table for ~p", [VHost]),
4207 + ets:new(ets_resources_table(VHost), [named_table, set, public]),
4213 +create_msg_table(DBRef, VHost, Date) ->
4214 + TName = messages_table(VHost, Date),
4215 + Query = ["CREATE TABLE ",TName," (",
4216 + "owner_id MEDIUMINT UNSIGNED, ",
4217 + "peer_name_id MEDIUMINT UNSIGNED, ",
4218 + "peer_server_id MEDIUMINT UNSIGNED, ",
4219 + "peer_resource_id MEDIUMINT(8) UNSIGNED, ",
4220 + "direction ENUM('to', 'from'), ",
4221 + "type ENUM('chat','error','groupchat','headline','normal') NOT NULL, ",
4224 + "timestamp DOUBLE, ",
4225 + "INDEX search_i (owner_id, peer_name_id, peer_server_id, peer_resource_id), ",
4226 + "FULLTEXT (body) "
4227 + ") ENGINE=MyISAM CHARACTER SET utf8;"
4229 + case sql_query_internal(DBRef, Query) of
4230 + {updated, _MySQLRes} ->
4231 + ?MYDEBUG("Created msg table for ~p at ~p", [VHost, Date]),
4237 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4239 +% internal ets cache (users, servers, resources)
4241 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4242 +update_servers_from_db(DBRef, VHost) ->
4243 + ?INFO_MSG("Reading servers from db for ~p", [VHost]),
4244 + SQuery = ["SELECT server, server_id FROM ",servers_table(VHost),";"],
4245 + {data, Result} = sql_query_internal(DBRef, SQuery),
4246 + true = ets:delete_all_objects(ets_servers_table(VHost)),
4247 + true = ets:insert(ets_servers_table(VHost), [ {Server, Server_id} || [Server, Server_id] <- Result]).
4249 +%update_users_from_db(DBRef, VHost) ->
4250 +% ?INFO_MSG("Reading users from db for ~p", [VHost]),
4251 +% SQuery = ["SELECT username, user_id FROM ",users_table(VHost),";"],
4252 +% {data, Result} = sql_query_internal(DBRef, SQuery),
4253 +% true = ets:delete_all_objects(ets_users_table(VHost)),
4254 +% true = ets:insert(ets_users_table(VHost), [ {Username, User_id} || [Username, User_id] <- Result]).
4256 +%get_user_name(DBRef, VHost, User_id) ->
4257 +% case ets:match(ets_users_table(VHost), {'$1', User_id}) of
4258 +% [[User]] -> User;
4259 +% % this can be in clustered environment
4261 +% %update_users_from_db(DBRef, VHost),
4262 +% SQuery = ["SELECT username FROM ",users_table(VHost)," ",
4263 +% "WHERE user_id=\"",User_id,"\";"],
4264 +% {data, [[Name]]} = sql_query_internal(DBRef, SQuery),
4265 +% % cache {user, id} pair
4266 +% ets:insert(ets_users_table(VHost), {Name, User_id}),
4270 +%get_server_name(DBRef, VHost, Server_id) ->
4271 +% case ets:match(ets_servers_table(VHost), {'$1', Server_id}) of
4272 +% [[Server]] -> Server;
4273 + % this can be in clustered environment
4275 +% update_servers_from_db(DBRef, VHost),
4276 +% [[Server1]] = ets:match(ets_servers_table(VHost), {'$1', Server_id}),
4280 +get_user_id_from_db(DBRef, VHost, User) ->
4281 + SQuery = ["SELECT user_id FROM ",users_table(VHost)," ",
4282 + "WHERE username=\"",User,"\";"],
4283 + case sql_query_internal(DBRef, SQuery) of
4284 + % no such user in db
4287 + {data, [[DBId]]} ->
4288 + % cache {user, id} pair
4289 + ets:insert(ets_users_table(VHost), {User, DBId}),
4292 +get_user_id(DBRef, VHost, User) ->
4294 + case ets:match(ets_users_table(VHost), {User, '$1'}) of
4297 + case get_user_id_from_db(DBRef, VHost, User) of
4298 + % no such user in db
4300 + IQuery = ["INSERT INTO ",users_table(VHost)," ",
4301 + "SET username=\"",User,"\";"],
4302 + case sql_query_internal_silent(DBRef, IQuery) of
4304 + {ok, NewId} = get_user_id_from_db(DBRef, VHost, User),
4306 + {error, Reason} ->
4307 + % this can be in clustered environment
4308 + match = ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>),
4309 + ?ERROR_MSG("Duplicate key name for ~p", [User]),
4310 + {ok, ClID} = get_user_id_from_db(DBRef, VHost, User),
4316 + [[EtsId]] -> EtsId
4319 +get_server_id(DBRef, VHost, Server) ->
4320 + case ets:match(ets_servers_table(VHost), {Server, '$1'}) of
4322 + IQuery = ["INSERT INTO ",servers_table(VHost)," ",
4323 + "SET server=\"",Server,"\";"],
4324 + case sql_query_internal_silent(DBRef, IQuery) of
4326 + SQuery = ["SELECT server_id FROM ",servers_table(VHost)," ",
4327 + "WHERE server=\"",Server,"\";"],
4328 + {data, [[Id]]} = sql_query_internal(DBRef, SQuery),
4329 + ets:insert(ets_servers_table(VHost), {Server, Id}),
4331 + {error, Reason} ->
4332 + % this can be in clustered environment
4333 + match = ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>),
4334 + ?ERROR_MSG("Duplicate key name for ~p", [Server]),
4335 + update_servers_from_db(DBRef, VHost),
4336 + [[Id1]] = ets:match(ets_servers_table(VHost), {Server, '$1'}),
4342 +get_resource_id_from_db(DBRef, VHost, Resource) ->
4343 + SQuery = ["SELECT resource_id FROM ",resources_table(VHost)," ",
4344 + "WHERE resource=\"",binary_to_list(ejabberd_odbc:escape(iolist_to_binary(Resource))),"\";"],
4345 + case sql_query_internal(DBRef, SQuery) of
4346 + % no such resource in db
4349 + {data, [[DBId]]} ->
4350 + % cache {resource, id} pair
4351 + ets:insert(ets_resources_table(VHost), {Resource, DBId}),
4354 +get_resource_id(DBRef, VHost, Resource) ->
4356 + case ets:match(ets_resources_table(VHost), {Resource, '$1'}) of
4359 + case get_resource_id_from_db(DBRef, VHost, Resource) of
4360 + % no such resource in db
4362 + IQuery = ["INSERT INTO ",resources_table(VHost)," ",
4363 + "SET resource=\"",binary_to_list(ejabberd_odbc:escape(iolist_to_binary(Resource))),"\";"],
4364 + case sql_query_internal_silent(DBRef, IQuery) of
4366 + {ok, NewId} = get_resource_id_from_db(DBRef, VHost, Resource),
4368 + {error, Reason} ->
4369 + % this can be in clustered environment
4370 + match = ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>),
4371 + ?ERROR_MSG("Duplicate key name for ~s", [Resource]),
4372 + {ok, ClID} = get_resource_id_from_db(DBRef, VHost, Resource),
4378 + [[EtsId]] -> EtsId
4381 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4385 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4386 +sql_query_internal(DBRef, Query) ->
4387 + case sql_query_internal_silent(DBRef, Query) of
4388 + {error, Reason} ->
4389 + ?ERROR_MSG("~p while ~p", [Reason, lists:append(Query)]),
4394 +sql_query_internal_silent(DBRef, Query) ->
4395 + ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
4396 + get_result(p1_mysql_conn:fetch(DBRef, Query, self(), ?MYSQL_TIMEOUT)).
4398 +get_result({updated, MySQLRes}) ->
4399 + {updated, p1_mysql:get_result_affected_rows(MySQLRes)};
4400 +get_result({data, MySQLRes}) ->
4401 + {data, p1_mysql:get_result_rows(MySQLRes)};
4402 +get_result({error, "query timed out"}) ->
4403 + {error, "query timed out"};
4404 +get_result({error, MySQLRes}) ->
4405 + Reason = p1_mysql:get_result_reason(MySQLRes),
4407 diff --git a/src/mod_logdb_mysql5.erl b/src/mod_logdb_mysql5.erl
4408 new file mode 100644
4409 index 0000000..d1f399f
4411 +++ b/src/mod_logdb_mysql5.erl
4413 +%%%----------------------------------------------------------------------
4414 +%%% File : mod_logdb_mysql5.erl
4415 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
4416 +%%% Purpose : MySQL 5 backend for mod_logdb
4417 +%%% Version : trunk
4418 +%%% Id : $Id: mod_logdb_mysql5.erl 1360 2009-07-30 06:00:14Z malik $
4419 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
4420 +%%%----------------------------------------------------------------------
4422 +-module(mod_logdb_mysql5).
4423 +-author('o.palij@gmail.com').
4425 +-include("mod_logdb.hrl").
4426 +-include("ejabberd.hrl").
4427 +-include("jlib.hrl").
4428 +-include("logger.hrl").
4430 +-behaviour(gen_logdb).
4431 +-behaviour(gen_server).
4434 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
4436 +-export([start/2, stop/1]).
4438 +-export([log_message/2,
4440 + rebuild_stats_at/2,
4441 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
4442 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
4444 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
4447 +% gen_server call timeout
4448 +-define(CALL_TIMEOUT, 30000).
4449 +-define(MYSQL_TIMEOUT, 60000).
4450 +-define(INDEX_SIZE, integer_to_list(170)).
4451 +-define(PROCNAME, mod_logdb_mysql5).
4453 +-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
4454 + list_to_string/1, string_to_list/1,
4455 + convert_timestamp_brief/1]).
4457 +-record(state, {dbref, vhost, server, port, db, user, password}).
4459 +% replace "." with "_"
4460 +escape_vhost(VHost) -> lists:map(fun(46) -> 95;
4462 + end, binary_to_list(VHost)).
4467 + "_" ++ escape_vhost(VHost) ++ "`".
4469 +messages_table(VHost, Date) ->
4470 + prefix() ++ "messages_" ++ Date ++ suffix(VHost).
4472 +% TODO: this needs to be redone to unify view name in stored procedure and in delete_messages_at/2
4473 +view_table(VHost, Date) ->
4474 + Table = messages_table(VHost, Date),
4475 + TablewoQ = lists:sublist(Table, 2, length(Table) - 2),
4476 + lists:append(["`v_", TablewoQ, "`"]).
4478 +stats_table(VHost) ->
4479 + prefix() ++ "stats" ++ suffix(VHost).
4481 +temp_table(VHost) ->
4482 + prefix() ++ "temp" ++ suffix(VHost).
4484 +settings_table(VHost) ->
4485 + prefix() ++ "settings" ++ suffix(VHost).
4487 +users_table(VHost) ->
4488 + prefix() ++ "users" ++ suffix(VHost).
4489 +servers_table(VHost) ->
4490 + prefix() ++ "servers" ++ suffix(VHost).
4491 +resources_table(VHost) ->
4492 + prefix() ++ "resources" ++ suffix(VHost).
4494 +logmessage_name(VHost) ->
4495 + prefix() ++ "logmessage" ++ suffix(VHost).
4497 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4499 +% gen_mod callbacks
4501 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4502 +start(VHost, Opts) ->
4503 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4504 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
4507 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4508 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
4510 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4512 +% gen_server callbacks
4514 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4515 +init([VHost, Opts]) ->
4518 + Server = gen_mod:get_opt(server, Opts, fun(A) -> A end, <<"localhost">>),
4519 + Port = gen_mod:get_opt(port, Opts, fun(A) -> A end, 3306),
4520 + DB = gen_mod:get_opt(db, Opts, fun(A) -> A end, <<"logdb">>),
4521 + User = gen_mod:get_opt(user, Opts, fun(A) -> A end, <<"root">>),
4522 + Password = gen_mod:get_opt(password, Opts, fun(A) -> A end, <<"">>),
4524 + St = #state{vhost=VHost,
4525 + server=Server, port=Port, db=DB,
4526 + user=User, password=Password},
4528 + case open_mysql_connection(St) of
4530 + State = St#state{dbref=DBRef},
4531 + ok = create_internals(State),
4532 + ok = create_stats_table(State),
4533 + ok = create_settings_table(State),
4534 + ok = create_users_table(State),
4535 + ok = create_servers_table(State),
4536 + ok = create_resources_table(State),
4537 + erlang:monitor(process, DBRef),
4539 + {error, Reason} ->
4540 + ?ERROR_MSG("MySQL connection failed: ~p~n", [Reason]),
4541 + {stop, db_connection_failed}
4544 +open_mysql_connection(#state{server=Server, port=Port, db=DB,
4545 + user=DBUser, password=Password} = _State) ->
4546 + LogFun = fun(debug, _Format, _Argument) ->
4547 + %?MYDEBUG(Format, Argument);
4549 + (error, Format, Argument) ->
4550 + ?ERROR_MSG(Format, Argument);
4551 + (Level, Format, Argument) ->
4552 + ?MYDEBUG("MySQL (~p)~n", [Level]),
4553 + ?MYDEBUG(Format, Argument)
4555 + ?INFO_MSG("Opening mysql connection ~s@~s:~p/~s", [DBUser, Server, Port, DB]),
4556 + p1_mysql_conn:start(binary_to_list(Server), Port,
4557 + binary_to_list(DBUser), binary_to_list(Password),
4558 + binary_to_list(DB), LogFun).
4560 +close_mysql_connection(DBRef) ->
4561 + ?MYDEBUG("Closing ~p mysql connection", [DBRef]),
4562 + catch p1_mysql_conn:stop(DBRef).
4564 +handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4565 + Reply = rebuild_stats_at_int(DBRef, VHost, Date),
4566 + {reply, Reply, State};
4567 +handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
4568 + {reply, error, State};
4569 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4570 + Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
4571 + ["\"",Timestamp,"\"",","]
4574 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
4576 + Query = ["DELETE FROM ",messages_table(VHost, Date)," ",
4577 + "WHERE timestamp IN (", Temp1],
4580 + case sql_query_internal(DBRef, Query) of
4582 + ?MYDEBUG("Aff=~p", [Aff]),
4583 + rebuild_stats_at_int(DBRef, VHost, Date);
4587 + {reply, Reply, State};
4588 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4589 + ok = delete_all_messages_by_user_at_int(DBRef, User, VHost, Date),
4590 + ok = delete_stats_by_user_at_int(DBRef, User, VHost, Date),
4591 + {reply, ok, State};
4592 +handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4594 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Date),";"]),
4595 + TQuery = ["DELETE FROM ",stats_table(VHost)," "
4596 + "WHERE at=\"",Date,"\";"],
4597 + {updated, _} = sql_query_internal(DBRef, TQuery),
4598 + VQuery = ["DROP VIEW IF EXISTS ",view_table(VHost,Date),";"],
4599 + {updated, _} = sql_query_internal(DBRef, VQuery),
4603 + case catch apply(Fun, []) of
4609 + {reply, Reply, State};
4610 +handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4611 + SName = stats_table(VHost),
4612 + Query = ["SELECT at, sum(count) ",
4613 + "FROM ",SName," ",
4615 + "ORDER BY DATE(at) DESC;"
4618 + case sql_query_internal(DBRef, Query) of
4620 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
4621 + {error, Reason} ->
4622 + % TODO: Duplicate error message ?
4625 + {reply, Reply, State};
4626 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4627 + SName = stats_table(VHost),
4628 + Query = ["SELECT username, sum(count) as allcount ",
4629 + "FROM ",SName," ",
4630 + "JOIN ",users_table(VHost)," ON owner_id=user_id "
4631 + "WHERE at=\"",Date,"\" ",
4632 + "GROUP BY username ",
4633 + "ORDER BY allcount DESC;"
4636 + case sql_query_internal(DBRef, Query) of
4638 + {ok, [ {User, list_to_integer(Count)} || [User, Count] <- Result ]};
4639 + {error, Reason} ->
4642 + {reply, Reply, State};
4643 +handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4644 + {reply, get_user_stats_int(DBRef, User, VHost), State};
4645 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4646 + Query = ["SELECT peer_name,",
4654 + "FROM ",view_table(VHost, Date)," "
4655 + "WHERE owner_name=\"",User,"\";"],
4657 + case sql_query_internal(DBRef, Query) of
4659 + Fun = fun([Peer_name, Peer_server, Peer_resource,
4664 + #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
4665 + direction=list_to_atom(Direction),
4667 + subject=Subject, body=Body,
4668 + timestamp=Timestamp}
4670 + {ok, lists:map(Fun, Result)};
4671 + {error, Reason} ->
4674 + {reply, Reply, State};
4675 +handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4676 + SName = stats_table(VHost),
4677 + Query = ["SELECT at ",
4678 + "FROM ",SName," ",
4680 + "ORDER BY DATE(at) DESC;"
4683 + case sql_query_internal(DBRef, Query) of
4685 + [ Date || [Date] <- Result ];
4686 + {error, Reason} ->
4689 + {reply, Reply, State};
4690 +handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4691 + Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
4692 + "FROM ",settings_table(VHost)," ",
4693 + "JOIN ",users_table(VHost)," ON user_id=owner_id;"],
4695 + case sql_query_internal(DBRef, Query) of
4697 + {ok, lists:map(fun([Owner, DoLogDef, DoLogL, DoNotLogL]) ->
4698 + #user_settings{owner_name=Owner,
4699 + dolog_default=list_to_bool(DoLogDef),
4700 + dolog_list=string_to_list(DoLogL),
4701 + donotlog_list=string_to_list(DoNotLogL)
4707 + {reply, Reply, State};
4708 +handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4709 + Query = ["SELECT dolog_default,dolog_list,donotlog_list FROM ",settings_table(VHost)," ",
4710 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4712 + case sql_query_internal(DBRef, Query) of
4715 + {data, [[Owner, DoLogDef, DoLogL, DoNotLogL]]} ->
4716 + {ok, #user_settings{owner_name=Owner,
4717 + dolog_default=list_to_bool(DoLogDef),
4718 + dolog_list=string_to_list(DoLogL),
4719 + donotlog_list=string_to_list(DoNotLogL)}};
4723 + {reply, Reply, State};
4724 +handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
4725 + dolog_list=DoLogL,
4726 + donotlog_list=DoNotLogL}},
4727 + _From, #state{dbref=DBRef, vhost=VHost} = State) ->
4728 + User_id = get_user_id(DBRef, VHost, User),
4729 + Query = ["UPDATE ",settings_table(VHost)," ",
4730 + "SET dolog_default=",bool_to_list(DoLogDef),", ",
4731 + "dolog_list='",list_to_string(DoLogL),"', ",
4732 + "donotlog_list='",list_to_string(DoNotLogL),"' ",
4733 + "WHERE owner_id=",User_id,";"],
4736 + case sql_query_internal(DBRef, Query) of
4738 + IQuery = ["INSERT INTO ",settings_table(VHost)," ",
4739 + "(owner_id, dolog_default, dolog_list, donotlog_list) ",
4741 + "(",User_id,",",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
4742 + case sql_query_internal_silent(DBRef, IQuery) of
4744 + ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
4746 + {error, Reason} ->
4747 + case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>) of
4752 + ?ERROR_MSG("Failed setup user ~p@~p: ~p", [User, VHost, Reason]),
4757 + ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
4762 + {reply, Reply, State};
4763 +handle_call({stop}, _From, #state{vhost=VHost}=State) ->
4764 + ?MYDEBUG("Stoping mysql5 backend for ~p", [VHost]),
4765 + {stop, normal, ok, State};
4766 +handle_call(Msg, _From, State) ->
4767 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
4770 +handle_cast({log_message, Msg}, #state{dbref=DBRef, vhost=VHost}=State) ->
4772 + Date = convert_timestamp_brief(Msg#msg.timestamp),
4773 + TableName = messages_table(VHost, Date),
4775 + Query = [ "CALL ",logmessage_name(VHost)," "
4776 + "('", TableName, "',",
4778 + "'", binary_to_list(Msg#msg.owner_name), "',",
4779 + "'", binary_to_list(Msg#msg.peer_name), "',",
4780 + "'", binary_to_list(Msg#msg.peer_server), "',",
4781 + "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.peer_resource) ), "',",
4782 + "'", atom_to_list(Msg#msg.direction), "',",
4783 + "'", binary_to_list(Msg#msg.type), "',",
4784 + "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.subject) ), "',",
4785 + "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.body) ), "',",
4786 + "'", Msg#msg.timestamp, "');"],
4788 + case sql_query_internal(DBRef, Query) of
4790 + ?MYDEBUG("Logged ok for ~s, peer: ~s", [ [Msg#msg.owner_name, <<"@">>, VHost],
4791 + [Msg#msg.peer_name, <<"@">>, Msg#msg.peer_server] ]),
4793 + {error, _Reason} ->
4799 +handle_cast({rebuild_stats}, State) ->
4800 + rebuild_all_stats_int(State),
4802 +handle_cast({drop_user, User}, #state{vhost=VHost} = State) ->
4804 + {ok, DBRef} = open_mysql_connection(State),
4805 + {ok, Dates} = get_user_stats_int(DBRef, User, VHost),
4806 + MDResult = lists:map(fun({Date, _}) ->
4807 + delete_all_messages_by_user_at_int(DBRef, User, VHost, Date)
4809 + StDResult = delete_all_stats_by_user_int(DBRef, User, VHost),
4810 + SDResult = delete_user_settings_int(DBRef, User, VHost),
4811 + case lists:all(fun(Result) when Result == ok ->
4813 + (Result) when Result == error ->
4815 + end, lists:append([MDResult, [StDResult], [SDResult]])) of
4817 + ?INFO_MSG("Removed ~s@~s", [User, VHost]);
4819 + ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost])
4821 + close_mysql_connection(DBRef)
4825 +handle_cast(Msg, State) ->
4826 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
4829 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
4830 + {stop, connection_dropped, State};
4831 +handle_info(Info, State) ->
4832 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
4835 +terminate(_Reason, #state{dbref=DBRef}=_State) ->
4836 + close_mysql_connection(DBRef),
4839 +code_change(_OldVsn, State, _Extra) ->
4842 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4844 +% gen_logdb callbacks
4846 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4847 +log_message(VHost, Msg) ->
4848 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4849 + gen_server:cast(Proc, {log_message, Msg}).
4850 +rebuild_stats(VHost) ->
4851 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4852 + gen_server:cast(Proc, {rebuild_stats}).
4853 +rebuild_stats_at(VHost, Date) ->
4854 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4855 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
4856 +delete_messages_by_user_at(VHost, Msgs, Date) ->
4857 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4858 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
4859 +delete_all_messages_by_user_at(User, VHost, Date) ->
4860 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4861 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
4862 +delete_messages_at(VHost, Date) ->
4863 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4864 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
4865 +get_vhost_stats(VHost) ->
4866 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4867 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
4868 +get_vhost_stats_at(VHost, Date) ->
4869 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4870 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
4871 +get_user_stats(User, VHost) ->
4872 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4873 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
4874 +get_user_messages_at(User, VHost, Date) ->
4875 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4876 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
4877 +get_dates(VHost) ->
4878 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4879 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
4880 +get_users_settings(VHost) ->
4881 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4882 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
4883 +get_user_settings(User, VHost) ->
4884 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4885 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
4886 +set_user_settings(User, VHost, Set) ->
4887 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4888 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
4889 +drop_user(User, VHost) ->
4890 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4891 + gen_server:cast(Proc, {drop_user, User}).
4893 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4897 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4898 +get_dates_int(DBRef, VHost) ->
4899 + case sql_query_internal(DBRef, ["SHOW TABLES"]) of
4901 + lists:foldl(fun([Table], Dates) ->
4902 + Reg = lists:sublist(prefix(),2,length(prefix())) ++ ".*" ++ escape_vhost(VHost),
4903 + case re:run(Table, Reg) of
4904 + {match, [{1, _}]} ->
4905 + case re:run(Table,"[0-9]+-[0-9]+-[0-9]+") of
4906 + {match, [{S, E}]} ->
4907 + lists:append(Dates, [lists:sublist(Table,S,E)]);
4919 +rebuild_all_stats_int(#state{vhost=VHost}=State) ->
4921 + {ok, DBRef} = open_mysql_connection(State),
4922 + ok = delete_nonexistent_stats(DBRef, VHost),
4923 + case lists:filter(fun(Date) ->
4924 + case catch rebuild_stats_at_int(DBRef, VHost, Date) of
4927 + {'EXIT', _} -> true
4929 + end, get_dates_int(DBRef, VHost)) of
4932 + ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]),
4935 + close_mysql_connection(DBRef)
4939 +rebuild_stats_at_int(DBRef, VHost, Date) ->
4940 + TempTable = temp_table(VHost),
4942 + Table = messages_table(VHost, Date),
4943 + STable = stats_table(VHost),
4945 + DQuery = [ "DELETE FROM ",STable," ",
4946 + "WHERE at='",Date,"';"],
4948 + ok = create_temp_table(DBRef, TempTable),
4949 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," WRITE, ",TempTable," WRITE;"]),
4950 + SQuery = ["INSERT INTO ",TempTable," ",
4951 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
4952 + "SELECT owner_id,peer_name_id,peer_server_id,\"",Date,"\",count(*) ",
4953 + "FROM ",Table," WHERE ext is NULL GROUP BY owner_id,peer_name_id,peer_server_id;"],
4954 + case sql_query_internal(DBRef, SQuery) of
4956 + Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]),
4958 + {data, [["0"]]} ->
4959 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]),
4960 + sql_query_internal(DBRef, ["UNLOCK TABLES;"]),
4961 + {updated, _} = sql_query_internal(DBRef, ["DROP VIEW IF EXISTS ",view_table(VHost,Date),";"]),
4962 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE, ",TempTable," WRITE;"]),
4963 + {updated, _} = sql_query_internal(DBRef, DQuery),
4966 + ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]),
4970 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE, ",TempTable," WRITE;"]),
4971 + {updated, _} = sql_query_internal(DBRef, DQuery),
4972 + SQuery1 = ["INSERT INTO ",STable," ",
4973 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
4974 + "SELECT owner_id,peer_name_id,peer_server_id,at,count ",
4975 + "FROM ",TempTable,";"],
4976 + case sql_query_internal(DBRef, SQuery1) of
4977 + {updated, _} -> ok;
4978 + {error, _} -> error
4980 + {error, _} -> error
4984 + case catch apply(Fun, []) of
4986 + ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
4990 + {'EXIT', Reason} ->
4991 + ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]),
4994 + sql_query_internal(DBRef, ["UNLOCK TABLES;"]),
4995 + sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]),
4998 +delete_nonexistent_stats(DBRef, VHost) ->
4999 + Dates = get_dates_int(DBRef, VHost),
5000 + STable = stats_table(VHost),
5002 + Temp = lists:flatmap(fun(Date) ->
5003 + ["\"",Date,"\"",","]
5009 + % replace last "," with ");"
5010 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
5011 + Query = ["DELETE FROM ",STable," ",
5012 + "WHERE at NOT IN (", Temp1],
5013 + case sql_query_internal(DBRef, Query) of
5021 +get_user_stats_int(DBRef, User, VHost) ->
5022 + SName = stats_table(VHost),
5023 + UName = users_table(VHost),
5024 + Query = ["SELECT stats.at, sum(stats.count) ",
5025 + "FROM ",UName," AS users ",
5026 + "JOIN ",SName," AS stats ON owner_id=user_id "
5027 + "WHERE users.username=\"",User,"\" ",
5028 + "GROUP BY stats.at "
5029 + "ORDER BY DATE(stats.at) DESC;"
5031 + case sql_query_internal(DBRef, Query) of
5033 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
5034 + {error, Result} ->
5038 +delete_all_messages_by_user_at_int(DBRef, User, VHost, Date) ->
5039 + DQuery = ["DELETE FROM ",messages_table(VHost, Date)," ",
5040 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
5041 + case sql_query_internal(DBRef, DQuery) of
5043 + ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]),
5049 +delete_all_stats_by_user_int(DBRef, User, VHost) ->
5050 + SQuery = ["DELETE FROM ",stats_table(VHost)," ",
5051 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
5052 + case sql_query_internal(DBRef, SQuery) of
5054 + ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]),
5056 + {error, _} -> error
5059 +delete_stats_by_user_at_int(DBRef, User, VHost, Date) ->
5060 + SQuery = ["DELETE FROM ",stats_table(VHost)," ",
5061 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\") ",
5062 + "AND at=\"",Date,"\";"],
5063 + case sql_query_internal(DBRef, SQuery) of
5065 + ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]),
5067 + {error, _} -> error
5070 +delete_user_settings_int(DBRef, User, VHost) ->
5071 + Query = ["DELETE FROM ",settings_table(VHost)," ",
5072 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
5073 + case sql_query_internal(DBRef, Query) of
5075 + ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]),
5077 + {error, Reason} ->
5078 + ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]),
5082 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5086 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5087 +create_temp_table(DBRef, Name) ->
5088 + Query = ["CREATE TABLE ",Name," (",
5089 + "owner_id MEDIUMINT UNSIGNED, ",
5090 + "peer_name_id MEDIUMINT UNSIGNED, ",
5091 + "peer_server_id MEDIUMINT UNSIGNED, ",
5092 + "at VARCHAR(11), ",
5094 + ") ENGINE=MyISAM CHARACTER SET utf8;"
5096 + case sql_query_internal(DBRef, Query) of
5097 + {updated, _} -> ok;
5098 + {error, _Reason} -> error
5101 +create_stats_table(#state{dbref=DBRef, vhost=VHost}=State) ->
5102 + SName = stats_table(VHost),
5103 + Query = ["CREATE TABLE ",SName," (",
5104 + "owner_id MEDIUMINT UNSIGNED, ",
5105 + "peer_name_id MEDIUMINT UNSIGNED, ",
5106 + "peer_server_id MEDIUMINT UNSIGNED, ",
5107 + "at VARCHAR(11), ",
5108 + "count INT(11), ",
5109 + "ext INTEGER DEFAULT NULL, "
5110 + "INDEX ext_i (ext), "
5111 + "INDEX(owner_id,peer_name_id,peer_server_id), ",
5113 + ") ENGINE=MyISAM CHARACTER SET utf8;"
5115 + case sql_query_internal_silent(DBRef, Query) of
5117 + ?MYDEBUG("Created stats table for ~p", [VHost]),
5118 + rebuild_all_stats_int(State),
5120 + {error, Reason} ->
5121 + case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#42S01">>) of
5123 + ?MYDEBUG("Stats table for ~p already exists", [VHost]),
5124 + CheckQuery = ["SHOW COLUMNS FROM ",SName," LIKE 'peer_%_id';"],
5125 + case sql_query_internal(DBRef, CheckQuery) of
5126 + {data, Elems} when length(Elems) == 2 ->
5127 + ?MYDEBUG("Stats table structure is ok", []),
5130 + ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []),
5131 + case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of
5133 + ?INFO_MSG("Successfully dropped ~p", [SName]);
5135 + ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName])
5140 + ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
5145 +create_settings_table(#state{dbref=DBRef, vhost=VHost}) ->
5146 + SName = settings_table(VHost),
5147 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
5148 + "owner_id MEDIUMINT UNSIGNED PRIMARY KEY, ",
5149 + "dolog_default TINYINT(1) NOT NULL DEFAULT 1, ",
5150 + "dolog_list TEXT, ",
5151 + "donotlog_list TEXT ",
5152 + ") ENGINE=InnoDB CHARACTER SET utf8;"
5154 + case sql_query_internal(DBRef, Query) of
5156 + ?MYDEBUG("Created settings table for ~p", [VHost]),
5162 +create_users_table(#state{dbref=DBRef, vhost=VHost}) ->
5163 + SName = users_table(VHost),
5164 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
5165 + "username TEXT NOT NULL, ",
5166 + "user_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
5167 + "UNIQUE INDEX(username(",?INDEX_SIZE,")) ",
5168 + ") ENGINE=InnoDB CHARACTER SET utf8;"
5170 + case sql_query_internal(DBRef, Query) of
5172 + ?MYDEBUG("Created users table for ~p", [VHost]),
5178 +create_servers_table(#state{dbref=DBRef, vhost=VHost}) ->
5179 + SName = servers_table(VHost),
5180 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
5181 + "server TEXT NOT NULL, ",
5182 + "server_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
5183 + "UNIQUE INDEX(server(",?INDEX_SIZE,")) ",
5184 + ") ENGINE=InnoDB CHARACTER SET utf8;"
5186 + case sql_query_internal(DBRef, Query) of
5188 + ?MYDEBUG("Created servers table for ~p", [VHost]),
5194 +create_resources_table(#state{dbref=DBRef, vhost=VHost}) ->
5195 + RName = resources_table(VHost),
5196 + Query = ["CREATE TABLE IF NOT EXISTS ",RName," (",
5197 + "resource TEXT NOT NULL, ",
5198 + "resource_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
5199 + "UNIQUE INDEX(resource(",?INDEX_SIZE,")) ",
5200 + ") ENGINE=InnoDB CHARACTER SET utf8;"
5202 + case sql_query_internal(DBRef, Query) of
5204 + ?MYDEBUG("Created resources table for ~p", [VHost]),
5210 +create_internals(#state{dbref=DBRef, vhost=VHost}) ->
5211 + sql_query_internal(DBRef, ["DROP PROCEDURE IF EXISTS ",logmessage_name(VHost),";"]),
5212 + case sql_query_internal(DBRef, [get_logmessage(VHost)]) of
5214 + ?MYDEBUG("Created logmessage for ~p", [VHost]),
5220 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5224 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5225 +sql_query_internal(DBRef, Query) ->
5226 + case sql_query_internal_silent(DBRef, Query) of
5227 + {error, Reason} ->
5228 + ?ERROR_MSG("~p while ~p", [Reason, lists:append(Query)]),
5233 +sql_query_internal_silent(DBRef, Query) ->
5234 + ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
5235 + get_result(p1_mysql_conn:fetch(DBRef, Query, self(), ?MYSQL_TIMEOUT)).
5237 +get_result({updated, MySQLRes}) ->
5238 + {updated, p1_mysql:get_result_affected_rows(MySQLRes)};
5239 +get_result({data, MySQLRes}) ->
5240 + {data, p1_mysql:get_result_rows(MySQLRes)};
5241 +get_result({error, "query timed out"}) ->
5242 + {error, "query timed out"};
5243 +get_result({error, MySQLRes}) ->
5244 + Reason = p1_mysql:get_result_reason(MySQLRes),
5247 +get_user_id(DBRef, VHost, User) ->
5248 + SQuery = ["SELECT user_id FROM ",users_table(VHost)," ",
5249 + "WHERE username=\"",User,"\";"],
5250 + case sql_query_internal(DBRef, SQuery) of
5252 + IQuery = ["INSERT INTO ",users_table(VHost)," ",
5253 + "SET username=\"",User,"\";"],
5254 + case sql_query_internal_silent(DBRef, IQuery) of
5256 + {data, [[DBIdNew]]} = sql_query_internal(DBRef, SQuery),
5258 + {error, Reason} ->
5259 + % this can be in clustered environment
5260 + match = ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>),
5261 + ?ERROR_MSG("Duplicate key name for ~p", [User]),
5262 + {data, [[ClID]]} = sql_query_internal(DBRef, SQuery),
5265 + {data, [[DBId]]} ->
5269 +get_logmessage(VHost) ->
5270 + UName = users_table(VHost),
5271 + SName = servers_table(VHost),
5272 + RName = resources_table(VHost),
5273 + StName = stats_table(VHost),
5275 +CREATE PROCEDURE ~s(tablename TEXT, atdate TEXT, owner TEXT, peer_name TEXT, peer_server TEXT, peer_resource TEXT, mdirection VARCHAR(4), mtype VARCHAR(10), msubject TEXT, mbody TEXT, mtimestamp DOUBLE)
5277 + DECLARE ownerID MEDIUMINT UNSIGNED;
5278 + DECLARE peer_nameID MEDIUMINT UNSIGNED;
5279 + DECLARE peer_serverID MEDIUMINT UNSIGNED;
5280 + DECLARE peer_resourceID MEDIUMINT UNSIGNED;
5281 + DECLARE Vmtype VARCHAR(10);
5282 + DECLARE Vmtimestamp DOUBLE;
5283 + DECLARE Vmdirection VARCHAR(4);
5284 + DECLARE Vmbody TEXT;
5285 + DECLARE Vmsubject TEXT;
5288 + DECLARE viewname TEXT;
5289 + DECLARE notable INT;
5290 + DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' SET @notable = 1;
5293 + SET @ownerID = NULL;
5294 + SET @peer_nameID = NULL;
5295 + SET @peer_serverID = NULL;
5296 + SET @peer_resourceID = NULL;
5298 + SET @Vmtype = mtype;
5299 + SET @Vmtimestamp = mtimestamp;
5300 + SET @Vmdirection = mdirection;
5301 + SET @Vmbody = mbody;
5302 + SET @Vmsubject = msubject;
5304 + SELECT user_id INTO @ownerID FROM ~s WHERE username=owner;
5305 + IF @ownerID IS NULL THEN
5306 + INSERT INTO ~s SET username=owner;
5307 + SET @ownerID = LAST_INSERT_ID();
5310 + SELECT user_id INTO @peer_nameID FROM ~s WHERE username=peer_name;
5311 + IF @peer_nameID IS NULL THEN
5312 + INSERT INTO ~s SET username=peer_name;
5313 + SET @peer_nameID = LAST_INSERT_ID();
5316 + SELECT server_id INTO @peer_serverID FROM ~s WHERE server=peer_server;
5317 + IF @peer_serverID IS NULL THEN
5318 + INSERT INTO ~s SET server=peer_server;
5319 + SET @peer_serverID = LAST_INSERT_ID();
5322 + SELECT resource_id INTO @peer_resourceID FROM ~s WHERE resource=peer_resource;
5323 + IF @peer_resourceID IS NULL THEN
5324 + INSERT INTO ~s SET resource=peer_resource;
5325 + SET @peer_resourceID = LAST_INSERT_ID();
5328 + SET @iq = CONCAT(\"INSERT INTO \",tablename,\" (owner_id, peer_name_id, peer_server_id, peer_resource_id, direction, type, subject, body, timestamp) VALUES (@ownerID,@peer_nameID,@peer_serverID,@peer_resourceID,@Vmdirection,@Vmtype,@Vmsubject,@Vmbody,@Vmtimestamp);\");
5329 + PREPARE insertmsg FROM @iq;
5331 + IF @notable = 1 THEN
5332 + SET @cq = CONCAT(\"CREATE TABLE \",tablename,\" (
5333 + owner_id MEDIUMINT UNSIGNED NOT NULL,
5334 + peer_name_id MEDIUMINT UNSIGNED NOT NULL,
5335 + peer_server_id MEDIUMINT UNSIGNED NOT NULL,
5336 + peer_resource_id MEDIUMINT(8) UNSIGNED NOT NULL,
5337 + direction ENUM('to', 'from') NOT NULL,
5338 + type ENUM('chat','error','groupchat','headline','normal') NOT NULL,
5341 + timestamp DOUBLE NOT NULL,
5342 + ext INTEGER DEFAULT NULL,
5343 + INDEX search_i (owner_id, peer_name_id, peer_server_id, peer_resource_id),
5344 + INDEX ext_i (ext),
5348 + CHARACTER SET utf8;\");
5349 + PREPARE createtable FROM @cq;
5350 + EXECUTE createtable;
5351 + DEALLOCATE PREPARE createtable;
5353 + SET @viewname = CONCAT(\"`v_\", TRIM(BOTH '`' FROM tablename), \"`\");
5354 + SET @cq = CONCAT(\"CREATE OR REPLACE VIEW \",@viewname,\" AS
5355 + SELECT owner.username AS owner_name,
5356 + peer.username AS peer_name,
5357 + servers.server AS peer_server,
5358 + resources.resource AS peer_resource,
5359 + messages.direction,
5363 + messages.timestamp
5369 + \", tablename,\" messages
5371 + owner.user_id=messages.owner_id and
5372 + peer.user_id=messages.peer_name_id and
5373 + servers.server_id=messages.peer_server_id and
5374 + resources.resource_id=messages.peer_resource_id
5375 + ORDER BY messages.timestamp;\");
5376 + PREPARE createview FROM @cq;
5377 + EXECUTE createview;
5378 + DEALLOCATE PREPARE createview;
5381 + PREPARE insertmsg FROM @iq;
5382 + EXECUTE insertmsg;
5383 + ELSEIF @notable = 0 THEN
5384 + EXECUTE insertmsg;
5387 + DEALLOCATE PREPARE insertmsg;
5389 + IF @notable = 0 THEN
5390 + UPDATE ~s SET count=count+1 WHERE owner_id=@ownerID AND peer_name_id=@peer_nameID AND peer_server_id=@peer_serverID AND at=atdate;
5391 + IF ROW_COUNT() = 0 THEN
5392 + INSERT INTO ~s (owner_id, peer_name_id, peer_server_id, at, count) VALUES (@ownerID, @peer_nameID, @peer_serverID, atdate, 1);
5395 +END;", [logmessage_name(VHost),UName,UName,UName,UName,SName,SName,RName,RName,UName,UName,SName,RName,StName,StName]).
5396 diff --git a/src/mod_logdb_pgsql.erl b/src/mod_logdb_pgsql.erl
5397 new file mode 100644
5398 index 0000000..3c2ae95
5400 +++ b/src/mod_logdb_pgsql.erl
5402 +% {ok, DBRef} = pgsql:connect([{host, "127.0.0.1"}, {database, "logdb"}, {user, "logdb"}, {password, "logdb"}, {port, 5432}, {as_binary, true}]).
5404 +% pgsql:squery(DBRef, "CREATE TABLE test.\"logdb_stats_test\" (owner_id INTEGER, peer_name_id INTEGER, peer_server_id INTEGER, at VARCHAR(20), count integer);" ).
5405 +%%%----------------------------------------------------------------------
5406 +%%% File : mod_logdb_pgsql.erl
5407 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
5408 +%%% Purpose : Posgresql backend for mod_logdb
5409 +%%% Version : trunk
5410 +%%% Id : $Id: mod_logdb_pgsql.erl 1360 2009-07-30 06:00:14Z malik $
5411 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
5412 +%%%----------------------------------------------------------------------
5414 +-module(mod_logdb_pgsql).
5415 +-author('o.palij@gmail.com').
5417 +-include("mod_logdb.hrl").
5418 +-include("ejabberd.hrl").
5419 +-include("jlib.hrl").
5420 +-include("logger.hrl").
5422 +-behaviour(gen_logdb).
5423 +-behaviour(gen_server).
5426 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
5428 +-export([start/2, stop/1]).
5430 +-export([log_message/2,
5432 + rebuild_stats_at/2,
5433 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
5434 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
5436 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
5439 +-export([view_table/3]).
5441 +% gen_server call timeout
5442 +-define(CALL_TIMEOUT, 30000).
5443 +-define(PGSQL_TIMEOUT, 60000).
5444 +-define(PROCNAME, mod_logdb_pgsql).
5446 +-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
5447 + list_to_string/1, string_to_list/1,
5448 + convert_timestamp_brief/1]).
5450 +-record(state, {dbref, vhost, server, port, db, user, password, schema}).
5452 +% replace "." with "_"
5453 +escape_vhost(VHost) -> lists:map(fun(46) -> 95;
5455 + end, binary_to_list(VHost)).
5458 + Schema ++ ".\"" ++ "logdb_".
5461 + "_" ++ escape_vhost(VHost) ++ "\"".
5463 +messages_table(VHost, Schema, Date) ->
5464 + prefix(Schema) ++ "messages_" ++ Date ++ suffix(VHost).
5466 +view_table(VHost, Schema, Date) ->
5467 + Table = messages_table(VHost, Schema, Date),
5468 + TablewoS = lists:sublist(Table, length(Schema) + 3, length(Table) - length(Schema) - 3),
5469 + lists:append([Schema, ".\"v_", TablewoS, "\""]).
5471 +stats_table(VHost, Schema) ->
5472 + prefix(Schema) ++ "stats" ++ suffix(VHost).
5474 +temp_table(VHost, Schema) ->
5475 + prefix(Schema) ++ "temp" ++ suffix(VHost).
5477 +settings_table(VHost, Schema) ->
5478 + prefix(Schema) ++ "settings" ++ suffix(VHost).
5480 +users_table(VHost, Schema) ->
5481 + prefix(Schema) ++ "users" ++ suffix(VHost).
5482 +servers_table(VHost, Schema) ->
5483 + prefix(Schema) ++ "servers" ++ suffix(VHost).
5484 +resources_table(VHost, Schema) ->
5485 + prefix(Schema) ++ "resources" ++ suffix(VHost).
5487 +logmessage_name(VHost, Schema) ->
5488 + prefix(Schema) ++ "logmessage" ++ suffix(VHost).
5490 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5492 +% gen_mod callbacks
5494 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5495 +start(VHost, Opts) ->
5496 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5497 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
5500 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5501 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
5503 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5505 +% gen_server callbacks
5507 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5508 +init([VHost, Opts]) ->
5509 + Server = gen_mod:get_opt(server, Opts, fun(A) -> A end, <<"localhost">>),
5510 + DB = gen_mod:get_opt(db, Opts, fun(A) -> A end, <<"ejabberd_logdb">>),
5511 + User = gen_mod:get_opt(user, Opts, fun(A) -> A end, <<"root">>),
5512 + Port = gen_mod:get_opt(port, Opts, fun(A) -> A end, 5432),
5513 + Password = gen_mod:get_opt(password, Opts, fun(A) -> A end, <<"">>),
5514 + Schema = binary_to_list(gen_mod:get_opt(schema, Opts, fun(A) -> A end, <<"public">>)),
5516 + ?MYDEBUG("Starting pgsql backend for ~s", [VHost]),
5518 + St = #state{vhost=VHost,
5519 + server=Server, port=Port, db=DB,
5520 + user=User, password=Password,
5523 + case open_pgsql_connection(St) of
5525 + State = St#state{dbref=DBRef},
5526 + ok = create_internals(State),
5527 + ok = create_stats_table(State),
5528 + ok = create_settings_table(State),
5529 + ok = create_users_table(State),
5530 + ok = create_servers_table(State),
5531 + ok = create_resources_table(State),
5532 + erlang:monitor(process, DBRef),
5534 + % this does not work
5535 + {error, Reason} ->
5536 + ?ERROR_MSG("PgSQL connection failed: ~p~n", [Reason]),
5537 + {stop, db_connection_failed};
5538 + % and this too, becouse pgsql_conn do exit() which can not be catched
5540 + ?ERROR_MSG("Rez: ~p~n", [Rez]),
5541 + {stop, db_connection_failed}
5544 +open_pgsql_connection(#state{server=Server, port=Port, db=DB, schema=Schema,
5545 + user=User, password=Password} = _State) ->
5546 + ?INFO_MSG("Opening pgsql connection ~s@~s:~p/~s", [User, Server, Port, DB]),
5547 + {ok, DBRef} = pgsql:connect(Server, DB, User, Password, Port),
5548 + {updated, _} = sql_query_internal(DBRef, ["SET SEARCH_PATH TO ",Schema,";"]),
5551 +close_pgsql_connection(DBRef) ->
5552 + ?MYDEBUG("Closing ~p pgsql connection", [DBRef]),
5553 + pgsql:terminate(DBRef).
5555 +handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5556 + Date = convert_timestamp_brief(Msg#msg.timestamp),
5557 + TableName = messages_table(VHost, Schema, Date),
5558 + ViewName = view_table(VHost, Schema, Date),
5560 + Query = [ "SELECT ", logmessage_name(VHost, Schema)," "
5561 + "('", TableName, "',",
5562 + "'", ViewName, "',",
5564 + "'", binary_to_list(Msg#msg.owner_name), "',",
5565 + "'", binary_to_list(Msg#msg.peer_name), "',",
5566 + "'", binary_to_list(Msg#msg.peer_server), "',",
5567 + "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.peer_resource) ), "',",
5568 + "'", atom_to_list(Msg#msg.direction), "',",
5569 + "'", binary_to_list(Msg#msg.type), "',",
5570 + "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.subject) ), "',",
5571 + "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.body) ), "',",
5572 + "'", Msg#msg.timestamp, "');"],
5574 + case sql_query_internal_silent(DBRef, Query) of
5575 + % TODO: change this
5576 + {data, [{"0"}]} ->
5577 + ?MYDEBUG("Logged ok for ~s, peer: ~s", [ [Msg#msg.owner_name, <<"@">>, VHost],
5578 + [Msg#msg.peer_name, <<"@">>, Msg#msg.peer_server] ]),
5580 + {error, _Reason} ->
5583 + {reply, ok, State};
5584 +handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5585 + Reply = rebuild_stats_at_int(DBRef, VHost, Schema, Date),
5586 + {reply, Reply, State};
5587 +handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
5588 + {reply, error, State};
5589 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5590 + Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
5591 + ["'",Timestamp,"'",","]
5594 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
5596 + Query = ["DELETE FROM ",messages_table(VHost, Schema, Date)," ",
5597 + "WHERE timestamp IN (", Temp1],
5600 + case sql_query_internal(DBRef, Query) of
5602 + rebuild_stats_at_int(DBRef, VHost, Schema, Date);
5606 + {reply, Reply, State};
5607 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5608 + ok = delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date),
5609 + ok = delete_stats_by_user_at_int(DBRef, Schema, User, VHost, Date),
5610 + {reply, ok, State};
5611 +handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5612 + {updated, _} = sql_query_internal(DBRef, ["DROP VIEW ",view_table(VHost, Schema, Date),";"]),
5614 + case sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Schema, Date)," CASCADE;"]) of
5616 + Query = ["DELETE FROM ",stats_table(VHost, Schema)," "
5617 + "WHERE at='",Date,"';"],
5618 + case sql_query_internal(DBRef, Query) of
5627 + {reply, Reply, State};
5628 +handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5629 + SName = stats_table(VHost, Schema),
5630 + Query = ["SELECT at, sum(count) ",
5631 + "FROM ",SName," ",
5633 + "ORDER BY DATE(at) DESC;"
5636 + case sql_query_internal(DBRef, Query) of
5638 + {ok, [ {Date, list_to_integer(Count)} || {Date, Count} <- Recs]};
5639 + {error, Reason} ->
5640 + % TODO: Duplicate error message ?
5643 + {reply, Reply, State};
5644 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5645 + SName = stats_table(VHost, Schema),
5646 + Query = ["SELECT username, sum(count) AS allcount ",
5647 + "FROM ",SName," ",
5648 + "JOIN ",users_table(VHost, Schema)," ON owner_id=user_id ",
5649 + "WHERE at='",Date,"' ",
5650 + "GROUP BY username ",
5651 + "ORDER BY allcount DESC;"
5654 + case sql_query_internal(DBRef, Query) of
5656 + RFun = fun({User, Count}) ->
5657 + {User, list_to_integer(Count)}
5659 + {ok, lists:reverse(lists:keysort(2, lists:map(RFun, Recs)))};
5660 + {error, Reason} ->
5664 + {reply, Reply, State};
5665 +handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5666 + {reply, get_user_stats_int(DBRef, Schema, User, VHost), State};
5667 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5668 + Query = ["SELECT peer_name,",
5676 + "FROM ",view_table(VHost, Schema, Date)," "
5677 + "WHERE owner_name='",User,"';"],
5679 + case sql_query_internal(DBRef, Query) of
5681 + Fun = fun({Peer_name, Peer_server, Peer_resource,
5686 + #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
5687 + direction=list_to_atom(Direction),
5689 + subject=Subject, body=Body,
5690 + timestamp=Timestamp}
5692 + {ok, lists:map(Fun, Recs)};
5693 + {error, Reason} ->
5696 + {reply, Reply, State};
5697 +handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5698 + SName = stats_table(VHost, Schema),
5699 + Query = ["SELECT at ",
5700 + "FROM ",SName," ",
5702 + "ORDER BY at DESC;"
5705 + case sql_query_internal(DBRef, Query) of
5707 + [ Date || {Date} <- Result ];
5708 + {error, Reason} ->
5711 + {reply, Reply, State};
5712 +handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5713 + Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
5714 + "FROM ",settings_table(VHost, Schema)," ",
5715 + "JOIN ",users_table(VHost, Schema)," ON user_id=owner_id;"],
5717 + case sql_query_internal(DBRef, Query) of
5719 + {ok, [#user_settings{owner_name=Owner,
5720 + dolog_default=list_to_bool(DoLogDef),
5721 + dolog_list=string_to_list(DoLogL),
5722 + donotlog_list=string_to_list(DoNotLogL)
5723 + } || {Owner, DoLogDef, DoLogL, DoNotLogL} <- Recs]};
5724 + {error, Reason} ->
5727 + {reply, Reply, State};
5728 +handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5729 + Query = ["SELECT dolog_default,dolog_list,donotlog_list ",
5730 + "FROM ",settings_table(VHost, Schema)," ",
5731 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
5733 + case sql_query_internal_silent(DBRef, Query) of
5736 + {data, [{DoLogDef, DoLogL, DoNotLogL}]} ->
5737 + {ok, #user_settings{owner_name=User,
5738 + dolog_default=list_to_bool(DoLogDef),
5739 + dolog_list=string_to_list(DoLogL),
5740 + donotlog_list=string_to_list(DoNotLogL)}};
5741 + {error, Reason} ->
5742 + ?ERROR_MSG("Failed to get_user_settings for ~s@~s: ~p", [User, VHost, Reason]),
5745 + {reply, Reply, State};
5746 +handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
5747 + dolog_list=DoLogL,
5748 + donotlog_list=DoNotLogL}},
5749 + _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5750 + User_id = get_user_id(DBRef, VHost, Schema, User),
5751 + Query = ["UPDATE ",settings_table(VHost, Schema)," ",
5752 + "SET dolog_default=",bool_to_list(DoLogDef),", ",
5753 + "dolog_list='",list_to_string(DoLogL),"', ",
5754 + "donotlog_list='",list_to_string(DoNotLogL),"' ",
5755 + "WHERE owner_id=",User_id,";"],
5758 + case sql_query_internal(DBRef, Query) of
5760 + IQuery = ["INSERT INTO ",settings_table(VHost, Schema)," ",
5761 + "(owner_id, dolog_default, dolog_list, donotlog_list) ",
5763 + "(",User_id,", ",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
5764 + case sql_query_internal(DBRef, IQuery) of
5766 + ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
5772 + ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
5777 + {reply, Reply, State};
5778 +handle_call({stop}, _From, State) ->
5779 + ?MYDEBUG("Stoping pgsql backend for ~p", [State#state.vhost]),
5780 + {stop, normal, ok, State};
5781 +handle_call(Msg, _From, State) ->
5782 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
5786 +handle_cast({rebuild_stats}, State) ->
5787 + rebuild_all_stats_int(State),
5789 +handle_cast({drop_user, User}, #state{vhost=VHost, schema=Schema}=State) ->
5791 + {ok, DBRef} = open_pgsql_connection(State),
5792 + {ok, Dates} = get_user_stats_int(DBRef, Schema, User, VHost),
5793 + MDResult = lists:map(fun({Date, _}) ->
5794 + delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date)
5796 + StDResult = delete_all_stats_by_user_int(DBRef, Schema, User, VHost),
5797 + SDResult = delete_user_settings_int(DBRef, Schema, User, VHost),
5798 + case lists:all(fun(Result) when Result == ok ->
5800 + (Result) when Result == error ->
5802 + end, lists:append([MDResult, [StDResult], [SDResult]])) of
5804 + ?INFO_MSG("Removed ~s@~s", [User, VHost]);
5806 + ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost])
5808 + close_pgsql_connection(DBRef)
5812 +handle_cast(Msg, State) ->
5813 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
5816 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
5817 + {stop, connection_dropped, State};
5818 +handle_info(Info, State) ->
5819 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
5822 +terminate(_Reason, #state{dbref=DBRef}=_State) ->
5823 + close_pgsql_connection(DBRef),
5826 +code_change(_OldVsn, State, _Extra) ->
5829 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5831 +% gen_logdb callbacks
5833 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5834 +log_message(VHost, Msg) ->
5835 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5836 + gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
5837 +rebuild_stats(VHost) ->
5838 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5839 + gen_server:cast(Proc, {rebuild_stats}).
5840 +rebuild_stats_at(VHost, Date) ->
5841 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5842 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
5843 +delete_messages_by_user_at(VHost, Msgs, Date) ->
5844 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5845 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
5846 +delete_all_messages_by_user_at(User, VHost, Date) ->
5847 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5848 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
5849 +delete_messages_at(VHost, Date) ->
5850 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5851 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
5852 +get_vhost_stats(VHost) ->
5853 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5854 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
5855 +get_vhost_stats_at(VHost, Date) ->
5856 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5857 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
5858 +get_user_stats(User, VHost) ->
5859 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5860 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
5861 +get_user_messages_at(User, VHost, Date) ->
5862 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5863 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
5864 +get_dates(VHost) ->
5865 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5866 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
5867 +get_users_settings(VHost) ->
5868 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5869 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
5870 +get_user_settings(User, VHost) ->
5871 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5872 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
5873 +set_user_settings(User, VHost, Set) ->
5874 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5875 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
5876 +drop_user(User, VHost) ->
5877 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5878 + gen_server:cast(Proc, {drop_user, User}).
5880 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5884 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5885 +get_dates_int(DBRef, VHost) ->
5886 + Query = ["SELECT n.nspname as \"Schema\",
5887 + c.relname as \"Name\",
5888 + CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' END as \"Type\",
5889 + r.rolname as \"Owner\"
5890 + FROM pg_catalog.pg_class c
5891 + JOIN pg_catalog.pg_roles r ON r.oid = c.relowner
5892 + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
5893 + WHERE c.relkind IN ('r','')
5894 + AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
5895 + AND c.relname ~ '^(.*",escape_vhost(VHost),".*)$'
5896 + AND pg_catalog.pg_table_is_visible(c.oid)
5898 + case sql_query_internal(DBRef, Query) of
5900 + lists:foldl(fun({_Schema, Table, _Type, _Owner}, Dates) ->
5901 + case re:run(Table,"[0-9]+-[0-9]+-[0-9]+") of
5902 + {match, [{S, E}]} ->
5903 + lists:append(Dates, [lists:sublist(Table,S,E)]);
5912 +rebuild_all_stats_int(#state{vhost=VHost, schema=Schema}=State) ->
5914 + {ok, DBRef} = open_pgsql_connection(State),
5915 + ok = delete_nonexistent_stats(DBRef, Schema, VHost),
5916 + case lists:filter(fun(Date) ->
5917 + case catch rebuild_stats_at_int(DBRef, VHost, Schema, Date) of
5920 + {'EXIT', _} -> true
5922 + end, get_dates_int(DBRef, VHost)) of
5925 + ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]),
5928 + close_pgsql_connection(DBRef)
5932 +rebuild_stats_at_int(DBRef, VHost, Schema, Date) ->
5933 + TempTable = temp_table(VHost, Schema),
5936 + Table = messages_table(VHost, Schema, Date),
5937 + STable = stats_table(VHost, Schema),
5939 + DQuery = [ "DELETE FROM ",STable," ",
5940 + "WHERE at='",Date,"';"],
5942 + ok = create_temp_table(DBRef, VHost, Schema),
5943 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," IN ACCESS EXCLUSIVE MODE;"]),
5944 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",TempTable," IN ACCESS EXCLUSIVE MODE;"]),
5945 + SQuery = ["INSERT INTO ",TempTable," ",
5946 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
5947 + "SELECT owner_id,peer_name_id,peer_server_id,'",Date,"'",",count(*) ",
5948 + "FROM ",Table," GROUP BY owner_id,peer_name_id,peer_server_id;"],
5949 + case sql_query_internal(DBRef, SQuery) of
5951 + Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]),
5953 + {data, [{"0"}]} ->
5954 + {updated, _} = sql_query_internal(DBRef, ["DROP VIEW ",view_table(VHost, Schema, Date),";"]),
5955 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table," CASCADE;"]),
5956 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," IN ACCESS EXCLUSIVE MODE;"]),
5957 + {updated, _} = sql_query_internal(DBRef, DQuery),
5960 + ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]),
5964 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," IN ACCESS EXCLUSIVE MODE;"]),
5965 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",TempTable," IN ACCESS EXCLUSIVE MODE;"]),
5966 + {updated, _} = sql_query_internal(DBRef, DQuery),
5967 + SQuery1 = ["INSERT INTO ",STable," ",
5968 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
5969 + "SELECT owner_id,peer_name_id,peer_server_id,at,count ",
5970 + "FROM ",TempTable,";"],
5971 + case sql_query_internal(DBRef, SQuery1) of
5972 + {updated, _} -> ok;
5973 + {error, _} -> error
5975 + {error, _} -> error
5979 + case sql_transaction_internal(DBRef, Fun) of
5981 + ?INFO_MSG("Rebuilded stats for ~s at ~s", [VHost, Date]),
5983 + {aborted, Reason} ->
5984 + ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]),
5987 + sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]),
5990 +delete_nonexistent_stats(DBRef, Schema, VHost) ->
5991 + Dates = get_dates_int(DBRef, VHost),
5992 + STable = stats_table(VHost, Schema),
5994 + Temp = lists:flatmap(fun(Date) ->
5995 + ["'",Date,"'",","]
6002 + % replace last "," with ");"
6003 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
6004 + Query = ["DELETE FROM ",STable," ",
6005 + "WHERE at NOT IN (", Temp1],
6006 + case sql_query_internal(DBRef, Query) of
6014 +get_user_stats_int(DBRef, Schema, User, VHost) ->
6015 + SName = stats_table(VHost, Schema),
6016 + UName = users_table(VHost, Schema),
6017 + Query = ["SELECT stats.at, sum(stats.count) ",
6018 + "FROM ",UName," AS users ",
6019 + "JOIN ",SName," AS stats ON owner_id=user_id "
6020 + "WHERE users.username='",User,"' ",
6021 + "GROUP BY stats.at "
6022 + "ORDER BY DATE(at) DESC;"
6024 + case sql_query_internal(DBRef, Query) of
6026 + {ok, [ {Date, list_to_integer(Count)} || {Date, Count} <- Recs ]};
6027 + {error, Result} ->
6031 +delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date) ->
6032 + DQuery = ["DELETE FROM ",messages_table(VHost, Schema, Date)," ",
6033 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
6034 + case sql_query_internal(DBRef, DQuery) of
6036 + ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]),
6042 +delete_all_stats_by_user_int(DBRef, Schema, User, VHost) ->
6043 + SQuery = ["DELETE FROM ",stats_table(VHost, Schema)," ",
6044 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
6045 + case sql_query_internal(DBRef, SQuery) of
6047 + ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]),
6049 + {error, _} -> error
6052 +delete_stats_by_user_at_int(DBRef, Schema, User, VHost, Date) ->
6053 + SQuery = ["DELETE FROM ",stats_table(VHost, Schema)," ",
6054 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"') ",
6055 + "AND at='",Date,"';"],
6056 + case sql_query_internal(DBRef, SQuery) of
6058 + ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]),
6060 + {error, _} -> error
6063 +delete_user_settings_int(DBRef, Schema, User, VHost) ->
6064 + Query = ["DELETE FROM ",settings_table(VHost, Schema)," ",
6065 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
6066 + case sql_query_internal(DBRef, Query) of
6068 + ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]),
6070 + {error, Reason} ->
6071 + ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]),
6075 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6079 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6080 +create_temp_table(DBRef, VHost, Schema) ->
6081 + TName = temp_table(VHost, Schema),
6082 + Query = ["CREATE TABLE ",TName," (",
6083 + "owner_id INTEGER, ",
6084 + "peer_name_id INTEGER, ",
6085 + "peer_server_id INTEGER, ",
6086 + "at VARCHAR(20), ",
6090 + case sql_query_internal(DBRef, Query) of
6091 + {updated, _} -> ok;
6092 + {error, _Reason} -> error
6095 +create_stats_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
6096 + SName = stats_table(VHost, Schema),
6100 + Query = ["CREATE TABLE ",SName," (",
6101 + "owner_id INTEGER, ",
6102 + "peer_name_id INTEGER, ",
6103 + "peer_server_id INTEGER, ",
6104 + "at VARCHAR(20), ",
6108 + case sql_query_internal_silent(DBRef, Query) of
6110 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"s_search_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (owner_id, peer_name_id, peer_server_id);"]),
6111 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"s_at_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (at);"]),
6113 + {error, Reason} ->
6114 + case lists:keysearch(code, 1, Reason) of
6115 + {value, {code, "42P07"}} ->
6118 + ?ERROR_MSG("Failed to create stats table for ~s: ~p", [VHost, Reason]),
6123 + case sql_transaction_internal(DBRef, Fun) of
6124 + {atomic, created} ->
6125 + ?MYDEBUG("Created stats table for ~s", [VHost]),
6126 + rebuild_all_stats_int(State),
6128 + {atomic, exists} ->
6129 + ?MYDEBUG("Stats table for ~s already exists", [VHost]),
6130 + {match, [{F, L}]} = re:run(SName, "\".*\""),
6131 + QTable = lists:sublist(SName, F+2, L-2),
6132 + OIDQuery = ["SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname='",QTable,"' AND pg_catalog.pg_table_is_visible(c.oid);"],
6133 + {data,[{OID}]} = sql_query_internal(DBRef, OIDQuery),
6134 + CheckQuery = ["SELECT a.attname FROM pg_catalog.pg_attribute a WHERE a.attrelid = '",OID,"' AND a.attnum > 0 AND NOT a.attisdropped AND a.attname ~ '^peer_.*_id$';"],
6135 + case sql_query_internal(DBRef, CheckQuery) of
6136 + {data, Elems} when length(Elems) == 2 ->
6137 + ?MYDEBUG("Stats table structure is ok", []),
6140 + ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []),
6141 + case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of
6143 + ?INFO_MSG("Successfully dropped ~p", [SName]);
6145 + ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName])
6149 + {error, _} -> error
6152 +create_settings_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
6153 + SName = settings_table(VHost, Schema),
6154 + Query = ["CREATE TABLE ",SName," (",
6155 + "owner_id INTEGER PRIMARY KEY, ",
6156 + "dolog_default BOOLEAN, ",
6157 + "dolog_list TEXT DEFAULT '', ",
6158 + "donotlog_list TEXT DEFAULT ''",
6161 + case sql_query_internal_silent(DBRef, Query) of
6163 + ?MYDEBUG("Created settings table for ~s", [VHost]),
6165 + {error, Reason} ->
6166 + case lists:keysearch(code, 1, Reason) of
6167 + {value, {code, "42P07"}} ->
6168 + ?MYDEBUG("Settings table for ~s already exists", [VHost]),
6171 + ?ERROR_MSG("Failed to create settings table for ~s: ~p", [VHost, Reason]),
6176 +create_users_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
6177 + SName = users_table(VHost, Schema),
6181 + Query = ["CREATE TABLE ",SName," (",
6182 + "username TEXT UNIQUE, ",
6183 + "user_id SERIAL PRIMARY KEY",
6186 + case sql_query_internal_silent(DBRef, Query) of
6188 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"username_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (username);"]),
6190 + {error, Reason} ->
6191 + case lists:keysearch(code, 1, Reason) of
6192 + {value, {code, "42P07"}} ->
6195 + ?ERROR_MSG("Failed to create users table for ~s: ~p", [VHost, Reason]),
6200 + case sql_transaction_internal(DBRef, Fun) of
6201 + {atomic, created} ->
6202 + ?MYDEBUG("Created users table for ~s", [VHost]),
6204 + {atomic, exists} ->
6205 + ?MYDEBUG("Users table for ~s already exists", [VHost]),
6207 + {aborted, _} -> error
6210 +create_servers_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
6211 + SName = servers_table(VHost, Schema),
6214 + Query = ["CREATE TABLE ",SName," (",
6215 + "server TEXT UNIQUE, ",
6216 + "server_id SERIAL PRIMARY KEY",
6219 + case sql_query_internal_silent(DBRef, Query) of
6221 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"server_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (server);"]),
6223 + {error, Reason} ->
6224 + case lists:keysearch(code, 1, Reason) of
6225 + {value, {code, "42P07"}} ->
6228 + ?ERROR_MSG("Failed to create servers table for ~s: ~p", [VHost, Reason]),
6233 + case sql_transaction_internal(DBRef, Fun) of
6234 + {atomic, created} ->
6235 + ?MYDEBUG("Created servers table for ~s", [VHost]),
6237 + {atomic, exists} ->
6238 + ?MYDEBUG("Servers table for ~s already exists", [VHost]),
6240 + {aborted, _} -> error
6243 +create_resources_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
6244 + RName = resources_table(VHost, Schema),
6246 + Query = ["CREATE TABLE ",RName," (",
6247 + "resource TEXT UNIQUE, ",
6248 + "resource_id SERIAL PRIMARY KEY",
6251 + case sql_query_internal_silent(DBRef, Query) of
6253 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"resource_i_",Schema,"_",escape_vhost(VHost),"\" ON ",RName," (resource);"]),
6255 + {error, Reason} ->
6256 + case lists:keysearch(code, 1, Reason) of
6257 + {value, {code, "42P07"}} ->
6260 + ?ERROR_MSG("Failed to create users table for ~s: ~p", [VHost, Reason]),
6265 + case sql_transaction_internal(DBRef, Fun) of
6266 + {atomic, created} ->
6267 + ?MYDEBUG("Created resources table for ~s", [VHost]),
6269 + {atomic, exists} ->
6270 + ?MYDEBUG("Resources table for ~s already exists", [VHost]),
6272 + {aborted, _} -> error
6275 +create_internals(#state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
6276 + sql_query_internal(DBRef, ["DROP FUNCTION IF EXISTS ",logmessage_name(VHost,Schema)," (tbname TEXT, atdt TEXT, owner TEXT, peer_name TEXT, peer_server TEXT, peer_resource TEXT, mdirection VARCHAR(4), mtype VARCHAR(9), msubj TEXT, mbody TEXT, mtimestamp DOUBLE PRECISION);"]),
6277 + case sql_query_internal(DBRef, [get_logmessage(VHost, Schema)]) of
6279 + ?MYDEBUG("Created logmessage for ~p", [VHost]),
6281 + {error, Reason} ->
6282 + case lists:keysearch(code, 1, Reason) of
6283 + {value, {code, "42704"}} ->
6284 + ?ERROR_MSG("plpgsql language must be installed into database '~s'. Use CREATE LANGUAGE...", [State#state.db]),
6291 +get_user_id(DBRef, VHost, Schema, User) ->
6292 + SQuery = ["SELECT user_id FROM ",users_table(VHost, Schema)," ",
6293 + "WHERE username='",User,"';"],
6294 + case sql_query_internal(DBRef, SQuery) of
6296 + IQuery = ["INSERT INTO ",users_table(VHost, Schema)," ",
6297 + "VALUES ('",User,"');"],
6298 + case sql_query_internal_silent(DBRef, IQuery) of
6300 + {data, [{DBIdNew}]} = sql_query_internal(DBRef, SQuery),
6302 + {error, Reason} ->
6303 + % this can be in clustered environment
6304 + {value, {code, "23505"}} = lists:keysearch(code, 1, Reason),
6305 + ?ERROR_MSG("Duplicate key name for ~p", [User]),
6306 + {data, [{ClID}]} = sql_query_internal(DBRef, SQuery),
6309 + {data, [{DBId}]} ->
6313 +get_logmessage(VHost,Schema) ->
6314 + UName = users_table(VHost,Schema),
6315 + SName = servers_table(VHost,Schema),
6316 + RName = resources_table(VHost,Schema),
6317 + StName = stats_table(VHost,Schema),
6318 + io_lib:format("CREATE OR REPLACE FUNCTION ~s (tbname TEXT, vname TEXT, atdt TEXT, owner TEXT, peer_name TEXT, peer_server TEXT, peer_resource TEXT, mdirection VARCHAR(4), mtype VARCHAR(9), msubj TEXT, mbody TEXT, mtimestamp DOUBLE PRECISION) RETURNS INTEGER AS $$
6321 + peer_nameID INTEGER;
6322 + peer_serverID INTEGER;
6323 + peer_resourceID INTEGER;
6324 + tablename ALIAS for $1;
6325 + viewname ALIAS for $2;
6326 + atdate ALIAS for $3;
6328 + SELECT INTO ownerID user_id FROM ~s WHERE username = owner;
6330 + INSERT INTO ~s (username) VALUES (owner);
6331 + ownerID := lastval();
6334 + SELECT INTO peer_nameID user_id FROM ~s WHERE username = peer_name;
6336 + INSERT INTO ~s (username) VALUES (peer_name);
6337 + peer_nameID := lastval();
6340 + SELECT INTO peer_serverID server_id FROM ~s WHERE server = peer_server;
6342 + INSERT INTO ~s (server) VALUES (peer_server);
6343 + peer_serverID := lastval();
6346 + SELECT INTO peer_resourceID resource_id FROM ~s WHERE resource = peer_resource;
6348 + INSERT INTO ~s (resource) VALUES (peer_resource);
6349 + peer_resourceID := lastval();
6353 + EXECUTE 'INSERT INTO ' || tablename || ' (owner_id, peer_name_id, peer_server_id, peer_resource_id, direction, type, subject, body, timestamp) VALUES (' || ownerID || ',' || peer_nameID || ',' || peer_serverID || ',' || peer_resourceID || ',''' || mdirection || ''',''' || mtype || ''',' || quote_literal(msubj) || ',' || quote_literal(mbody) || ',' || mtimestamp || ')';
6354 + EXCEPTION WHEN undefined_table THEN
6355 + EXECUTE 'CREATE TABLE ' || tablename || ' (' ||
6356 + 'owner_id INTEGER, ' ||
6357 + 'peer_name_id INTEGER, ' ||
6358 + 'peer_server_id INTEGER, ' ||
6359 + 'peer_resource_id INTEGER, ' ||
6360 + 'direction VARCHAR(4) CHECK (direction IN (''to'',''from'')), ' ||
6361 + 'type VARCHAR(9) CHECK (type IN (''chat'',''error'',''groupchat'',''headline'',''normal'')), ' ||
6362 + 'subject TEXT, ' ||
6364 + 'timestamp DOUBLE PRECISION)';
6365 + EXECUTE 'CREATE INDEX \"search_i_' || '~s' || '_' || atdate || '_' || '~s' || '\"' || ' ON ' || tablename || ' (owner_id, peer_name_id, peer_server_id, peer_resource_id)';
6367 + EXECUTE 'CREATE OR REPLACE VIEW ' || viewname || ' AS ' ||
6368 + 'SELECT owner.username AS owner_name, ' ||
6369 + 'peer.username AS peer_name, ' ||
6370 + 'servers.server AS peer_server, ' ||
6371 + 'resources.resource AS peer_resource, ' ||
6372 + 'messages.direction, ' ||
6373 + 'messages.type, ' ||
6374 + 'messages.subject, ' ||
6375 + 'messages.body, ' ||
6376 + 'messages.timestamp ' ||
6381 + '~s resources, ' ||
6382 + tablename || ' messages ' ||
6384 + 'owner.user_id=messages.owner_id and ' ||
6385 + 'peer.user_id=messages.peer_name_id and ' ||
6386 + 'servers.server_id=messages.peer_server_id and ' ||
6387 + 'resources.resource_id=messages.peer_resource_id ' ||
6388 + 'ORDER BY messages.timestamp';
6390 + EXECUTE 'INSERT INTO ' || tablename || ' (owner_id, peer_name_id, peer_server_id, peer_resource_id, direction, type, subject, body, timestamp) VALUES (' || ownerID || ',' || peer_nameID || ',' || peer_serverID || ',' || peer_resourceID || ',''' || mdirection || ''',''' || mtype || ''',' || quote_literal(msubj) || ',' || quote_literal(mbody) || ',' || mtimestamp || ')';
6393 + UPDATE ~s SET count=count+1 where at=atdate and owner_id=ownerID and peer_name_id=peer_nameID and peer_server_id=peer_serverID;
6395 + INSERT INTO ~s (owner_id, peer_name_id, peer_server_id, at, count) VALUES (ownerID, peer_nameID, peer_serverID, atdate, 1);
6399 +$$ LANGUAGE plpgsql;
6400 +", [logmessage_name(VHost,Schema),UName,UName,UName,UName,SName,SName,RName,RName,Schema,escape_vhost(VHost),UName,UName,SName,RName,StName,StName]).
6402 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6406 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6407 +% like do_transaction/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
6408 +sql_transaction_internal(DBRef, Fun) ->
6409 + case sql_query_internal(DBRef, ["BEGIN;"]) of
6411 + case catch Fun() of
6413 + rollback_internal(DBRef, Err);
6414 + {error, _} = Err ->
6415 + rollback_internal(DBRef, Err);
6416 + {'EXIT', _} = Err ->
6417 + rollback_internal(DBRef, Err);
6419 + case sql_query_internal(DBRef, ["COMMIT;"]) of
6420 + {error, _} -> rollback_internal(DBRef, {commit_error});
6423 + {atomic, _} -> Res;
6424 + _ -> {atomic, Res}
6429 + {aborted, {begin_error}}
6432 +% like rollback/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
6433 +rollback_internal(DBRef, Reason) ->
6434 + Res = sql_query_internal(DBRef, ["ROLLBACK;"]),
6435 + {aborted, {Reason, {rollback_result, Res}}}.
6437 +sql_query_internal(DBRef, Query) ->
6438 + case sql_query_internal_silent(DBRef, Query) of
6439 + {error, undefined, Rez} ->
6440 + ?ERROR_MSG("Got undefined result: ~p while ~p", [Rez, lists:append(Query)]),
6441 + {error, undefined};
6443 + ?ERROR_MSG("Failed: ~p while ~p", [Error, lists:append(Query)]),
6448 +sql_query_internal_silent(DBRef, Query) ->
6449 + ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
6450 + % TODO: use pquery?
6451 + get_result(pgsql:squery(DBRef, Query)).
6453 +get_result({ok, ["CREATE TABLE"]}) ->
6455 +get_result({ok, ["DROP TABLE"]}) ->
6457 +get_result({ok, ["ALTER TABLE"]}) ->
6459 +get_result({ok,["DROP VIEW"]}) ->
6461 +get_result({ok,["DROP FUNCTION"]}) ->
6463 +get_result({ok, ["CREATE INDEX"]}) ->
6465 +get_result({ok, ["CREATE FUNCTION"]}) ->
6467 +get_result({ok, [{[$S, $E, $L, $E, $C, $T, $ | _Rest], _Rows, Recs}]}) ->
6470 + lists:map(fun(Elem) when is_binary(Elem) ->
6471 + binary_to_list(Elem);
6472 + (Elem) when is_list(Elem) ->
6474 + (Elem) when is_integer(Elem) ->
6475 + integer_to_list(Elem);
6476 + (Elem) when is_float(Elem) ->
6477 + float_to_list(Elem);
6478 + (Elem) when is_boolean(Elem) ->
6479 + atom_to_list(Elem);
6481 + ?ERROR_MSG("Unknown element type ~p", [Elem]),
6485 + Res = lists:map(Fun, Recs),
6486 + %{data, [list_to_tuple(Rec) || Rec <- Recs]};
6488 +get_result({ok, ["INSERT " ++ OIDN]}) ->
6489 + [_OID, N] = string:tokens(OIDN, " "),
6490 + {updated, list_to_integer(N)};
6491 +get_result({ok, ["DELETE " ++ N]}) ->
6492 + {updated, list_to_integer(N)};
6493 +get_result({ok, ["UPDATE " ++ N]}) ->
6494 + {updated, list_to_integer(N)};
6495 +get_result({ok, ["BEGIN"]}) ->
6497 +get_result({ok, ["LOCK TABLE"]}) ->
6499 +get_result({ok, ["ROLLBACK"]}) ->
6501 +get_result({ok, ["COMMIT"]}) ->
6503 +get_result({ok, ["SET"]}) ->
6505 +get_result({ok, [{error, Error}]}) ->
6508 + {error, undefined, Rez}.
6510 diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl
6511 index b1c5c92..df99681 100644
6512 --- a/src/mod_muc_room.erl
6513 +++ b/src/mod_muc_room.erl
6514 @@ -743,6 +743,12 @@ handle_sync_event({change_config, Config}, _From,
6515 handle_sync_event({change_state, NewStateData}, _From,
6516 StateName, _StateData) ->
6517 {reply, {ok, NewStateData}, StateName, NewStateData};
6518 +handle_sync_event({get_jid_nick, Jid}, _From, StateName, StateData) ->
6519 + R = case ?DICT:find(jlib:jid_tolower(Jid), StateData#state.users) of
6521 + {ok, {user, _, Nick, _, _}} -> Nick
6523 + {reply, R, StateName, StateData};
6524 handle_sync_event(_Event, _From, StateName,
6526 Reply = ok, {reply, Reply, StateName, StateData}.
6527 diff --git a/src/mod_roster.erl b/src/mod_roster.erl
6528 index 7415aa3..f2a69f9 100644
6529 --- a/src/mod_roster.erl
6530 +++ b/src/mod_roster.erl
6533 -include("ejabberd_web_admin.hrl").
6535 +-include("mod_logdb.hrl").
6537 -export_type([subscription/0]).
6539 start(Host, Opts) ->
6540 @@ -1358,6 +1360,14 @@ user_roster(User, Server, Query, Lang) ->
6542 Items = get_roster(LUser, LServer),
6543 SItems = lists:sort(Items),
6545 + Settings = case gen_mod:is_loaded(Server, mod_logdb) of
6547 + mod_logdb:get_user_settings(User, Server);
6552 FItems = case SItems of
6553 [] -> [?CT(<<"None">>)];
6555 @@ -1415,7 +1425,33 @@ user_roster(User, Server, Query, Lang) ->
6556 [?INPUTT(<<"submit">>,
6558 (ejabberd_web_admin:term_to_id(R#roster.jid))/binary>>,
6561 + case gen_mod:is_loaded(Server, mod_logdb) of
6563 + Peer = jlib:jid_to_string(R#roster.jid),
6564 + A = lists:member(Peer, Settings#user_settings.dolog_list),
6565 + B = lists:member(Peer, Settings#user_settings.donotlog_list),
6569 + {<<"donotlog">>, <<"Do Not Log Messages">>};
6571 + {<<"dolog">>, <<"Log Messages">>};
6572 + Settings#user_settings.dolog_default == true ->
6573 + {<<"donotlog">>, <<"Do Not Log Messages">>};
6574 + Settings#user_settings.dolog_default == false ->
6575 + {<<"dolog">>, <<"Log Messages">>}
6578 + ?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
6579 + [?INPUTT(<<"submit">>,
6581 + (ejabberd_web_admin:term_to_id(R#roster.jid))/binary>>,
6590 @@ -1540,9 +1576,42 @@ user_roster_item_parse_query(User, Server, Items,
6598 + case lists:keysearch(
6599 + <<"donotlog">>, (ejabberd_web_admin:term_to_id(JID))/binary, 1, Query) of
6601 + Peer = jlib:jid_to_string(JID),
6602 + Settings = mod_logdb:get_user_settings(User, Server),
6603 + DNLL = case lists:member(Peer, Settings#user_settings.donotlog_list) of
6604 + false -> lists:append(Settings#user_settings.donotlog_list, [Peer]);
6605 + true -> Settings#user_settings.donotlog_list
6607 + DLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.dolog_list),
6608 + Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
6609 + % TODO: check returned value
6610 + ok = mod_logdb:set_user_settings(User, Server, Sett),
6613 + case lists:keysearch(
6614 + <<"dolog">>, (ejabberd_web_admin:term_to_id(JID))/binary, 1, Query) of
6616 + Peer = jlib:jid_to_string(JID),
6617 + Settings = mod_logdb:get_user_settings(User, Server),
6618 + DLL = case lists:member(Peer, Settings#user_settings.dolog_list) of
6619 + false -> lists:append(Settings#user_settings.dolog_list, [Peer]);
6620 + true -> Settings#user_settings.dolog_list
6622 + DNLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.donotlog_list),
6623 + Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
6624 + % TODO: check returned value
6625 + ok = mod_logdb:set_user_settings(User, Server, Sett),