1 diff --git a/priv/msgs/nl.msg b/priv/msgs/nl.msg
2 index c8bb951..f6f6dc5 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 46e2f77..139dc5d 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 e5ea42e..10575d0 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 f6f9553..56b5dd5 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/src/gen_logdb.erl b/src/gen_logdb.erl
135 index 0000000..06a894b
137 +++ b/src/gen_logdb.erl
139 +%%%----------------------------------------------------------------------
140 +%%% File : gen_logdb.erl
141 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
142 +%%% Purpose : Describes generic behaviour for mod_logdb backends.
144 +%%% Id : $Id: gen_logdb.erl 1273 2009-02-05 18:12:57Z malik $
145 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
146 +%%%----------------------------------------------------------------------
149 +-author('o.palij@gmail.com').
151 +-export([behaviour_info/1]).
153 +behaviour_info(callbacks) ->
155 + % called from handle_info(start, _)
156 + % it should logon database and return reference to started instance
157 + % start(VHost, Opts) -> {ok, SPid} | error
158 + % Options - list of options to connect to db
159 + % Types: Options = list() -> [] |
160 + % [{user, "logdb"},
162 + % {db, "logdb"}] | ...
163 + % VHost = list() -> "jabber.example.org"
166 + % called from cleanup/1
167 + % it should logoff database and do cleanup
169 + % Types: VHost = list() -> "jabber.example.org"
172 + % called from handle_call({addlog, _}, _, _)
173 + % it should log messages to database
174 + % log_message(VHost, Msg) -> ok | error
176 + % VHost = list() -> "jabber.example.org"
177 + % Msg = record() -> #msg
180 + % called from ejabberdctl rebuild_stats
181 + % it should rebuild stats table (if used) for vhost
182 + % rebuild_stats(VHost)
184 + % VHost = list() -> "jabber.example.org"
185 + {rebuild_stats, 1},
187 + % it should rebuild stats table (if used) for vhost at Date
188 + % rebuild_stats_at(VHost, Date)
190 + % VHost = list() -> "jabber.example.org"
191 + % Date = list() -> "2007-02-12"
192 + {rebuild_stats_at, 2},
194 + % called from user_messages_at_parse_query/5
195 + % it should delete selected user messages at date
196 + % delete_messages_by_user_at(VHost, Msgs, Date) -> ok | error
198 + % VHost = list() -> "jabber.example.org"
199 + % Msgs = list() -> [ #msg1, msg2, ... ]
200 + % Date = list() -> "2007-02-12"
201 + {delete_messages_by_user_at, 3},
203 + % called from user_messages_parse_query/4 | vhost_messages_at_parse_query/4
204 + % it should delete all user messages at date
205 + % delete_all_messages_by_user_at(User, VHost, Date) -> ok | error
207 + % User = list() -> "admin"
208 + % VHost = list() -> "jabber.example.org"
209 + % Date = list() -> "2007-02-12"
210 + {delete_all_messages_by_user_at, 3},
212 + % called from vhost_messages_parse_query/3
213 + % it should delete messages for vhost at date and update stats
214 + % delete_messages_at(VHost, Date) -> ok | error
216 + % VHost = list() -> "jabber.example.org"
217 + % Date = list() -> "2007-02-12"
218 + {delete_messages_at, 2},
220 + % called from ejabberd_web_admin:vhost_messages_stats/3
221 + % it should return sorted list of count of messages by dates for vhost
222 + % get_vhost_stats(VHost) -> {ok, [{Date1, Msgs_count1}, {Date2, Msgs_count2}, ... ]} |
225 + % VHost = list() -> "jabber.example.org"
226 + % DateN = list() -> "2007-02-12"
227 + % Msgs_countN = number() -> 241
228 + {get_vhost_stats, 1},
230 + % called from ejabberd_web_admin:vhost_messages_stats_at/4
231 + % it should return sorted list of count of messages by users at date for vhost
232 + % get_vhost_stats_at(VHost, Date) -> {ok, [{User1, Msgs_count1}, {User2, Msgs_count2}, ....]} |
235 + % VHost = list() -> "jabber.example.org"
236 + % Date = list() -> "2007-02-12"
237 + % UserN = list() -> "admin"
238 + % Msgs_countN = number() -> 241
239 + {get_vhost_stats_at, 2},
241 + % called from ejabberd_web_admin:user_messages_stats/4
242 + % it should return sorted list of count of messages by date for user at vhost
243 + % get_user_stats(User, VHost) -> {ok, [{Date1, Msgs_count1}, {Date2, Msgs_count2}, ...]} |
246 + % User = list() -> "admin"
247 + % VHost = list() -> "jabber.example.org"
248 + % DateN = list() -> "2007-02-12"
249 + % Msgs_countN = number() -> 241
250 + {get_user_stats, 2},
252 + % called from ejabberd_web_admin:user_messages_stats_at/5
253 + % it should return all user messages at date
254 + % get_user_messages_at(User, VHost, Date) -> {ok, Msgs} | {error, Reason}
256 + % User = list() -> "admin"
257 + % VHost = list() -> "jabber.example.org"
258 + % Date = list() -> "2007-02-12"
259 + % Msgs = list() -> [ #msg1, msg2, ... ]
260 + {get_user_messages_at, 3},
262 + % called from many places
263 + % it should return list of dates for vhost
264 + % get_dates(VHost) -> [Date1, Date2, ... ]
266 + % VHost = list() -> "jabber.example.org"
267 + % DateN = list() -> "2007-02-12"
270 + % called from start
271 + % it should return list with users settings for VHost in db
272 + % get_users_settings(VHost) -> [#user_settings1, #user_settings2, ... ] | error
274 + % VHost = list() -> "jabber.example.org"
275 + {get_users_settings, 1},
277 + % called from many places
278 + % it should return User settings at VHost from db
279 + % get_user_settings(User, VHost) -> error | {ok, #user_settings}
281 + % User = list() -> "admin"
282 + % VHost = list() -> "jabber.example.org"
283 + {get_user_settings, 2},
285 + % called from web admin
286 + % it should set User settings at VHost
287 + % set_user_settings(User, VHost, #user_settings) -> ok | error
289 + % User = list() -> "admin"
290 + % VHost = list() -> "jabber.example.org"
291 + {set_user_settings, 3},
293 + % called from remove_user (ejabberd hook)
294 + % it should remove user messages and settings at VHost
295 + % drop_user(User, VHost) -> ok | error
297 + % User = list() -> "admin"
298 + % VHost = list() -> "jabber.example.org"
301 +behaviour_info(_) ->
303 diff --git a/src/mod_logdb.erl b/src/mod_logdb.erl
305 index 0000000..72f1982
307 +++ b/src/mod_logdb.erl
309 +%%%----------------------------------------------------------------------
310 +%%% File : mod_logdb.erl
311 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
312 +%%% Purpose : Frontend for log user messages to db
314 +%%% Id : $Id: mod_logdb.erl 1360 2009-07-30 06:00:14Z malik $
315 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
316 +%%%----------------------------------------------------------------------
319 +-author('o.palij@gmail.com').
321 +-behaviour(gen_server).
322 +-behaviour(gen_mod).
325 +-export([start_link/2]).
327 +-export([start/2,stop/1]).
329 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
331 +-export([send_packet/3, receive_packet/4, remove_user/2]).
332 +-export([get_local_identity/5,
333 + get_local_features/5,
335 + adhoc_local_items/4,
336 + adhoc_local_commands/4
337 +% get_sm_identity/5,
338 +% get_sm_features/5,
341 +% adhoc_sm_commands/4]).
344 +-export([rebuild_stats/3,
345 + copy_messages/1, copy_messages_ctl/3, copy_messages_int_tc/1]).
347 +-export([get_vhost_stats/1, get_vhost_stats_at/2,
348 + get_user_stats/2, get_user_messages_at/3,
351 + convert_timestamp/1, convert_timestamp_brief/1,
352 + get_user_settings/2, set_user_settings/3,
353 + user_messages_at_parse_query/4, user_messages_parse_query/3,
354 + vhost_messages_parse_query/2, vhost_messages_at_parse_query/4,
355 + list_to_bool/1, bool_to_list/1,
356 + list_to_string/1, string_to_list/1,
357 + get_module_settings/1, set_module_settings/2,
358 + purge_old_records/2]).
360 +-export([webadmin_menu/3,
363 + user_parse_query/5]).
365 +-export([vhost_messages_stats/3,
366 + vhost_messages_stats_at/4,
367 + user_messages_stats/4,
368 + user_messages_stats_at/5]).
370 +-include("mod_logdb.hrl").
371 +-include("ejabberd.hrl").
372 +-include("mod_roster.hrl").
373 +-include("jlib.hrl").
374 +-include("ejabberd_ctl.hrl").
375 +-include("adhoc.hrl").
376 +-include("ejabberd_web_admin.hrl").
377 +-include("ejabberd_http.hrl").
378 +-include("logger.hrl").
380 +-define(PROCNAME, ejabberd_mod_logdb).
381 +% gen_server call timeout
382 +-define(CALL_TIMEOUT, 10000).
384 +-record(state, {vhost, dbmod, backendPid, monref, purgeRef, pollRef, dbopts, dbs, dolog_default, ignore_jids, groupchat, purge_older_days, poll_users_settings, drop_messages_on_user_removal}).
386 +ets_settings_table(VHost) -> list_to_atom("ets_logdb_settings_" ++ binary_to_list(VHost)).
388 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
390 +% gen_mod/gen_server callbacks
392 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
393 +% ejabberd starts module
394 +start(VHost, Opts) ->
396 + {gen_mod:get_module_proc(VHost, ?PROCNAME),
397 + {?MODULE, start_link, [VHost, Opts]},
402 + % add child to ejabberd_sup
403 + supervisor:start_child(ejabberd_sup, ChildSpec).
405 +% supervisor starts gen_server
406 +start_link(VHost, Opts) ->
407 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
408 + {ok, Pid} = gen_server:start_link({local, Proc}, ?MODULE, [VHost, Opts], []),
412 +init([VHost, Opts]) ->
413 + process_flag(trap_exit, true),
414 + DBsRaw = gen_mod:get_opt(dbs, Opts, fun(A) -> A end, [{mnesia, []}]),
415 + DBs = case lists:keysearch(mnesia, 1, DBsRaw) of
416 + false -> lists:append(DBsRaw, [{mnesia,[]}]);
417 + {value, _} -> DBsRaw
419 + VHostDB = gen_mod:get_opt(vhosts, Opts, fun(A) -> A end, [{VHost, mnesia}]),
420 + % 10 is default becouse of using in clustered environment
421 + PollUsersSettings = gen_mod:get_opt(poll_users_settings, Opts, fun(A) -> A end, 10),
424 + case lists:keysearch(VHost, 1, VHostDB) of
426 + ?WARNING_MSG("There is no logging backend defined for '~s', switching to mnesia", [VHost]),
428 + {value,{_, DBNameResult}} ->
429 + case lists:keysearch(DBNameResult, 1, DBs) of
431 + ?WARNING_MSG("There is no such logging backend '~s' defined for '~s', switching to mnesia", [DBNameResult, VHost]),
433 + {value, {_, DBOptsResult}} ->
434 + {DBNameResult, DBOptsResult}
438 + ?MYDEBUG("Starting mod_logdb for '~s' with '~s' backend", [VHost, DBName]),
440 + DBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(DBName)),
442 + {ok, #state{vhost=VHost,
445 + % dbs used for convert messages from one backend to other
447 + dolog_default=gen_mod:get_opt(dolog_default, Opts, fun(A) -> A end, true),
448 + drop_messages_on_user_removal=gen_mod:get_opt(drop_messages_on_user_removal, Opts, fun(A) -> A end, true),
449 + ignore_jids=gen_mod:get_opt(ignore_jids, Opts, fun(A) -> A end, []),
450 + groupchat=gen_mod:get_opt(groupchat, Opts, fun(A) -> A end, none),
451 + purge_older_days=gen_mod:get_opt(purge_older_days, Opts, fun(A) -> A end, never),
452 + poll_users_settings=PollUsersSettings}}.
454 +cleanup(#state{vhost=VHost} = _State) ->
455 + ?MYDEBUG("Stopping ~s for ~p", [?MODULE, VHost]),
457 + %ets:delete(ets_settings_table(VHost)),
459 + ejabberd_hooks:delete(remove_user, VHost, ?MODULE, remove_user, 90),
460 + ejabberd_hooks:delete(user_send_packet, VHost, ?MODULE, send_packet, 90),
461 + ejabberd_hooks:delete(user_receive_packet, VHost, ?MODULE, receive_packet, 90),
462 + %ejabberd_hooks:delete(adhoc_sm_commands, VHost, ?MODULE, adhoc_sm_commands, 110),
463 + %ejabberd_hooks:delete(adhoc_sm_items, VHost, ?MODULE, adhoc_sm_items, 110),
464 + ejabberd_hooks:delete(adhoc_local_commands, VHost, ?MODULE, adhoc_local_commands, 50),
465 + ejabberd_hooks:delete(adhoc_local_items, VHost, ?MODULE, adhoc_local_items, 50),
466 + %ejabberd_hooks:delete(disco_sm_identity, VHost, ?MODULE, get_sm_identity, 50),
467 + %ejabberd_hooks:delete(disco_sm_features, VHost, ?MODULE, get_sm_features, 50),
468 + %ejabberd_hooks:delete(disco_sm_items, VHost, ?MODULE, get_sm_items, 50),
469 + ejabberd_hooks:delete(disco_local_identity, VHost, ?MODULE, get_local_identity, 50),
470 + ejabberd_hooks:delete(disco_local_features, VHost, ?MODULE, get_local_features, 50),
471 + ejabberd_hooks:delete(disco_local_items, VHost, ?MODULE, get_local_items, 50),
473 + ejabberd_hooks:delete(webadmin_menu_host, VHost, ?MODULE, webadmin_menu, 70),
474 + ejabberd_hooks:delete(webadmin_user, VHost, ?MODULE, webadmin_user, 50),
475 + ejabberd_hooks:delete(webadmin_page_host, VHost, ?MODULE, webadmin_page, 50),
476 + ejabberd_hooks:delete(webadmin_user_parse_query, VHost, ?MODULE, user_parse_query, 50),
478 + ?MYDEBUG("Removed hooks for ~p", [VHost]),
480 + %ejabberd_ctl:unregister_commands(VHost, [{"rebuild_stats", "rebuild mod_logdb module stats for vhost"}], ?MODULE, rebuild_stats),
481 + %Supported_backends = lists:flatmap(fun({Backend, _Opts}) ->
482 + % [atom_to_list(Backend), " "]
483 + % end, State#state.dbs),
484 + %ejabberd_ctl:unregister_commands(
486 + % [{"copy_messages backend", "copy messages from backend to current backend. backends could be: " ++ Supported_backends }],
487 + % ?MODULE, copy_messages_ctl),
488 + ?MYDEBUG("Unregistered commands for ~p", [VHost]).
491 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
492 + %gen_server:call(Proc, {cleanup}),
493 + %?MYDEBUG("Cleanup in stop finished!!!!", []),
494 + %timer:sleep(10000),
495 + ok = supervisor:terminate_child(ejabberd_sup, Proc),
496 + ok = supervisor:delete_child(ejabberd_sup, Proc).
498 +handle_call({cleanup}, _From, State) ->
500 + ?MYDEBUG("Cleanup finished!!!!!", []),
501 + {reply, ok, State};
502 +handle_call({get_dates}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
503 + Reply = DBMod:get_dates(VHost),
504 + {reply, Reply, State};
505 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
506 +% ejabberd_web_admin callbacks
507 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
508 +handle_call({delete_messages_by_user_at, PMsgs, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
509 + Reply = DBMod:delete_messages_by_user_at(VHost, PMsgs, binary_to_list(Date)),
510 + {reply, Reply, State};
511 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
512 + Reply = DBMod:delete_all_messages_by_user_at(binary_to_list(User), VHost, binary_to_list(Date)),
513 + {reply, Reply, State};
514 +handle_call({delete_messages_at, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
515 + Reply = DBMod:delete_messages_at(VHost, Date),
516 + {reply, Reply, State};
517 +handle_call({get_vhost_stats}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
518 + Reply = DBMod:get_vhost_stats(VHost),
519 + {reply, Reply, State};
520 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
521 + Reply = DBMod:get_vhost_stats_at(VHost, binary_to_list(Date)),
522 + {reply, Reply, State};
523 +handle_call({get_user_stats, User}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
524 + Reply = DBMod:get_user_stats(binary_to_list(User), VHost),
525 + {reply, Reply, State};
526 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
527 + Reply = DBMod:get_user_messages_at(binary_to_list(User), VHost, binary_to_list(Date)),
528 + {reply, Reply, State};
529 +handle_call({get_user_settings, User}, _From, #state{dbmod=_DBMod, vhost=VHost}=State) ->
530 + Reply = case ets:match_object(ets_settings_table(VHost),
531 + #user_settings{owner_name=User, _='_'}) of
533 + _ -> #user_settings{owner_name=User,
534 + dolog_default=State#state.dolog_default,
538 + {reply, Reply, State};
539 +% TODO: remove User ??
540 +handle_call({set_user_settings, User, GSet}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
541 + Set = GSet#user_settings{owner_name=User},
543 + case ets:match_object(ets_settings_table(VHost),
544 + #user_settings{owner_name=User, _='_'}) of
548 + case DBMod:set_user_settings(binary_to_list(User), VHost, Set) of
552 + true = ets:insert(ets_settings_table(VHost), Set),
556 + {reply, Reply, State};
557 +handle_call({get_module_settings}, _From, State) ->
558 + {reply, State, State};
559 +handle_call({set_module_settings, #state{purge_older_days=PurgeDays,
560 + poll_users_settings=PollSec} = Settings},
562 + #state{purgeRef=PurgeRefOld,
563 + pollRef=PollRefOld,
564 + purge_older_days=PurgeDaysOld,
565 + poll_users_settings=PollSecOld} = State) ->
567 + PurgeDays == never, PurgeDaysOld /= never ->
568 + {ok, cancel} = timer:cancel(PurgeRefOld),
570 + is_integer(PurgeDays), PurgeDaysOld == never ->
571 + set_purge_timer(PurgeDays);
577 + PollSec == PollSecOld ->
579 + PollSec == 0, PollSecOld /= 0 ->
580 + {ok, cancel} = timer:cancel(PollRefOld),
582 + is_integer(PollSec), PollSecOld == 0 ->
583 + set_poll_timer(PollSec);
584 + is_integer(PollSec), PollSecOld /= 0 ->
585 + {ok, cancel} = timer:cancel(PollRefOld),
586 + set_poll_timer(PollSec)
589 + NewState = State#state{dolog_default=Settings#state.dolog_default,
590 + ignore_jids=Settings#state.ignore_jids,
591 + groupchat=Settings#state.groupchat,
592 + drop_messages_on_user_removal=Settings#state.drop_messages_on_user_removal,
593 + purge_older_days=PurgeDays,
594 + poll_users_settings=PollSec,
597 + {reply, ok, NewState};
598 +handle_call(Msg, _From, State) ->
599 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
601 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
602 +% end ejabberd_web_admin callbacks
603 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
605 +% ejabberd_hooks call
606 +handle_cast({addlog, Direction, Owner, Peer, Packet}, #state{dbmod=DBMod, vhost=VHost}=State) ->
607 + case filter(Owner, Peer, State) of
609 + case catch packet_parse(Owner, Peer, Packet, Direction, State) of
612 + {'EXIT', Reason} ->
613 + ?ERROR_MSG("Failed to parse: ~p", [Reason]);
615 + DBMod:log_message(VHost, Msg)
621 +handle_cast({remove_user, User}, #state{dbmod=DBMod, vhost=VHost}=State) ->
622 + case State#state.drop_messages_on_user_removal of
624 + DBMod:drop_user(binary_to_list(User), VHost),
625 + ?INFO_MSG("Launched ~s@~s removal", [User, VHost]);
627 + ?INFO_MSG("Message removing is disabled. Keeping messages for ~s@~s", [User, VHost])
630 +% ejabberdctl rebuild_stats/3
631 +handle_cast({rebuild_stats}, #state{dbmod=DBMod, vhost=VHost}=State) ->
632 + DBMod:rebuild_stats(VHost),
634 +handle_cast({copy_messages, Backend}, State) ->
635 + spawn(?MODULE, copy_messages, [[State, Backend]]),
637 +handle_cast({copy_messages, Backend, Date}, State) ->
638 + spawn(?MODULE, copy_messages, [[State, Backend, Date]]),
640 +handle_cast(Msg, State) ->
641 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
644 +% return: disabled | timer reference
645 +set_purge_timer(PurgeDays) ->
648 + Days when is_integer(Days) ->
649 + {ok, Ref1} = timer:send_interval(timer:hours(24), scheduled_purging),
653 +% return: disabled | timer reference
654 +set_poll_timer(PollSec) ->
657 + {ok, Ref2} = timer:send_interval(timer:seconds(PollSec), poll_users_settings),
659 + % db polling disabled
663 + {ok, Ref3} = timer:send_interval(timer:seconds(10), poll_users_settings),
667 +% actual starting of logging
668 +% from timer:send_after (in init)
669 +handle_info(start, #state{dbmod=DBMod, vhost=VHost}=State) ->
670 + case DBMod:start(VHost, State#state.dbopts) of
671 + {error,{already_started,_}} ->
672 + ?MYDEBUG("backend module already started - trying to stop it", []),
674 + {stop, already_started, State};
676 + timer:sleep(30000),
677 + ?ERROR_MSG("Failed to start: ~p", [Reason]),
678 + {stop, db_connection_failed, State};
680 + ?INFO_MSG("~p connection established", [DBMod]),
682 + MonRef = erlang:monitor(process, SPid),
684 + ets:new(ets_settings_table(VHost), [named_table,public,set,{keypos, #user_settings.owner_name}]),
685 + DoLog = case DBMod:get_users_settings(VHost) of
686 + {ok, Settings} -> [Sett#user_settings{owner_name = iolist_to_binary(Sett#user_settings.owner_name)} || Sett <- Settings];
687 + {error, _Reason} -> []
689 + ets:insert(ets_settings_table(VHost), DoLog),
691 + TrefPurge = set_purge_timer(State#state.purge_older_days),
692 + TrefPoll = set_poll_timer(State#state.poll_users_settings),
694 + ejabberd_hooks:add(remove_user, VHost, ?MODULE, remove_user, 90),
695 + ejabberd_hooks:add(user_send_packet, VHost, ?MODULE, send_packet, 90),
696 + ejabberd_hooks:add(user_receive_packet, VHost, ?MODULE, receive_packet, 90),
698 + ejabberd_hooks:add(disco_local_items, VHost, ?MODULE, get_local_items, 50),
699 + ejabberd_hooks:add(disco_local_features, VHost, ?MODULE, get_local_features, 50),
700 + ejabberd_hooks:add(disco_local_identity, VHost, ?MODULE, get_local_identity, 50),
701 + %ejabberd_hooks:add(disco_sm_items, VHost, ?MODULE, get_sm_items, 50),
702 + %ejabberd_hooks:add(disco_sm_features, VHost, ?MODULE, get_sm_features, 50),
703 + %ejabberd_hooks:add(disco_sm_identity, VHost, ?MODULE, get_sm_identity, 50),
704 + ejabberd_hooks:add(adhoc_local_items, VHost, ?MODULE, adhoc_local_items, 50),
705 + ejabberd_hooks:add(adhoc_local_commands, VHost, ?MODULE, adhoc_local_commands, 50),
706 + %ejabberd_hooks:add(adhoc_sm_items, VHost, ?MODULE, adhoc_sm_items, 50),
707 + %ejabberd_hooks:add(adhoc_sm_commands, VHost, ?MODULE, adhoc_sm_commands, 50),
709 + ejabberd_hooks:add(webadmin_menu_host, VHost, ?MODULE, webadmin_menu, 70),
710 + ejabberd_hooks:add(webadmin_user, VHost, ?MODULE, webadmin_user, 50),
711 + ejabberd_hooks:add(webadmin_page_host, VHost, ?MODULE, webadmin_page, 50),
712 + ejabberd_hooks:add(webadmin_user_parse_query, VHost, ?MODULE, user_parse_query, 50),
714 + ?MYDEBUG("Added hooks for ~p", [VHost]),
716 + %ejabberd_ctl:register_commands(
718 + % [{"rebuild_stats", "rebuild mod_logdb module stats for vhost"}],
719 + % ?MODULE, rebuild_stats),
720 + %Supported_backends = lists:flatmap(fun({Backend, _Opts}) ->
721 + % [atom_to_list(Backend), " "]
722 + % end, State#state.dbs),
723 + %ejabberd_ctl:register_commands(
725 + % [{"copy_messages backend", "copy messages from backend to current backend. backends could be: " ++ Supported_backends }],
726 + % ?MODULE, copy_messages_ctl),
727 + ?MYDEBUG("Registered commands for ~p", [VHost]),
729 + NewState=State#state{monref = MonRef, backendPid=SPid, purgeRef=TrefPurge, pollRef=TrefPoll},
730 + {noreply, NewState};
732 + ?ERROR_MSG("Rez=~p", [Rez]),
733 + timer:sleep(30000),
734 + {stop, db_connection_failed, State}
736 +% from timer:send_interval/2 (in start handle_info)
737 +handle_info(scheduled_purging, #state{vhost=VHost, purge_older_days=Days} = State) ->
738 + ?MYDEBUG("Starting scheduled purging of old records for ~p", [VHost]),
739 + spawn(?MODULE, purge_old_records, [VHost, integer_to_list(Days)]),
741 +% from timer:send_interval/2 (in start handle_info)
742 +handle_info(poll_users_settings, #state{dbmod=DBMod, vhost=VHost}=State) ->
743 + {ok, DoLog} = DBMod:get_users_settings(VHost),
744 + ?MYDEBUG("DoLog=~p", [DoLog]),
745 + true = ets:delete_all_objects(ets_settings_table(VHost)),
746 + ets:insert(ets_settings_table(VHost), DoLog),
748 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
749 + {stop, db_connection_dropped, State};
750 +handle_info({fetch_result, _, _}, State) ->
751 + ?MYDEBUG("Got timed out mysql fetch result", []),
753 +handle_info(Info, State) ->
754 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
757 +terminate(db_connection_failed, _State) ->
759 +terminate(db_connection_dropped, State) ->
760 + ?MYDEBUG("Got terminate with db_connection_dropped", []),
763 +terminate(Reason, #state{monref=undefined} = State) ->
764 + ?MYDEBUG("Got terminate with undefined monref.~nReason: ~p", [Reason]),
767 +terminate(Reason, #state{dbmod=DBMod, vhost=VHost, monref=MonRef, backendPid=Pid} = State) ->
768 + ?INFO_MSG("Reason: ~p", [Reason]),
769 + case erlang:is_process_alive(Pid) of
771 + erlang:demonitor(MonRef, [flush]),
779 +code_change(_OldVsn, State, _Extra) ->
782 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
784 +% ejabberd_hooks callbacks
786 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
787 +% TODO: change to/from to list as sql stores it as list
788 +send_packet(Owner, Peer, P) ->
789 + VHost = Owner#jid.lserver,
790 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
791 + gen_server:cast(Proc, {addlog, to, Owner, Peer, P}).
793 +receive_packet(_JID, Peer, Owner, P) ->
794 + VHost = Owner#jid.lserver,
795 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
796 + gen_server:cast(Proc, {addlog, from, Owner, Peer, P}).
798 +remove_user(User, Server) ->
799 + LUser = jlib:nodeprep(User),
800 + LServer = jlib:nameprep(Server),
801 + Proc = gen_mod:get_module_proc(LServer, ?PROCNAME),
802 + gen_server:cast(Proc, {remove_user, LUser}).
804 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
808 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
809 +rebuild_stats(_Val, VHost, ["rebuild_stats"]) ->
810 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
811 + gen_server:cast(Proc, {rebuild_stats}),
812 + {stop, ?STATUS_SUCCESS};
813 +rebuild_stats(Val, _VHost, _Args) ->
816 +copy_messages_ctl(_Val, VHost, ["copy_messages", Backend]) ->
817 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
818 + gen_server:cast(Proc, {copy_messages, Backend}),
819 + {stop, ?STATUS_SUCCESS};
820 +copy_messages_ctl(_Val, VHost, ["copy_messages", Backend, Date]) ->
821 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
822 + gen_server:cast(Proc, {copy_messages, Backend, Date}),
823 + {stop, ?STATUS_SUCCESS};
824 +copy_messages_ctl(Val, _VHost, _Args) ->
826 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
830 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
832 +% handle_cast({addlog, E}, _)
833 +% raw packet -> #msg
834 +packet_parse(Owner, Peer, Packet, Direction, State) ->
835 + case xml:get_subtag(Packet, <<"body">>) of
840 + case xml:get_tag_attr_s(<<"type">>, Packet) of
841 + <<"error">> -> throw(ignore);
842 + [] -> <<"normal">>;
846 + case Message_type of
847 + <<"groupchat">> when State#state.groupchat == send, Direction == to ->
849 + <<"groupchat">> when State#state.groupchat == send, Direction == from ->
851 + <<"groupchat">> when State#state.groupchat == half ->
852 + Rooms = ets:match(muc_online_room, '$1'),
853 + Ni=lists:foldl(fun([{muc_online_room, {GName, GHost}, Pid}], Names) ->
854 + case gen_fsm:sync_send_all_state_event(Pid, {get_jid_nick,Owner}) of
857 + lists:append(Names, [jlib:jid_to_string({GName, GHost, Nick})])
860 + case lists:member(jlib:jid_to_string(Peer), Ni) of
861 + true when Direction == from ->
866 + <<"groupchat">> when State#state.groupchat == none ->
872 + Message_body = xml:get_tag_cdata(Body_xml),
874 + case xml:get_subtag(Packet, <<"subject">>) of
878 + xml:get_tag_cdata(Subject_xml)
881 + OwnerName = stringprep:tolower(Owner#jid.user),
882 + PName = stringprep:tolower(Peer#jid.user),
883 + PServer = stringprep:tolower(Peer#jid.server),
884 + PResource = Peer#jid.resource,
886 + #msg{timestamp = get_timestamp(),
887 + owner_name = OwnerName,
889 + peer_server = PServer,
890 + peer_resource = PResource,
891 + direction = Direction,
892 + type = Message_type,
893 + subject = Message_subject,
894 + body = Message_body}
897 +% called from handle_cast({addlog, _}, _) -> true (log messages) | false (do not log messages)
898 +filter(Owner, Peer, State) ->
899 + OwnerBin = << (Owner#jid.luser)/binary, "@", (Owner#jid.lserver)/binary >>,
900 + OwnerServ = << "@", (Owner#jid.lserver)/binary >>,
901 + PeerBin = << (Peer#jid.luser)/binary, "@", (Peer#jid.lserver)/binary >>,
902 + PeerServ = << "@", (Peer#jid.lserver)/binary >>,
904 + LogTo = case ets:match_object(ets_settings_table(State#state.vhost),
905 + #user_settings{owner_name=Owner#jid.luser, _='_'}) of
906 + [#user_settings{dolog_default=Default,
908 + donotlog_list=DNLL}] ->
910 + A = lists:member(PeerBin, DLL),
911 + B = lists:member(PeerBin, DNLL),
915 + Default == true -> true;
916 + Default == false -> false;
917 + true -> State#state.dolog_default
919 + _ -> State#state.dolog_default
921 + lists:all(fun(O) -> O end,
922 + [not lists:member(OwnerBin, State#state.ignore_jids),
923 + not lists:member(PeerBin, State#state.ignore_jids),
924 + not lists:member(OwnerServ, State#state.ignore_jids),
925 + not lists:member(PeerServ, State#state.ignore_jids),
928 +purge_old_records(VHost, Days) ->
929 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
931 + Dates = ?MODULE:get_dates(VHost),
932 + DateNow = calendar:datetime_to_gregorian_seconds({date(), {0,0,1}}),
933 + DateDiff = list_to_integer(Days)*24*60*60,
934 + ?MYDEBUG("Purging tables older than ~s days", [Days]),
935 + lists:foreach(fun(Date) ->
936 + [Year, Month, Day] = ejabberd_regexp:split(iolist_to_binary(Date), <<"[^0-9]+">>),
937 + DateInSec = calendar:datetime_to_gregorian_seconds({{binary_to_integer(Year), binary_to_integer(Month), binary_to_integer(Day)}, {0,0,1}}),
939 + (DateNow - DateInSec) > DateDiff ->
940 + gen_server:call(Proc, {delete_messages_at, Date});
942 + ?MYDEBUG("Skipping messages at ~p", [Date])
946 +% called from get_vhost_stats/2, get_user_stats/3
947 +sort_stats(Stats) ->
948 + % Stats = [{"2003-4-15",1}, {"2006-8-18",1}, ... ]
949 + CFun = fun({TableName, Count}) ->
950 + [Year, Month, Day] = ejabberd_regexp:split(iolist_to_binary(TableName), <<"[^0-9]+">>),
951 + { calendar:datetime_to_gregorian_seconds({{binary_to_integer(Year), binary_to_integer(Month), binary_to_integer(Day)}, {0,0,1}}), Count }
953 + % convert to [{63364377601,1}, {63360662401,1}, ... ]
954 + CStats = lists:map(CFun, Stats),
956 + SortedStats = lists:reverse(lists:keysort(1, CStats)),
957 + % convert to [{"2007-12-9",1}, {"2007-10-27",1}, ... ] sorted list
958 + [{mod_logdb:convert_timestamp_brief(TableSec), Count} || {TableSec, Count} <- SortedStats].
960 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
962 +% Date/Time operations
964 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
965 +% return float seconds elapsed from "zero hour" as list
967 + {MegaSec, Sec, MicroSec} = now(),
968 + [List] = io_lib:format("~.5f", [MegaSec*1000000 + Sec + MicroSec/1000000]),
971 +% convert float seconds elapsed from "zero hour" to local time "%Y-%m-%d %H:%M:%S" string
972 +convert_timestamp(Seconds) when is_list(Seconds) ->
973 + case string:to_float(Seconds++".0") of
974 + {F,_} when is_float(F) -> convert_timestamp(F);
975 + _ -> erlang:error(badarg, [Seconds])
977 +convert_timestamp(Seconds) when is_float(Seconds) ->
978 + GregSec = trunc(Seconds + 719528*86400),
979 + UnivDT = calendar:gregorian_seconds_to_datetime(GregSec),
980 + {{Year, Month, Day},{Hour, Minute, Sec}} = calendar:universal_time_to_local_time(UnivDT),
981 + integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day) ++ " " ++ integer_to_list(Hour) ++ ":" ++ integer_to_list(Minute) ++ ":" ++ integer_to_list(Sec).
983 +% convert float seconds elapsed from "zero hour" to local time "%Y-%m-%d" string
984 +convert_timestamp_brief(Seconds) when is_list(Seconds) ->
985 + convert_timestamp_brief(list_to_float(Seconds));
986 +convert_timestamp_brief(Seconds) when is_float(Seconds) ->
987 + GregSec = trunc(Seconds + 719528*86400),
988 + UnivDT = calendar:gregorian_seconds_to_datetime(GregSec),
989 + {{Year, Month, Day},{_Hour, _Minute, _Sec}} = calendar:universal_time_to_local_time(UnivDT),
990 + integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day);
991 +convert_timestamp_brief(Seconds) when is_integer(Seconds) ->
992 + {{Year, Month, Day},{_Hour, _Minute, _Sec}} = calendar:gregorian_seconds_to_datetime(Seconds),
993 + integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day).
995 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
997 +% DB operations (get)
999 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1000 +get_vhost_stats(VHost) ->
1001 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1002 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
1004 +get_vhost_stats_at(VHost, Date) ->
1005 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1006 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
1008 +get_user_stats(User, VHost) ->
1009 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1010 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
1012 +get_user_messages_at(User, VHost, Date) ->
1013 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1014 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
1016 +get_dates(VHost) ->
1017 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1018 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
1020 +get_user_settings(User, VHost) ->
1021 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1022 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
1024 +set_user_settings(User, VHost, Set) ->
1025 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1026 + gen_server:call(Proc, {set_user_settings, User, Set}).
1028 +get_module_settings(VHost) ->
1029 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1030 + gen_server:call(Proc, {get_module_settings}).
1032 +set_module_settings(VHost, Settings) ->
1033 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1034 + gen_server:call(Proc, {set_module_settings, Settings}).
1036 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1038 +% Web admin callbacks (delete)
1040 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1041 +user_messages_at_parse_query(VHost, Date, Msgs, Query) ->
1042 + case lists:keysearch(<<"delete">>, 1, Query) of
1044 + PMsgs = lists:filter(
1046 + ID = jlib:encode_base64(term_to_binary(Msg#msg.timestamp)),
1047 + lists:member({<<"selected">>, ID}, Query)
1049 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1050 + gen_server:call(Proc, {delete_messages_by_user_at, PMsgs, Date}, ?CALL_TIMEOUT);
1055 +user_messages_parse_query(User, VHost, Query) ->
1056 + case lists:keysearch(<<"delete">>, 1, Query) of
1058 + Dates = get_dates(VHost),
1059 + PDates = lists:filter(
1061 + ID = jlib:encode_base64( << User/binary, (iolist_to_binary(Date))/binary >> ),
1062 + lists:member({<<"selected">>, ID}, Query)
1064 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1065 + Rez = lists:foldl(
1068 + [gen_server:call(Proc,
1069 + {delete_all_messages_by_user_at, User, iolist_to_binary(Date)},
1072 + case lists:member(error, Rez) of
1082 +vhost_messages_parse_query(VHost, Query) ->
1083 + case lists:keysearch(<<"delete">>, 1, Query) of
1085 + Dates = get_dates(VHost),
1086 + PDates = lists:filter(
1088 + ID = jlib:encode_base64( << VHost/binary, (iolist_to_binary(Date))/binary >> ),
1089 + lists:member({<<"selected">>, ID}, Query)
1091 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1092 + Rez = lists:foldl(fun(Date, Acc) ->
1093 + lists:append(Acc, [gen_server:call(Proc,
1094 + {delete_messages_at, Date},
1097 + case lists:member(error, Rez) of
1107 +vhost_messages_at_parse_query(VHost, Date, Stats, Query) ->
1108 + case lists:keysearch(<<"delete">>, 1, Query) of
1110 + PStats = lists:filter(
1111 + fun({User, _Count}) ->
1112 + ID = jlib:encode_base64( << (iolist_to_binary(User))/binary, VHost/binary >> ),
1113 + lists:member({<<"selected">>, ID}, Query)
1115 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
1116 + Rez = lists:foldl(fun({User, _Count}, Acc) ->
1117 + lists:append(Acc, [gen_server:call(Proc,
1118 + {delete_all_messages_by_user_at,
1119 + iolist_to_binary(User), iolist_to_binary(Date)},
1122 + case lists:member(error, Rez) of
1132 +copy_messages([#state{vhost=VHost}=State, From]) ->
1133 + ?INFO_MSG("Going to copy messages from ~p for ~p", [From, VHost]),
1135 + {FromDBName, FromDBOpts} =
1136 + case lists:keysearch(list_to_atom(From), 1, State#state.dbs) of
1137 + {value, {FN, FO}} ->
1140 + ?ERROR_MSG("Failed to find record for ~p in dbs", [From]),
1144 + FromDBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(FromDBName)),
1146 + {ok, _FromPid} = FromDBMod:start(VHost, FromDBOpts),
1148 + Dates = FromDBMod:get_dates(VHost),
1149 + DatesLength = length(Dates),
1151 + lists:foldl(fun(Date, Acc) ->
1152 + case copy_messages_int([FromDBMod, State#state.dbmod, VHost, Date]) of
1154 + ?INFO_MSG("Copied messages at ~p (~p/~p)", [Date, Acc, DatesLength]);
1156 + ?ERROR_MSG("Failed to copy messages at ~p (~p/~p): ~p", [Date, Acc, DatesLength, Value]),
1157 + FromDBMod:stop(VHost),
1162 + ?INFO_MSG("Copied messages from ~p", [From]),
1163 + FromDBMod:stop(VHost);
1164 +copy_messages([#state{vhost=VHost}=State, From, Date]) ->
1165 + {value, {FromDBName, FromDBOpts}} = lists:keysearch(list_to_atom(From), 1, State#state.dbs),
1166 + FromDBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(FromDBName)),
1167 + {ok, _FromPid} = FromDBMod:start(VHost, FromDBOpts),
1168 + case catch copy_messages_int([FromDBMod, State#state.dbmod, VHost, Date]) of
1169 + {'exit', Reason} ->
1170 + ?ERROR_MSG("Failed to copy messages at ~p: ~p", [Date, Reason]);
1172 + ?INFO_MSG("Copied messages at ~p", [Date]);
1174 + ?ERROR_MSG("Failed to copy messages at ~p: ~p", [Date, Value])
1176 + FromDBMod:stop(VHost).
1178 +copy_messages_int([FromDBMod, ToDBMod, VHost, Date]) ->
1179 + ets:new(mod_logdb_temp, [named_table, set, public]),
1180 + {Time, Value} = timer:tc(?MODULE, copy_messages_int_tc, [[FromDBMod, ToDBMod, VHost, Date]]),
1181 + ets:delete_all_objects(mod_logdb_temp),
1182 + ets:delete(mod_logdb_temp),
1183 + ?INFO_MSG("copy_messages at ~p elapsed ~p sec", [Date, Time/1000000]),
1186 +copy_messages_int_tc([FromDBMod, ToDBMod, VHost, Date]) ->
1187 + ?INFO_MSG("Going to copy messages from ~p for ~p at ~p", [FromDBMod, VHost, Date]),
1189 + ok = FromDBMod:rebuild_stats_at(VHost, binary_to_list(Date)),
1190 + catch mod_logdb:rebuild_stats_at(VHost, Date),
1191 + {ok, FromStats} = FromDBMod:get_vhost_stats_at(VHost, binary_to_list(Date)),
1192 + ToStats = case mod_logdb:get_vhost_stats_at(VHost, Date) of
1193 + {ok, Stats} -> Stats;
1197 + FromStatsS = lists:keysort(1, FromStats),
1198 + ToStatsS = lists:keysort(1, ToStats),
1200 + StatsLength = length(FromStats),
1203 + % destination table is empty
1204 + FromDBMod /= mod_logdb_mnesia_old, ToStats == [] ->
1205 + fun({User, _Count}, Acc) ->
1206 + {ok, Msgs} = FromDBMod:get_user_messages_at(binary_to_list(User), VHost, binary_to_list(Date)),
1208 + lists:foldl(fun(Msg, MFAcc) ->
1209 + ok = ToDBMod:log_message(VHost, Msg),
1213 + ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
1214 + %timer:sleep(100),
1217 + % destination table is not empty
1218 + FromDBMod /= mod_logdb_mnesia_old, ToStats /= [] ->
1219 + fun({User, _Count}, Acc) ->
1220 + {ok, ToMsgs} = ToDBMod:get_user_messages_at(binary_to_list(User), VHost, binary_to_list(Date)),
1221 + lists:foreach(fun(#msg{timestamp=Tst}) when length(Tst) == 16 ->
1222 + ets:insert(mod_logdb_temp, {Tst});
1223 + % mysql, pgsql removes final zeros after decimal point
1224 + (#msg{timestamp=Tst}) when length(Tst) < 16 ->
1225 + {F, _} = string:to_float(Tst++".0"),
1226 + [T] = io_lib:format("~.5f", [F]),
1227 + ets:insert(mod_logdb_temp, {T})
1229 + {ok, Msgs} = FromDBMod:get_user_messages_at(binary_to_list(User), VHost, binary_to_list(Date)),
1231 + lists:foldl(fun(#msg{timestamp=ToTimestamp} = Msg, MFAcc) ->
1232 + case ets:member(mod_logdb_temp, ToTimestamp) of
1234 + ok = ToDBMod:log_message(VHost, Msg),
1235 + ets:insert(mod_logdb_temp, {ToTimestamp}),
1242 + ets:delete_all_objects(mod_logdb_temp),
1243 + ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
1244 + %timer:sleep(100),
1247 + % copying from mod_logmnesia
1249 + fun({User, _Count}, Acc) ->
1251 + case ToDBMod:get_user_messages_at(binary_to_list(User), VHost, binary_to_list(Date)) of
1255 + lists:foreach(fun(#msg{timestamp=Tst}) when length(Tst) == 16 ->
1256 + ets:insert(mod_logdb_temp, {Tst});
1257 + % mysql, pgsql removes final zeros after decimal point
1258 + (#msg{timestamp=Tst}) when length(Tst) < 15 ->
1259 + {F, _} = string:to_float(Tst++".0"),
1260 + [T] = io_lib:format("~.5f", [F]),
1261 + ets:insert(mod_logdb_temp, {T})
1266 + {ok, Msgs} = FromDBMod:get_user_messages_at(binary_to_list(User), VHost, binary_to_list(Date)),
1270 + fun({msg, TU, TS, TR, FU, FS, FR, Type, Subj, Body, Timest},
1272 + [Timestamp] = if is_float(Timest) == true ->
1273 + io_lib:format("~.5f", [Timest]);
1274 + % early versions of mod_logmnesia
1275 + is_integer(Timest) == true ->
1276 + io_lib:format("~.5f", [Timest-719528*86400.0]);
1278 + ?ERROR_MSG("Incorrect timestamp ~p", [Timest]),
1281 + case ets:member(mod_logdb_temp, Timestamp) of
1286 + TMsg = #msg{timestamp=Timestamp,
1288 + peer_name=FU, peer_server=FS, peer_resource=FR,
1291 + subject=Subj, body=Body},
1292 + ok = ToDBMod:log_message(VHost, TMsg);
1298 + FMsg = #msg{timestamp=Timestamp,
1300 + peer_name=TU, peer_server=TS, peer_resource=TR,
1303 + subject=Subj, body=Body},
1304 + ok = ToDBMod:log_message(VHost, FMsg);
1307 + ets:insert(mod_logdb_temp, {Timestamp}),
1309 + true -> % not ets:member
1312 + end, 0, Msgs), % foldl
1314 + ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
1315 + %timer:sleep(100),
1318 + end, % if FromDBMod /= mod_logdb_mnesia_old
1321 + FromStats == [] ->
1322 + ?INFO_MSG("No messages were found at ~p", [Date]);
1323 + FromStatsS == ToStatsS ->
1324 + ?INFO_MSG("Stats are equal at ~p", [Date]);
1325 + FromStatsS /= ToStatsS ->
1326 + lists:foldl(CopyFun, 0, FromStats),
1327 + ok = ToDBMod:rebuild_stats_at(VHost, binary_to_list(Date))
1328 + %timer:sleep(1000)
1333 +list_to_bool(Num) when is_binary(Num) ->
1334 + list_to_bool(binary_to_list(Num));
1335 +list_to_bool(Num) when is_list(Num) ->
1336 + case lists:member(Num, ["t", "true", "y", "yes", "1"]) of
1340 + case lists:member(Num, ["f", "false", "n", "no", "0"]) of
1348 +bool_to_list(true) ->
1350 +bool_to_list(false) ->
1353 +list_to_string([]) ->
1355 +list_to_string(List) when is_list(List) ->
1356 + Str = lists:flatmap(fun(Elm) when is_binary(Elm) ->
1357 + binary_to_list(Elm) ++ "\n";
1358 + (Elm) when is_list(Elm) ->
1361 + lists:sublist(Str, length(Str)-1).
1363 +string_to_list(null) ->
1365 +string_to_list([]) ->
1367 +string_to_list(String) ->
1368 + ejabberd_regexp:split(iolist_to_binary(String), <<"\n">>).
1370 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1372 +% ad-hoc (copy/pasted from mod_configure.erl)
1374 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1375 +-define(ITEMS_RESULT(Allow, LNode, Fallback),
1380 + case get_local_items(LServer, LNode,
1381 + jlib:jid_to_string(To), Lang) of
1389 +get_local_items(Acc, From, #jid{lserver = LServer} = To, <<"">>, Lang) ->
1390 + case gen_mod:is_loaded(LServer, mod_adhoc) of
1394 + Items = case Acc of
1395 + {result, Its} -> Its;
1398 + AllowUser = acl:match_rule(LServer, mod_logdb, From),
1399 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1401 + AllowUser == allow; AllowAdmin == allow ->
1402 + case get_local_items(LServer, [],
1403 + jlib:jid_to_string(To), Lang) of
1405 + {result, Items ++ Res};
1406 + {error, _Error} ->
1413 +get_local_items(Acc, From, #jid{lserver = LServer} = To, Node, Lang) ->
1414 + case gen_mod:is_loaded(LServer, mod_adhoc) of
1418 + LNode = str:tokens(Node, <<"/">>),
1419 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1421 + [<<"mod_logdb">>] ->
1422 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1423 + [<<"mod_logdb_users">>] ->
1424 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1425 + [<<"mod_logdb_users">>, <<$@, _/binary>>] ->
1426 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1427 + [<<"mod_logdb_users">>, _User] ->
1428 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1429 + [<<"mod_logdb_settings">>] ->
1430 + ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
1436 +-define(T(Lang, Text), translate:translate(Lang, Text)).
1438 +-define(NODE(Name, Node),
1439 + #xmlel{name = <<"item">>,
1441 + [{<<"jid">>, Server}, {<<"name">>, ?T(Lang, Name)},
1442 + {<<"node">>, Node}],
1445 +get_local_items(_Host, [], Server, Lang) ->
1447 + [?NODE(<<"Messages logging engine">>, <<"mod_logdb">>)]
1449 +get_local_items(_Host, [<<"mod_logdb">>], Server, Lang) ->
1451 + [?NODE(<<"Messages logging engine users">>, <<"mod_logdb_users">>),
1452 + ?NODE(<<"Messages logging engine settings">>, <<"mod_logdb_settings">>)]
1454 +get_local_items(Host, [<<"mod_logdb_users">>], Server, Lang) ->
1455 + {result, get_all_vh_users(Host, Server, Lang)};
1456 +get_local_items(Host, [<<"mod_logdb_users">>, <<$@, Diap/binary>>], Server, Lang) ->
1457 + case catch ejabberd_auth:get_vh_registered_users(Host) of
1458 + {'EXIT', _Reason} ->
1459 + ?ERR_INTERNAL_SERVER_ERROR;
1461 + SUsers = lists:sort([{S, U} || {U, S} <- Users]),
1463 + [S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
1464 + N1 = jlib:binary_to_integer(S1),
1465 + N2 = jlib:binary_to_integer(S2),
1466 + Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
1467 + lists:map(fun({S, U}) ->
1468 + ?NODE(<< U/binary, "@", S/binary >>,
1469 + << (iolist_to_binary("mod_logdb_users/"))/binary, U/binary, "@", S/binary >>)
1472 + {'EXIT', _Reason} ->
1473 + ?ERR_NOT_ACCEPTABLE;
1478 +get_local_items(_Host, [<<"mod_logdb_users">>, _User], _Server, _Lang) ->
1480 +get_local_items(_Host, [<<"mod_logdb_settings">>], _Server, _Lang) ->
1482 +get_local_items(_Host, Item, _Server, _Lang) ->
1483 + ?MYDEBUG("asked for items in ~p", [Item]),
1484 + {error, ?ERR_ITEM_NOT_FOUND}.
1486 +-define(INFO_RESULT(Allow, Feats),
1488 + deny -> {error, ?ERR_FORBIDDEN};
1489 + allow -> {result, Feats}
1492 +get_local_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
1493 + case gen_mod:is_loaded(LServer, mod_adhoc) of
1497 + LNode = str:tokens(Node, <<"/">>),
1498 + AllowUser = acl:match_rule(LServer, mod_logdb, From),
1499 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1501 + [<<"mod_logdb">>] when AllowUser == allow; AllowAdmin == allow ->
1502 + ?INFO_RESULT(allow, [?NS_COMMANDS]);
1503 + [<<"mod_logdb">>] ->
1504 + ?INFO_RESULT(deny, [?NS_COMMANDS]);
1505 + [<<"mod_logdb_users">>] ->
1506 + ?INFO_RESULT(AllowAdmin, []);
1507 + [<<"mod_logdb_users">>, [$@ | _]] ->
1508 + ?INFO_RESULT(AllowAdmin, []);
1509 + [<<"mod_logdb_users">>, _User] ->
1510 + ?INFO_RESULT(AllowAdmin, [?NS_COMMANDS]);
1511 + [<<"mod_logdb_settings">>] ->
1512 + ?INFO_RESULT(AllowAdmin, [?NS_COMMANDS]);
1520 +-define(INFO_IDENTITY(Category, Type, Name, Lang),
1521 + [#xmlel{name = <<"identity">>,
1523 + [{<<"category">>, Category}, {<<"type">>, Type},
1524 + {<<"name">>, ?T(Lang, Name)}],
1527 +-define(INFO_COMMAND(Name, Lang),
1528 + ?INFO_IDENTITY(<<"automation">>, <<"command-node">>,
1531 +get_local_identity(Acc, _From, _To, Node, Lang) ->
1532 + LNode = str:tokens(Node, <<"/">>),
1534 + [<<"mod_logdb">>] ->
1535 + ?INFO_COMMAND(<<"Messages logging engine">>, Lang);
1536 + [<<"mod_logdb_users">>] ->
1537 + ?INFO_COMMAND(<<"Messages logging engine users">>, Lang);
1538 + [<<"mod_logdb_users">>, [$@ | _]] ->
1540 + [<<"mod_logdb_users">>, User] ->
1541 + ?INFO_COMMAND(User, Lang);
1542 + [<<"mod_logdb_settings">>] ->
1543 + ?INFO_COMMAND(<<"Messages logging engine settings">>, Lang);
1550 +%get_sm_items(Acc, From, To, Node, Lang) ->
1551 +% ?MYDEBUG("get_sm_items Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
1554 +%get_sm_features(Acc, From, To, Node, Lang) ->
1555 +% ?MYDEBUG("get_sm_features Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
1558 +%get_sm_identity(Acc, From, To, Node, Lang) ->
1559 +% ?MYDEBUG("get_sm_identity Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
1562 +adhoc_local_items(Acc, From, #jid{lserver = LServer, server = Server} = To,
1564 + Items = case Acc of
1565 + {result, Its} -> Its;
1568 + Nodes = recursively_get_local_items(LServer, "", Server, Lang),
1569 + Nodes1 = lists:filter(
1571 + Nd = xml:get_tag_attr_s("node", N),
1572 + F = get_local_features([], From, To, Nd, Lang),
1574 + {result, [?NS_COMMANDS]} ->
1580 + {result, Items ++ Nodes1}.
1582 +recursively_get_local_items(_LServer, <<"mod_logdb_users">>, _Server, _Lang) ->
1584 +recursively_get_local_items(LServer, Node, Server, Lang) ->
1585 + LNode = str:tokens(Node, <<"/">>),
1586 + Items = case get_local_items(LServer, LNode, Server, Lang) of
1589 + {error, _Error} ->
1592 + Nodes = lists:flatten(
1595 + S = xml:get_tag_attr_s("jid", N),
1596 + Nd = xml:get_tag_attr_s("node", N),
1597 + if (S /= Server) or (Nd == "") ->
1600 + [N, recursively_get_local_items(
1601 + LServer, Nd, Server, Lang)]
1606 +-define(COMMANDS_RESULT(Allow, From, To, Request),
1609 + {error, ?ERR_FORBIDDEN};
1611 + adhoc_local_commands(From, To, Request)
1614 +adhoc_local_commands(Acc, From, #jid{lserver = LServer} = To,
1615 + #adhoc_request{node = Node} = Request) ->
1616 + LNode = str:tokens(Node, <<"/">>),
1617 + AllowUser = acl:match_rule(LServer, mod_logdb, From),
1618 + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
1620 + [<<"mod_logdb">>] when AllowUser == allow; AllowAdmin == allow ->
1621 + ?COMMANDS_RESULT(allow, From, To, Request);
1622 + [<<"mod_logdb_users">>, _User] when AllowAdmin == allow ->
1623 + ?COMMANDS_RESULT(allow, From, To, Request);
1624 + [<<"mod_logdb_settings">>] when AllowAdmin == allow ->
1625 + ?COMMANDS_RESULT(allow, From, To, Request);
1630 +adhoc_local_commands(From, #jid{lserver = LServer} = _To,
1631 + #adhoc_request{lang = Lang,
1633 + sessionid = SessionID,
1635 + xdata = XData} = Request) ->
1636 + LNode = str:tokens(Node, <<"/">>),
1637 + %% If the "action" attribute is not present, it is
1638 + %% understood as "execute". If there was no <actions/>
1639 + %% element in the first response (which there isn't in our
1640 + %% case), "execute" and "complete" are equivalent.
1641 + ActionIsExecute = lists:member(Action,
1642 + [<<"">>, <<"execute">>, <<"complete">>]),
1643 + if Action == <<"cancel">> ->
1644 + %% User cancels request
1645 + adhoc:produce_response(
1647 + #adhoc_response{status = canceled});
1648 + XData == false, ActionIsExecute ->
1649 + %% User requests form
1650 + case get_form(LServer, LNode, From, Lang) of
1652 + adhoc:produce_response(
1654 + #adhoc_response{status = executing,
1655 + elements = Form});
1659 + XData /= false, ActionIsExecute ->
1660 + %% User returns form.
1661 + case jlib:parse_xdata_submit(XData) of
1663 + {error, ?ERR_BAD_REQUEST};
1665 + case catch set_form(From, LServer, LNode, Lang, Fields) of
1667 + adhoc:produce_response(
1668 + #adhoc_response{lang = Lang,
1670 + sessionid = SessionID,
1671 + status = completed});
1672 + {'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
1673 + {error, Error} -> {error, Error}
1677 + {error, ?ERR_BAD_REQUEST}
1680 +-define(LISTLINE(Label, Value),
1681 + #xmlel{name = <<"option">>,
1682 + attrs = [{<<"label">>, ?T(Lang, Label)}],
1683 + children = [#xmlel{name = <<"value">>, attrs = [],
1684 + children = [{xmlcdata, Value}]
1686 +-define(DEFVAL(Value), #xmlel{name = <<"value">>, attrs = [],
1687 + children = [{xmlcdata, Value}]}).
1689 +get_user_form(LUser, LServer, Lang) ->
1690 + %From = jlib:jid_to_string(jlib:jid_remove_resource(Jid)),
1691 + #user_settings{dolog_default=DLD,
1693 + donotlog_list=DNLL} = get_user_settings(LUser, LServer),
1695 + [#xmlel{name = <<"x">>,
1696 + attrs = [{<<"xmlns">>, ?NS_XDATA}],
1698 + #xmlel{name = <<"title">>, attrs = [],
1701 + ?T(Lang, <<"Messages logging engine settings">>)}]},
1702 + #xmlel{name = <<"instructions">>, attrs = [],
1705 + << (?T(Lang, <<"Set logging preferences">>))/binary, (iolist_to_binary(": "))/binary,
1706 + LUser/binary, "@", LServer/binary >> }]},
1707 + #xmlel{name = <<"field">>,
1708 + attrs = [{<<"type">>, <<"list-single">>},
1709 + {<<"label">>, ?T(Lang, <<"Default">>)},
1710 + {<<"var">>, <<"dolog_default">>}],
1712 + [?DEFVAL(jlib:atom_to_binary(DLD)),
1713 + ?LISTLINE(<<"Log Messages">>, <<"true">>),
1714 + ?LISTLINE(<<"Do Not Log Messages">>, <<"false">>)
1716 + #xmlel{name = <<"field">>,
1717 + attrs = [{<<"type">>, <<"text-multi">>},
1718 + {<<"label">>, ?T(Lang, <<"Log Messages">>)},
1719 + {<<"var">>, <<"dolog_list">>}],
1720 + children = [#xmlel{name = <<"value">>, attrs = [],
1721 + children = [{xmlcdata, iolist_to_binary(list_to_string(DLL))}]}
1724 + #xmlel{name = <<"field">>,
1725 + attrs = [{<<"type">>, <<"text-multi">>},
1726 + {<<"label">>, ?T(Lang, <<"Do Not Log Messages">>)},
1727 + {<<"var">>, <<"donotlog_list">>}],
1728 + children = [#xmlel{name = <<"value">>, attrs = [],
1729 + children = [{xmlcdata, iolist_to_binary(list_to_string(DNLL))}]}
1734 +get_settings_form(Host, Lang) ->
1735 + #state{dbmod=_DBMod,
1737 + dolog_default=DLD,
1738 + ignore_jids=IgnoreJids,
1739 + groupchat=GroupChat,
1740 + purge_older_days=PurgeDaysT,
1741 + drop_messages_on_user_removal=MRemoval,
1742 + poll_users_settings=PollTime} = mod_logdb:get_module_settings(Host),
1744 + %Backends = lists:map(fun({Backend, _Opts}) ->
1745 + % ?LISTLINE(jlib:atom_to_binary(Backend), jlib:atom_to_binary(Backend))
1747 + %DB = iolist_to_binary(lists:sublist(atom_to_list(DBMod), length(atom_to_list(?MODULE)) + 2, length(atom_to_list(DBMod)))),
1748 + %DBsL = lists:append([?DEFVAL(DB)], Backends),
1751 + case PurgeDaysT of
1752 + never -> <<"never">>;
1753 + Num when is_integer(Num) -> integer_to_binary(Num);
1754 + _ -> <<"unknown">>
1757 + [#xmlel{name = <<"x">>,
1758 + attrs = [{<<"xmlns">>, ?NS_XDATA}],
1759 + children = [#xmlel{name = <<"title">>, attrs = [],
1762 + <<(?T(Lang, <<"Messages logging engine settings">>))/binary,
1763 + (iolist_to_binary(" (run-time)"))/binary >>}]},
1764 + #xmlel{name = <<"instructions">>, attrs = [],
1766 + [{xmlcdata, ?T(Lang, <<"Set run-time settings">>)}]},
1767 +% #xmlel{name = <<"field">>,
1768 +% attrs = [{<<"type">>, <<"list-single">>},
1769 +% {<<"label">>, ?T(Lang, <<"Backend">>)},
1770 +% {<<"var">>, <<"backend">>}],
1771 +% children = DBsL},
1772 +% #xmlel{name = <<"field">>,
1773 +% attrs = [{<<"type">>, <<"text-multi">>},
1774 +% {<<"label">>, ?T(Lang, <<"dbs">>)},
1775 +% {<<"var">>, <<"dbs">>}],
1776 +% children = [#xmlel{name = <<"value">>, attrs = [],
1777 +% children = [{xmlcdata, iolist_to_binary(lists:flatten(io_lib:format("~p.",[DBs])))}]}
1780 + #xmlel{name = <<"field">>,
1781 + attrs = [{<<"type">>, <<"list-single">>},
1782 + {<<"label">>, ?T(Lang, <<"Default">>)},
1783 + {<<"var">>, <<"dolog_default">>}],
1785 + [?DEFVAL(jlib:atom_to_binary(DLD)),
1786 + ?LISTLINE(<<"Log Messages">>, <<"true">>),
1787 + ?LISTLINE(<<"Do Not Log Messages">>, <<"false">>)
1789 + #xmlel{name = <<"field">>,
1790 + attrs = [{<<"type">>, <<"list-single">>},
1791 + {<<"label">>, ?T(Lang, <<"Drop messages on user removal">>)},
1792 + {<<"var">>, <<"drop_messages_on_user_removal">>}],
1794 + [?DEFVAL(jlib:atom_to_binary(MRemoval)),
1795 + ?LISTLINE(<<"Drop">>, <<"true">>),
1796 + ?LISTLINE(<<"Do not drop">>, <<"false">>)
1798 + #xmlel{name = <<"field">>,
1799 + attrs = [{<<"type">>, <<"list-single">>},
1800 + {<<"label">>, ?T(Lang, <<"Groupchat messages logging">>)},
1801 + {<<"var">>, <<"groupchat">>}],
1803 + [?DEFVAL(jlib:atom_to_binary(GroupChat)),
1804 + ?LISTLINE(<<"all">>, <<"all">>),
1805 + ?LISTLINE(<<"none">>, <<"none">>),
1806 + ?LISTLINE(<<"send">>, <<"send">>),
1807 + ?LISTLINE(<<"half">>, <<"half">>)
1809 + #xmlel{name = <<"field">>,
1810 + attrs = [{<<"type">>, <<"text-multi">>},
1811 + {<<"label">>, ?T(Lang, <<"Jids/Domains to ignore">>)},
1812 + {<<"var">>, <<"ignore_list">>}],
1813 + children = [#xmlel{name = <<"value">>, attrs = [],
1814 + children = [{xmlcdata, iolist_to_binary(list_to_string(IgnoreJids))}]}
1817 + #xmlel{name = <<"field">>,
1818 + attrs = [{<<"type">>, <<"text-single">>},
1819 + {<<"label">>, ?T(Lang, <<"Purge messages older than (days)">>)},
1820 + {<<"var">>, <<"purge_older_days">>}],
1821 + children = [#xmlel{name = <<"value">>, attrs = [],
1822 + children = [{xmlcdata, iolist_to_binary(PurgeDays)}]}
1825 + #xmlel{name = <<"field">>,
1826 + attrs = [{<<"type">>, <<"text-single">>},
1827 + {<<"label">>, ?T(Lang, <<"Poll users settings (seconds)">>)},
1828 + {<<"var">>, <<"poll_users_settings">>}],
1829 + children = [#xmlel{name = <<"value">>, attrs = [],
1830 + children = [{xmlcdata, integer_to_binary(PollTime)}]}
1836 +%get_form(_Host, [<<"mod_logdb">>], #jid{luser = LUser, lserver = LServer} = _Jid, Lang) ->
1837 +% get_user_form(LUser, LServer, Lang);
1838 +get_form(_Host, [<<"mod_logdb_users">>, User], _JidFrom, Lang) ->
1839 + #jid{luser=LUser, lserver=LServer} = jlib:string_to_jid(User),
1840 + get_user_form(LUser, LServer, Lang);
1841 +get_form(Host, [<<"mod_logdb_settings">>], _JidFrom, Lang) ->
1842 + get_settings_form(Host, Lang);
1843 +get_form(_Host, Command, _, _Lang) ->
1844 + ?MYDEBUG("asked for form ~p", [Command]),
1845 + {error, ?ERR_SERVICE_UNAVAILABLE}.
1847 +check_log_list([]) ->
1849 +check_log_list([<<>>]) ->
1851 +check_log_list([Head | Tail]) ->
1852 + case binary:match(Head, <<$@>>) of
1853 + nomatch -> throw(error);
1856 + % this check for Head to be valid jid
1857 + case jlib:string_to_jid(Head) of
1861 + check_log_list(Tail)
1864 +check_ignore_list([]) ->
1866 +check_ignore_list([<<>>]) ->
1868 +check_ignore_list([<<>> | Tail]) ->
1869 + check_ignore_list(Tail);
1870 +check_ignore_list([Head | Tail]) ->
1871 + case binary:match(Head, <<$@>>) of
1873 + nomatch -> throw(error)
1875 + % this check for Head to be valid jid
1876 + case jlib:string_to_jid(Head) of
1878 + % this check for Head to be valid domain "@domain.org"
1880 + << $@, Rest/binary >> ->
1881 + % TODO: this allows spaces and special characters in Head. May be change to nodeprep?
1882 + case jlib:nameprep(Rest) of
1883 + error -> throw(error);
1884 + _ -> check_ignore_list(Tail)
1889 + check_ignore_list(Tail)
1892 +parse_users_settings(XData) ->
1893 + DLD = case lists:keysearch(<<"dolog_default">>, 1, XData) of
1894 + {value, {_, [String]}} when String == <<"true">>; String == <<"false">> ->
1895 + list_to_bool(String);
1897 + throw(bad_request)
1899 + DLL = case lists:keysearch(<<"dolog_list">>, 1, XData) of
1901 + throw(bad_request);
1902 + {value, {_, [[]]}} ->
1904 + {value, {_, List1}} ->
1905 + case catch check_log_list(List1) of
1907 + throw(bad_request);
1912 + DNLL = case lists:keysearch(<<"donotlog_list">>, 1, XData) of
1914 + throw(bad_request);
1915 + {value, {_, [[]]}} ->
1917 + {value, {_, List2}} ->
1918 + case catch check_log_list(List2) of
1920 + throw(bad_request);
1925 + #user_settings{dolog_default=DLD,
1927 + donotlog_list=DNLL}.
1929 +parse_module_settings(XData) ->
1930 + DLD = case lists:keysearch(<<"dolog_default">>, 1, XData) of
1931 + {value, {_, [Str1]}} when Str1 == <<"true">>; Str1 == <<"false">> ->
1932 + list_to_bool(Str1);
1934 + throw(bad_request)
1936 + MRemoval = case lists:keysearch(<<"drop_messages_on_user_removal">>, 1, XData) of
1937 + {value, {_, [Str5]}} when Str5 == <<"true">>; Str5 == <<"false">> ->
1938 + list_to_bool(Str5);
1940 + throw(bad_request)
1942 + GroupChat = case lists:keysearch(<<"groupchat">>, 1, XData) of
1943 + {value, {_, [Str2]}} when Str2 == <<"none">>;
1944 + Str2 == <<"all">>;
1945 + Str2 == <<"send">>;
1946 + Str2 == <<"half">> ->
1947 + jlib:binary_to_atom(Str2);
1949 + throw(bad_request)
1951 + Ignore = case lists:keysearch(<<"ignore_list">>, 1, XData) of
1952 + {value, {_, List}} ->
1953 + case catch check_ignore_list(List) of
1957 + throw(bad_request)
1960 + throw(bad_request)
1962 + Purge = case lists:keysearch(<<"purge_older_days">>, 1, XData) of
1963 + {value, {_, [<<"never">>]}} ->
1965 + {value, {_, [Str3]}} ->
1966 + case catch binary_to_integer(Str3) of
1967 + {'EXIT', {badarg, _}} -> throw(bad_request);
1971 + throw(bad_request)
1973 + Poll = case lists:keysearch(<<"poll_users_settings">>, 1, XData) of
1974 + {value, {_, [Str4]}} ->
1975 + case catch binary_to_integer(Str4) of
1976 + {'EXIT', {badarg, _}} -> throw(bad_request);
1980 + throw(bad_request)
1982 + #state{dolog_default=DLD,
1983 + groupchat=GroupChat,
1984 + ignore_jids=Ignore,
1985 + purge_older_days=Purge,
1986 + drop_messages_on_user_removal=MRemoval,
1987 + poll_users_settings=Poll}.
1989 +set_form(From, _Host, [<<"mod_logdb">>], _Lang, XData) ->
1990 + #jid{luser=LUser, lserver=LServer} = From,
1991 + case catch parse_users_settings(XData) of
1993 + {error, ?ERR_BAD_REQUEST};
1994 + {'EXIT', Reason} ->
1995 + ?ERROR_MSG("Failed to set form ~p", [Reason]),
1996 + {error, ?ERR_BAD_REQUEST};
1998 + case mod_logdb:set_user_settings(LUser, LServer, UserSettings) of
2002 + {error, ?ERR_INTERNAL_SERVER_ERROR}
2005 +set_form(_From, _Host, [<<"mod_logdb_users">>, User], _Lang, XData) ->
2006 + #jid{luser=LUser, lserver=LServer} = jlib:string_to_jid(User),
2007 + case catch parse_users_settings(XData) of
2008 + bad_request -> {error, ?ERR_BAD_REQUEST};
2009 + {'EXIT', Reason} ->
2010 + ?ERROR_MSG("Failed to set form ~p", [Reason]),
2011 + {error, ?ERR_BAD_REQUEST};
2013 + case mod_logdb:set_user_settings(LUser, LServer, UserSettings) of
2017 + {error, ?ERR_INTERNAL_SERVER_ERROR}
2020 +set_form(_From, Host, [<<"mod_logdb_settings">>], _Lang, XData) ->
2021 + case catch parse_module_settings(XData) of
2022 + bad_request -> {error, ?ERR_BAD_REQUEST};
2023 + {'EXIT', Reason} ->
2024 + ?ERROR_MSG("Failed to set form ~p", [Reason]),
2025 + {error, ?ERR_BAD_REQUEST};
2027 + case mod_logdb:set_module_settings(Host, Settings) of
2031 + {error, ?ERR_INTERNAL_SERVER_ERROR}
2034 +set_form(From, _Host, Node, _Lang, XData) ->
2035 + User = jlib:jid_to_string(jlib:jid_remove_resource(From)),
2036 + ?MYDEBUG("set form for ~p at ~p XData=~p", [User, Node, XData]),
2037 + {error, ?ERR_SERVICE_UNAVAILABLE}.
2039 +%adhoc_sm_items(Acc, From, To, Request) ->
2040 +% ?MYDEBUG("adhoc_sm_items Acc=~p From=~p To=~p Request=~p", [Acc, From, To, Request]),
2043 +%adhoc_sm_commands(Acc, From, To, Request) ->
2044 +% ?MYDEBUG("adhoc_sm_commands Acc=~p From=~p To=~p Request=~p", [Acc, From, To, Request]),
2047 +get_all_vh_users(Host, Server, Lang) ->
2048 + case catch ejabberd_auth:get_vh_registered_users(Host) of
2049 + {'EXIT', _Reason} ->
2052 + SUsers = lists:sort([{S, U} || {U, S} <- Users]),
2053 + case length(SUsers) of
2054 + N when N =< 100 ->
2055 + lists:map(fun({S, U}) ->
2056 + ?NODE(<< U/binary, "@", S/binary >>,
2057 + << (iolist_to_binary("mod_logdb_users/"))/binary, U/binary, "@", S/binary >>)
2061 + NParts = trunc(math:sqrt(N * 0.618)) + 1,
2062 + M = trunc(N / NParts) + 1,
2063 + lists:map(fun(K) ->
2066 + (iolist_to_binary(integer_to_list(K)))/binary,
2068 + (iolist_to_binary(integer_to_list(L)))/binary
2070 + {FS, FU} = lists:nth(K, SUsers),
2072 + if L < N -> lists:nth(L, SUsers);
2073 + true -> lists:last(SUsers)
2076 + <<FU/binary, "@", FS/binary,
2078 + LU/binary, "@", LS/binary>>,
2079 + ?NODE(Name, << (iolist_to_binary("mod_logdb_users/"))/binary, Node/binary >>)
2080 + end, lists:seq(1, N, M))
2084 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2088 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2089 +webadmin_menu(Acc, _Host, Lang) ->
2090 + [{<<"messages">>, ?T(<<"Users Messages">>)} | Acc].
2092 +webadmin_user(Acc, User, Server, Lang) ->
2093 + Sett = get_user_settings(User, Server),
2095 + case Sett#user_settings.dolog_default of
2097 + ?INPUTT(<<"submit">>, <<"dolog">>, <<"Log Messages">>);
2099 + ?INPUTT(<<"submit">>, <<"donotlog">>, <<"Do Not Log Messages">>);
2102 + Acc ++ [?XE(<<"h3">>, [?ACT(<<"messages/">>, <<"Messages">>), ?C(<<" ">>), Log])].
2104 +webadmin_page(_, Host,
2105 + #request{path = [<<"messages">>],
2108 + Res = vhost_messages_stats(Host, Query, Lang),
2110 +webadmin_page(_, Host,
2111 + #request{path = [<<"messages">>, Date],
2114 + Res = vhost_messages_stats_at(Host, Query, Lang, Date),
2116 +webadmin_page(_, Host,
2117 + #request{path = [<<"user">>, U, <<"messages">>],
2120 + Res = user_messages_stats(U, Host, Query, Lang),
2122 +webadmin_page(_, Host,
2123 + #request{path = [<<"user">>, U, <<"messages">>, Date],
2126 + Res = mod_logdb:user_messages_stats_at(U, Host, Query, Lang, Date),
2128 +webadmin_page(Acc, _Host, _R) -> Acc.
2130 +user_parse_query(_, <<"dolog">>, User, Server, _Query) ->
2131 + Sett = get_user_settings(User, Server),
2132 + % TODO: check returned value
2133 + set_user_settings(User, Server, Sett#user_settings{dolog_default=true}),
2135 +user_parse_query(_, <<"donotlog">>, User, Server, _Query) ->
2136 + Sett = get_user_settings(User, Server),
2137 + % TODO: check returned value
2138 + set_user_settings(User, Server, Sett#user_settings{dolog_default=false}),
2140 +user_parse_query(Acc, _Action, _User, _Server, _Query) ->
2143 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2147 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2148 +vhost_messages_stats(Server, Query, Lang) ->
2149 + Res = case catch vhost_messages_parse_query(Server, Query) of
2150 + {'EXIT', Reason} ->
2151 + ?ERROR_MSG("~p", [Reason]),
2153 + VResult -> VResult
2155 + {Time, Value} = timer:tc(mod_logdb, get_vhost_stats, [Server]),
2156 + ?INFO_MSG("get_vhost_stats(~p) elapsed ~p sec", [Server, Time/1000000]),
2157 + %case get_vhost_stats(Server) of
2159 + {'EXIT', CReason} ->
2160 + ?ERROR_MSG("Failed to get_vhost_stats: ~p", [CReason]),
2161 + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))];
2162 + {error, GReason} ->
2163 + ?ERROR_MSG("Failed to get_vhost_stats: ~p", [GReason]),
2164 + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))];
2166 + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s">>), [Server])))];
2168 + Fun = fun({Date, Count}) ->
2169 + DateBin = iolist_to_binary(Date),
2170 + ID = jlib:encode_base64( << Server/binary, DateBin/binary >> ),
2172 + [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
2173 + [?INPUT(<<"checkbox">>, <<"selected">>, ID)]),
2174 + ?XE(<<"td">>, [?AC(DateBin, DateBin)]),
2175 + ?XC(<<"td">>, integer_to_binary(Count))
2179 + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Logged messages for ~s">>), [Server])))] ++
2181 + ok -> [?CT(<<"Submitted">>), ?P];
2182 + error -> [?CT(<<"Bad format">>), ?P];
2185 + [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
2190 + ?XCT(<<"td">>, <<"Date">>),
2191 + ?XCT(<<"td">>, <<"Count">>)
2194 + lists:map(Fun, Dates)
2197 + ?INPUTT(<<"submit">>, <<"delete">>, <<"Delete Selected">>)
2201 +vhost_messages_stats_at(Server, Query, Lang, Date) ->
2202 + {Time, Value} = timer:tc(mod_logdb, get_vhost_stats_at, [Server, Date]),
2203 + ?INFO_MSG("get_vhost_stats_at(~p,~p) elapsed ~p sec", [Server, Date, Time/1000000]),
2204 + %case get_vhost_stats_at(Server, Date) of
2206 + {'EXIT', CReason} ->
2207 + ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [CReason]),
2208 + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))];
2209 + {error, GReason} ->
2210 + ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [GReason]),
2211 + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))];
2213 + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s at ~s">>), [Server, Date])))];
2215 + Res = case catch vhost_messages_at_parse_query(Server, Date, Stats, Query) of
2216 + {'EXIT', Reason} ->
2217 + ?ERROR_MSG("~p", [Reason]),
2219 + VResult -> VResult
2221 + Fun = fun({User, Count}) ->
2222 + UserBin = iolist_to_binary(User),
2223 + ID = jlib:encode_base64( << UserBin/binary, Server/binary >> ),
2225 + [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
2226 + [?INPUT(<<"checkbox">>, <<"selected">>, ID)]),
2227 + ?XE(<<"td">>, [?AC(<< <<"../user/">>/binary, UserBin/binary, <<"/messages/">>/binary, Date/binary >>, UserBin)]),
2228 + ?XC(<<"td">>, integer_to_binary(Count))
2231 + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Logged messages for ~s at ~s">>), [Server, Date])))] ++
2233 + ok -> [?CT(<<"Submitted">>), ?P];
2234 + error -> [?CT(<<"Bad format">>), ?P];
2237 + [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
2242 + ?XCT(<<"td">>, <<"User">>),
2243 + ?XCT(<<"td">>, <<"Count">>)
2246 + lists:map(Fun, Stats)
2249 + ?INPUTT(<<"submit">>, <<"delete">>, <<"Delete Selected">>)
2253 +user_messages_stats(User, Server, Query, Lang) ->
2254 + Jid = jlib:jid_to_string({User, Server, ""}),
2256 + Res = case catch user_messages_parse_query(User, Server, Query) of
2257 + {'EXIT', Reason} ->
2258 + ?ERROR_MSG("~p", [Reason]),
2260 + VResult -> VResult
2263 + {Time, Value} = timer:tc(mod_logdb, get_user_stats, [User, Server]),
2264 + ?INFO_MSG("get_user_stats(~p,~p) elapsed ~p sec", [User, Server, Time/1000000]),
2267 + {'EXIT', CReason} ->
2268 + ?ERROR_MSG("Failed to get_user_stats: ~p", [CReason]),
2269 + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching days">>))];
2270 + {error, GReason} ->
2271 + ?ERROR_MSG("Failed to get_user_stats: ~p", [GReason]),
2272 + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching days">>))];
2274 + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s">>), [Jid])))];
2276 + Fun = fun({Date, Count}) ->
2277 + DateBin = iolist_to_binary(Date),
2278 + ID = jlib:encode_base64( << User/binary, DateBin/binary >> ),
2280 + [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
2281 + [?INPUT(<<"checkbox">>, <<"selected">>, ID)]),
2282 + ?XE(<<"td">>, [?AC(DateBin, DateBin)]),
2283 + ?XC(<<"td">>, iolist_to_binary(integer_to_list(Count)))
2286 + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T("Logged messages for ~s"), [Jid])))] ++
2288 + ok -> [?CT(<<"Submitted">>), ?P];
2289 + error -> [?CT(<<"Bad format">>), ?P];
2292 + [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
2297 + ?XCT(<<"td">>, <<"Date">>),
2298 + ?XCT(<<"td">>, <<"Count">>)
2301 + lists:map(Fun, Dates)
2304 + ?INPUTT(<<"submit">>, <<"delete">>, <<"Delete Selected">>)
2308 +search_user_nick(User, List) ->
2309 + case lists:keysearch(User, 1, List) of
2310 + {value,{User, []}} ->
2312 + {value,{User, Nick}} ->
2318 +user_messages_stats_at(User, Server, Query, Lang, Date) ->
2319 + Jid = jlib:jid_to_string({User, Server, ""}),
2321 + {Time, Value} = timer:tc(mod_logdb, get_user_messages_at, [User, Server, Date]),
2322 + ?INFO_MSG("get_user_messages_at(~p,~p,~p) elapsed ~p sec", [User, Server, Date, Time/1000000]),
2324 + {'EXIT', CReason} ->
2325 + ?ERROR_MSG("Failed to get_user_messages_at: ~p", [CReason]),
2326 + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching messages">>))];
2327 + {error, GReason} ->
2328 + ?ERROR_MSG("Failed to get_user_messages_at: ~p", [GReason]),
2329 + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching messages">>))];
2331 + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s at ~s">>), [Jid, Date])))];
2332 + {ok, User_messages} ->
2333 + Res = case catch user_messages_at_parse_query(Server,
2337 + {'EXIT', Reason} ->
2338 + ?ERROR_MSG("~p", [Reason]),
2340 + VResult -> VResult
2343 + UR = ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]),
2345 + lists:map(fun(Item) ->
2346 + {jlib:jid_to_string(Item#roster.jid), Item#roster.name}
2349 + UniqUsers = lists:foldl(fun(#msg{peer_name=PName, peer_server=PServer}, List) ->
2350 + ToAdd = PName++"@"++PServer,
2351 + case lists:member(ToAdd, List) of
2353 + false -> lists:append([ToAdd], List)
2355 + end, [], User_messages),
2357 + % Users to filter (sublist of UniqUsers)
2358 + CheckedUsers = case lists:keysearch(<<"filter">>, 1, Query) of
2360 + lists:filter(fun(UFUser) ->
2361 + ID = jlib:encode_base64(term_to_binary(UFUser)),
2362 + lists:member({<<"selected">>, ID}, Query)
2367 + % UniqUsers in html (noone selected -> everyone selected)
2368 + Users = lists:map(fun(UHUser) ->
2369 + ID = jlib:encode_base64(term_to_binary(UHUser)),
2370 + Input = case lists:member(UHUser, CheckedUsers) of
2371 + true -> [?INPUTC(<<"checkbox">>, <<"selected">>, ID)];
2372 + false when CheckedUsers == [] -> [?INPUTC(<<"checkbox">>, <<"selected">>, ID)];
2373 + false -> [?INPUT(<<"checkbox">>, <<"selected">>, ID)]
2376 + case search_user_nick(UHUser, UserRoster) of
2377 + nothing -> <<"">>;
2378 + N -> iolist_to_binary( " ("++ N ++")" )
2381 + [?XE(<<"td">>, Input),
2382 + ?XC(<<"td">>, iolist_to_binary(UHUser++Nick))])
2383 + end, lists:sort(UniqUsers)),
2384 + % Messages to show (based on Users)
2385 + User_messages_filtered = case CheckedUsers of
2386 + [] -> User_messages;
2387 + _ -> lists:filter(fun(#msg{peer_name=PName, peer_server=PServer}) ->
2388 + lists:member(PName++"@"++PServer, CheckedUsers)
2389 + end, User_messages)
2392 + Msgs_Fun = fun(#msg{timestamp=Timestamp,
2394 + direction=Direction,
2395 + peer_name=PName, peer_server=PServer, peer_resource=PRes,
2398 + Text = case Subject of
2399 + "" -> iolist_to_binary(Body);
2400 + _ -> iolist_to_binary([binary_to_list(?T(<<"Subject">>)) ++ ": " ++ Subject ++ "\n" ++ Body])
2402 + Resource = case PRes of
2408 + case search_user_nick(PName++"@"++PServer, UserRoster) of
2409 + nothing when PServer == Server ->
2411 + nothing when Type == "groupchat", Direction == from ->
2412 + PName++"@"++PServer++Resource;
2414 + PName++"@"++PServer;
2417 + ID = jlib:encode_base64(term_to_binary(Timestamp)),
2419 + [?XE(<<"td">>, [?INPUT(<<"checkbox">>, <<"selected">>, ID)]),
2420 + ?XC(<<"td">>, iolist_to_binary(convert_timestamp(Timestamp))),
2421 + ?XC(<<"td">>, iolist_to_binary(atom_to_list(Direction)++": "++UserNick)),
2422 + ?XE(<<"td">>, [?XC(<<"pre">>, Text)])])
2424 + % Filtered user messages in html
2425 + Msgs = lists:map(Msgs_Fun, lists:sort(User_messages_filtered)),
2427 + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Logged messages for ~s at ~s">>), [Jid, Date])))] ++
2429 + ok -> [?CT(<<"Submitted">>), ?P];
2430 + error -> [?CT(<<"Bad format">>), ?P];
2433 + [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
2437 + ?XCT(<<"td">>, <<"User">>)
2443 + ?INPUTT(<<"submit">>, <<"filter">>, <<"Filter Selected">>)
2449 + ?XCT(<<"td">>, <<"Date, Time">>),
2450 + ?XCT(<<"td">>, <<"Direction: Jid">>),
2451 + ?XCT(<<"td">>, <<"Body">>)
2456 + ?INPUTT(<<"submit">>, <<"delete">>, <<"Delete Selected">>),
2461 diff --git a/src/mod_logdb.hrl b/src/mod_logdb.hrl
2462 new file mode 100644
2463 index 0000000..d44f0df
2465 +++ b/src/mod_logdb.hrl
2467 +%%%----------------------------------------------------------------------
2468 +%%% File : mod_logdb.hrl
2469 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
2471 +%%% Version : trunk
2472 +%%% Id : $Id: mod_logdb.hrl 1273 2009-02-05 18:12:57Z malik $
2473 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
2474 +%%%----------------------------------------------------------------------
2476 +-define(logdb_debug, true).
2478 +-ifdef(logdb_debug).
2479 +-define(MYDEBUG(Format, Args), io:format("D(~p:~p:~p) : "++Format++"~n",
2480 + [calendar:local_time(),?MODULE,?LINE]++Args)).
2482 +-define(MYDEBUG(_F,_A),[]).
2485 +-record(msg, {timestamp,
2487 + peer_name, peer_server, peer_resource,
2492 +-record(user_settings, {owner_name,
2495 + donotlog_list=[]}).
2497 +-define(INPUTC(Type, Name, Value),
2498 + ?XA(<<"input">>, [{<<"type">>, Type},
2499 + {<<"name">>, Name},
2500 + {<<"value">>, Value},
2501 + {<<"checked">>, <<"true">>}])).
2502 diff --git a/src/mod_logdb_mnesia.erl b/src/mod_logdb_mnesia.erl
2503 new file mode 100644
2504 index 0000000..a8ae766
2506 +++ b/src/mod_logdb_mnesia.erl
2508 +%%%----------------------------------------------------------------------
2509 +%%% File : mod_logdb_mnesia.erl
2510 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
2511 +%%% Purpose : mnesia backend for mod_logdb
2512 +%%% Version : trunk
2513 +%%% Id : $Id: mod_logdb_mnesia.erl 1273 2009-02-05 18:12:57Z malik $
2514 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
2515 +%%%----------------------------------------------------------------------
2517 +-module(mod_logdb_mnesia).
2518 +-author('o.palij@gmail.com').
2520 +-include("mod_logdb.hrl").
2521 +-include("ejabberd.hrl").
2522 +-include("jlib.hrl").
2523 +-include("logger.hrl").
2525 +-behaviour(gen_logdb).
2526 +-behaviour(gen_server).
2529 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
2531 +-export([start/2, stop/1]).
2533 +-export([log_message/2,
2535 + rebuild_stats_at/2,
2536 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
2537 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
2539 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
2542 +-define(PROCNAME, mod_logdb_mnesia).
2543 +-define(CALL_TIMEOUT, 10000).
2545 +-record(state, {vhost}).
2547 +-record(stats, {user, at, count}).
2553 + "_" ++ binary_to_list(VHost).
2555 +stats_table(VHost) ->
2556 + list_to_atom(prefix() ++ "stats" ++ suffix(VHost)).
2558 +table_name(VHost, Date) ->
2559 + list_to_atom(prefix() ++ "messages_" ++ Date ++ suffix(VHost)).
2561 +settings_table(VHost) ->
2562 + list_to_atom(prefix() ++ "settings" ++ suffix(VHost)).
2564 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2566 +% gen_mod callbacks
2568 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2569 +start(VHost, Opts) ->
2570 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2571 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
2574 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2575 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
2577 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2579 +% gen_server callbacks
2581 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2582 +init([VHost, _Opts]) ->
2583 + case mnesia:system_info(is_running) of
2585 + ok = create_stats_table(VHost),
2586 + ok = create_settings_table(VHost),
2587 + {ok, #state{vhost=VHost}};
2589 + ?ERROR_MSG("Mnesia not running", []),
2590 + {stop, db_connection_failed};
2592 + ?ERROR_MSG("Mnesia status: ~p", [Status]),
2593 + {stop, db_connection_failed}
2596 +handle_call({log_message, Msg}, _From, #state{vhost=VHost}=State) ->
2597 + {reply, log_message_int(VHost, Msg), State};
2598 +handle_call({rebuild_stats}, _From, #state{vhost=VHost}=State) ->
2599 + {atomic, ok} = delete_nonexistent_stats(VHost),
2601 + lists:foreach(fun(Date) ->
2602 + rebuild_stats_at_int(VHost, Date)
2603 + end, get_dates_int(VHost)),
2604 + {reply, Reply, State};
2605 +handle_call({rebuild_stats_at, Date}, _From, #state{vhost=VHost}=State) ->
2606 + Reply = rebuild_stats_at_int(VHost, Date),
2607 + {reply, Reply, State};
2608 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{vhost=VHost}=State) ->
2609 + Table = table_name(VHost, Date),
2613 + mnesia:write_lock_table(stats_table(VHost)),
2614 + mnesia:write_lock_table(Table),
2615 + mnesia:delete_object(Table, Msg, write)
2618 + DRez = case mnesia:transaction(Fun) of
2619 + {aborted, Reason} ->
2620 + ?ERROR_MSG("Failed to delete_messages_by_user_at at ~p for ~p: ~p", [Date, VHost, Reason]),
2626 + case rebuild_stats_at_int(VHost, Date) of
2632 + {reply, Reply, State};
2633 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{vhost=VHost}=State) ->
2634 + {reply, delete_all_messages_by_user_at_int(User, VHost, Date), State};
2635 +handle_call({delete_messages_at, Date}, _From, #state{vhost=VHost}=State) ->
2637 + case mnesia:delete_table(table_name(VHost, Date)) of
2639 + delete_stats_by_vhost_at_int(VHost, Date);
2640 + {aborted, Reason} ->
2641 + ?ERROR_MSG("Failed to delete_messages_at for ~p at ~p", [VHost, Date, Reason]),
2644 + {reply, Reply, State};
2645 +handle_call({get_vhost_stats}, _From, #state{vhost=VHost}=State) ->
2646 + Fun = fun(#stats{at=Date, count=Count}, Stats) ->
2647 + case lists:keysearch(Date, 1, Stats) of
2649 + lists:append(Stats, [{Date, Count}]);
2650 + {value, {_, TempCount}} ->
2651 + lists:keyreplace(Date, 1, Stats, {Date, TempCount+Count})
2655 + case mnesia:transaction(fun() ->
2656 + mnesia:foldl(Fun, [], stats_table(VHost))
2658 + {atomic, Result} -> {ok, mod_logdb:sort_stats(Result)};
2659 + {aborted, Reason} -> {error, Reason}
2661 + {reply, Reply, State};
2662 +handle_call({get_vhost_stats_at, Date}, _From, #state{vhost=VHost}=State) ->
2664 + Pat = #stats{user='$1', at=Date, count='$2'},
2665 + mnesia:select(stats_table(VHost), [{Pat, [], [['$1', '$2']]}])
2668 + case mnesia:transaction(Fun) of
2669 + {atomic, Result} ->
2670 + {ok, lists:reverse(lists:keysort(2, [{User, Count} || [User, Count] <- Result]))};
2671 + {aborted, Reason} ->
2674 + {reply, Reply, State};
2675 +handle_call({get_user_stats, User}, _From, #state{vhost=VHost}=State) ->
2676 + {reply, get_user_stats_int(User, VHost), State};
2677 +handle_call({get_user_messages_at, User, Date}, _From, #state{vhost=VHost}=State) ->
2679 + case mnesia:transaction(fun() ->
2680 + Pat = #msg{owner_name=User, _='_'},
2681 + mnesia:select(table_name(VHost, Date),
2682 + [{Pat, [], ['$_']}])
2684 + {atomic, Result} -> {ok, Result};
2685 + {aborted, Reason} ->
2688 + {reply, Reply, State};
2689 +handle_call({get_dates}, _From, #state{vhost=VHost}=State) ->
2690 + {reply, get_dates_int(VHost), State};
2691 +handle_call({get_users_settings}, _From, #state{vhost=VHost}=State) ->
2692 + Reply = mnesia:dirty_match_object(settings_table(VHost), #user_settings{_='_'}),
2693 + {reply, {ok, Reply}, State};
2694 +handle_call({get_user_settings, User}, _From, #state{vhost=VHost}=State) ->
2696 + case mnesia:dirty_match_object(settings_table(VHost), #user_settings{owner_name=User, _='_'}) of
2701 + {reply, Reply, State};
2702 +handle_call({set_user_settings, _User, Set}, _From, #state{vhost=VHost}=State) ->
2703 + ?MYDEBUG("~p~n~p", [settings_table(VHost), Set]),
2704 + Reply = mnesia:dirty_write(settings_table(VHost), Set),
2705 + ?MYDEBUG("~p", [Reply]),
2706 + {reply, Reply, State};
2707 +handle_call({drop_user, User}, _From, #state{vhost=VHost}=State) ->
2708 + {ok, Dates} = get_user_stats_int(User, VHost),
2709 + MDResult = lists:map(fun({Date, _}) ->
2710 + delete_all_messages_by_user_at_int(User, VHost, Date)
2712 + SDResult = delete_user_settings_int(User, VHost),
2714 + case lists:all(fun(Result) when Result == ok ->
2716 + (Result) when Result == error ->
2718 + end, lists:append(MDResult, [SDResult])) of
2724 + {reply, Reply, State};
2725 +handle_call({stop}, _From, State) ->
2726 + {stop, normal, ok, State};
2727 +handle_call(Msg, _From, State) ->
2728 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
2731 +handle_cast(Msg, State) ->
2732 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
2735 +handle_info(Info, State) ->
2736 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
2739 +terminate(_Reason, _State) ->
2742 +code_change(_OldVsn, State, _Extra) ->
2745 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2747 +% gen_logdb callbacks
2749 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2750 +log_message(VHost, Msg) ->
2751 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2752 + gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
2753 +rebuild_stats(VHost) ->
2754 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2755 + gen_server:call(Proc, {rebuild_stats}, ?CALL_TIMEOUT).
2756 +rebuild_stats_at(VHost, Date) ->
2757 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2758 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
2759 +delete_messages_by_user_at(VHost, Msgs, Date) ->
2760 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2761 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
2762 +delete_all_messages_by_user_at(User, VHost, Date) ->
2763 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2764 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
2765 +delete_messages_at(VHost, Date) ->
2766 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2767 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
2768 +get_vhost_stats(VHost) ->
2769 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2770 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
2771 +get_vhost_stats_at(VHost, Date) ->
2772 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2773 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
2774 +get_user_stats(User, VHost) ->
2775 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2776 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
2777 +get_user_messages_at(User, VHost, Date) ->
2778 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2779 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
2780 +get_dates(VHost) ->
2781 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2782 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
2783 +get_user_settings(User, VHost) ->
2784 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2785 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
2786 +get_users_settings(VHost) ->
2787 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2788 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
2789 +set_user_settings(User, VHost, Set) ->
2790 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2791 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
2792 +drop_user(User, VHost) ->
2793 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
2794 + gen_server:call(Proc, {drop_user, User}, ?CALL_TIMEOUT).
2796 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2800 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2801 +log_message_int(VHost, #msg{timestamp=Timestamp}=MsgBin) ->
2802 + Date = mod_logdb:convert_timestamp_brief(Timestamp),
2804 + Msg = #msg{timestamp = MsgBin#msg.timestamp,
2805 + owner_name = binary_to_list(MsgBin#msg.owner_name),
2806 + peer_name = binary_to_list(MsgBin#msg.peer_name),
2807 + peer_server = binary_to_list(MsgBin#msg.peer_server),
2808 + peer_resource = binary_to_list(MsgBin#msg.peer_resource),
2809 + direction = MsgBin#msg.direction,
2810 + type = binary_to_list(MsgBin#msg.type),
2811 + subject = binary_to_list(MsgBin#msg.subject),
2812 + body = binary_to_list(MsgBin#msg.body)},
2814 + ATable = table_name(VHost, Date),
2816 + mnesia:write_lock_table(ATable),
2817 + mnesia:write(ATable, Msg, write)
2819 + % log message, increment stats for both users
2820 + case mnesia:transaction(Fun) of
2821 + % if table does not exists - create it and try to log message again
2822 + {aborted,{no_exists, _Table}} ->
2823 + case create_msg_table(VHost, Date) of
2824 + {aborted, CReason} ->
2825 + ?ERROR_MSG("Failed to log message: ~p", [CReason]),
2828 + ?MYDEBUG("Created msg table for ~s at ~s", [VHost, Date]),
2829 + log_message_int(VHost, MsgBin)
2831 + {aborted, TReason} ->
2832 + ?ERROR_MSG("Failed to log message: ~p", [TReason]),
2835 + ?MYDEBUG("Logged ok for ~s, peer: ~s", [ [Msg#msg.owner_name, <<"@">>, VHost],
2836 + [Msg#msg.peer_name, <<"@">>, Msg#msg.peer_server] ]),
2837 + increment_user_stats(Msg#msg.owner_name, VHost, Date)
2840 +increment_user_stats(Owner, VHost, Date) ->
2842 + Pat = #stats{user=Owner, at=Date, count='$1'},
2843 + mnesia:write_lock_table(stats_table(VHost)),
2844 + case mnesia:select(stats_table(VHost), [{Pat, [], ['$_']}]) of
2846 + mnesia:write(stats_table(VHost),
2847 + #stats{user=Owner,
2852 + mnesia:delete_object(stats_table(VHost),
2853 + #stats{user=Owner,
2855 + count=Stats#stats.count},
2857 + New = Stats#stats{count = Stats#stats.count+1},
2859 + New#stats.count > 0 -> mnesia:write(stats_table(VHost),
2866 + case mnesia:transaction(Fun) of
2867 + {aborted, Reason} ->
2868 + ?ERROR_MSG("Failed to update stats for ~s@~s: ~p", [Owner, VHost, Reason]),
2871 + ?MYDEBUG("Updated stats for ~s@~s", [Owner, VHost]),
2875 +get_dates_int(VHost) ->
2876 + Tables = mnesia:system_info(tables),
2877 + lists:foldl(fun(ATable, Dates) ->
2878 + Table = term_to_binary(ATable),
2879 + case ejabberd_regexp:run( Table, << VHost/binary, <<"$">>/binary >> ) of
2881 + case re:run(Table, "_[0-9]+-[0-9]+-[0-9]+_") of
2882 + {match, [{S, E}]} ->
2883 + lists:append(Dates, [lists:sublist(binary_to_list(Table), S+2, E-2)]);
2892 +rebuild_stats_at_int(VHost, Date) ->
2893 + Table = table_name(VHost, Date),
2894 + STable = stats_table(VHost),
2895 + CFun = fun(Msg, Stats) ->
2896 + Owner = Msg#msg.owner_name,
2897 + case lists:keysearch(Owner, 1, Stats) of
2898 + {value, {_, Count}} ->
2899 + lists:keyreplace(Owner, 1, Stats, {Owner, Count + 1});
2901 + lists:append(Stats, [{Owner, 1}])
2904 + DFun = fun(#stats{at=SDate} = Stat, _Acc)
2905 + when SDate == Date ->
2906 + mnesia:delete_object(stats_table(VHost), Stat, write);
2907 + (_Stat, _Acc) -> ok
2909 + % TODO: Maybe unregister hooks ?
2910 + case mnesia:transaction(fun() ->
2911 + mnesia:write_lock_table(Table),
2912 + mnesia:write_lock_table(STable),
2913 + % Delete all stats for VHost at Date
2914 + mnesia:foldl(DFun, [], STable),
2915 + % Calc stats for VHost at Date
2916 + case mnesia:foldl(CFun, [], Table) of
2919 + % Write new calc'ed stats
2920 + lists:foreach(fun({Owner, Count}) ->
2921 + WStat = #stats{user=Owner, at=Date, count=Count},
2922 + mnesia:write(stats_table(VHost), WStat, write)
2927 + {aborted, Reason} ->
2928 + ?ERROR_MSG("Failed to rebuild_stats_at for ~p at ~p: ~p", [VHost, Date, Reason]),
2932 + {atomic, empty} ->
2933 + {atomic,ok} = mnesia:delete_table(Table),
2934 + ?MYDEBUG("Dropped table at ~p", [Date]),
2938 +delete_nonexistent_stats(VHost) ->
2939 + Dates = get_dates_int(VHost),
2940 + mnesia:transaction(fun() ->
2941 + mnesia:foldl(fun(#stats{at=Date} = Stat, _Acc) ->
2942 + case lists:member(Date, Dates) of
2943 + false -> mnesia:delete_object(Stat);
2946 + end, ok, stats_table(VHost))
2949 +delete_stats_by_vhost_at_int(VHost, Date) ->
2950 + StatsDelete = fun(#stats{at=SDate} = Stat, _Acc)
2951 + when SDate == Date ->
2952 + mnesia:delete_object(stats_table(VHost), Stat, write),
2954 + (_Msg, _Acc) -> ok
2956 + case mnesia:transaction(fun() ->
2957 + mnesia:write_lock_table(stats_table(VHost)),
2958 + mnesia:foldl(StatsDelete, ok, stats_table(VHost))
2960 + {aborted, Reason} ->
2961 + ?ERROR_MSG("Failed to update stats at ~p for ~p: ~p", [Date, VHost, Reason]),
2962 + rebuild_stats_at_int(VHost, Date);
2964 + ?INFO_MSG("Updated stats at ~p for ~p", [Date, VHost]),
2968 +get_user_stats_int(User, VHost) ->
2969 + case mnesia:transaction(fun() ->
2970 + Pat = #stats{user=User, at='$1', count='$2'},
2971 + mnesia:select(stats_table(VHost), [{Pat, [], [['$1', '$2']]}])
2973 + {atomic, Result} ->
2974 + {ok, mod_logdb:sort_stats([{Date, Count} || [Date, Count] <- Result])};
2975 + {aborted, Reason} ->
2979 +delete_all_messages_by_user_at_int(User, VHost, Date) ->
2980 + Table = table_name(VHost, Date),
2981 + MsgDelete = fun(#msg{owner_name=Owner} = Msg, _Acc)
2982 + when Owner == User ->
2983 + mnesia:delete_object(Table, Msg, write),
2985 + (_Msg, _Acc) -> ok
2987 + DRez = case mnesia:transaction(fun() ->
2988 + mnesia:foldl(MsgDelete, ok, Table)
2990 + {aborted, Reason} ->
2991 + ?ERROR_MSG("Failed to delete_all_messages_by_user_at for ~p@~p at ~p: ~p", [User, VHost, Date, Reason]),
2996 + case rebuild_stats_at_int(VHost, Date) of
3003 +delete_user_settings_int(User, VHost) ->
3004 + STable = settings_table(VHost),
3005 + case mnesia:dirty_match_object(STable, #user_settings{owner_name=User, _='_'}) of
3009 + mnesia:dirty_delete_object(STable, UserSettings)
3012 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3016 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3017 +create_stats_table(VHost) ->
3018 + SName = stats_table(VHost),
3019 + case mnesia:create_table(SName,
3020 + [{disc_only_copies, [node()]},
3022 + {attributes, record_info(fields, stats)},
3023 + {record_name, stats}
3026 + ?MYDEBUG("Created stats table for ~p", [VHost]),
3027 + lists:foreach(fun(Date) ->
3028 + rebuild_stats_at_int(VHost, Date)
3029 + end, get_dates_int(VHost)),
3031 + {aborted, {already_exists, _}} ->
3032 + ?MYDEBUG("Stats table for ~p already exists", [VHost]),
3034 + {aborted, Reason} ->
3035 + ?ERROR_MSG("Failed to create stats table: ~p", [Reason]),
3039 +create_settings_table(VHost) ->
3040 + SName = settings_table(VHost),
3041 + case mnesia:create_table(SName,
3042 + [{disc_copies, [node()]},
3044 + {attributes, record_info(fields, user_settings)},
3045 + {record_name, user_settings}
3048 + ?MYDEBUG("Created settings table for ~p", [VHost]),
3050 + {aborted, {already_exists, _}} ->
3051 + ?MYDEBUG("Settings table for ~p already exists", [VHost]),
3053 + {aborted, Reason} ->
3054 + ?ERROR_MSG("Failed to create settings table: ~p", [Reason]),
3058 +create_msg_table(VHost, Date) ->
3059 + mnesia:create_table(
3060 + table_name(VHost, Date),
3061 + [{disc_only_copies, [node()]},
3063 + {attributes, record_info(fields, msg)},
3064 + {record_name, msg}]).
3065 diff --git a/src/mod_logdb_mnesia_old.erl b/src/mod_logdb_mnesia_old.erl
3066 new file mode 100644
3067 index 0000000..e962d9a
3069 +++ b/src/mod_logdb_mnesia_old.erl
3071 +%%%----------------------------------------------------------------------
3072 +%%% File : mod_logdb_mnesia_old.erl
3073 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
3074 +%%% Purpose : mod_logmnesia backend for mod_logdb (should be used only for copy_tables functionality)
3075 +%%% Version : trunk
3076 +%%% Id : $Id: mod_logdb_mnesia_old.erl 1273 2009-02-05 18:12:57Z malik $
3077 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
3078 +%%%----------------------------------------------------------------------
3080 +-module(mod_logdb_mnesia_old).
3081 +-author('o.palij@gmail.com').
3083 +-include("ejabberd.hrl").
3084 +-include("jlib.hrl").
3085 +-include("logger.hrl").
3087 +-behaviour(gen_logdb).
3089 +-export([start/2, stop/1,
3092 + rebuild_stats_at/2,
3093 + rebuild_stats_at1/2,
3094 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
3095 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
3097 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
3100 +-record(stats, {user, server, table, count}).
3101 +-record(msg, {to_user, to_server, to_resource, from_user, from_server, from_resource, id, type, subject, body, timestamp}).
3103 +tables_prefix() -> "messages_".
3104 +% stats_table should not start with tables_prefix(VHost) !
3105 +% i.e. lists:prefix(tables_prefix(VHost), atom_to_list(stats_table())) must be /= true
3106 +stats_table() -> list_to_atom("messages-stats").
3107 +% table name as atom from Date
3108 +-define(ATABLE(Date), list_to_atom(tables_prefix() ++ Date)).
3109 +-define(LTABLE(Date), tables_prefix() ++ Date).
3111 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3113 +% gen_logdb callbacks
3115 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3116 +start(_Opts, _VHost) ->
3117 + case mnesia:system_info(is_running) of
3119 + ok = create_stats_table(),
3122 + ?ERROR_MSG("Mnesia not running", []),
3125 + ?ERROR_MSG("Mnesia status: ~p", [Status]),
3132 +log_message(_VHost, _Msg) ->
3135 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3137 +% gen_logdb callbacks (maintaince)
3139 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3140 +rebuild_stats(_VHost) ->
3143 +rebuild_stats_at(VHost, Date) ->
3144 + Table = ?LTABLE(Date),
3145 + {Time, Value}=timer:tc(?MODULE, rebuild_stats_at1, [VHost, Table]),
3146 + ?INFO_MSG("rebuild_stats_at ~p elapsed ~p sec: ~p~n", [Date, Time/1000000, Value]),
3148 +rebuild_stats_at1(VHost, Table) ->
3149 + CFun = fun(Msg, Stats) ->
3150 + To = Msg#msg.to_user ++ "@" ++ Msg#msg.to_server,
3152 + Msg#msg.to_server == VHost ->
3153 + case lists:keysearch(To, 1, Stats) of
3154 + {value, {Who_to, Count_to}} ->
3155 + lists:keyreplace(To, 1, Stats, {Who_to, Count_to + 1});
3157 + lists:append(Stats, [{To, 1}])
3162 + From = Msg#msg.from_user ++ "@" ++ Msg#msg.from_server,
3164 + Msg#msg.from_server == VHost ->
3165 + case lists:keysearch(From, 1, Stats_to) of
3166 + {value, {Who_from, Count_from}} ->
3167 + lists:keyreplace(From, 1, Stats_to, {Who_from, Count_from + 1});
3169 + lists:append(Stats_to, [{From, 1}])
3176 + DFun = fun(#stats{table=STable, server=Server} = Stat, _Acc)
3177 + when STable == Table, Server == VHost ->
3178 + mnesia:delete_object(stats_table(), Stat, write);
3179 + (_Stat, _Acc) -> ok
3181 + case mnesia:transaction(fun() ->
3182 + mnesia:write_lock_table(list_to_atom(Table)),
3183 + mnesia:write_lock_table(stats_table()),
3184 + % Calc stats for VHost at Date
3185 + AStats = mnesia:foldl(CFun, [], list_to_atom(Table)),
3186 + % Delete all stats for VHost at Date
3187 + mnesia:foldl(DFun, [], stats_table()),
3188 + % Write new calc'ed stats
3189 + lists:foreach(fun({Who, Count}) ->
3190 + Jid = jlib:string_to_jid(Who),
3191 + JUser = Jid#jid.user,
3192 + WStat = #stats{user=JUser, server=VHost, table=Table, count=Count},
3193 + mnesia:write(stats_table(), WStat, write)
3196 + {aborted, Reason} ->
3197 + ?ERROR_MSG("Failed to rebuild_stats_at for ~p at ~p: ~p", [VHost, Table, Reason]),
3203 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3205 +% gen_logdb callbacks (delete)
3207 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3208 +delete_messages_by_user_at(_VHost, _Msgs, _Date) ->
3211 +delete_all_messages_by_user_at(_User, _VHost, _Date) ->
3214 +delete_messages_at(VHost, Date) ->
3215 + Table = list_to_atom(tables_prefix() ++ Date),
3217 + DFun = fun(#msg{to_server=To_server, from_server=From_server}=Msg, _Acc)
3218 + when To_server == VHost; From_server == VHost ->
3219 + mnesia:delete_object(Table, Msg, write);
3220 + (_Msg, _Acc) -> ok
3223 + case mnesia:transaction(fun() ->
3224 + mnesia:foldl(DFun, [], Table)
3226 + {aborted, Reason} ->
3227 + ?ERROR_MSG("Failed to delete_messages_at for ~p at ~p: ~p", [VHost, Date, Reason]),
3233 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3235 +% gen_logdb callbacks (get)
3237 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3238 +get_vhost_stats(_VHost) ->
3239 + {error, "does not emplemented"}.
3241 +get_vhost_stats_at(VHost, Date) ->
3243 + Pat = #stats{user='$1', server=VHost, table=tables_prefix()++Date, count = '$2'},
3244 + mnesia:select(stats_table(), [{Pat, [], [['$1', '$2']]}])
3246 + case mnesia:transaction(Fun) of
3247 + {atomic, Result} ->
3248 + RFun = fun([User, Count]) ->
3251 + {ok, lists:reverse(lists:keysort(2, lists:map(RFun, Result)))};
3252 + {aborted, Reason} -> {error, Reason}
3255 +get_user_stats(_User, _VHost) ->
3256 + {error, "does not emplemented"}.
3258 +get_user_messages_at(User, VHost, Date) ->
3259 + Table_name = tables_prefix() ++ Date,
3260 + case mnesia:transaction(fun() ->
3261 + Pat_to = #msg{to_user=User, to_server=VHost, _='_'},
3262 + Pat_from = #msg{from_user=User, from_server=VHost, _='_'},
3263 + mnesia:select(list_to_atom(Table_name),
3264 + [{Pat_to, [], ['$_']},
3265 + {Pat_from, [], ['$_']}])
3267 + {atomic, Result} ->
3268 + Msgs = lists:map(fun(#msg{to_user=To_user, to_server=To_server, to_resource=To_res,
3269 + from_user=From_user, from_server=From_server, from_resource=From_res,
3272 + body=Body, timestamp=Timestamp} = _Msg) ->
3273 + Subject = case Subj of
3277 + {msg, To_user, To_server, To_res, From_user, From_server, From_res, Type, Subject, Body, Timestamp}
3280 + {aborted, Reason} ->
3284 +get_dates(_VHost) ->
3285 + Tables = mnesia:system_info(tables),
3287 + lists:filter(fun(Table) ->
3288 + lists:prefix(tables_prefix(), atom_to_list(Table))
3291 + lists:map(fun(Table) ->
3292 + lists:sublist(atom_to_list(Table),
3293 + length(tables_prefix())+1,
3294 + length(atom_to_list(Table)))
3298 +get_users_settings(_VHost) ->
3300 +get_user_settings(_User, _VHost) ->
3302 +set_user_settings(_User, _VHost, _Set) ->
3304 +drop_user(_User, _VHost) ->
3307 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3311 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3312 +% called from db_logon/2
3313 +create_stats_table() ->
3314 + SName = stats_table(),
3315 + case mnesia:create_table(SName,
3316 + [{disc_only_copies, [node()]},
3318 + {attributes, record_info(fields, stats)},
3319 + {record_name, stats}
3322 + ?INFO_MSG("Created stats table", []),
3324 + {aborted, {already_exists, _}} ->
3326 + {aborted, Reason} ->
3327 + ?ERROR_MSG("Failed to create stats table: ~p", [Reason]),
3330 diff --git a/src/mod_logdb_mysql.erl b/src/mod_logdb_mysql.erl
3331 new file mode 100644
3332 index 0000000..62f437c
3334 +++ b/src/mod_logdb_mysql.erl
3336 +%%%----------------------------------------------------------------------
3337 +%%% File : mod_logdb_mysql.erl
3338 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
3339 +%%% Purpose : MySQL backend for mod_logdb
3340 +%%% Version : trunk
3341 +%%% Id : $Id: mod_logdb_mysql.erl 1360 2009-07-30 06:00:14Z malik $
3342 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
3343 +%%%----------------------------------------------------------------------
3345 +-module(mod_logdb_mysql).
3346 +-author('o.palij@gmail.com').
3348 +-include("mod_logdb.hrl").
3349 +-include("ejabberd.hrl").
3350 +-include("jlib.hrl").
3351 +-include("logger.hrl").
3353 +-behaviour(gen_logdb).
3354 +-behaviour(gen_server).
3357 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
3359 +-export([start/2, stop/1]).
3361 +-export([log_message/2,
3363 + rebuild_stats_at/2,
3364 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
3365 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
3367 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
3370 +% gen_server call timeout
3371 +-define(CALL_TIMEOUT, 30000).
3372 +-define(MYSQL_TIMEOUT, 60000).
3373 +-define(INDEX_SIZE, integer_to_list(170)).
3374 +-define(PROCNAME, mod_logdb_mysql).
3376 +-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
3377 + list_to_string/1, string_to_list/1,
3378 + convert_timestamp_brief/1]).
3380 +-record(state, {dbref, vhost, server, port, db, user, password}).
3382 +% replace "." with "_"
3383 +escape_vhost(VHost) -> lists:map(fun(46) -> 95;
3385 + end, binary_to_list(VHost)).
3390 + "_" ++ escape_vhost(VHost) ++ "`".
3392 +messages_table(VHost, Date) ->
3393 + prefix() ++ "messages_" ++ Date ++ suffix(VHost).
3395 +stats_table(VHost) ->
3396 + prefix() ++ "stats" ++ suffix(VHost).
3398 +temp_table(VHost) ->
3399 + prefix() ++ "temp" ++ suffix(VHost).
3401 +settings_table(VHost) ->
3402 + prefix() ++ "settings" ++ suffix(VHost).
3404 +users_table(VHost) ->
3405 + prefix() ++ "users" ++ suffix(VHost).
3406 +servers_table(VHost) ->
3407 + prefix() ++ "servers" ++ suffix(VHost).
3408 +resources_table(VHost) ->
3409 + prefix() ++ "resources" ++ suffix(VHost).
3411 +ets_users_table(VHost) -> list_to_atom("logdb_users_" ++ binary_to_list(VHost)).
3412 +ets_servers_table(VHost) -> list_to_atom("logdb_servers_" ++ binary_to_list(VHost)).
3413 +ets_resources_table(VHost) -> list_to_atom("logdb_resources_" ++ binary_to_list(VHost)).
3415 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3417 +% gen_mod callbacks
3419 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3420 +start(VHost, Opts) ->
3421 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3422 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
3425 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3426 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
3428 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3430 +% gen_server callbacks
3432 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3433 +init([VHost, Opts]) ->
3436 + Server = gen_mod:get_opt(server, Opts, fun(A) -> A end, <<"localhost">>),
3437 + Port = gen_mod:get_opt(port, Opts, fun(A) -> A end, 3306),
3438 + DB = gen_mod:get_opt(db, Opts, fun(A) -> A end, <<"logdb">>),
3439 + User = gen_mod:get_opt(user, Opts, fun(A) -> A end, <<"root">>),
3440 + Password = gen_mod:get_opt(password, Opts, fun(A) -> A end, <<"">>),
3442 + St = #state{vhost=VHost,
3443 + server=Server, port=Port, db=DB,
3444 + user=User, password=Password},
3446 + case open_mysql_connection(St) of
3448 + State = St#state{dbref=DBRef},
3449 + ok = create_stats_table(State),
3450 + ok = create_settings_table(State),
3451 + ok = create_users_table(State),
3452 + % clear ets cache every ...
3453 + timer:send_interval(timer:hours(12), clear_ets_tables),
3454 + ok = create_servers_table(State),
3455 + ok = create_resources_table(State),
3456 + erlang:monitor(process, DBRef),
3458 + {error, Reason} ->
3459 + ?ERROR_MSG("MySQL connection failed: ~p~n", [Reason]),
3460 + {stop, db_connection_failed}
3463 +open_mysql_connection(#state{server=Server, port=Port, db=DB,
3464 + user=DBUser, password=Password} = _State) ->
3465 + LogFun = fun(debug, _Format, _Argument) ->
3466 + %?MYDEBUG(Format, Argument);
3468 + (error, Format, Argument) ->
3469 + ?ERROR_MSG(Format, Argument);
3470 + (Level, Format, Argument) ->
3471 + ?MYDEBUG("MySQL (~p)~n", [Level]),
3472 + ?MYDEBUG(Format, Argument)
3474 + ?INFO_MSG("Opening mysql connection ~s@~s:~p/~s", [DBUser, Server, Port, DB]),
3475 + p1_mysql_conn:start(binary_to_list(Server), Port,
3476 + binary_to_list(DBUser), binary_to_list(Password),
3477 + binary_to_list(DB), LogFun).
3479 +close_mysql_connection(DBRef) ->
3480 + ?MYDEBUG("Closing ~p mysql connection", [DBRef]),
3481 + catch p1_mysql_conn:stop(DBRef).
3483 +handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3484 + Date = convert_timestamp_brief(Msg#msg.timestamp),
3486 + Table = messages_table(VHost, Date),
3487 + Owner_id = get_user_id(DBRef, VHost, binary_to_list(Msg#msg.owner_name)),
3488 + Peer_name_id = get_user_id(DBRef, VHost, binary_to_list(Msg#msg.peer_name)),
3489 + Peer_server_id = get_server_id(DBRef, VHost, binary_to_list(Msg#msg.peer_server)),
3490 + Peer_resource_id = get_resource_id(DBRef, VHost, binary_to_list(Msg#msg.peer_resource)),
3492 + Query = ["INSERT INTO ",Table," ",
3495 + "peer_server_id,",
3496 + "peer_resource_id,",
3503 + "('", Owner_id, "',",
3504 + "'", Peer_name_id, "',",
3505 + "'", Peer_server_id, "',",
3506 + "'", Peer_resource_id, "',",
3507 + "'", atom_to_list(Msg#msg.direction), "',",
3508 + "'", binary_to_list(Msg#msg.type), "',",
3509 + "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.subject) ), "',",
3510 + "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.body) ), "',",
3511 + "'", Msg#msg.timestamp, "');"],
3514 + case sql_query_internal_silent(DBRef, Query) of
3516 + ?MYDEBUG("Logged ok for ~s, peer: ~s", [ [Msg#msg.owner_name, <<"@">>, VHost],
3517 + [Msg#msg.peer_name, <<"@">>, Msg#msg.peer_server] ]),
3518 + increment_user_stats(DBRef, Msg#msg.owner_name, Owner_id, VHost, Peer_name_id, Peer_server_id, Date);
3519 + {error, Reason} ->
3520 + case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#42S02">>) of
3521 + % Table doesn't exist
3523 + case create_msg_table(DBRef, VHost, Date) of
3527 + {updated, _} = sql_query_internal(DBRef, Query),
3528 + increment_user_stats(DBRef, binary_to_list(Msg#msg.owner_name), Owner_id, VHost, Peer_name_id, Peer_server_id, Date)
3531 + ?ERROR_MSG("Failed to log message: ~p", [Reason]),
3535 + {reply, Reply, State};
3536 +handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3537 + Reply = rebuild_stats_at_int(DBRef, VHost, Date),
3538 + {reply, Reply, State};
3539 +handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
3540 + {reply, error, State};
3541 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3542 + Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
3543 + ["\"",Timestamp,"\"",","]
3546 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
3548 + Query = ["DELETE FROM ",messages_table(VHost, Date)," ",
3549 + "WHERE timestamp IN (", Temp1],
3552 + case sql_query_internal(DBRef, Query) of
3554 + ?MYDEBUG("Aff=~p", [Aff]),
3555 + rebuild_stats_at_int(DBRef, VHost, Date);
3559 + {reply, Reply, State};
3560 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3561 + ok = delete_all_messages_by_user_at_int(DBRef, User, VHost, Date),
3562 + ok = delete_stats_by_user_at_int(DBRef, User, VHost, Date),
3563 + {reply, ok, State};
3564 +handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3566 + case sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Date),";"]) of
3568 + Query = ["DELETE FROM ",stats_table(VHost)," "
3569 + "WHERE at=\"",Date,"\";"],
3570 + case sql_query_internal(DBRef, Query) of
3579 + {reply, Reply, State};
3580 +handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3581 + SName = stats_table(VHost),
3582 + Query = ["SELECT at, sum(count) ",
3583 + "FROM ",SName," ",
3585 + "ORDER BY DATE(at) DESC;"
3588 + case sql_query_internal(DBRef, Query) of
3590 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
3591 + {error, Reason} ->
3592 + % TODO: Duplicate error message ?
3595 + {reply, Reply, State};
3596 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3597 + SName = stats_table(VHost),
3598 + Query = ["SELECT username, sum(count) AS allcount ",
3599 + "FROM ",SName," ",
3600 + "JOIN ",users_table(VHost)," ON owner_id=user_id "
3601 + "WHERE at=\"",Date,"\" "
3602 + "GROUP BY username ",
3603 + "ORDER BY allcount DESC;"
3606 + case sql_query_internal(DBRef, Query) of
3608 + {ok, lists:reverse(
3610 + [ {User, list_to_integer(Count)} || [User, Count] <- Result]))};
3611 + {error, Reason} ->
3615 + {reply, Reply, State};
3616 +handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3617 + {reply, get_user_stats_int(DBRef, User, VHost), State};
3618 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3619 + TName = messages_table(VHost, Date),
3620 + UName = users_table(VHost),
3621 + SName = servers_table(VHost),
3622 + RName = resources_table(VHost),
3623 + Query = ["SELECT users.username,",
3624 + "servers.server,",
3625 + "resources.resource,",
3626 + "messages.direction,"
3628 + "messages.subject,"
3630 + "messages.timestamp "
3631 + "FROM ",TName," AS messages "
3632 + "JOIN ",UName," AS users ON peer_name_id=user_id ",
3633 + "JOIN ",SName," AS servers ON peer_server_id=server_id ",
3634 + "JOIN ",RName," AS resources ON peer_resource_id=resource_id ",
3635 + "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\" ",
3636 + "ORDER BY timestamp ASC;"],
3638 + case sql_query_internal(DBRef, Query) of
3640 + Fun = fun([Peer_name, Peer_server, Peer_resource,
3645 + #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
3646 + direction=list_to_atom(Direction),
3648 + subject=Subject, body=Body,
3649 + timestamp=Timestamp}
3651 + {ok, lists:map(Fun, Result)};
3652 + {error, Reason} ->
3655 + {reply, Reply, State};
3656 +handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3657 + SName = stats_table(VHost),
3658 + Query = ["SELECT at ",
3659 + "FROM ",SName," ",
3661 + "ORDER BY DATE(at) DESC;"
3664 + case sql_query_internal(DBRef, Query) of
3666 + [ Date || [Date] <- Result ];
3667 + {error, Reason} ->
3670 + {reply, Reply, State};
3671 +handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3672 + Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
3673 + "FROM ",settings_table(VHost)," ",
3674 + "JOIN ",users_table(VHost)," ON user_id=owner_id;"],
3676 + case sql_query_internal(DBRef, Query) of
3678 + {ok, lists:map(fun([Owner, DoLogDef, DoLogL, DoNotLogL]) ->
3679 + #user_settings{owner_name=Owner,
3680 + dolog_default=list_to_bool(DoLogDef),
3681 + dolog_list=string_to_list(DoLogL),
3682 + donotlog_list=string_to_list(DoNotLogL)
3688 + {reply, Reply, State};
3689 +handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
3690 + Query = ["SELECT dolog_default,dolog_list,donotlog_list FROM ",settings_table(VHost)," ",
3691 + "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\";"],
3693 + case sql_query_internal(DBRef, Query) of
3696 + {data, [[Owner, DoLogDef, DoLogL, DoNotLogL]]} ->
3697 + {ok, #user_settings{owner_name=Owner,
3698 + dolog_default=list_to_bool(DoLogDef),
3699 + dolog_list=string_to_list(DoLogL),
3700 + donotlog_list=string_to_list(DoNotLogL)}};
3704 + {reply, Reply, State};
3705 +handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
3706 + dolog_list=DoLogL,
3707 + donotlog_list=DoNotLogL}},
3708 + _From, #state{dbref=DBRef, vhost=VHost} = State) ->
3709 + User_id = get_user_id(DBRef, VHost, User),
3711 + Query = ["UPDATE ",settings_table(VHost)," ",
3712 + "SET dolog_default=",bool_to_list(DoLogDef),", ",
3713 + "dolog_list='",list_to_string(DoLogL),"', ",
3714 + "donotlog_list='",list_to_string(DoNotLogL),"' ",
3715 + "WHERE owner_id=\"",User_id,"\";"],
3718 + case sql_query_internal(DBRef, Query) of
3720 + IQuery = ["INSERT INTO ",settings_table(VHost)," ",
3721 + "(owner_id, dolog_default, dolog_list, donotlog_list) ",
3723 + "('",User_id,"', ",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
3724 + case sql_query_internal_silent(DBRef, IQuery) of
3726 + ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
3728 + {error, Reason} ->
3729 + case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>) of
3734 + ?ERROR_MSG("Failed setup user ~p@~p: ~p", [User, VHost, Reason]),
3739 + ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
3744 + {reply, Reply, State};
3745 +handle_call({stop}, _From, #state{vhost=VHost}=State) ->
3746 + ets:delete(ets_users_table(VHost)),
3747 + ets:delete(ets_servers_table(VHost)),
3748 + ?MYDEBUG("Stoping mysql backend for ~p", [VHost]),
3749 + {stop, normal, ok, State};
3750 +handle_call(Msg, _From, State) ->
3751 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
3754 +handle_cast({rebuild_stats}, State) ->
3755 + rebuild_all_stats_int(State),
3757 +handle_cast({drop_user, User}, #state{vhost=VHost} = State) ->
3759 + {ok, DBRef} = open_mysql_connection(State),
3760 + {ok, Dates} = get_user_stats_int(DBRef, User, VHost),
3761 + MDResult = lists:map(fun({Date, _}) ->
3762 + delete_all_messages_by_user_at_int(DBRef, User, VHost, Date)
3764 + StDResult = delete_all_stats_by_user_int(DBRef, User, VHost),
3765 + SDResult = delete_user_settings_int(DBRef, User, VHost),
3766 + case lists:all(fun(Result) when Result == ok ->
3768 + (Result) when Result == error ->
3770 + end, lists:append([MDResult, [StDResult], [SDResult]])) of
3772 + ?INFO_MSG("Removed ~s@~s", [User, VHost]);
3774 + ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost])
3776 + close_mysql_connection(DBRef)
3780 +handle_cast(Msg, State) ->
3781 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
3784 +handle_info(clear_ets_tables, State) ->
3785 + ets:delete_all_objects(ets_users_table(State#state.vhost)),
3786 + ets:delete_all_objects(ets_resources_table(State#state.vhost)),
3788 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
3789 + {stop, connection_dropped, State};
3790 +handle_info(Info, State) ->
3791 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
3794 +terminate(_Reason, #state{dbref=DBRef}=_State) ->
3795 + close_mysql_connection(DBRef),
3798 +code_change(_OldVsn, State, _Extra) ->
3801 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3803 +% gen_logdb callbacks
3805 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3806 +log_message(VHost, Msg) ->
3807 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3808 + gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
3809 +rebuild_stats(VHost) ->
3810 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3811 + gen_server:cast(Proc, {rebuild_stats}).
3812 +rebuild_stats_at(VHost, Date) ->
3813 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3814 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
3815 +delete_messages_by_user_at(VHost, Msgs, Date) ->
3816 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3817 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
3818 +delete_all_messages_by_user_at(User, VHost, Date) ->
3819 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3820 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
3821 +delete_messages_at(VHost, Date) ->
3822 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3823 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
3824 +get_vhost_stats(VHost) ->
3825 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3826 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
3827 +get_vhost_stats_at(VHost, Date) ->
3828 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3829 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
3830 +get_user_stats(User, VHost) ->
3831 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3832 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
3833 +get_user_messages_at(User, VHost, Date) ->
3834 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3835 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
3836 +get_dates(VHost) ->
3837 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3838 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
3839 +get_users_settings(VHost) ->
3840 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3841 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
3842 +get_user_settings(User, VHost) ->
3843 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3844 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
3845 +set_user_settings(User, VHost, Set) ->
3846 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3847 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
3848 +drop_user(User, VHost) ->
3849 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
3850 + gen_server:cast(Proc, {drop_user, User}).
3852 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3856 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3857 +increment_user_stats(DBRef, User_name, User_id, VHost, PNameID, PServerID, Date) ->
3858 + SName = stats_table(VHost),
3859 + UQuery = ["UPDATE ",SName," ",
3860 + "SET count=count+1 ",
3861 + "WHERE owner_id=\"",User_id,"\" AND peer_name_id=\"",PNameID,"\" AND peer_server_id=\"",PServerID,"\" AND at=\"",Date,"\";"],
3863 + case sql_query_internal(DBRef, UQuery) of
3865 + IQuery = ["INSERT INTO ",SName," ",
3866 + "(owner_id, peer_name_id, peer_server_id, at, count) ",
3868 + "('",User_id,"', '",PNameID,"', '",PServerID,"', '",Date,"', '1');"],
3869 + case sql_query_internal(DBRef, IQuery) of
3871 + ?MYDEBUG("New stats for ~s@~s at ~s", [User_name, VHost, Date]),
3877 + ?MYDEBUG("Updated stats for ~s@~s at ~s", [User_name, VHost, Date]),
3883 +get_dates_int(DBRef, VHost) ->
3884 + case sql_query_internal(DBRef, ["SHOW TABLES"]) of
3886 + lists:foldl(fun([Table], Dates) ->
3887 + Reg = lists:sublist(prefix(),2,length(prefix())) ++ ".*" ++ escape_vhost(VHost),
3888 + case re:run(Table, Reg) of
3889 + {match, [{1, _}]} ->
3890 + ?MYDEBUG("matched ~p against ~p", [Table, Reg]),
3891 + case re:run(Table,"[0-9]+-[0-9]+-[0-9]+") of
3892 + {match, [{S, E}]} ->
3893 + lists:append(Dates, [lists:sublist(Table,S,E)]);
3905 +rebuild_all_stats_int(#state{vhost=VHost}=State) ->
3907 + {ok, DBRef} = open_mysql_connection(State),
3908 + ok = delete_nonexistent_stats(DBRef, VHost),
3909 + case lists:filter(fun(Date) ->
3910 + case catch rebuild_stats_at_int(DBRef, VHost, Date) of
3913 + {'EXIT', _} -> true
3915 + end, get_dates_int(DBRef, VHost)) of
3918 + ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]),
3921 + close_mysql_connection(DBRef)
3925 +rebuild_stats_at_int(DBRef, VHost, Date) ->
3926 + TempTable = temp_table(VHost),
3928 + Table = messages_table(VHost, Date),
3929 + STable = stats_table(VHost),
3931 + DQuery = [ "DELETE FROM ",STable," ",
3932 + "WHERE at='",Date,"';"],
3934 + ok = create_temp_table(DBRef, TempTable),
3935 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," WRITE, ",TempTable," WRITE;"]),
3936 + SQuery = ["INSERT INTO ",TempTable," ",
3937 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
3938 + "SELECT owner_id,peer_name_id,peer_server_id,\"",Date,"\",count(*) ",
3939 + "FROM ",Table," GROUP BY owner_id,peer_name_id,peer_server_id;"],
3940 + case sql_query_internal(DBRef, SQuery) of
3942 + Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]),
3944 + {data, [["0"]]} ->
3945 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]),
3946 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE;"]),
3947 + {updated, _} = sql_query_internal(DBRef, DQuery),
3950 + ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]),
3954 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE, ",TempTable," WRITE;"]),
3955 + {updated, _} = sql_query_internal(DBRef, DQuery),
3956 + SQuery1 = ["INSERT INTO ",STable," ",
3957 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
3958 + "SELECT owner_id,peer_name_id,peer_server_id,at,count ",
3959 + "FROM ",TempTable,";"],
3960 + case sql_query_internal(DBRef, SQuery1) of
3961 + {updated, _} -> ok;
3962 + {error, _} -> error
3964 + {error, _} -> error
3968 + case catch apply(Fun, []) of
3970 + ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
3974 + {'EXIT', Reason} ->
3975 + ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]),
3978 + sql_query_internal(DBRef, ["UNLOCK TABLES;"]),
3979 + sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]),
3983 +delete_nonexistent_stats(DBRef, VHost) ->
3984 + Dates = get_dates_int(DBRef, VHost),
3985 + STable = stats_table(VHost),
3987 + Temp = lists:flatmap(fun(Date) ->
3988 + ["\"",Date,"\"",","]
3995 + % replace last "," with ");"
3996 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
3997 + Query = ["DELETE FROM ",STable," ",
3998 + "WHERE at NOT IN (", Temp1],
3999 + case sql_query_internal(DBRef, Query) of
4007 +get_user_stats_int(DBRef, User, VHost) ->
4008 + SName = stats_table(VHost),
4009 + Query = ["SELECT at, sum(count) as allcount ",
4010 + "FROM ",SName," ",
4011 + "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\" ",
4013 + "ORDER BY DATE(at) DESC;"
4015 + case sql_query_internal(DBRef, Query) of
4017 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result]};
4018 + {error, Result} ->
4022 +delete_all_messages_by_user_at_int(DBRef, User, VHost, Date) ->
4023 + DQuery = ["DELETE FROM ",messages_table(VHost, Date)," ",
4024 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4025 + case sql_query_internal(DBRef, DQuery) of
4027 + ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]),
4033 +delete_all_stats_by_user_int(DBRef, User, VHost) ->
4034 + SQuery = ["DELETE FROM ",stats_table(VHost)," ",
4035 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4036 + case sql_query_internal(DBRef, SQuery) of
4038 + ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]),
4040 + {error, _} -> error
4043 +delete_stats_by_user_at_int(DBRef, User, VHost, Date) ->
4044 + SQuery = ["DELETE FROM ",stats_table(VHost)," ",
4045 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\") ",
4046 + "AND at=\"",Date,"\";"],
4047 + case sql_query_internal(DBRef, SQuery) of
4049 + ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]),
4051 + {error, _} -> error
4054 +delete_user_settings_int(DBRef, User, VHost) ->
4055 + Query = ["DELETE FROM ",settings_table(VHost)," ",
4056 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4057 + case sql_query_internal(DBRef, Query) of
4059 + ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]),
4061 + {error, Reason} ->
4062 + ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]),
4066 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4070 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4071 +create_temp_table(DBRef, Name) ->
4072 + Query = ["CREATE TABLE ",Name," (",
4073 + "owner_id MEDIUMINT UNSIGNED, ",
4074 + "peer_name_id MEDIUMINT UNSIGNED, ",
4075 + "peer_server_id MEDIUMINT UNSIGNED, ",
4076 + "at VARCHAR(11), ",
4078 + ") ENGINE=MyISAM CHARACTER SET utf8;"
4080 + case sql_query_internal(DBRef, Query) of
4081 + {updated, _} -> ok;
4082 + {error, _Reason} -> error
4085 +create_stats_table(#state{dbref=DBRef, vhost=VHost}=State) ->
4086 + SName = stats_table(VHost),
4087 + Query = ["CREATE TABLE ",SName," (",
4088 + "owner_id MEDIUMINT UNSIGNED, ",
4089 + "peer_name_id MEDIUMINT UNSIGNED, ",
4090 + "peer_server_id MEDIUMINT UNSIGNED, ",
4091 + "at varchar(20), ",
4092 + "count int(11), ",
4093 + "INDEX(owner_id, peer_name_id, peer_server_id), ",
4095 + ") ENGINE=InnoDB CHARACTER SET utf8;"
4097 + case sql_query_internal_silent(DBRef, Query) of
4099 + ?INFO_MSG("Created stats table for ~p", [VHost]),
4100 + rebuild_all_stats_int(State),
4102 + {error, Reason} ->
4103 + case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#42S01">>) of
4105 + ?MYDEBUG("Stats table for ~p already exists", [VHost]),
4106 + CheckQuery = ["SHOW COLUMNS FROM ",SName," LIKE 'peer_%_id';"],
4107 + case sql_query_internal(DBRef, CheckQuery) of
4108 + {data, Elems} when length(Elems) == 2 ->
4109 + ?MYDEBUG("Stats table structure is ok", []),
4112 + ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []),
4113 + case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of
4115 + ?INFO_MSG("Successfully dropped ~p", [SName]);
4117 + ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName])
4122 + ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
4127 +create_settings_table(#state{dbref=DBRef, vhost=VHost}) ->
4128 + SName = settings_table(VHost),
4129 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
4130 + "owner_id MEDIUMINT UNSIGNED PRIMARY KEY, ",
4131 + "dolog_default TINYINT(1) NOT NULL DEFAULT 1, ",
4132 + "dolog_list TEXT, ",
4133 + "donotlog_list TEXT ",
4134 + ") ENGINE=InnoDB CHARACTER SET utf8;"
4136 + case sql_query_internal(DBRef, Query) of
4138 + ?MYDEBUG("Created settings table for ~p", [VHost]),
4144 +create_users_table(#state{dbref=DBRef, vhost=VHost}) ->
4145 + SName = users_table(VHost),
4146 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
4147 + "username TEXT NOT NULL, ",
4148 + "user_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
4149 + "UNIQUE INDEX(username(",?INDEX_SIZE,")) ",
4150 + ") ENGINE=InnoDB CHARACTER SET utf8;"
4152 + case sql_query_internal(DBRef, Query) of
4154 + ?MYDEBUG("Created users table for ~p", [VHost]),
4155 + ets:new(ets_users_table(VHost), [named_table, set, public]),
4156 + %update_users_from_db(DBRef, VHost),
4162 +create_servers_table(#state{dbref=DBRef, vhost=VHost}) ->
4163 + SName = servers_table(VHost),
4164 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
4165 + "server TEXT NOT NULL, ",
4166 + "server_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
4167 + "UNIQUE INDEX(server(",?INDEX_SIZE,")) ",
4168 + ") ENGINE=InnoDB CHARACTER SET utf8;"
4170 + case sql_query_internal(DBRef, Query) of
4172 + ?MYDEBUG("Created servers table for ~p", [VHost]),
4173 + ets:new(ets_servers_table(VHost), [named_table, set, public]),
4174 + update_servers_from_db(DBRef, VHost),
4180 +create_resources_table(#state{dbref=DBRef, vhost=VHost}) ->
4181 + RName = resources_table(VHost),
4182 + Query = ["CREATE TABLE IF NOT EXISTS ",RName," (",
4183 + "resource TEXT NOT NULL, ",
4184 + "resource_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
4185 + "UNIQUE INDEX(resource(",?INDEX_SIZE,")) ",
4186 + ") ENGINE=InnoDB CHARACTER SET utf8;"
4188 + case sql_query_internal(DBRef, Query) of
4190 + ?MYDEBUG("Created resources table for ~p", [VHost]),
4191 + ets:new(ets_resources_table(VHost), [named_table, set, public]),
4197 +create_msg_table(DBRef, VHost, Date) ->
4198 + TName = messages_table(VHost, Date),
4199 + Query = ["CREATE TABLE ",TName," (",
4200 + "owner_id MEDIUMINT UNSIGNED, ",
4201 + "peer_name_id MEDIUMINT UNSIGNED, ",
4202 + "peer_server_id MEDIUMINT UNSIGNED, ",
4203 + "peer_resource_id MEDIUMINT(8) UNSIGNED, ",
4204 + "direction ENUM('to', 'from'), ",
4205 + "type ENUM('chat','error','groupchat','headline','normal') NOT NULL, ",
4208 + "timestamp DOUBLE, ",
4209 + "INDEX search_i (owner_id, peer_name_id, peer_server_id, peer_resource_id), ",
4210 + "FULLTEXT (body) "
4211 + ") ENGINE=MyISAM CHARACTER SET utf8;"
4213 + case sql_query_internal(DBRef, Query) of
4214 + {updated, _MySQLRes} ->
4215 + ?MYDEBUG("Created msg table for ~p at ~p", [VHost, Date]),
4221 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4223 +% internal ets cache (users, servers, resources)
4225 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4226 +update_servers_from_db(DBRef, VHost) ->
4227 + ?INFO_MSG("Reading servers from db for ~p", [VHost]),
4228 + SQuery = ["SELECT server, server_id FROM ",servers_table(VHost),";"],
4229 + {data, Result} = sql_query_internal(DBRef, SQuery),
4230 + true = ets:delete_all_objects(ets_servers_table(VHost)),
4231 + true = ets:insert(ets_servers_table(VHost), [ {Server, Server_id} || [Server, Server_id] <- Result]).
4233 +%update_users_from_db(DBRef, VHost) ->
4234 +% ?INFO_MSG("Reading users from db for ~p", [VHost]),
4235 +% SQuery = ["SELECT username, user_id FROM ",users_table(VHost),";"],
4236 +% {data, Result} = sql_query_internal(DBRef, SQuery),
4237 +% true = ets:delete_all_objects(ets_users_table(VHost)),
4238 +% true = ets:insert(ets_users_table(VHost), [ {Username, User_id} || [Username, User_id] <- Result]).
4240 +%get_user_name(DBRef, VHost, User_id) ->
4241 +% case ets:match(ets_users_table(VHost), {'$1', User_id}) of
4242 +% [[User]] -> User;
4243 +% % this can be in clustered environment
4245 +% %update_users_from_db(DBRef, VHost),
4246 +% SQuery = ["SELECT username FROM ",users_table(VHost)," ",
4247 +% "WHERE user_id=\"",User_id,"\";"],
4248 +% {data, [[Name]]} = sql_query_internal(DBRef, SQuery),
4249 +% % cache {user, id} pair
4250 +% ets:insert(ets_users_table(VHost), {Name, User_id}),
4254 +%get_server_name(DBRef, VHost, Server_id) ->
4255 +% case ets:match(ets_servers_table(VHost), {'$1', Server_id}) of
4256 +% [[Server]] -> Server;
4257 + % this can be in clustered environment
4259 +% update_servers_from_db(DBRef, VHost),
4260 +% [[Server1]] = ets:match(ets_servers_table(VHost), {'$1', Server_id}),
4264 +get_user_id_from_db(DBRef, VHost, User) ->
4265 + SQuery = ["SELECT user_id FROM ",users_table(VHost)," ",
4266 + "WHERE username=\"",User,"\";"],
4267 + case sql_query_internal(DBRef, SQuery) of
4268 + % no such user in db
4271 + {data, [[DBId]]} ->
4272 + % cache {user, id} pair
4273 + ets:insert(ets_users_table(VHost), {User, DBId}),
4276 +get_user_id(DBRef, VHost, User) ->
4278 + case ets:match(ets_users_table(VHost), {User, '$1'}) of
4281 + case get_user_id_from_db(DBRef, VHost, User) of
4282 + % no such user in db
4284 + IQuery = ["INSERT INTO ",users_table(VHost)," ",
4285 + "SET username=\"",User,"\";"],
4286 + case sql_query_internal_silent(DBRef, IQuery) of
4288 + {ok, NewId} = get_user_id_from_db(DBRef, VHost, User),
4290 + {error, Reason} ->
4291 + % this can be in clustered environment
4292 + match = ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>),
4293 + ?ERROR_MSG("Duplicate key name for ~p", [User]),
4294 + {ok, ClID} = get_user_id_from_db(DBRef, VHost, User),
4300 + [[EtsId]] -> EtsId
4303 +get_server_id(DBRef, VHost, Server) ->
4304 + case ets:match(ets_servers_table(VHost), {Server, '$1'}) of
4306 + IQuery = ["INSERT INTO ",servers_table(VHost)," ",
4307 + "SET server=\"",Server,"\";"],
4308 + case sql_query_internal_silent(DBRef, IQuery) of
4310 + SQuery = ["SELECT server_id FROM ",servers_table(VHost)," ",
4311 + "WHERE server=\"",Server,"\";"],
4312 + {data, [[Id]]} = sql_query_internal(DBRef, SQuery),
4313 + ets:insert(ets_servers_table(VHost), {Server, Id}),
4315 + {error, Reason} ->
4316 + % this can be in clustered environment
4317 + match = ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>),
4318 + ?ERROR_MSG("Duplicate key name for ~p", [Server]),
4319 + update_servers_from_db(DBRef, VHost),
4320 + [[Id1]] = ets:match(ets_servers_table(VHost), {Server, '$1'}),
4326 +get_resource_id_from_db(DBRef, VHost, Resource) ->
4327 + SQuery = ["SELECT resource_id FROM ",resources_table(VHost)," ",
4328 + "WHERE resource=\"",binary_to_list(ejabberd_odbc:escape(iolist_to_binary(Resource))),"\";"],
4329 + case sql_query_internal(DBRef, SQuery) of
4330 + % no such resource in db
4333 + {data, [[DBId]]} ->
4334 + % cache {resource, id} pair
4335 + ets:insert(ets_resources_table(VHost), {Resource, DBId}),
4338 +get_resource_id(DBRef, VHost, Resource) ->
4340 + case ets:match(ets_resources_table(VHost), {Resource, '$1'}) of
4343 + case get_resource_id_from_db(DBRef, VHost, Resource) of
4344 + % no such resource in db
4346 + IQuery = ["INSERT INTO ",resources_table(VHost)," ",
4347 + "SET resource=\"",binary_to_list(ejabberd_odbc:escape(iolist_to_binary(Resource))),"\";"],
4348 + case sql_query_internal_silent(DBRef, IQuery) of
4350 + {ok, NewId} = get_resource_id_from_db(DBRef, VHost, Resource),
4352 + {error, Reason} ->
4353 + % this can be in clustered environment
4354 + match = ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>),
4355 + ?ERROR_MSG("Duplicate key name for ~s", [Resource]),
4356 + {ok, ClID} = get_resource_id_from_db(DBRef, VHost, Resource),
4362 + [[EtsId]] -> EtsId
4365 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4369 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4370 +sql_query_internal(DBRef, Query) ->
4371 + case sql_query_internal_silent(DBRef, Query) of
4372 + {error, Reason} ->
4373 + ?ERROR_MSG("~p while ~p", [Reason, lists:append(Query)]),
4378 +sql_query_internal_silent(DBRef, Query) ->
4379 + ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
4380 + get_result(p1_mysql_conn:fetch(DBRef, Query, self(), ?MYSQL_TIMEOUT)).
4382 +get_result({updated, MySQLRes}) ->
4383 + {updated, p1_mysql:get_result_affected_rows(MySQLRes)};
4384 +get_result({data, MySQLRes}) ->
4385 + {data, p1_mysql:get_result_rows(MySQLRes)};
4386 +get_result({error, "query timed out"}) ->
4387 + {error, "query timed out"};
4388 +get_result({error, MySQLRes}) ->
4389 + Reason = p1_mysql:get_result_reason(MySQLRes),
4391 diff --git a/src/mod_logdb_mysql5.erl b/src/mod_logdb_mysql5.erl
4392 new file mode 100644
4393 index 0000000..d1f399f
4395 +++ b/src/mod_logdb_mysql5.erl
4397 +%%%----------------------------------------------------------------------
4398 +%%% File : mod_logdb_mysql5.erl
4399 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
4400 +%%% Purpose : MySQL 5 backend for mod_logdb
4401 +%%% Version : trunk
4402 +%%% Id : $Id: mod_logdb_mysql5.erl 1360 2009-07-30 06:00:14Z malik $
4403 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
4404 +%%%----------------------------------------------------------------------
4406 +-module(mod_logdb_mysql5).
4407 +-author('o.palij@gmail.com').
4409 +-include("mod_logdb.hrl").
4410 +-include("ejabberd.hrl").
4411 +-include("jlib.hrl").
4412 +-include("logger.hrl").
4414 +-behaviour(gen_logdb).
4415 +-behaviour(gen_server).
4418 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
4420 +-export([start/2, stop/1]).
4422 +-export([log_message/2,
4424 + rebuild_stats_at/2,
4425 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
4426 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
4428 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
4431 +% gen_server call timeout
4432 +-define(CALL_TIMEOUT, 30000).
4433 +-define(MYSQL_TIMEOUT, 60000).
4434 +-define(INDEX_SIZE, integer_to_list(170)).
4435 +-define(PROCNAME, mod_logdb_mysql5).
4437 +-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
4438 + list_to_string/1, string_to_list/1,
4439 + convert_timestamp_brief/1]).
4441 +-record(state, {dbref, vhost, server, port, db, user, password}).
4443 +% replace "." with "_"
4444 +escape_vhost(VHost) -> lists:map(fun(46) -> 95;
4446 + end, binary_to_list(VHost)).
4451 + "_" ++ escape_vhost(VHost) ++ "`".
4453 +messages_table(VHost, Date) ->
4454 + prefix() ++ "messages_" ++ Date ++ suffix(VHost).
4456 +% TODO: this needs to be redone to unify view name in stored procedure and in delete_messages_at/2
4457 +view_table(VHost, Date) ->
4458 + Table = messages_table(VHost, Date),
4459 + TablewoQ = lists:sublist(Table, 2, length(Table) - 2),
4460 + lists:append(["`v_", TablewoQ, "`"]).
4462 +stats_table(VHost) ->
4463 + prefix() ++ "stats" ++ suffix(VHost).
4465 +temp_table(VHost) ->
4466 + prefix() ++ "temp" ++ suffix(VHost).
4468 +settings_table(VHost) ->
4469 + prefix() ++ "settings" ++ suffix(VHost).
4471 +users_table(VHost) ->
4472 + prefix() ++ "users" ++ suffix(VHost).
4473 +servers_table(VHost) ->
4474 + prefix() ++ "servers" ++ suffix(VHost).
4475 +resources_table(VHost) ->
4476 + prefix() ++ "resources" ++ suffix(VHost).
4478 +logmessage_name(VHost) ->
4479 + prefix() ++ "logmessage" ++ suffix(VHost).
4481 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4483 +% gen_mod callbacks
4485 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4486 +start(VHost, Opts) ->
4487 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4488 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
4491 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4492 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
4494 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4496 +% gen_server callbacks
4498 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4499 +init([VHost, Opts]) ->
4502 + Server = gen_mod:get_opt(server, Opts, fun(A) -> A end, <<"localhost">>),
4503 + Port = gen_mod:get_opt(port, Opts, fun(A) -> A end, 3306),
4504 + DB = gen_mod:get_opt(db, Opts, fun(A) -> A end, <<"logdb">>),
4505 + User = gen_mod:get_opt(user, Opts, fun(A) -> A end, <<"root">>),
4506 + Password = gen_mod:get_opt(password, Opts, fun(A) -> A end, <<"">>),
4508 + St = #state{vhost=VHost,
4509 + server=Server, port=Port, db=DB,
4510 + user=User, password=Password},
4512 + case open_mysql_connection(St) of
4514 + State = St#state{dbref=DBRef},
4515 + ok = create_internals(State),
4516 + ok = create_stats_table(State),
4517 + ok = create_settings_table(State),
4518 + ok = create_users_table(State),
4519 + ok = create_servers_table(State),
4520 + ok = create_resources_table(State),
4521 + erlang:monitor(process, DBRef),
4523 + {error, Reason} ->
4524 + ?ERROR_MSG("MySQL connection failed: ~p~n", [Reason]),
4525 + {stop, db_connection_failed}
4528 +open_mysql_connection(#state{server=Server, port=Port, db=DB,
4529 + user=DBUser, password=Password} = _State) ->
4530 + LogFun = fun(debug, _Format, _Argument) ->
4531 + %?MYDEBUG(Format, Argument);
4533 + (error, Format, Argument) ->
4534 + ?ERROR_MSG(Format, Argument);
4535 + (Level, Format, Argument) ->
4536 + ?MYDEBUG("MySQL (~p)~n", [Level]),
4537 + ?MYDEBUG(Format, Argument)
4539 + ?INFO_MSG("Opening mysql connection ~s@~s:~p/~s", [DBUser, Server, Port, DB]),
4540 + p1_mysql_conn:start(binary_to_list(Server), Port,
4541 + binary_to_list(DBUser), binary_to_list(Password),
4542 + binary_to_list(DB), LogFun).
4544 +close_mysql_connection(DBRef) ->
4545 + ?MYDEBUG("Closing ~p mysql connection", [DBRef]),
4546 + catch p1_mysql_conn:stop(DBRef).
4548 +handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4549 + Reply = rebuild_stats_at_int(DBRef, VHost, Date),
4550 + {reply, Reply, State};
4551 +handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
4552 + {reply, error, State};
4553 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4554 + Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
4555 + ["\"",Timestamp,"\"",","]
4558 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
4560 + Query = ["DELETE FROM ",messages_table(VHost, Date)," ",
4561 + "WHERE timestamp IN (", Temp1],
4564 + case sql_query_internal(DBRef, Query) of
4566 + ?MYDEBUG("Aff=~p", [Aff]),
4567 + rebuild_stats_at_int(DBRef, VHost, Date);
4571 + {reply, Reply, State};
4572 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4573 + ok = delete_all_messages_by_user_at_int(DBRef, User, VHost, Date),
4574 + ok = delete_stats_by_user_at_int(DBRef, User, VHost, Date),
4575 + {reply, ok, State};
4576 +handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4578 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Date),";"]),
4579 + TQuery = ["DELETE FROM ",stats_table(VHost)," "
4580 + "WHERE at=\"",Date,"\";"],
4581 + {updated, _} = sql_query_internal(DBRef, TQuery),
4582 + VQuery = ["DROP VIEW IF EXISTS ",view_table(VHost,Date),";"],
4583 + {updated, _} = sql_query_internal(DBRef, VQuery),
4587 + case catch apply(Fun, []) of
4593 + {reply, Reply, State};
4594 +handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4595 + SName = stats_table(VHost),
4596 + Query = ["SELECT at, sum(count) ",
4597 + "FROM ",SName," ",
4599 + "ORDER BY DATE(at) DESC;"
4602 + case sql_query_internal(DBRef, Query) of
4604 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
4605 + {error, Reason} ->
4606 + % TODO: Duplicate error message ?
4609 + {reply, Reply, State};
4610 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4611 + SName = stats_table(VHost),
4612 + Query = ["SELECT username, sum(count) as allcount ",
4613 + "FROM ",SName," ",
4614 + "JOIN ",users_table(VHost)," ON owner_id=user_id "
4615 + "WHERE at=\"",Date,"\" ",
4616 + "GROUP BY username ",
4617 + "ORDER BY allcount DESC;"
4620 + case sql_query_internal(DBRef, Query) of
4622 + {ok, [ {User, list_to_integer(Count)} || [User, Count] <- Result ]};
4623 + {error, Reason} ->
4626 + {reply, Reply, State};
4627 +handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4628 + {reply, get_user_stats_int(DBRef, User, VHost), State};
4629 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4630 + Query = ["SELECT peer_name,",
4638 + "FROM ",view_table(VHost, Date)," "
4639 + "WHERE owner_name=\"",User,"\";"],
4641 + case sql_query_internal(DBRef, Query) of
4643 + Fun = fun([Peer_name, Peer_server, Peer_resource,
4648 + #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
4649 + direction=list_to_atom(Direction),
4651 + subject=Subject, body=Body,
4652 + timestamp=Timestamp}
4654 + {ok, lists:map(Fun, Result)};
4655 + {error, Reason} ->
4658 + {reply, Reply, State};
4659 +handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4660 + SName = stats_table(VHost),
4661 + Query = ["SELECT at ",
4662 + "FROM ",SName," ",
4664 + "ORDER BY DATE(at) DESC;"
4667 + case sql_query_internal(DBRef, Query) of
4669 + [ Date || [Date] <- Result ];
4670 + {error, Reason} ->
4673 + {reply, Reply, State};
4674 +handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4675 + Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
4676 + "FROM ",settings_table(VHost)," ",
4677 + "JOIN ",users_table(VHost)," ON user_id=owner_id;"],
4679 + case sql_query_internal(DBRef, Query) of
4681 + {ok, lists:map(fun([Owner, DoLogDef, DoLogL, DoNotLogL]) ->
4682 + #user_settings{owner_name=Owner,
4683 + dolog_default=list_to_bool(DoLogDef),
4684 + dolog_list=string_to_list(DoLogL),
4685 + donotlog_list=string_to_list(DoNotLogL)
4691 + {reply, Reply, State};
4692 +handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
4693 + Query = ["SELECT dolog_default,dolog_list,donotlog_list FROM ",settings_table(VHost)," ",
4694 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
4696 + case sql_query_internal(DBRef, Query) of
4699 + {data, [[Owner, DoLogDef, DoLogL, DoNotLogL]]} ->
4700 + {ok, #user_settings{owner_name=Owner,
4701 + dolog_default=list_to_bool(DoLogDef),
4702 + dolog_list=string_to_list(DoLogL),
4703 + donotlog_list=string_to_list(DoNotLogL)}};
4707 + {reply, Reply, State};
4708 +handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
4709 + dolog_list=DoLogL,
4710 + donotlog_list=DoNotLogL}},
4711 + _From, #state{dbref=DBRef, vhost=VHost} = State) ->
4712 + User_id = get_user_id(DBRef, VHost, User),
4713 + Query = ["UPDATE ",settings_table(VHost)," ",
4714 + "SET dolog_default=",bool_to_list(DoLogDef),", ",
4715 + "dolog_list='",list_to_string(DoLogL),"', ",
4716 + "donotlog_list='",list_to_string(DoNotLogL),"' ",
4717 + "WHERE owner_id=",User_id,";"],
4720 + case sql_query_internal(DBRef, Query) of
4722 + IQuery = ["INSERT INTO ",settings_table(VHost)," ",
4723 + "(owner_id, dolog_default, dolog_list, donotlog_list) ",
4725 + "(",User_id,",",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
4726 + case sql_query_internal_silent(DBRef, IQuery) of
4728 + ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
4730 + {error, Reason} ->
4731 + case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>) of
4736 + ?ERROR_MSG("Failed setup user ~p@~p: ~p", [User, VHost, Reason]),
4741 + ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
4746 + {reply, Reply, State};
4747 +handle_call({stop}, _From, #state{vhost=VHost}=State) ->
4748 + ?MYDEBUG("Stoping mysql5 backend for ~p", [VHost]),
4749 + {stop, normal, ok, State};
4750 +handle_call(Msg, _From, State) ->
4751 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
4754 +handle_cast({log_message, Msg}, #state{dbref=DBRef, vhost=VHost}=State) ->
4756 + Date = convert_timestamp_brief(Msg#msg.timestamp),
4757 + TableName = messages_table(VHost, Date),
4759 + Query = [ "CALL ",logmessage_name(VHost)," "
4760 + "('", TableName, "',",
4762 + "'", binary_to_list(Msg#msg.owner_name), "',",
4763 + "'", binary_to_list(Msg#msg.peer_name), "',",
4764 + "'", binary_to_list(Msg#msg.peer_server), "',",
4765 + "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.peer_resource) ), "',",
4766 + "'", atom_to_list(Msg#msg.direction), "',",
4767 + "'", binary_to_list(Msg#msg.type), "',",
4768 + "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.subject) ), "',",
4769 + "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.body) ), "',",
4770 + "'", Msg#msg.timestamp, "');"],
4772 + case sql_query_internal(DBRef, Query) of
4774 + ?MYDEBUG("Logged ok for ~s, peer: ~s", [ [Msg#msg.owner_name, <<"@">>, VHost],
4775 + [Msg#msg.peer_name, <<"@">>, Msg#msg.peer_server] ]),
4777 + {error, _Reason} ->
4783 +handle_cast({rebuild_stats}, State) ->
4784 + rebuild_all_stats_int(State),
4786 +handle_cast({drop_user, User}, #state{vhost=VHost} = State) ->
4788 + {ok, DBRef} = open_mysql_connection(State),
4789 + {ok, Dates} = get_user_stats_int(DBRef, User, VHost),
4790 + MDResult = lists:map(fun({Date, _}) ->
4791 + delete_all_messages_by_user_at_int(DBRef, User, VHost, Date)
4793 + StDResult = delete_all_stats_by_user_int(DBRef, User, VHost),
4794 + SDResult = delete_user_settings_int(DBRef, User, VHost),
4795 + case lists:all(fun(Result) when Result == ok ->
4797 + (Result) when Result == error ->
4799 + end, lists:append([MDResult, [StDResult], [SDResult]])) of
4801 + ?INFO_MSG("Removed ~s@~s", [User, VHost]);
4803 + ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost])
4805 + close_mysql_connection(DBRef)
4809 +handle_cast(Msg, State) ->
4810 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
4813 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
4814 + {stop, connection_dropped, State};
4815 +handle_info(Info, State) ->
4816 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
4819 +terminate(_Reason, #state{dbref=DBRef}=_State) ->
4820 + close_mysql_connection(DBRef),
4823 +code_change(_OldVsn, State, _Extra) ->
4826 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4828 +% gen_logdb callbacks
4830 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4831 +log_message(VHost, Msg) ->
4832 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4833 + gen_server:cast(Proc, {log_message, Msg}).
4834 +rebuild_stats(VHost) ->
4835 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4836 + gen_server:cast(Proc, {rebuild_stats}).
4837 +rebuild_stats_at(VHost, Date) ->
4838 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4839 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
4840 +delete_messages_by_user_at(VHost, Msgs, Date) ->
4841 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4842 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
4843 +delete_all_messages_by_user_at(User, VHost, Date) ->
4844 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4845 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
4846 +delete_messages_at(VHost, Date) ->
4847 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4848 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
4849 +get_vhost_stats(VHost) ->
4850 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4851 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
4852 +get_vhost_stats_at(VHost, Date) ->
4853 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4854 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
4855 +get_user_stats(User, VHost) ->
4856 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4857 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
4858 +get_user_messages_at(User, VHost, Date) ->
4859 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4860 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
4861 +get_dates(VHost) ->
4862 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4863 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
4864 +get_users_settings(VHost) ->
4865 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4866 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
4867 +get_user_settings(User, VHost) ->
4868 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4869 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
4870 +set_user_settings(User, VHost, Set) ->
4871 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4872 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
4873 +drop_user(User, VHost) ->
4874 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
4875 + gen_server:cast(Proc, {drop_user, User}).
4877 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4881 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4882 +get_dates_int(DBRef, VHost) ->
4883 + case sql_query_internal(DBRef, ["SHOW TABLES"]) of
4885 + lists:foldl(fun([Table], Dates) ->
4886 + Reg = lists:sublist(prefix(),2,length(prefix())) ++ ".*" ++ escape_vhost(VHost),
4887 + case re:run(Table, Reg) of
4888 + {match, [{1, _}]} ->
4889 + case re:run(Table,"[0-9]+-[0-9]+-[0-9]+") of
4890 + {match, [{S, E}]} ->
4891 + lists:append(Dates, [lists:sublist(Table,S,E)]);
4903 +rebuild_all_stats_int(#state{vhost=VHost}=State) ->
4905 + {ok, DBRef} = open_mysql_connection(State),
4906 + ok = delete_nonexistent_stats(DBRef, VHost),
4907 + case lists:filter(fun(Date) ->
4908 + case catch rebuild_stats_at_int(DBRef, VHost, Date) of
4911 + {'EXIT', _} -> true
4913 + end, get_dates_int(DBRef, VHost)) of
4916 + ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]),
4919 + close_mysql_connection(DBRef)
4923 +rebuild_stats_at_int(DBRef, VHost, Date) ->
4924 + TempTable = temp_table(VHost),
4926 + Table = messages_table(VHost, Date),
4927 + STable = stats_table(VHost),
4929 + DQuery = [ "DELETE FROM ",STable," ",
4930 + "WHERE at='",Date,"';"],
4932 + ok = create_temp_table(DBRef, TempTable),
4933 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," WRITE, ",TempTable," WRITE;"]),
4934 + SQuery = ["INSERT INTO ",TempTable," ",
4935 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
4936 + "SELECT owner_id,peer_name_id,peer_server_id,\"",Date,"\",count(*) ",
4937 + "FROM ",Table," WHERE ext is NULL GROUP BY owner_id,peer_name_id,peer_server_id;"],
4938 + case sql_query_internal(DBRef, SQuery) of
4940 + Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]),
4942 + {data, [["0"]]} ->
4943 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]),
4944 + sql_query_internal(DBRef, ["UNLOCK TABLES;"]),
4945 + {updated, _} = sql_query_internal(DBRef, ["DROP VIEW IF EXISTS ",view_table(VHost,Date),";"]),
4946 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE, ",TempTable," WRITE;"]),
4947 + {updated, _} = sql_query_internal(DBRef, DQuery),
4950 + ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]),
4954 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE, ",TempTable," WRITE;"]),
4955 + {updated, _} = sql_query_internal(DBRef, DQuery),
4956 + SQuery1 = ["INSERT INTO ",STable," ",
4957 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
4958 + "SELECT owner_id,peer_name_id,peer_server_id,at,count ",
4959 + "FROM ",TempTable,";"],
4960 + case sql_query_internal(DBRef, SQuery1) of
4961 + {updated, _} -> ok;
4962 + {error, _} -> error
4964 + {error, _} -> error
4968 + case catch apply(Fun, []) of
4970 + ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
4974 + {'EXIT', Reason} ->
4975 + ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]),
4978 + sql_query_internal(DBRef, ["UNLOCK TABLES;"]),
4979 + sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]),
4982 +delete_nonexistent_stats(DBRef, VHost) ->
4983 + Dates = get_dates_int(DBRef, VHost),
4984 + STable = stats_table(VHost),
4986 + Temp = lists:flatmap(fun(Date) ->
4987 + ["\"",Date,"\"",","]
4993 + % replace last "," with ");"
4994 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
4995 + Query = ["DELETE FROM ",STable," ",
4996 + "WHERE at NOT IN (", Temp1],
4997 + case sql_query_internal(DBRef, Query) of
5005 +get_user_stats_int(DBRef, User, VHost) ->
5006 + SName = stats_table(VHost),
5007 + UName = users_table(VHost),
5008 + Query = ["SELECT stats.at, sum(stats.count) ",
5009 + "FROM ",UName," AS users ",
5010 + "JOIN ",SName," AS stats ON owner_id=user_id "
5011 + "WHERE users.username=\"",User,"\" ",
5012 + "GROUP BY stats.at "
5013 + "ORDER BY DATE(stats.at) DESC;"
5015 + case sql_query_internal(DBRef, Query) of
5017 + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
5018 + {error, Result} ->
5022 +delete_all_messages_by_user_at_int(DBRef, User, VHost, Date) ->
5023 + DQuery = ["DELETE FROM ",messages_table(VHost, Date)," ",
5024 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
5025 + case sql_query_internal(DBRef, DQuery) of
5027 + ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]),
5033 +delete_all_stats_by_user_int(DBRef, User, VHost) ->
5034 + SQuery = ["DELETE FROM ",stats_table(VHost)," ",
5035 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
5036 + case sql_query_internal(DBRef, SQuery) of
5038 + ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]),
5040 + {error, _} -> error
5043 +delete_stats_by_user_at_int(DBRef, User, VHost, Date) ->
5044 + SQuery = ["DELETE FROM ",stats_table(VHost)," ",
5045 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\") ",
5046 + "AND at=\"",Date,"\";"],
5047 + case sql_query_internal(DBRef, SQuery) of
5049 + ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]),
5051 + {error, _} -> error
5054 +delete_user_settings_int(DBRef, User, VHost) ->
5055 + Query = ["DELETE FROM ",settings_table(VHost)," ",
5056 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
5057 + case sql_query_internal(DBRef, Query) of
5059 + ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]),
5061 + {error, Reason} ->
5062 + ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]),
5066 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5070 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5071 +create_temp_table(DBRef, Name) ->
5072 + Query = ["CREATE TABLE ",Name," (",
5073 + "owner_id MEDIUMINT UNSIGNED, ",
5074 + "peer_name_id MEDIUMINT UNSIGNED, ",
5075 + "peer_server_id MEDIUMINT UNSIGNED, ",
5076 + "at VARCHAR(11), ",
5078 + ") ENGINE=MyISAM CHARACTER SET utf8;"
5080 + case sql_query_internal(DBRef, Query) of
5081 + {updated, _} -> ok;
5082 + {error, _Reason} -> error
5085 +create_stats_table(#state{dbref=DBRef, vhost=VHost}=State) ->
5086 + SName = stats_table(VHost),
5087 + Query = ["CREATE TABLE ",SName," (",
5088 + "owner_id MEDIUMINT UNSIGNED, ",
5089 + "peer_name_id MEDIUMINT UNSIGNED, ",
5090 + "peer_server_id MEDIUMINT UNSIGNED, ",
5091 + "at VARCHAR(11), ",
5092 + "count INT(11), ",
5093 + "ext INTEGER DEFAULT NULL, "
5094 + "INDEX ext_i (ext), "
5095 + "INDEX(owner_id,peer_name_id,peer_server_id), ",
5097 + ") ENGINE=MyISAM CHARACTER SET utf8;"
5099 + case sql_query_internal_silent(DBRef, Query) of
5101 + ?MYDEBUG("Created stats table for ~p", [VHost]),
5102 + rebuild_all_stats_int(State),
5104 + {error, Reason} ->
5105 + case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#42S01">>) of
5107 + ?MYDEBUG("Stats table for ~p already exists", [VHost]),
5108 + CheckQuery = ["SHOW COLUMNS FROM ",SName," LIKE 'peer_%_id';"],
5109 + case sql_query_internal(DBRef, CheckQuery) of
5110 + {data, Elems} when length(Elems) == 2 ->
5111 + ?MYDEBUG("Stats table structure is ok", []),
5114 + ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []),
5115 + case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of
5117 + ?INFO_MSG("Successfully dropped ~p", [SName]);
5119 + ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName])
5124 + ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
5129 +create_settings_table(#state{dbref=DBRef, vhost=VHost}) ->
5130 + SName = settings_table(VHost),
5131 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
5132 + "owner_id MEDIUMINT UNSIGNED PRIMARY KEY, ",
5133 + "dolog_default TINYINT(1) NOT NULL DEFAULT 1, ",
5134 + "dolog_list TEXT, ",
5135 + "donotlog_list TEXT ",
5136 + ") ENGINE=InnoDB CHARACTER SET utf8;"
5138 + case sql_query_internal(DBRef, Query) of
5140 + ?MYDEBUG("Created settings table for ~p", [VHost]),
5146 +create_users_table(#state{dbref=DBRef, vhost=VHost}) ->
5147 + SName = users_table(VHost),
5148 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
5149 + "username TEXT NOT NULL, ",
5150 + "user_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
5151 + "UNIQUE INDEX(username(",?INDEX_SIZE,")) ",
5152 + ") ENGINE=InnoDB CHARACTER SET utf8;"
5154 + case sql_query_internal(DBRef, Query) of
5156 + ?MYDEBUG("Created users table for ~p", [VHost]),
5162 +create_servers_table(#state{dbref=DBRef, vhost=VHost}) ->
5163 + SName = servers_table(VHost),
5164 + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
5165 + "server TEXT NOT NULL, ",
5166 + "server_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
5167 + "UNIQUE INDEX(server(",?INDEX_SIZE,")) ",
5168 + ") ENGINE=InnoDB CHARACTER SET utf8;"
5170 + case sql_query_internal(DBRef, Query) of
5172 + ?MYDEBUG("Created servers table for ~p", [VHost]),
5178 +create_resources_table(#state{dbref=DBRef, vhost=VHost}) ->
5179 + RName = resources_table(VHost),
5180 + Query = ["CREATE TABLE IF NOT EXISTS ",RName," (",
5181 + "resource TEXT NOT NULL, ",
5182 + "resource_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
5183 + "UNIQUE INDEX(resource(",?INDEX_SIZE,")) ",
5184 + ") ENGINE=InnoDB CHARACTER SET utf8;"
5186 + case sql_query_internal(DBRef, Query) of
5188 + ?MYDEBUG("Created resources table for ~p", [VHost]),
5194 +create_internals(#state{dbref=DBRef, vhost=VHost}) ->
5195 + sql_query_internal(DBRef, ["DROP PROCEDURE IF EXISTS ",logmessage_name(VHost),";"]),
5196 + case sql_query_internal(DBRef, [get_logmessage(VHost)]) of
5198 + ?MYDEBUG("Created logmessage for ~p", [VHost]),
5204 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5208 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5209 +sql_query_internal(DBRef, Query) ->
5210 + case sql_query_internal_silent(DBRef, Query) of
5211 + {error, Reason} ->
5212 + ?ERROR_MSG("~p while ~p", [Reason, lists:append(Query)]),
5217 +sql_query_internal_silent(DBRef, Query) ->
5218 + ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
5219 + get_result(p1_mysql_conn:fetch(DBRef, Query, self(), ?MYSQL_TIMEOUT)).
5221 +get_result({updated, MySQLRes}) ->
5222 + {updated, p1_mysql:get_result_affected_rows(MySQLRes)};
5223 +get_result({data, MySQLRes}) ->
5224 + {data, p1_mysql:get_result_rows(MySQLRes)};
5225 +get_result({error, "query timed out"}) ->
5226 + {error, "query timed out"};
5227 +get_result({error, MySQLRes}) ->
5228 + Reason = p1_mysql:get_result_reason(MySQLRes),
5231 +get_user_id(DBRef, VHost, User) ->
5232 + SQuery = ["SELECT user_id FROM ",users_table(VHost)," ",
5233 + "WHERE username=\"",User,"\";"],
5234 + case sql_query_internal(DBRef, SQuery) of
5236 + IQuery = ["INSERT INTO ",users_table(VHost)," ",
5237 + "SET username=\"",User,"\";"],
5238 + case sql_query_internal_silent(DBRef, IQuery) of
5240 + {data, [[DBIdNew]]} = sql_query_internal(DBRef, SQuery),
5242 + {error, Reason} ->
5243 + % this can be in clustered environment
5244 + match = ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>),
5245 + ?ERROR_MSG("Duplicate key name for ~p", [User]),
5246 + {data, [[ClID]]} = sql_query_internal(DBRef, SQuery),
5249 + {data, [[DBId]]} ->
5253 +get_logmessage(VHost) ->
5254 + UName = users_table(VHost),
5255 + SName = servers_table(VHost),
5256 + RName = resources_table(VHost),
5257 + StName = stats_table(VHost),
5259 +CREATE PROCEDURE ~s(tablename TEXT, atdate TEXT, owner TEXT, peer_name TEXT, peer_server TEXT, peer_resource TEXT, mdirection VARCHAR(4), mtype VARCHAR(10), msubject TEXT, mbody TEXT, mtimestamp DOUBLE)
5261 + DECLARE ownerID MEDIUMINT UNSIGNED;
5262 + DECLARE peer_nameID MEDIUMINT UNSIGNED;
5263 + DECLARE peer_serverID MEDIUMINT UNSIGNED;
5264 + DECLARE peer_resourceID MEDIUMINT UNSIGNED;
5265 + DECLARE Vmtype VARCHAR(10);
5266 + DECLARE Vmtimestamp DOUBLE;
5267 + DECLARE Vmdirection VARCHAR(4);
5268 + DECLARE Vmbody TEXT;
5269 + DECLARE Vmsubject TEXT;
5272 + DECLARE viewname TEXT;
5273 + DECLARE notable INT;
5274 + DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' SET @notable = 1;
5277 + SET @ownerID = NULL;
5278 + SET @peer_nameID = NULL;
5279 + SET @peer_serverID = NULL;
5280 + SET @peer_resourceID = NULL;
5282 + SET @Vmtype = mtype;
5283 + SET @Vmtimestamp = mtimestamp;
5284 + SET @Vmdirection = mdirection;
5285 + SET @Vmbody = mbody;
5286 + SET @Vmsubject = msubject;
5288 + SELECT user_id INTO @ownerID FROM ~s WHERE username=owner;
5289 + IF @ownerID IS NULL THEN
5290 + INSERT INTO ~s SET username=owner;
5291 + SET @ownerID = LAST_INSERT_ID();
5294 + SELECT user_id INTO @peer_nameID FROM ~s WHERE username=peer_name;
5295 + IF @peer_nameID IS NULL THEN
5296 + INSERT INTO ~s SET username=peer_name;
5297 + SET @peer_nameID = LAST_INSERT_ID();
5300 + SELECT server_id INTO @peer_serverID FROM ~s WHERE server=peer_server;
5301 + IF @peer_serverID IS NULL THEN
5302 + INSERT INTO ~s SET server=peer_server;
5303 + SET @peer_serverID = LAST_INSERT_ID();
5306 + SELECT resource_id INTO @peer_resourceID FROM ~s WHERE resource=peer_resource;
5307 + IF @peer_resourceID IS NULL THEN
5308 + INSERT INTO ~s SET resource=peer_resource;
5309 + SET @peer_resourceID = LAST_INSERT_ID();
5312 + SET @iq = CONCAT(\"INSERT INTO \",tablename,\" (owner_id, peer_name_id, peer_server_id, peer_resource_id, direction, type, subject, body, timestamp) VALUES (@ownerID,@peer_nameID,@peer_serverID,@peer_resourceID,@Vmdirection,@Vmtype,@Vmsubject,@Vmbody,@Vmtimestamp);\");
5313 + PREPARE insertmsg FROM @iq;
5315 + IF @notable = 1 THEN
5316 + SET @cq = CONCAT(\"CREATE TABLE \",tablename,\" (
5317 + owner_id MEDIUMINT UNSIGNED NOT NULL,
5318 + peer_name_id MEDIUMINT UNSIGNED NOT NULL,
5319 + peer_server_id MEDIUMINT UNSIGNED NOT NULL,
5320 + peer_resource_id MEDIUMINT(8) UNSIGNED NOT NULL,
5321 + direction ENUM('to', 'from') NOT NULL,
5322 + type ENUM('chat','error','groupchat','headline','normal') NOT NULL,
5325 + timestamp DOUBLE NOT NULL,
5326 + ext INTEGER DEFAULT NULL,
5327 + INDEX search_i (owner_id, peer_name_id, peer_server_id, peer_resource_id),
5328 + INDEX ext_i (ext),
5332 + CHARACTER SET utf8;\");
5333 + PREPARE createtable FROM @cq;
5334 + EXECUTE createtable;
5335 + DEALLOCATE PREPARE createtable;
5337 + SET @viewname = CONCAT(\"`v_\", TRIM(BOTH '`' FROM tablename), \"`\");
5338 + SET @cq = CONCAT(\"CREATE OR REPLACE VIEW \",@viewname,\" AS
5339 + SELECT owner.username AS owner_name,
5340 + peer.username AS peer_name,
5341 + servers.server AS peer_server,
5342 + resources.resource AS peer_resource,
5343 + messages.direction,
5347 + messages.timestamp
5353 + \", tablename,\" messages
5355 + owner.user_id=messages.owner_id and
5356 + peer.user_id=messages.peer_name_id and
5357 + servers.server_id=messages.peer_server_id and
5358 + resources.resource_id=messages.peer_resource_id
5359 + ORDER BY messages.timestamp;\");
5360 + PREPARE createview FROM @cq;
5361 + EXECUTE createview;
5362 + DEALLOCATE PREPARE createview;
5365 + PREPARE insertmsg FROM @iq;
5366 + EXECUTE insertmsg;
5367 + ELSEIF @notable = 0 THEN
5368 + EXECUTE insertmsg;
5371 + DEALLOCATE PREPARE insertmsg;
5373 + IF @notable = 0 THEN
5374 + UPDATE ~s SET count=count+1 WHERE owner_id=@ownerID AND peer_name_id=@peer_nameID AND peer_server_id=@peer_serverID AND at=atdate;
5375 + IF ROW_COUNT() = 0 THEN
5376 + INSERT INTO ~s (owner_id, peer_name_id, peer_server_id, at, count) VALUES (@ownerID, @peer_nameID, @peer_serverID, atdate, 1);
5379 +END;", [logmessage_name(VHost),UName,UName,UName,UName,SName,SName,RName,RName,UName,UName,SName,RName,StName,StName]).
5380 diff --git a/src/mod_logdb_pgsql.erl b/src/mod_logdb_pgsql.erl
5381 new file mode 100644
5382 index 0000000..3c2ae95
5384 +++ b/src/mod_logdb_pgsql.erl
5386 +% {ok, DBRef} = pgsql:connect([{host, "127.0.0.1"}, {database, "logdb"}, {user, "logdb"}, {password, "logdb"}, {port, 5432}, {as_binary, true}]).
5388 +% pgsql:squery(DBRef, "CREATE TABLE test.\"logdb_stats_test\" (owner_id INTEGER, peer_name_id INTEGER, peer_server_id INTEGER, at VARCHAR(20), count integer);" ).
5389 +%%%----------------------------------------------------------------------
5390 +%%% File : mod_logdb_pgsql.erl
5391 +%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
5392 +%%% Purpose : Posgresql backend for mod_logdb
5393 +%%% Version : trunk
5394 +%%% Id : $Id: mod_logdb_pgsql.erl 1360 2009-07-30 06:00:14Z malik $
5395 +%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
5396 +%%%----------------------------------------------------------------------
5398 +-module(mod_logdb_pgsql).
5399 +-author('o.palij@gmail.com').
5401 +-include("mod_logdb.hrl").
5402 +-include("ejabberd.hrl").
5403 +-include("jlib.hrl").
5404 +-include("logger.hrl").
5406 +-behaviour(gen_logdb).
5407 +-behaviour(gen_server).
5410 +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
5412 +-export([start/2, stop/1]).
5414 +-export([log_message/2,
5416 + rebuild_stats_at/2,
5417 + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
5418 + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
5420 + get_users_settings/1, get_user_settings/2, set_user_settings/3,
5423 +-export([view_table/3]).
5425 +% gen_server call timeout
5426 +-define(CALL_TIMEOUT, 30000).
5427 +-define(PGSQL_TIMEOUT, 60000).
5428 +-define(PROCNAME, mod_logdb_pgsql).
5430 +-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
5431 + list_to_string/1, string_to_list/1,
5432 + convert_timestamp_brief/1]).
5434 +-record(state, {dbref, vhost, server, port, db, user, password, schema}).
5436 +% replace "." with "_"
5437 +escape_vhost(VHost) -> lists:map(fun(46) -> 95;
5439 + end, binary_to_list(VHost)).
5442 + Schema ++ ".\"" ++ "logdb_".
5445 + "_" ++ escape_vhost(VHost) ++ "\"".
5447 +messages_table(VHost, Schema, Date) ->
5448 + prefix(Schema) ++ "messages_" ++ Date ++ suffix(VHost).
5450 +view_table(VHost, Schema, Date) ->
5451 + Table = messages_table(VHost, Schema, Date),
5452 + TablewoS = lists:sublist(Table, length(Schema) + 3, length(Table) - length(Schema) - 3),
5453 + lists:append([Schema, ".\"v_", TablewoS, "\""]).
5455 +stats_table(VHost, Schema) ->
5456 + prefix(Schema) ++ "stats" ++ suffix(VHost).
5458 +temp_table(VHost, Schema) ->
5459 + prefix(Schema) ++ "temp" ++ suffix(VHost).
5461 +settings_table(VHost, Schema) ->
5462 + prefix(Schema) ++ "settings" ++ suffix(VHost).
5464 +users_table(VHost, Schema) ->
5465 + prefix(Schema) ++ "users" ++ suffix(VHost).
5466 +servers_table(VHost, Schema) ->
5467 + prefix(Schema) ++ "servers" ++ suffix(VHost).
5468 +resources_table(VHost, Schema) ->
5469 + prefix(Schema) ++ "resources" ++ suffix(VHost).
5471 +logmessage_name(VHost, Schema) ->
5472 + prefix(Schema) ++ "logmessage" ++ suffix(VHost).
5474 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5476 +% gen_mod callbacks
5478 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5479 +start(VHost, Opts) ->
5480 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5481 + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
5484 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5485 + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
5487 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5489 +% gen_server callbacks
5491 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5492 +init([VHost, Opts]) ->
5493 + Server = gen_mod:get_opt(server, Opts, fun(A) -> A end, <<"localhost">>),
5494 + DB = gen_mod:get_opt(db, Opts, fun(A) -> A end, <<"ejabberd_logdb">>),
5495 + User = gen_mod:get_opt(user, Opts, fun(A) -> A end, <<"root">>),
5496 + Port = gen_mod:get_opt(port, Opts, fun(A) -> A end, 5432),
5497 + Password = gen_mod:get_opt(password, Opts, fun(A) -> A end, <<"">>),
5498 + Schema = binary_to_list(gen_mod:get_opt(schema, Opts, fun(A) -> A end, <<"public">>)),
5500 + ?MYDEBUG("Starting pgsql backend for ~s", [VHost]),
5502 + St = #state{vhost=VHost,
5503 + server=Server, port=Port, db=DB,
5504 + user=User, password=Password,
5507 + case open_pgsql_connection(St) of
5509 + State = St#state{dbref=DBRef},
5510 + ok = create_internals(State),
5511 + ok = create_stats_table(State),
5512 + ok = create_settings_table(State),
5513 + ok = create_users_table(State),
5514 + ok = create_servers_table(State),
5515 + ok = create_resources_table(State),
5516 + erlang:monitor(process, DBRef),
5518 + % this does not work
5519 + {error, Reason} ->
5520 + ?ERROR_MSG("PgSQL connection failed: ~p~n", [Reason]),
5521 + {stop, db_connection_failed};
5522 + % and this too, becouse pgsql_conn do exit() which can not be catched
5524 + ?ERROR_MSG("Rez: ~p~n", [Rez]),
5525 + {stop, db_connection_failed}
5528 +open_pgsql_connection(#state{server=Server, port=Port, db=DB, schema=Schema,
5529 + user=User, password=Password} = _State) ->
5530 + ?INFO_MSG("Opening pgsql connection ~s@~s:~p/~s", [User, Server, Port, DB]),
5531 + {ok, DBRef} = pgsql:connect(Server, DB, User, Password, Port),
5532 + {updated, _} = sql_query_internal(DBRef, ["SET SEARCH_PATH TO ",Schema,";"]),
5535 +close_pgsql_connection(DBRef) ->
5536 + ?MYDEBUG("Closing ~p pgsql connection", [DBRef]),
5537 + pgsql:terminate(DBRef).
5539 +handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5540 + Date = convert_timestamp_brief(Msg#msg.timestamp),
5541 + TableName = messages_table(VHost, Schema, Date),
5542 + ViewName = view_table(VHost, Schema, Date),
5544 + Query = [ "SELECT ", logmessage_name(VHost, Schema)," "
5545 + "('", TableName, "',",
5546 + "'", ViewName, "',",
5548 + "'", binary_to_list(Msg#msg.owner_name), "',",
5549 + "'", binary_to_list(Msg#msg.peer_name), "',",
5550 + "'", binary_to_list(Msg#msg.peer_server), "',",
5551 + "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.peer_resource) ), "',",
5552 + "'", atom_to_list(Msg#msg.direction), "',",
5553 + "'", binary_to_list(Msg#msg.type), "',",
5554 + "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.subject) ), "',",
5555 + "'", binary_to_list( ejabberd_odbc:escape(Msg#msg.body) ), "',",
5556 + "'", Msg#msg.timestamp, "');"],
5558 + case sql_query_internal_silent(DBRef, Query) of
5559 + % TODO: change this
5560 + {data, [{"0"}]} ->
5561 + ?MYDEBUG("Logged ok for ~s, peer: ~s", [ [Msg#msg.owner_name, <<"@">>, VHost],
5562 + [Msg#msg.peer_name, <<"@">>, Msg#msg.peer_server] ]),
5564 + {error, _Reason} ->
5567 + {reply, ok, State};
5568 +handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5569 + Reply = rebuild_stats_at_int(DBRef, VHost, Schema, Date),
5570 + {reply, Reply, State};
5571 +handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
5572 + {reply, error, State};
5573 +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5574 + Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
5575 + ["'",Timestamp,"'",","]
5578 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
5580 + Query = ["DELETE FROM ",messages_table(VHost, Schema, Date)," ",
5581 + "WHERE timestamp IN (", Temp1],
5584 + case sql_query_internal(DBRef, Query) of
5586 + rebuild_stats_at_int(DBRef, VHost, Schema, Date);
5590 + {reply, Reply, State};
5591 +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5592 + ok = delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date),
5593 + ok = delete_stats_by_user_at_int(DBRef, Schema, User, VHost, Date),
5594 + {reply, ok, State};
5595 +handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5596 + {updated, _} = sql_query_internal(DBRef, ["DROP VIEW ",view_table(VHost, Schema, Date),";"]),
5598 + case sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Schema, Date)," CASCADE;"]) of
5600 + Query = ["DELETE FROM ",stats_table(VHost, Schema)," "
5601 + "WHERE at='",Date,"';"],
5602 + case sql_query_internal(DBRef, Query) of
5611 + {reply, Reply, State};
5612 +handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5613 + SName = stats_table(VHost, Schema),
5614 + Query = ["SELECT at, sum(count) ",
5615 + "FROM ",SName," ",
5617 + "ORDER BY DATE(at) DESC;"
5620 + case sql_query_internal(DBRef, Query) of
5622 + {ok, [ {Date, list_to_integer(Count)} || {Date, Count} <- Recs]};
5623 + {error, Reason} ->
5624 + % TODO: Duplicate error message ?
5627 + {reply, Reply, State};
5628 +handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5629 + SName = stats_table(VHost, Schema),
5630 + Query = ["SELECT username, sum(count) AS allcount ",
5631 + "FROM ",SName," ",
5632 + "JOIN ",users_table(VHost, Schema)," ON owner_id=user_id ",
5633 + "WHERE at='",Date,"' ",
5634 + "GROUP BY username ",
5635 + "ORDER BY allcount DESC;"
5638 + case sql_query_internal(DBRef, Query) of
5640 + RFun = fun({User, Count}) ->
5641 + {User, list_to_integer(Count)}
5643 + {ok, lists:reverse(lists:keysort(2, lists:map(RFun, Recs)))};
5644 + {error, Reason} ->
5648 + {reply, Reply, State};
5649 +handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5650 + {reply, get_user_stats_int(DBRef, Schema, User, VHost), State};
5651 +handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5652 + Query = ["SELECT peer_name,",
5660 + "FROM ",view_table(VHost, Schema, Date)," "
5661 + "WHERE owner_name='",User,"';"],
5663 + case sql_query_internal(DBRef, Query) of
5665 + Fun = fun({Peer_name, Peer_server, Peer_resource,
5670 + #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
5671 + direction=list_to_atom(Direction),
5673 + subject=Subject, body=Body,
5674 + timestamp=Timestamp}
5676 + {ok, lists:map(Fun, Recs)};
5677 + {error, Reason} ->
5680 + {reply, Reply, State};
5681 +handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5682 + SName = stats_table(VHost, Schema),
5683 + Query = ["SELECT at ",
5684 + "FROM ",SName," ",
5686 + "ORDER BY at DESC;"
5689 + case sql_query_internal(DBRef, Query) of
5691 + [ Date || {Date} <- Result ];
5692 + {error, Reason} ->
5695 + {reply, Reply, State};
5696 +handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5697 + Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
5698 + "FROM ",settings_table(VHost, Schema)," ",
5699 + "JOIN ",users_table(VHost, Schema)," ON user_id=owner_id;"],
5701 + case sql_query_internal(DBRef, Query) of
5703 + {ok, [#user_settings{owner_name=Owner,
5704 + dolog_default=list_to_bool(DoLogDef),
5705 + dolog_list=string_to_list(DoLogL),
5706 + donotlog_list=string_to_list(DoNotLogL)
5707 + } || {Owner, DoLogDef, DoLogL, DoNotLogL} <- Recs]};
5708 + {error, Reason} ->
5711 + {reply, Reply, State};
5712 +handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5713 + Query = ["SELECT dolog_default,dolog_list,donotlog_list ",
5714 + "FROM ",settings_table(VHost, Schema)," ",
5715 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
5717 + case sql_query_internal_silent(DBRef, Query) of
5720 + {data, [{DoLogDef, DoLogL, DoNotLogL}]} ->
5721 + {ok, #user_settings{owner_name=User,
5722 + dolog_default=list_to_bool(DoLogDef),
5723 + dolog_list=string_to_list(DoLogL),
5724 + donotlog_list=string_to_list(DoNotLogL)}};
5725 + {error, Reason} ->
5726 + ?ERROR_MSG("Failed to get_user_settings for ~s@~s: ~p", [User, VHost, Reason]),
5729 + {reply, Reply, State};
5730 +handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
5731 + dolog_list=DoLogL,
5732 + donotlog_list=DoNotLogL}},
5733 + _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
5734 + User_id = get_user_id(DBRef, VHost, Schema, User),
5735 + Query = ["UPDATE ",settings_table(VHost, Schema)," ",
5736 + "SET dolog_default=",bool_to_list(DoLogDef),", ",
5737 + "dolog_list='",list_to_string(DoLogL),"', ",
5738 + "donotlog_list='",list_to_string(DoNotLogL),"' ",
5739 + "WHERE owner_id=",User_id,";"],
5742 + case sql_query_internal(DBRef, Query) of
5744 + IQuery = ["INSERT INTO ",settings_table(VHost, Schema)," ",
5745 + "(owner_id, dolog_default, dolog_list, donotlog_list) ",
5747 + "(",User_id,", ",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
5748 + case sql_query_internal(DBRef, IQuery) of
5750 + ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
5756 + ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
5761 + {reply, Reply, State};
5762 +handle_call({stop}, _From, State) ->
5763 + ?MYDEBUG("Stoping pgsql backend for ~p", [State#state.vhost]),
5764 + {stop, normal, ok, State};
5765 +handle_call(Msg, _From, State) ->
5766 + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
5770 +handle_cast({rebuild_stats}, State) ->
5771 + rebuild_all_stats_int(State),
5773 +handle_cast({drop_user, User}, #state{vhost=VHost, schema=Schema}=State) ->
5775 + {ok, DBRef} = open_pgsql_connection(State),
5776 + {ok, Dates} = get_user_stats_int(DBRef, Schema, User, VHost),
5777 + MDResult = lists:map(fun({Date, _}) ->
5778 + delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date)
5780 + StDResult = delete_all_stats_by_user_int(DBRef, Schema, User, VHost),
5781 + SDResult = delete_user_settings_int(DBRef, Schema, User, VHost),
5782 + case lists:all(fun(Result) when Result == ok ->
5784 + (Result) when Result == error ->
5786 + end, lists:append([MDResult, [StDResult], [SDResult]])) of
5788 + ?INFO_MSG("Removed ~s@~s", [User, VHost]);
5790 + ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost])
5792 + close_pgsql_connection(DBRef)
5796 +handle_cast(Msg, State) ->
5797 + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
5800 +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
5801 + {stop, connection_dropped, State};
5802 +handle_info(Info, State) ->
5803 + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
5806 +terminate(_Reason, #state{dbref=DBRef}=_State) ->
5807 + close_pgsql_connection(DBRef),
5810 +code_change(_OldVsn, State, _Extra) ->
5813 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5815 +% gen_logdb callbacks
5817 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5818 +log_message(VHost, Msg) ->
5819 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5820 + gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
5821 +rebuild_stats(VHost) ->
5822 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5823 + gen_server:cast(Proc, {rebuild_stats}).
5824 +rebuild_stats_at(VHost, Date) ->
5825 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5826 + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
5827 +delete_messages_by_user_at(VHost, Msgs, Date) ->
5828 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5829 + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
5830 +delete_all_messages_by_user_at(User, VHost, Date) ->
5831 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5832 + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
5833 +delete_messages_at(VHost, Date) ->
5834 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5835 + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
5836 +get_vhost_stats(VHost) ->
5837 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5838 + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
5839 +get_vhost_stats_at(VHost, Date) ->
5840 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5841 + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
5842 +get_user_stats(User, VHost) ->
5843 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5844 + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
5845 +get_user_messages_at(User, VHost, Date) ->
5846 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5847 + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
5848 +get_dates(VHost) ->
5849 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5850 + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
5851 +get_users_settings(VHost) ->
5852 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5853 + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
5854 +get_user_settings(User, VHost) ->
5855 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5856 + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
5857 +set_user_settings(User, VHost, Set) ->
5858 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5859 + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
5860 +drop_user(User, VHost) ->
5861 + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
5862 + gen_server:cast(Proc, {drop_user, User}).
5864 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5868 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5869 +get_dates_int(DBRef, VHost) ->
5870 + Query = ["SELECT n.nspname as \"Schema\",
5871 + c.relname as \"Name\",
5872 + CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' END as \"Type\",
5873 + r.rolname as \"Owner\"
5874 + FROM pg_catalog.pg_class c
5875 + JOIN pg_catalog.pg_roles r ON r.oid = c.relowner
5876 + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
5877 + WHERE c.relkind IN ('r','')
5878 + AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
5879 + AND c.relname ~ '^(.*",escape_vhost(VHost),".*)$'
5880 + AND pg_catalog.pg_table_is_visible(c.oid)
5882 + case sql_query_internal(DBRef, Query) of
5884 + lists:foldl(fun({_Schema, Table, _Type, _Owner}, Dates) ->
5885 + case re:run(Table,"[0-9]+-[0-9]+-[0-9]+") of
5886 + {match, [{S, E}]} ->
5887 + lists:append(Dates, [lists:sublist(Table,S,E)]);
5896 +rebuild_all_stats_int(#state{vhost=VHost, schema=Schema}=State) ->
5898 + {ok, DBRef} = open_pgsql_connection(State),
5899 + ok = delete_nonexistent_stats(DBRef, Schema, VHost),
5900 + case lists:filter(fun(Date) ->
5901 + case catch rebuild_stats_at_int(DBRef, VHost, Schema, Date) of
5904 + {'EXIT', _} -> true
5906 + end, get_dates_int(DBRef, VHost)) of
5909 + ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]),
5912 + close_pgsql_connection(DBRef)
5916 +rebuild_stats_at_int(DBRef, VHost, Schema, Date) ->
5917 + TempTable = temp_table(VHost, Schema),
5920 + Table = messages_table(VHost, Schema, Date),
5921 + STable = stats_table(VHost, Schema),
5923 + DQuery = [ "DELETE FROM ",STable," ",
5924 + "WHERE at='",Date,"';"],
5926 + ok = create_temp_table(DBRef, VHost, Schema),
5927 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," IN ACCESS EXCLUSIVE MODE;"]),
5928 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",TempTable," IN ACCESS EXCLUSIVE MODE;"]),
5929 + SQuery = ["INSERT INTO ",TempTable," ",
5930 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
5931 + "SELECT owner_id,peer_name_id,peer_server_id,'",Date,"'",",count(*) ",
5932 + "FROM ",Table," GROUP BY owner_id,peer_name_id,peer_server_id;"],
5933 + case sql_query_internal(DBRef, SQuery) of
5935 + Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]),
5937 + {data, [{"0"}]} ->
5938 + {updated, _} = sql_query_internal(DBRef, ["DROP VIEW ",view_table(VHost, Schema, Date),";"]),
5939 + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table," CASCADE;"]),
5940 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," IN ACCESS EXCLUSIVE MODE;"]),
5941 + {updated, _} = sql_query_internal(DBRef, DQuery),
5944 + ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]),
5948 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," IN ACCESS EXCLUSIVE MODE;"]),
5949 + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",TempTable," IN ACCESS EXCLUSIVE MODE;"]),
5950 + {updated, _} = sql_query_internal(DBRef, DQuery),
5951 + SQuery1 = ["INSERT INTO ",STable," ",
5952 + "(owner_id,peer_name_id,peer_server_id,at,count) ",
5953 + "SELECT owner_id,peer_name_id,peer_server_id,at,count ",
5954 + "FROM ",TempTable,";"],
5955 + case sql_query_internal(DBRef, SQuery1) of
5956 + {updated, _} -> ok;
5957 + {error, _} -> error
5959 + {error, _} -> error
5963 + case sql_transaction_internal(DBRef, Fun) of
5965 + ?INFO_MSG("Rebuilded stats for ~s at ~s", [VHost, Date]),
5967 + {aborted, Reason} ->
5968 + ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]),
5971 + sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]),
5974 +delete_nonexistent_stats(DBRef, Schema, VHost) ->
5975 + Dates = get_dates_int(DBRef, VHost),
5976 + STable = stats_table(VHost, Schema),
5978 + Temp = lists:flatmap(fun(Date) ->
5979 + ["'",Date,"'",","]
5986 + % replace last "," with ");"
5987 + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
5988 + Query = ["DELETE FROM ",STable," ",
5989 + "WHERE at NOT IN (", Temp1],
5990 + case sql_query_internal(DBRef, Query) of
5998 +get_user_stats_int(DBRef, Schema, User, VHost) ->
5999 + SName = stats_table(VHost, Schema),
6000 + UName = users_table(VHost, Schema),
6001 + Query = ["SELECT stats.at, sum(stats.count) ",
6002 + "FROM ",UName," AS users ",
6003 + "JOIN ",SName," AS stats ON owner_id=user_id "
6004 + "WHERE users.username='",User,"' ",
6005 + "GROUP BY stats.at "
6006 + "ORDER BY DATE(at) DESC;"
6008 + case sql_query_internal(DBRef, Query) of
6010 + {ok, [ {Date, list_to_integer(Count)} || {Date, Count} <- Recs ]};
6011 + {error, Result} ->
6015 +delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date) ->
6016 + DQuery = ["DELETE FROM ",messages_table(VHost, Schema, Date)," ",
6017 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
6018 + case sql_query_internal(DBRef, DQuery) of
6020 + ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]),
6026 +delete_all_stats_by_user_int(DBRef, Schema, User, VHost) ->
6027 + SQuery = ["DELETE FROM ",stats_table(VHost, Schema)," ",
6028 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
6029 + case sql_query_internal(DBRef, SQuery) of
6031 + ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]),
6033 + {error, _} -> error
6036 +delete_stats_by_user_at_int(DBRef, Schema, User, VHost, Date) ->
6037 + SQuery = ["DELETE FROM ",stats_table(VHost, Schema)," ",
6038 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"') ",
6039 + "AND at='",Date,"';"],
6040 + case sql_query_internal(DBRef, SQuery) of
6042 + ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]),
6044 + {error, _} -> error
6047 +delete_user_settings_int(DBRef, Schema, User, VHost) ->
6048 + Query = ["DELETE FROM ",settings_table(VHost, Schema)," ",
6049 + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
6050 + case sql_query_internal(DBRef, Query) of
6052 + ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]),
6054 + {error, Reason} ->
6055 + ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]),
6059 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6063 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6064 +create_temp_table(DBRef, VHost, Schema) ->
6065 + TName = temp_table(VHost, Schema),
6066 + Query = ["CREATE TABLE ",TName," (",
6067 + "owner_id INTEGER, ",
6068 + "peer_name_id INTEGER, ",
6069 + "peer_server_id INTEGER, ",
6070 + "at VARCHAR(20), ",
6074 + case sql_query_internal(DBRef, Query) of
6075 + {updated, _} -> ok;
6076 + {error, _Reason} -> error
6079 +create_stats_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
6080 + SName = stats_table(VHost, Schema),
6084 + Query = ["CREATE TABLE ",SName," (",
6085 + "owner_id INTEGER, ",
6086 + "peer_name_id INTEGER, ",
6087 + "peer_server_id INTEGER, ",
6088 + "at VARCHAR(20), ",
6092 + case sql_query_internal_silent(DBRef, Query) of
6094 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"s_search_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (owner_id, peer_name_id, peer_server_id);"]),
6095 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"s_at_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (at);"]),
6097 + {error, Reason} ->
6098 + case lists:keysearch(code, 1, Reason) of
6099 + {value, {code, "42P07"}} ->
6102 + ?ERROR_MSG("Failed to create stats table for ~s: ~p", [VHost, Reason]),
6107 + case sql_transaction_internal(DBRef, Fun) of
6108 + {atomic, created} ->
6109 + ?MYDEBUG("Created stats table for ~s", [VHost]),
6110 + rebuild_all_stats_int(State),
6112 + {atomic, exists} ->
6113 + ?MYDEBUG("Stats table for ~s already exists", [VHost]),
6114 + {match, [{F, L}]} = re:run(SName, "\".*\""),
6115 + QTable = lists:sublist(SName, F+2, L-2),
6116 + OIDQuery = ["SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname='",QTable,"' AND pg_catalog.pg_table_is_visible(c.oid);"],
6117 + {data,[{OID}]} = sql_query_internal(DBRef, OIDQuery),
6118 + CheckQuery = ["SELECT a.attname FROM pg_catalog.pg_attribute a WHERE a.attrelid = '",OID,"' AND a.attnum > 0 AND NOT a.attisdropped AND a.attname ~ '^peer_.*_id$';"],
6119 + case sql_query_internal(DBRef, CheckQuery) of
6120 + {data, Elems} when length(Elems) == 2 ->
6121 + ?MYDEBUG("Stats table structure is ok", []),
6124 + ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []),
6125 + case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of
6127 + ?INFO_MSG("Successfully dropped ~p", [SName]);
6129 + ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName])
6133 + {error, _} -> error
6136 +create_settings_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
6137 + SName = settings_table(VHost, Schema),
6138 + Query = ["CREATE TABLE ",SName," (",
6139 + "owner_id INTEGER PRIMARY KEY, ",
6140 + "dolog_default BOOLEAN, ",
6141 + "dolog_list TEXT DEFAULT '', ",
6142 + "donotlog_list TEXT DEFAULT ''",
6145 + case sql_query_internal_silent(DBRef, Query) of
6147 + ?MYDEBUG("Created settings table for ~s", [VHost]),
6149 + {error, Reason} ->
6150 + case lists:keysearch(code, 1, Reason) of
6151 + {value, {code, "42P07"}} ->
6152 + ?MYDEBUG("Settings table for ~s already exists", [VHost]),
6155 + ?ERROR_MSG("Failed to create settings table for ~s: ~p", [VHost, Reason]),
6160 +create_users_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
6161 + SName = users_table(VHost, Schema),
6165 + Query = ["CREATE TABLE ",SName," (",
6166 + "username TEXT UNIQUE, ",
6167 + "user_id SERIAL PRIMARY KEY",
6170 + case sql_query_internal_silent(DBRef, Query) of
6172 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"username_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (username);"]),
6174 + {error, Reason} ->
6175 + case lists:keysearch(code, 1, Reason) of
6176 + {value, {code, "42P07"}} ->
6179 + ?ERROR_MSG("Failed to create users table for ~s: ~p", [VHost, Reason]),
6184 + case sql_transaction_internal(DBRef, Fun) of
6185 + {atomic, created} ->
6186 + ?MYDEBUG("Created users table for ~s", [VHost]),
6188 + {atomic, exists} ->
6189 + ?MYDEBUG("Users table for ~s already exists", [VHost]),
6191 + {aborted, _} -> error
6194 +create_servers_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
6195 + SName = servers_table(VHost, Schema),
6198 + Query = ["CREATE TABLE ",SName," (",
6199 + "server TEXT UNIQUE, ",
6200 + "server_id SERIAL PRIMARY KEY",
6203 + case sql_query_internal_silent(DBRef, Query) of
6205 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"server_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (server);"]),
6207 + {error, Reason} ->
6208 + case lists:keysearch(code, 1, Reason) of
6209 + {value, {code, "42P07"}} ->
6212 + ?ERROR_MSG("Failed to create servers table for ~s: ~p", [VHost, Reason]),
6217 + case sql_transaction_internal(DBRef, Fun) of
6218 + {atomic, created} ->
6219 + ?MYDEBUG("Created servers table for ~s", [VHost]),
6221 + {atomic, exists} ->
6222 + ?MYDEBUG("Servers table for ~s already exists", [VHost]),
6224 + {aborted, _} -> error
6227 +create_resources_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
6228 + RName = resources_table(VHost, Schema),
6230 + Query = ["CREATE TABLE ",RName," (",
6231 + "resource TEXT UNIQUE, ",
6232 + "resource_id SERIAL PRIMARY KEY",
6235 + case sql_query_internal_silent(DBRef, Query) of
6237 + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"resource_i_",Schema,"_",escape_vhost(VHost),"\" ON ",RName," (resource);"]),
6239 + {error, Reason} ->
6240 + case lists:keysearch(code, 1, Reason) of
6241 + {value, {code, "42P07"}} ->
6244 + ?ERROR_MSG("Failed to create users table for ~s: ~p", [VHost, Reason]),
6249 + case sql_transaction_internal(DBRef, Fun) of
6250 + {atomic, created} ->
6251 + ?MYDEBUG("Created resources table for ~s", [VHost]),
6253 + {atomic, exists} ->
6254 + ?MYDEBUG("Resources table for ~s already exists", [VHost]),
6256 + {aborted, _} -> error
6259 +create_internals(#state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
6260 + sql_query_internal(DBRef, ["DROP FUNCTION IF EXISTS ",logmessage_name(VHost,Schema)," (tbname TEXT, atdt TEXT, owner TEXT, peer_name TEXT, peer_server TEXT, peer_resource TEXT, mdirection VARCHAR(4), mtype VARCHAR(9), msubj TEXT, mbody TEXT, mtimestamp DOUBLE PRECISION);"]),
6261 + case sql_query_internal(DBRef, [get_logmessage(VHost, Schema)]) of
6263 + ?MYDEBUG("Created logmessage for ~p", [VHost]),
6265 + {error, Reason} ->
6266 + case lists:keysearch(code, 1, Reason) of
6267 + {value, {code, "42704"}} ->
6268 + ?ERROR_MSG("plpgsql language must be installed into database '~s'. Use CREATE LANGUAGE...", [State#state.db]),
6275 +get_user_id(DBRef, VHost, Schema, User) ->
6276 + SQuery = ["SELECT user_id FROM ",users_table(VHost, Schema)," ",
6277 + "WHERE username='",User,"';"],
6278 + case sql_query_internal(DBRef, SQuery) of
6280 + IQuery = ["INSERT INTO ",users_table(VHost, Schema)," ",
6281 + "VALUES ('",User,"');"],
6282 + case sql_query_internal_silent(DBRef, IQuery) of
6284 + {data, [{DBIdNew}]} = sql_query_internal(DBRef, SQuery),
6286 + {error, Reason} ->
6287 + % this can be in clustered environment
6288 + {value, {code, "23505"}} = lists:keysearch(code, 1, Reason),
6289 + ?ERROR_MSG("Duplicate key name for ~p", [User]),
6290 + {data, [{ClID}]} = sql_query_internal(DBRef, SQuery),
6293 + {data, [{DBId}]} ->
6297 +get_logmessage(VHost,Schema) ->
6298 + UName = users_table(VHost,Schema),
6299 + SName = servers_table(VHost,Schema),
6300 + RName = resources_table(VHost,Schema),
6301 + StName = stats_table(VHost,Schema),
6302 + io_lib:format("CREATE OR REPLACE FUNCTION ~s (tbname TEXT, vname TEXT, atdt TEXT, owner TEXT, peer_name TEXT, peer_server TEXT, peer_resource TEXT, mdirection VARCHAR(4), mtype VARCHAR(9), msubj TEXT, mbody TEXT, mtimestamp DOUBLE PRECISION) RETURNS INTEGER AS $$
6305 + peer_nameID INTEGER;
6306 + peer_serverID INTEGER;
6307 + peer_resourceID INTEGER;
6308 + tablename ALIAS for $1;
6309 + viewname ALIAS for $2;
6310 + atdate ALIAS for $3;
6312 + SELECT INTO ownerID user_id FROM ~s WHERE username = owner;
6314 + INSERT INTO ~s (username) VALUES (owner);
6315 + ownerID := lastval();
6318 + SELECT INTO peer_nameID user_id FROM ~s WHERE username = peer_name;
6320 + INSERT INTO ~s (username) VALUES (peer_name);
6321 + peer_nameID := lastval();
6324 + SELECT INTO peer_serverID server_id FROM ~s WHERE server = peer_server;
6326 + INSERT INTO ~s (server) VALUES (peer_server);
6327 + peer_serverID := lastval();
6330 + SELECT INTO peer_resourceID resource_id FROM ~s WHERE resource = peer_resource;
6332 + INSERT INTO ~s (resource) VALUES (peer_resource);
6333 + peer_resourceID := lastval();
6337 + EXECUTE 'INSERT INTO ' || tablename || ' (owner_id, peer_name_id, peer_server_id, peer_resource_id, direction, type, subject, body, timestamp) VALUES (' || ownerID || ',' || peer_nameID || ',' || peer_serverID || ',' || peer_resourceID || ',''' || mdirection || ''',''' || mtype || ''',' || quote_literal(msubj) || ',' || quote_literal(mbody) || ',' || mtimestamp || ')';
6338 + EXCEPTION WHEN undefined_table THEN
6339 + EXECUTE 'CREATE TABLE ' || tablename || ' (' ||
6340 + 'owner_id INTEGER, ' ||
6341 + 'peer_name_id INTEGER, ' ||
6342 + 'peer_server_id INTEGER, ' ||
6343 + 'peer_resource_id INTEGER, ' ||
6344 + 'direction VARCHAR(4) CHECK (direction IN (''to'',''from'')), ' ||
6345 + 'type VARCHAR(9) CHECK (type IN (''chat'',''error'',''groupchat'',''headline'',''normal'')), ' ||
6346 + 'subject TEXT, ' ||
6348 + 'timestamp DOUBLE PRECISION)';
6349 + EXECUTE 'CREATE INDEX \"search_i_' || '~s' || '_' || atdate || '_' || '~s' || '\"' || ' ON ' || tablename || ' (owner_id, peer_name_id, peer_server_id, peer_resource_id)';
6351 + EXECUTE 'CREATE OR REPLACE VIEW ' || viewname || ' AS ' ||
6352 + 'SELECT owner.username AS owner_name, ' ||
6353 + 'peer.username AS peer_name, ' ||
6354 + 'servers.server AS peer_server, ' ||
6355 + 'resources.resource AS peer_resource, ' ||
6356 + 'messages.direction, ' ||
6357 + 'messages.type, ' ||
6358 + 'messages.subject, ' ||
6359 + 'messages.body, ' ||
6360 + 'messages.timestamp ' ||
6365 + '~s resources, ' ||
6366 + tablename || ' messages ' ||
6368 + 'owner.user_id=messages.owner_id and ' ||
6369 + 'peer.user_id=messages.peer_name_id and ' ||
6370 + 'servers.server_id=messages.peer_server_id and ' ||
6371 + 'resources.resource_id=messages.peer_resource_id ' ||
6372 + 'ORDER BY messages.timestamp';
6374 + EXECUTE 'INSERT INTO ' || tablename || ' (owner_id, peer_name_id, peer_server_id, peer_resource_id, direction, type, subject, body, timestamp) VALUES (' || ownerID || ',' || peer_nameID || ',' || peer_serverID || ',' || peer_resourceID || ',''' || mdirection || ''',''' || mtype || ''',' || quote_literal(msubj) || ',' || quote_literal(mbody) || ',' || mtimestamp || ')';
6377 + UPDATE ~s SET count=count+1 where at=atdate and owner_id=ownerID and peer_name_id=peer_nameID and peer_server_id=peer_serverID;
6379 + INSERT INTO ~s (owner_id, peer_name_id, peer_server_id, at, count) VALUES (ownerID, peer_nameID, peer_serverID, atdate, 1);
6383 +$$ LANGUAGE plpgsql;
6384 +", [logmessage_name(VHost,Schema),UName,UName,UName,UName,SName,SName,RName,RName,Schema,escape_vhost(VHost),UName,UName,SName,RName,StName,StName]).
6386 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6390 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6391 +% like do_transaction/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
6392 +sql_transaction_internal(DBRef, Fun) ->
6393 + case sql_query_internal(DBRef, ["BEGIN;"]) of
6395 + case catch Fun() of
6397 + rollback_internal(DBRef, Err);
6398 + {error, _} = Err ->
6399 + rollback_internal(DBRef, Err);
6400 + {'EXIT', _} = Err ->
6401 + rollback_internal(DBRef, Err);
6403 + case sql_query_internal(DBRef, ["COMMIT;"]) of
6404 + {error, _} -> rollback_internal(DBRef, {commit_error});
6407 + {atomic, _} -> Res;
6408 + _ -> {atomic, Res}
6413 + {aborted, {begin_error}}
6416 +% like rollback/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
6417 +rollback_internal(DBRef, Reason) ->
6418 + Res = sql_query_internal(DBRef, ["ROLLBACK;"]),
6419 + {aborted, {Reason, {rollback_result, Res}}}.
6421 +sql_query_internal(DBRef, Query) ->
6422 + case sql_query_internal_silent(DBRef, Query) of
6423 + {error, undefined, Rez} ->
6424 + ?ERROR_MSG("Got undefined result: ~p while ~p", [Rez, lists:append(Query)]),
6425 + {error, undefined};
6427 + ?ERROR_MSG("Failed: ~p while ~p", [Error, lists:append(Query)]),
6432 +sql_query_internal_silent(DBRef, Query) ->
6433 + ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
6434 + % TODO: use pquery?
6435 + get_result(pgsql:squery(DBRef, Query)).
6437 +get_result({ok, ["CREATE TABLE"]}) ->
6439 +get_result({ok, ["DROP TABLE"]}) ->
6441 +get_result({ok, ["ALTER TABLE"]}) ->
6443 +get_result({ok,["DROP VIEW"]}) ->
6445 +get_result({ok,["DROP FUNCTION"]}) ->
6447 +get_result({ok, ["CREATE INDEX"]}) ->
6449 +get_result({ok, ["CREATE FUNCTION"]}) ->
6451 +get_result({ok, [{[$S, $E, $L, $E, $C, $T, $ | _Rest], _Rows, Recs}]}) ->
6454 + lists:map(fun(Elem) when is_binary(Elem) ->
6455 + binary_to_list(Elem);
6456 + (Elem) when is_list(Elem) ->
6458 + (Elem) when is_integer(Elem) ->
6459 + integer_to_list(Elem);
6460 + (Elem) when is_float(Elem) ->
6461 + float_to_list(Elem);
6462 + (Elem) when is_boolean(Elem) ->
6463 + atom_to_list(Elem);
6465 + ?ERROR_MSG("Unknown element type ~p", [Elem]),
6469 + Res = lists:map(Fun, Recs),
6470 + %{data, [list_to_tuple(Rec) || Rec <- Recs]};
6472 +get_result({ok, ["INSERT " ++ OIDN]}) ->
6473 + [_OID, N] = string:tokens(OIDN, " "),
6474 + {updated, list_to_integer(N)};
6475 +get_result({ok, ["DELETE " ++ N]}) ->
6476 + {updated, list_to_integer(N)};
6477 +get_result({ok, ["UPDATE " ++ N]}) ->
6478 + {updated, list_to_integer(N)};
6479 +get_result({ok, ["BEGIN"]}) ->
6481 +get_result({ok, ["LOCK TABLE"]}) ->
6483 +get_result({ok, ["ROLLBACK"]}) ->
6485 +get_result({ok, ["COMMIT"]}) ->
6487 +get_result({ok, ["SET"]}) ->
6489 +get_result({ok, [{error, Error}]}) ->
6492 + {error, undefined, Rez}.
6494 diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl
6495 index df06bce..b460f46 100644
6496 --- a/src/mod_muc_room.erl
6497 +++ b/src/mod_muc_room.erl
6498 @@ -757,6 +757,12 @@ handle_sync_event({change_state, NewStateData}, _From,
6499 handle_sync_event({process_item_change, Item, UJID}, _From, StateName, StateData) ->
6500 NSD = process_item_change(Item, StateData, UJID),
6501 {reply, {ok, NSD}, StateName, NSD};
6502 +handle_sync_event({get_jid_nick, Jid}, _From, StateName, StateData) ->
6503 + R = case (?DICT):find(jlib:jid_tolower(Jid), StateData#state.users) of
6505 + {ok, {user, _, Nick, _, _}} -> Nick
6507 + {reply, R, StateName, StateData};
6508 handle_sync_event(_Event, _From, StateName,
6510 Reply = ok, {reply, Reply, StateName, StateData}.
6511 diff --git a/src/mod_roster.erl b/src/mod_roster.erl
6512 index 31fbeb1..764a628 100644
6513 --- a/src/mod_roster.erl
6514 +++ b/src/mod_roster.erl
6517 -include("ejabberd_web_admin.hrl").
6519 +-include("mod_logdb.hrl").
6521 -export_type([subscription/0]).
6523 start(Host, Opts) ->
6524 @@ -1426,6 +1428,14 @@ user_roster(User, Server, Query, Lang) ->
6526 Items = get_roster(LUser, LServer),
6527 SItems = lists:sort(Items),
6529 + Settings = case gen_mod:is_loaded(Server, mod_logdb) of
6531 + mod_logdb:get_user_settings(User, Server);
6536 FItems = case SItems of
6537 [] -> [?CT(<<"None">>)];
6539 @@ -1483,7 +1493,33 @@ user_roster(User, Server, Query, Lang) ->
6540 [?INPUTT(<<"submit">>,
6542 (ejabberd_web_admin:term_to_id(R#roster.jid))/binary>>,
6545 + case gen_mod:is_loaded(Server, mod_logdb) of
6547 + Peer = jlib:jid_to_string(R#roster.jid),
6548 + A = lists:member(Peer, Settings#user_settings.dolog_list),
6549 + B = lists:member(Peer, Settings#user_settings.donotlog_list),
6553 + {<<"donotlog">>, <<"Do Not Log Messages">>};
6555 + {<<"dolog">>, <<"Log Messages">>};
6556 + Settings#user_settings.dolog_default == true ->
6557 + {<<"donotlog">>, <<"Do Not Log Messages">>};
6558 + Settings#user_settings.dolog_default == false ->
6559 + {<<"dolog">>, <<"Log Messages">>}
6562 + ?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
6563 + [?INPUTT(<<"submit">>,
6565 + (ejabberd_web_admin:term_to_id(R#roster.jid))/binary>>,
6574 @@ -1608,9 +1644,42 @@ user_roster_item_parse_query(User, Server, Items,
6582 + case lists:keysearch(
6583 + <<"donotlog">>, (ejabberd_web_admin:term_to_id(JID))/binary, 1, Query) of
6585 + Peer = jlib:jid_to_string(JID),
6586 + Settings = mod_logdb:get_user_settings(User, Server),
6587 + DNLL = case lists:member(Peer, Settings#user_settings.donotlog_list) of
6588 + false -> lists:append(Settings#user_settings.donotlog_list, [Peer]);
6589 + true -> Settings#user_settings.donotlog_list
6591 + DLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.dolog_list),
6592 + Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
6593 + % TODO: check returned value
6594 + ok = mod_logdb:set_user_settings(User, Server, Sett),
6597 + case lists:keysearch(
6598 + <<"dolog">>, (ejabberd_web_admin:term_to_id(JID))/binary, 1, Query) of
6600 + Peer = jlib:jid_to_string(JID),
6601 + Settings = mod_logdb:get_user_settings(User, Server),
6602 + DLL = case lists:member(Peer, Settings#user_settings.dolog_list) of
6603 + false -> lists:append(Settings#user_settings.dolog_list, [Peer]);
6604 + true -> Settings#user_settings.dolog_list
6606 + DNLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.donotlog_list),
6607 + Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
6608 + % TODO: check returned value
6609 + ok = mod_logdb:set_user_settings(User, Server, Sett),