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