---- mod_logdb.erl.orig 2009-11-22 13:06:23.000000000 +0200
-+++ mod_logdb.erl 2009-11-22 13:06:16.000000000 +0200
-@@ -0,0 +1,2095 @@
+From 9a2ed8d2b20ef052b71b065e686cc049d18999ac Mon Sep 17 00:00:00 2001
+From: Oleh Palii <o.palij@gmail.com>
+Date: Sat, 31 Aug 2019 11:04:57 +0300
+Subject: [PATCH 1/3] apply mod_logdb to 19.08
+
+---
+ priv/msgs/nl.msg | 14 +
+ priv/msgs/pl.msg | 26 +
+ priv/msgs/ru.msg | 30 +
+ priv/msgs/uk.msg | 30 +
+ rebar.config | 4 +-
+ src/gen_logdb.erl | 162 ++++
+ src/mod_logdb.erl | 1951 ++++++++++++++++++++++++++++++++++++++
+ src/mod_logdb.hrl | 33 +
+ src/mod_logdb_mnesia.erl | 553 +++++++++++
+ src/mod_logdb_mysql.erl | 1050 ++++++++++++++++++++
+ src/mod_logdb_mysql5.erl | 979 +++++++++++++++++++
+ src/mod_logdb_pgsql.erl | 1104 +++++++++++++++++++++
+ src/mod_roster.erl | 77 +-
+ 13 files changed, 6007 insertions(+), 6 deletions(-)
+ create mode 100644 src/gen_logdb.erl
+ create mode 100644 src/mod_logdb.erl
+ create mode 100644 src/mod_logdb.hrl
+ create mode 100644 src/mod_logdb_mnesia.erl
+ create mode 100644 src/mod_logdb_mysql.erl
+ create mode 100644 src/mod_logdb_mysql5.erl
+ create mode 100644 src/mod_logdb_pgsql.erl
+
+diff --git a/priv/msgs/nl.msg b/priv/msgs/nl.msg
+index 8009d529ff..fd3d3b0ebb 100644
+--- a/priv/msgs/nl.msg
++++ b/priv/msgs/nl.msg
+@@ -345,3 +345,17 @@
+ {"Your contact offline message queue is full. The message has been discarded.","Te veel offline berichten voor dit contactpersoon. Het bericht is niet opgeslagen."}.
+ {"Your Jabber account was successfully created.","Uw Jabber-account is succesvol gecreeerd."}.
+ {"Your Jabber account was successfully deleted.","Uw Jabber-account is succesvol verwijderd."}.
++% mod_logdb
++{"Users Messages", "Gebruikersberichten"}.
++{"Date", "Datum"}.
++{"Count", "Aantal"}.
++{"Logged messages for ~s", "Gelogde berichten van ~s"}.
++{"Logged messages for ~s at ~s", "Gelogde berichten van ~s op ~s"}.
++{" at ", " op "}.
++{"No logged messages for ~s", "Geen gelogde berichten van ~s"}.
++{"No logged messages for ~s at ~s", "Geen gelogde berichten van ~s op ~s"}.
++{"Date, Time", "Datum en tijd"}.
++{"Direction: Jid", "Richting: Jabber ID"}.
++{"Subject", "Onderwerp"}.
++{"Body", "Berichtveld"}.
++{"Messages", "Berichten"}.
+diff --git a/priv/msgs/pl.msg b/priv/msgs/pl.msg
+index 2ca75b259c..fffae5742e 100644
+--- a/priv/msgs/pl.msg
++++ b/priv/msgs/pl.msg
+@@ -444,3 +444,29 @@
+ {"You're not allowed to create nodes","Nie masz uprawnień do tworzenia węzłów"}.
+ {"Your Jabber account was successfully created.","Twoje konto zostało stworzone."}.
+ {"Your Jabber account was successfully deleted.","Twoje konto zostało usunięte."}.
++% mod_logdb
++{"Users Messages", "Wiadomości użytkownika"}.
++{"Date", "Data"}.
++{"Count", "Liczba"}.
++{"Logged messages for ~s", "Zapisane wiadomości dla ~s"}.
++{"Logged messages for ~s at", "Zapisane wiadomości dla ~s o ~s"}.
++{" at ", " o "}.
++{"No logged messages for ~s", "Brak zapisanych wiadomości dla ~s"}.
++{"No logged messages for ~s at ~s", "Brak zapisanych wiadomości dla ~s o ~s"}.
++{"Date, Time", "Data, Godzina"}.
++{"Direction: Jid", "Kierunek: Jid"}.
++{"Subject", "Temat"}.
++{"Body", "Treść"}.
++{"Messages","Wiadomości"}.
++{"Filter Selected", "Odfiltruj zaznaczone"}.
++{"Do Not Log Messages", "Nie zapisuj wiadomości"}.
++{"Log Messages", "Zapisuj wiadomości"}.
++{"Messages logging engine", "System zapisywania historii rozmów"}.
++{"Default", "Domyślne"}.
++{"Set logging preferences", "Ustaw preferencje zapisywania"}.
++{"Messages logging engine settings", "Ustawienia systemu logowania"}.
++{"Set run-time settings", "Zapisz ustawienia systemu logowania"}.
++{"Groupchat messages logging", "Zapisywanie rozmów z konferencji"}.
++{"Jids/Domains to ignore", "JID/Domena która ma być ignorowana"}.
++{"Purge messages older than (days)", "Usuń wiadomości starsze niż (w dniach)"}.
++{"Poll users settings (seconds)", "Czas aktualizacji preferencji użytkowników (sekundy)"}.
+diff --git a/priv/msgs/ru.msg b/priv/msgs/ru.msg
+index f7dff97ea1..42be5d4f15 100644
+--- a/priv/msgs/ru.msg
++++ b/priv/msgs/ru.msg
+@@ -507,3 +507,33 @@
+ {"Your Jabber account was successfully created.","Ваш Jabber-аккаунт был успешно создан."}.
+ {"Your Jabber account was successfully deleted.","Ваш Jabber-аккаунт был успешно удален."}.
+ {"Your subscription request and/or messages to ~s have been blocked. To unblock your subscription request, visit ~s","Ваши запросы на добавление в контакт-лист, а также сообщения к ~s блокируются. Для снятия блокировки перейдите по ссылке ~s"}.
++% mod_logdb.erl
++{"Users Messages", "Сообщения пользователей"}.
++{"Date", "Дата"}.
++{"Count", "Количество"}.
++{"Logged messages for ~s", "Сохранённые cообщения для ~s"}.
++{"Logged messages for ~s at ~s", "Сохранённые cообщения для ~s за ~s"}.
++{" at ", " за "}.
++{"No logged messages for ~s", "Отсутствуют сообщения для ~s"}.
++{"No logged messages for ~s at ~s", "Отсутствуют сообщения для ~s за ~s"}.
++{"Date, Time", "Дата, Время"}.
++{"Direction: Jid", "Направление: Jid"}.
++{"Subject", "Тема"}.
++{"Body", "Текст"}.
++{"Messages", "Сообщения"}.
++{"Filter Selected", "Отфильтровать выделенные"}.
++{"Do Not Log Messages", "Не сохранять сообщения"}.
++{"Log Messages", "Сохранять сообщения"}.
++{"Messages logging engine", "Система логирования сообщений"}.
++{"Default", "По умолчанию"}.
++{"Set logging preferences", "Задайте настройки логирования"}.
++{"Messages logging engine users", "Пользователи системы логирования сообщений"}.
++{"Messages logging engine settings", "Настройки системы логирования сообщений"}.
++{"Set run-time settings", "Задайте текущие настройки"}.
++{"Groupchat messages logging", "Логирование сообщений типа groupchat"}.
++{"Jids/Domains to ignore", "Игнорировать следующие jids/домены"}.
++{"Purge messages older than (days)", "Удалять сообщения старее чем (дни)"}.
++{"Poll users settings (seconds)", "Обновлять настройки пользователей через (секунд)"}.
++{"Drop", "Удалять"}.
++{"Do not drop", "Не удалять"}.
++{"Drop messages on user removal", "Удалять сообщения при удалении пользователя"}.
+diff --git a/priv/msgs/uk.msg b/priv/msgs/uk.msg
+index 0fbc336d51..c0b90047fa 100644
+--- a/priv/msgs/uk.msg
++++ b/priv/msgs/uk.msg
+@@ -349,3 +349,33 @@
+ {"Your contact offline message queue is full. The message has been discarded.","Черга повідомлень, що не були доставлені, переповнена. Повідомлення не було збережено."}.
+ {"Your Jabber account was successfully created.","Ваш Jabber-акаунт було успішно створено."}.
+ {"Your Jabber account was successfully deleted.","Ваш Jabber-акаунт було успішно видалено."}.
++% mod_logdb
++{"Users Messages", "Повідомлення користувачів"}.
++{"Date", "Дата"}.
++{"Count", "Кількість"}.
++{"Logged messages for ~s", "Збережені повідомлення для ~s"}.
++{"Logged messages for ~s at ~s", "Збережені повідомлення для ~s за ~s"}.
++{" at ", " за "}.
++{"No logged messages for ~s", "Відсутні повідомлення для ~s"}.
++{"No logged messages for ~s at ~s", "Відсутні повідомлення для ~s за ~s"}.
++{"Date, Time", "Дата, Час"}.
++{"Direction: Jid", "Напрямок: Jid"}.
++{"Subject", "Тема"}.
++{"Body", "Текст"}.
++{"Messages", "Повідомлення"}.
++{"Filter Selected", "Відфільтрувати виділені"}.
++{"Do Not Log Messages", "Не зберігати повідомлення"}.
++{"Log Messages", "Зберігати повідомлення"}.
++{"Messages logging engine", "Система збереження повідомлень"}.
++{"Default", "За замовчуванням"}.
++{"Set logging preferences", "Вкажіть налагоджування збереження повідомлень"}.
++{"Messages logging engine users", "Користувачі системи збереження повідомлень"}.
++{"Messages logging engine settings", "Налагоджування системи збереження повідомлень"}.
++{"Set run-time settings", "Вкажіть поточні налагоджування"}.
++{"Groupchat messages logging", "Збереження повідомлень типу groupchat"}.
++{"Jids/Domains to ignore", "Ігнорувати наступні jids/домени"}.
++{"Purge messages older than (days)", "Видаляти повідомлення старіші ніж (дні)"}.
++{"Poll users settings (seconds)", "Оновлювати налагоджування користувачів кожні (секунд)"}.
++{"Drop", "Видаляти"}.
++{"Do not drop", "Не видаляти"}.
++{"Drop messages on user removal", "Видаляти повідомлення під час видалення користувача"}.
+diff --git a/rebar.config b/rebar.config
+index e05fe84e6e..c3b87afd28 100644
+--- a/rebar.config
++++ b/rebar.config
+@@ -35,8 +35,8 @@
+ {mqtree, ".*", {git, "https://github.com/processone/mqtree", {tag, "1.0.4"}}},
+ {if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.29"}}}},
+ {if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.30"}}}},
+- {if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql",
+- {tag, "1.0.11"}}}},
++ {if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/paleg/p1_mysql",
++ {tag, "1.0.11_multi"}}}},
+ {if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql",
+ {tag, "1.1.8"}}}},
+ {if_var_true, sqlite, {sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3",
+diff --git a/src/gen_logdb.erl b/src/gen_logdb.erl
+new file mode 100644
+index 0000000000..8bad112969
+--- /dev/null
++++ b/src/gen_logdb.erl
+@@ -0,0 +1,162 @@
++%%%----------------------------------------------------------------------
++%%% File : gen_logdb.erl
++%%% Author : Oleg Palij (mailto:o.palij@gmail.com)
++%%% Purpose : Describes generic behaviour for mod_logdb backends.
++%%% Url : https://paleg.github.io/mod_logdb/
++%%%----------------------------------------------------------------------
++
++-module(gen_logdb).
++-author('o.palij@gmail.com').
++
++-export([behaviour_info/1]).
++
++behaviour_info(callbacks) ->
++ [
++ % called from handle_info(start, _)
++ % it should logon database and return reference to started instance
++ % start(VHost, Opts) -> {ok, SPid} | error
++ % Options - list of options to connect to db
++ % Types: Options = list() -> [] |
++ % [{user, "logdb"},
++ % {pass, "1234"},
++ % {db, "logdb"}] | ...
++ % VHost = list() -> "jabber.example.org"
++ {start, 2},
++
++ % called from cleanup/1
++ % it should logoff database and do cleanup
++ % stop(VHost)
++ % Types: VHost = list() -> "jabber.example.org"
++ {stop, 1},
++
++ % called from handle_call({addlog, _}, _, _)
++ % it should log messages to database
++ % log_message(VHost, Msg) -> ok | error
++ % Types:
++ % VHost = list() -> "jabber.example.org"
++ % Msg = record() -> #msg
++ {log_message, 2},
++
++ % called from ejabberdctl rebuild_stats
++ % it should rebuild stats table (if used) for vhost
++ % rebuild_stats(VHost)
++ % Types:
++ % VHost = list() -> "jabber.example.org"
++ {rebuild_stats, 1},
++
++ % it should rebuild stats table (if used) for vhost at Date
++ % rebuild_stats_at(VHost, Date)
++ % Types:
++ % VHost = list() -> "jabber.example.org"
++ % Date = list() -> "2007-02-12"
++ {rebuild_stats_at, 2},
++
++ % called from user_messages_at_parse_query/5
++ % it should delete selected user messages at date
++ % delete_messages_by_user_at(VHost, Msgs, Date) -> ok | error
++ % Types:
++ % VHost = list() -> "jabber.example.org"
++ % Msgs = list() -> [ #msg1, msg2, ... ]
++ % Date = list() -> "2007-02-12"
++ {delete_messages_by_user_at, 3},
++
++ % called from user_messages_parse_query/4 | vhost_messages_at_parse_query/4
++ % it should delete all user messages at date
++ % delete_all_messages_by_user_at(User, VHost, Date) -> ok | error
++ % Types:
++ % User = list() -> "admin"
++ % VHost = list() -> "jabber.example.org"
++ % Date = list() -> "2007-02-12"
++ {delete_all_messages_by_user_at, 3},
++
++ % called from vhost_messages_parse_query/3
++ % it should delete messages for vhost at date and update stats
++ % delete_messages_at(VHost, Date) -> ok | error
++ % Types:
++ % VHost = list() -> "jabber.example.org"
++ % Date = list() -> "2007-02-12"
++ {delete_messages_at, 2},
++
++ % called from ejabberd_web_admin:vhost_messages_stats/3
++ % it should return sorted list of count of messages by dates for vhost
++ % get_vhost_stats(VHost) -> {ok, [{Date1, Msgs_count1}, {Date2, Msgs_count2}, ... ]} |
++ % {error, Reason}
++ % Types:
++ % VHost = list() -> "jabber.example.org"
++ % DateN = list() -> "2007-02-12"
++ % Msgs_countN = number() -> 241
++ {get_vhost_stats, 1},
++
++ % called from ejabberd_web_admin:vhost_messages_stats_at/4
++ % it should return sorted list of count of messages by users at date for vhost
++ % get_vhost_stats_at(VHost, Date) -> {ok, [{User1, Msgs_count1}, {User2, Msgs_count2}, ....]} |
++ % {error, Reason}
++ % Types:
++ % VHost = list() -> "jabber.example.org"
++ % Date = list() -> "2007-02-12"
++ % UserN = list() -> "admin"
++ % Msgs_countN = number() -> 241
++ {get_vhost_stats_at, 2},
++
++ % called from ejabberd_web_admin:user_messages_stats/4
++ % it should return sorted list of count of messages by date for user at vhost
++ % get_user_stats(User, VHost) -> {ok, [{Date1, Msgs_count1}, {Date2, Msgs_count2}, ...]} |
++ % {error, Reason}
++ % Types:
++ % User = list() -> "admin"
++ % VHost = list() -> "jabber.example.org"
++ % DateN = list() -> "2007-02-12"
++ % Msgs_countN = number() -> 241
++ {get_user_stats, 2},
++
++ % called from ejabberd_web_admin:user_messages_stats_at/5
++ % it should return all user messages at date
++ % get_user_messages_at(User, VHost, Date) -> {ok, Msgs} | {error, Reason}
++ % Types:
++ % User = list() -> "admin"
++ % VHost = list() -> "jabber.example.org"
++ % Date = list() -> "2007-02-12"
++ % Msgs = list() -> [ #msg1, msg2, ... ]
++ {get_user_messages_at, 3},
++
++ % called from many places
++ % it should return list of dates for vhost
++ % get_dates(VHost) -> [Date1, Date2, ... ]
++ % Types:
++ % VHost = list() -> "jabber.example.org"
++ % DateN = list() -> "2007-02-12"
++ {get_dates, 1},
++
++ % called from start
++ % it should return list with users settings for VHost in db
++ % get_users_settings(VHost) -> [#user_settings1, #user_settings2, ... ] | error
++ % Types:
++ % VHost = list() -> "jabber.example.org"
++ {get_users_settings, 1},
++
++ % called from many places
++ % it should return User settings at VHost from db
++ % get_user_settings(User, VHost) -> error | {ok, #user_settings}
++ % Types:
++ % User = list() -> "admin"
++ % VHost = list() -> "jabber.example.org"
++ {get_user_settings, 2},
++
++ % called from web admin
++ % it should set User settings at VHost
++ % set_user_settings(User, VHost, #user_settings) -> ok | error
++ % Types:
++ % User = list() -> "admin"
++ % VHost = list() -> "jabber.example.org"
++ {set_user_settings, 3},
++
++ % called from remove_user (ejabberd hook)
++ % it should remove user messages and settings at VHost
++ % drop_user(User, VHost) -> ok | error
++ % Types:
++ % User = list() -> "admin"
++ % VHost = list() -> "jabber.example.org"
++ {drop_user, 2}
++ ];
++behaviour_info(_) ->
++ undefined.
+diff --git a/src/mod_logdb.erl b/src/mod_logdb.erl
+new file mode 100644
+index 0000000000..bf0240d139
+--- /dev/null
++++ b/src/mod_logdb.erl
+@@ -0,0 +1,1951 @@
+%%%----------------------------------------------------------------------
+%%% File : mod_logdb.erl
-+%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
++%%% Author : Oleg Palij (mailto:o.palij@gmail.com)
+%%% Purpose : Frontend for log user messages to db
-+%%% Version : trunk
-+%%% Id : $Id$
-+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
++%%% Url : https://paleg.github.io/mod_logdb/
+%%%----------------------------------------------------------------------
+
+-module(mod_logdb).
+% supervisor
+-export([start_link/2]).
+% gen_mod
-+-export([start/2,stop/1]).
++-export([start/2, stop/1,
++ mod_opt_type/1,
++ depends/2, reload/3]).
+% gen_server
-+-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
++-export([code_change/3,
++ handle_call/3, handle_cast/2, handle_info/2,
++ init/1, terminate/2]).
+% hooks
-+-export([send_packet/3, receive_packet/4, offline_packet/3, remove_user/2]).
++-export([send_packet/1, receive_packet/1, offline_message/1, remove_user/2]).
+-export([get_local_identity/5,
-+ get_local_features/5,
++ get_local_features/5,
+ get_local_items/5,
+ adhoc_local_items/4,
+ adhoc_local_commands/4
-+% get_sm_identity/5,
-+% get_sm_features/5,
-+% get_sm_items/5,
-+% adhoc_sm_items/4,
-+% adhoc_sm_commands/4]).
+ ]).
+% ejabberdctl
-+-export([rebuild_stats/3,
++-export([rebuild_stats/1,
+ copy_messages/1, copy_messages_ctl/3, copy_messages_int_tc/1]).
+%
+-export([get_vhost_stats/1, get_vhost_stats_at/2,
+ user_messages_stats_at/5]).
+
+-include("mod_logdb.hrl").
-+-include("ejabberd.hrl").
++-include("xmpp.hrl").
+-include("mod_roster.hrl").
-+-include("jlib.hrl").
-+-include("ejabberd_ctl.hrl").
++-include("ejabberd_commands.hrl").
+-include("adhoc.hrl").
-+-include("web/ejabberd_web_admin.hrl").
-+-include("web/ejabberd_http.hrl").
++-include("ejabberd_web_admin.hrl").
++-include("ejabberd_http.hrl").
++-include("logger.hrl").
+
+-define(PROCNAME, ejabberd_mod_logdb).
+% gen_server call timeout
+
+-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}).
+
-+ets_settings_table(VHost) -> list_to_atom("ets_logdb_settings_" ++ VHost).
++ets_settings_table(VHost) -> list_to_atom("ets_logdb_settings_" ++ binary_to_list(VHost)).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+ worker,
+ [?MODULE]},
+ % add child to ejabberd_sup
-+ supervisor:start_child(ejabberd_sup, ChildSpec).
++ supervisor:start_child(ejabberd_gen_mod_sup, ChildSpec).
++
++depends(_Host, _Opts) ->
++ [].
++
++reload(_Host, _NewOpts, _OldOpts) ->
++ % TODO
++ ok.
+
+% supervisor starts gen_server
+start_link(VHost, Opts) ->
+ {ok, Pid}.
+
+init([VHost, Opts]) ->
-+ ?MYDEBUG("Starting mod_logdb", []),
+ process_flag(trap_exit, true),
-+ DBs = gen_mod:get_opt(dbs, Opts, [{mnesia, []}]),
-+ VHostDB = gen_mod:get_opt(vhosts, Opts, [{VHost, mnesia}]),
-+ % 10 is default becouse of using in clustered environment
-+ PollUsersSettings = gen_mod:get_opt(poll_users_settings, Opts, 10),
++ DBsRaw = gen_mod:get_opt(dbs, Opts, fun(A) -> A end, [{mnesia, []}]),
++ DBs = case lists:keysearch(mnesia, 1, DBsRaw) of
++ false -> lists:append(DBsRaw, [{mnesia,[]}]);
++ {value, _} -> DBsRaw
++ end,
++ VHostDB = gen_mod:get_opt(vhosts, Opts, fun(A) -> A end, [{VHost, mnesia}]),
++ % 10 is default because of using in clustered environment
++ PollUsersSettings = gen_mod:get_opt(poll_users_settings, Opts, fun(A) -> A end, 10),
+
-+ {value,{_, DBName}} = lists:keysearch(VHost, 1, VHostDB),
-+ {value, {DBName, DBOpts}} = lists:keysearch(DBName, 1, DBs),
++ {DBName, DBOpts} =
++ case lists:keysearch(VHost, 1, VHostDB) of
++ false ->
++ ?WARNING_MSG("There is no logging backend defined for '~s', switching to mnesia", [VHost]),
++ {mnesia, []};
++ {value,{_, DBNameResult}} ->
++ case lists:keysearch(DBNameResult, 1, DBs) of
++ false ->
++ ?WARNING_MSG("There is no such logging backend '~s' defined for '~s', switching to mnesia", [DBNameResult, VHost]),
++ {mnesia, []};
++ {value, {_, DBOptsResult}} ->
++ {DBNameResult, DBOptsResult}
++ end
++ end,
+
-+ ?MYDEBUG("Starting mod_logdb for ~p with ~p backend", [VHost, DBName]),
++ ?MYDEBUG("Starting mod_logdb for '~s' with '~s' backend", [VHost, DBName]),
+
+ DBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(DBName)),
+
+ dbopts=DBOpts,
+ % dbs used for convert messages from one backend to other
+ dbs=DBs,
-+ dolog_default=gen_mod:get_opt(dolog_default, Opts, true),
-+ drop_messages_on_user_removal=gen_mod:get_opt(drop_messages_on_user_removal, Opts, true),
-+ ignore_jids=gen_mod:get_opt(ignore_jids, Opts, []),
-+ groupchat=gen_mod:get_opt(groupchat, Opts, none),
-+ purge_older_days=gen_mod:get_opt(purge_older_days, Opts, never),
++ dolog_default=gen_mod:get_opt(dolog_default, Opts, fun(A) -> A end, true),
++ drop_messages_on_user_removal=gen_mod:get_opt(drop_messages_on_user_removal, Opts, fun(A) -> A end, true),
++ ignore_jids=gen_mod:get_opt(ignore_jids, Opts, fun(A) -> A end, []),
++ groupchat=gen_mod:get_opt(groupchat, Opts, fun(A) -> A end, none),
++ purge_older_days=gen_mod:get_opt(purge_older_days, Opts, fun(A) -> A end, never),
+ poll_users_settings=PollUsersSettings}}.
+
+cleanup(#state{vhost=VHost} = _State) ->
+ ejabberd_hooks:delete(remove_user, VHost, ?MODULE, remove_user, 90),
+ ejabberd_hooks:delete(user_send_packet, VHost, ?MODULE, send_packet, 90),
+ ejabberd_hooks:delete(user_receive_packet, VHost, ?MODULE, receive_packet, 90),
-+ ejabberd_hooks:delete(offline_message_hook, VHost, ?MODULE, offline_packet, 10),
-+ %ejabberd_hooks:delete(adhoc_sm_commands, VHost, ?MODULE, adhoc_sm_commands, 110),
-+ %ejabberd_hooks:delete(adhoc_sm_items, VHost, ?MODULE, adhoc_sm_items, 110),
-+ ejabberd_hooks:delete(adhoc_local_commands, VHost, ?MODULE, adhoc_local_commands, 110),
-+ ejabberd_hooks:delete(adhoc_local_items, VHost, ?MODULE, adhoc_local_items, 110),
-+ %ejabberd_hooks:delete(disco_sm_identity, VHost, ?MODULE, get_sm_identity, 110),
-+ %ejabberd_hooks:delete(disco_sm_features, VHost, ?MODULE, get_sm_features, 110),
-+ %ejabberd_hooks:delete(disco_sm_items, VHost, ?MODULE, get_sm_items, 110),
-+ ejabberd_hooks:delete(disco_local_identity, VHost, ?MODULE, get_local_identity, 110),
-+ ejabberd_hooks:delete(disco_local_features, VHost, ?MODULE, get_local_features, 110),
-+ ejabberd_hooks:delete(disco_local_items, VHost, ?MODULE, get_local_items, 110),
++ ejabberd_hooks:delete(offline_message_hook, VHost, ?MODULE, offline_message, 40),
++
++ ejabberd_hooks:delete(adhoc_local_commands, VHost, ?MODULE, adhoc_local_commands, 50),
++ ejabberd_hooks:delete(adhoc_local_items, VHost, ?MODULE, adhoc_local_items, 50),
++ ejabberd_hooks:delete(disco_local_identity, VHost, ?MODULE, get_local_identity, 50),
++ ejabberd_hooks:delete(disco_local_features, VHost, ?MODULE, get_local_features, 50),
++ ejabberd_hooks:delete(disco_local_items, VHost, ?MODULE, get_local_items, 50),
+
+ ejabberd_hooks:delete(webadmin_menu_host, VHost, ?MODULE, webadmin_menu, 70),
+ ejabberd_hooks:delete(webadmin_user, VHost, ?MODULE, webadmin_user, 50),
+
+ ?MYDEBUG("Removed hooks for ~p", [VHost]),
+
-+ %ejabberd_ctl:unregister_commands(VHost, [{"rebuild_stats", "rebuild mod_logdb module stats for vhost"}], ?MODULE, rebuild_stats),
-+ %Supported_backends = lists:flatmap(fun({Backend, _Opts}) ->
-+ % [atom_to_list(Backend), " "]
-+ % end, State#state.dbs),
-+ %ejabberd_ctl:unregister_commands(
-+ % VHost,
-+ % [{"copy_messages backend", "copy messages from backend to current backend. backends could be: " ++ Supported_backends }],
-+ % ?MODULE, copy_messages_ctl),
++ ejabberd_commands:unregister_commands(get_commands_spec()),
+ ?MYDEBUG("Unregistered commands for ~p", [VHost]).
+
+stop(VHost) ->
+ %gen_server:call(Proc, {cleanup}),
+ %?MYDEBUG("Cleanup in stop finished!!!!", []),
+ %timer:sleep(10000),
-+ ok = supervisor:terminate_child(ejabberd_sup, Proc),
-+ ok = supervisor:delete_child(ejabberd_sup, Proc).
++ ok = supervisor:terminate_child(ejabberd_gen_mod_sup, Proc),
++ ok = supervisor:delete_child(ejabberd_gen_mod_sup, Proc).
++
++get_commands_spec() ->
++ [#ejabberd_commands{name = rebuild_stats, tags = [logdb],
++ desc = "Rebuild mod_logdb stats for given host",
++ module = ?MODULE, function = rebuild_stats,
++ args = [{host, binary}],
++ result = {res, rescode}},
++ #ejabberd_commands{name = copy_messages, tags = [logdb],
++ desc = "Copy logdb messages from given backend to current backend for given host",
++ module = ?MODULE, function = copy_messages_ctl,
++ args = [{host, binary}, {backend, binary}, {date, binary}],
++ result = {res, rescode}}].
++
++mod_opt_type(dbs) ->
++ fun (A) when is_list(A) -> A end;
++mod_opt_type(vhosts) ->
++ fun (A) when is_list(A) -> A end;
++mod_opt_type(poll_users_settings) ->
++ fun (I) when is_integer(I) -> I end;
++mod_opt_type(groupchat) ->
++ fun (all) -> all;
++ (send) -> send;
++ (none) -> none
++ end;
++mod_opt_type(dolog_default) ->
++ fun (B) when is_boolean(B) -> B end;
++mod_opt_type(ignore_jids) ->
++ fun (A) when is_list(A) -> A end;
++mod_opt_type(purge_older_days) ->
++ fun (I) when is_integer(I) -> I end;
++mod_opt_type(_) ->
++ [dbs, vhosts, poll_users_settings, groupchat, dolog_default, ignore_jids, purge_older_days].
+
+handle_call({cleanup}, _From, State) ->
+ cleanup(State),
+% ejabberd_web_admin callbacks
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+handle_call({delete_messages_by_user_at, PMsgs, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
-+ Reply = DBMod:delete_messages_by_user_at(VHost, PMsgs, Date),
++ Reply = DBMod:delete_messages_by_user_at(VHost, PMsgs, binary_to_list(Date)),
+ {reply, Reply, State};
+handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
-+ Reply = DBMod:delete_all_messages_by_user_at(User, VHost, Date),
++ Reply = DBMod:delete_all_messages_by_user_at(binary_to_list(User), VHost, binary_to_list(Date)),
+ {reply, Reply, State};
+handle_call({delete_messages_at, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
+ Reply = DBMod:delete_messages_at(VHost, Date),
+ Reply = DBMod:get_vhost_stats(VHost),
+ {reply, Reply, State};
+handle_call({get_vhost_stats_at, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
-+ Reply = DBMod:get_vhost_stats_at(VHost, Date),
++ Reply = DBMod:get_vhost_stats_at(VHost, binary_to_list(Date)),
+ {reply, Reply, State};
+handle_call({get_user_stats, User}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
-+ Reply = DBMod:get_user_stats(User, VHost),
++ Reply = DBMod:get_user_stats(binary_to_list(User), VHost),
+ {reply, Reply, State};
+handle_call({get_user_messages_at, User, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
-+ Reply = DBMod:get_user_messages_at(User, VHost, Date),
++ Reply = DBMod:get_user_messages_at(binary_to_list(User), VHost, binary_to_list(Date)),
+ {reply, Reply, State};
+handle_call({get_user_settings, User}, _From, #state{dbmod=_DBMod, vhost=VHost}=State) ->
+ Reply = case ets:match_object(ets_settings_table(VHost),
+ case ets:match_object(ets_settings_table(VHost),
+ #user_settings{owner_name=User, _='_'}) of
+ [Set] ->
-+ ?MYDEBUG("Settings is equal", []),
+ ok;
+ _ ->
-+ case DBMod:set_user_settings(User, VHost, Set) of
++ case DBMod:set_user_settings(binary_to_list(User), VHost, Set) of
+ error ->
+ error;
+ ok ->
+handle_cast({remove_user, User}, #state{dbmod=DBMod, vhost=VHost}=State) ->
+ case State#state.drop_messages_on_user_removal of
+ true ->
-+ DBMod:drop_user(User, VHost),
++ DBMod:drop_user(binary_to_list(User), VHost),
+ ?INFO_MSG("Launched ~s@~s removal", [User, VHost]);
+ false ->
+ ?INFO_MSG("Message removing is disabled. Keeping messages for ~s@~s", [User, VHost])
+ DBMod:rebuild_stats(VHost),
+ {noreply, State};
+handle_cast({copy_messages, Backend}, State) ->
-+ spawn(?MODULE, copy_messages, [[State, Backend]]),
++ spawn(?MODULE, copy_messages, [[State, Backend, []]]),
+ {noreply, State};
+handle_cast({copy_messages, Backend, Date}, State) ->
-+ spawn(?MODULE, copy_messages, [[State, Backend, Date]]),
++ spawn(?MODULE, copy_messages, [[State, Backend, [binary_to_list(Date)]]]),
+ {noreply, State};
+handle_cast(Msg, State) ->
+ ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
+ {stop, db_connection_failed, State};
+ {ok, SPid} ->
+ ?INFO_MSG("~p connection established", [DBMod]),
-+
++
+ MonRef = erlang:monitor(process, SPid),
+
+ ets:new(ets_settings_table(VHost), [named_table,public,set,{keypos, #user_settings.owner_name}]),
-+ {ok, DoLog} = DBMod:get_users_settings(VHost),
++ DoLog = case DBMod:get_users_settings(VHost) of
++ {ok, Settings} -> [Sett#user_settings{owner_name = iolist_to_binary(Sett#user_settings.owner_name)} || Sett <- Settings];
++ {error, _Reason} -> []
++ end,
+ ets:insert(ets_settings_table(VHost), DoLog),
+
+ TrefPurge = set_purge_timer(State#state.purge_older_days),
+ ejabberd_hooks:add(remove_user, VHost, ?MODULE, remove_user, 90),
+ ejabberd_hooks:add(user_send_packet, VHost, ?MODULE, send_packet, 90),
+ ejabberd_hooks:add(user_receive_packet, VHost, ?MODULE, receive_packet, 90),
-+ ejabberd_hooks:add(offline_message_hook, VHost, ?MODULE, offline_packet, 10),
-+
-+ ejabberd_hooks:add(disco_local_items, VHost, ?MODULE, get_local_items, 110),
-+ ejabberd_hooks:add(disco_local_features, VHost, ?MODULE, get_local_features, 110),
-+ ejabberd_hooks:add(disco_local_identity, VHost, ?MODULE, get_local_identity, 110),
-+ %ejabberd_hooks:add(disco_sm_items, VHost, ?MODULE, get_sm_items, 110),
-+ %ejabberd_hooks:add(disco_sm_features, VHost, ?MODULE, get_sm_features, 110),
-+ %ejabberd_hooks:add(disco_sm_identity, VHost, ?MODULE, get_sm_identity, 110),
-+ ejabberd_hooks:add(adhoc_local_items, VHost, ?MODULE, adhoc_local_items, 110),
-+ ejabberd_hooks:add(adhoc_local_commands, VHost, ?MODULE, adhoc_local_commands, 110),
-+ %ejabberd_hooks:add(adhoc_sm_items, VHost, ?MODULE, adhoc_sm_items, 110),
-+ %ejabberd_hooks:add(adhoc_sm_commands, VHost, ?MODULE, adhoc_sm_commands, 110),
++ ejabberd_hooks:add(offline_message_hook, VHost, ?MODULE, offline_message, 40),
++
++ ejabberd_hooks:add(adhoc_local_commands, VHost, ?MODULE, adhoc_local_commands, 50),
++ ejabberd_hooks:add(disco_local_items, VHost, ?MODULE, get_local_items, 50),
++ ejabberd_hooks:add(disco_local_identity, VHost, ?MODULE, get_local_identity, 50),
++ ejabberd_hooks:add(disco_local_features, VHost, ?MODULE, get_local_features, 50),
++ ejabberd_hooks:add(adhoc_local_items, VHost, ?MODULE, adhoc_local_items, 50),
+
+ ejabberd_hooks:add(webadmin_menu_host, VHost, ?MODULE, webadmin_menu, 70),
+ ejabberd_hooks:add(webadmin_user, VHost, ?MODULE, webadmin_user, 50),
+
+ ?MYDEBUG("Added hooks for ~p", [VHost]),
+
-+ %ejabberd_ctl:register_commands(
-+ % VHost,
-+ % [{"rebuild_stats", "rebuild mod_logdb module stats for vhost"}],
-+ % ?MODULE, rebuild_stats),
-+ %Supported_backends = lists:flatmap(fun({Backend, _Opts}) ->
-+ % [atom_to_list(Backend), " "]
-+ % end, State#state.dbs),
-+ %ejabberd_ctl:register_commands(
-+ % VHost,
-+ % [{"copy_messages backend", "copy messages from backend to current backend. backends could be: " ++ Supported_backends }],
-+ % ?MODULE, copy_messages_ctl),
++ ejabberd_commands:register_commands(get_commands_spec()),
+ ?MYDEBUG("Registered commands for ~p", [VHost]),
+
+ NewState=State#state{monref = MonRef, backendPid=SPid, purgeRef=TrefPurge, pollRef=TrefPoll},
+%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% TODO: change to/from to list as sql stores it as list
-+send_packet(Owner, Peer, P) ->
++send_packet({Pkt, #{jid := Owner} = C2SState}) ->
+ VHost = Owner#jid.lserver,
++ Peer = xmpp:get_to(Pkt),
++ %?MYDEBUG("send_packet. Peer=~p, Owner=~p", [Peer, Owner]),
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
-+ gen_server:cast(Proc, {addlog, to, Owner, Peer, P}).
++ gen_server:cast(Proc, {addlog, to, Owner, Peer, Pkt}),
++ {Pkt, C2SState}.
+
-+offline_packet(Peer, Owner, P) ->
++receive_packet({Pkt, #{jid := Owner} = C2SState}) ->
+ VHost = Owner#jid.lserver,
++ Peer = xmpp:get_from(Pkt),
++ %?MYDEBUG("receive_packet. Pkt=~p", [Pkt]),
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
-+ gen_server:cast(Proc, {addlog, from, Owner, Peer, P}).
++ gen_server:cast(Proc, {addlog, from, Owner, Peer, Pkt}),
++ {Pkt, C2SState}.
+
-+receive_packet(_JID, Peer, Owner, P) ->
++offline_message({_Action, #message{from = Peer, to = Owner} = Pkt} = Acc) ->
+ VHost = Owner#jid.lserver,
++ %?MYDEBUG("offline_message. Pkt=~p", [Pkt]),
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
-+ gen_server:cast(Proc, {addlog, from, Owner, Peer, P}).
++ gen_server:cast(Proc, {addlog, from, Owner, Peer, Pkt}),
++ Acc.
+
+remove_user(User, Server) ->
-+ LUser = jlib:nodeprep(User),
-+ LServer = jlib:nameprep(Server),
++ LUser = jid:nodeprep(User),
++ LServer = jid:nameprep(Server),
+ Proc = gen_mod:get_module_proc(LServer, ?PROCNAME),
+ gen_server:cast(Proc, {remove_user, LUser}).
+
+% ejabberdctl
+%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-+rebuild_stats(_Val, VHost, ["rebuild_stats"]) ->
++rebuild_stats(VHost) ->
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
+ gen_server:cast(Proc, {rebuild_stats}),
-+ {stop, ?STATUS_SUCCESS};
-+rebuild_stats(Val, _VHost, _Args) ->
-+ Val.
++ ok.
+
-+copy_messages_ctl(_Val, VHost, ["copy_messages", Backend]) ->
++copy_messages_ctl(VHost, Backend, <<"all">>) ->
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
+ gen_server:cast(Proc, {copy_messages, Backend}),
-+ {stop, ?STATUS_SUCCESS};
-+copy_messages_ctl(_Val, VHost, ["copy_messages", Backend, Date]) ->
++ ok;
++copy_messages_ctl(VHost, Backend, Date) ->
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
+ gen_server:cast(Proc, {copy_messages, Backend, Date}),
-+ {stop, ?STATUS_SUCCESS};
-+copy_messages_ctl(Val, _VHost, _Args) ->
-+ Val.
++ ok.
++
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% misc operations
+
+% handle_cast({addlog, E}, _)
+% raw packet -> #msg
-+packet_parse(Owner, Peer, Packet, Direction, State) ->
-+ case xml:get_subtag(Packet, "body") of
-+ false ->
-+ ignore;
-+ Body_xml ->
-+ Message_type =
-+ case xml:get_tag_attr_s("type", Packet) of
-+ [] -> "normal";
-+ MType -> MType
-+ end,
-+
-+ case Message_type of
-+ "groupchat" when State#state.groupchat == send, Direction == to ->
-+ ok;
-+ "groupchat" when State#state.groupchat == send, Direction == from ->
-+ throw(ignore);
-+ "groupchat" when State#state.groupchat == half ->
-+ Rooms = ets:match(muc_online_room, '$1'),
-+ Ni=lists:foldl(fun([{muc_online_room, {GName, GHost}, Pid}], Names) ->
-+ case gen_fsm:sync_send_all_state_event(Pid, {get_jid_nick,Owner}) of
-+ [] -> Names;
-+ Nick ->
-+ lists:append(Names, [jlib:jid_to_string({GName, GHost, Nick})])
-+ end
-+ end, [], Rooms),
-+ case lists:member(jlib:jid_to_string(Peer), Ni) of
-+ true when Direction == from ->
-+ throw(ignore);
-+ _ ->
-+ ok
-+ end;
-+ "groupchat" when State#state.groupchat == none ->
-+ throw(ignore);
-+ _ ->
-+ ok
-+ end,
++packet_parse(_Owner, _Peer, #message{type = error}, _Direction, _State) ->
++ ignore;
++packet_parse(_Owner, _Peer, #message{meta = #{sm_copy := true}}, _Direction, _State) ->
++ ignore;
++packet_parse(_Owner, _Peer, #message{meta = #{from_offline := true}}, _Direction, _State) ->
++ ignore;
++packet_parse(Owner, Peer, #message{body = Body, subject = Subject, type = Type}, Direction, State) ->
++ %?MYDEBUG("Owner=~p, Peer=~p, Direction=~p", [Owner, Peer, Direction]),
++ %?MYDEBUG("Body=~p, Subject=~p, Type=~p", [Body, Subject, Type]),
++ SubjectText = xmpp:get_text(Subject),
++ BodyText = xmpp:get_text(Body),
++ if (SubjectText == <<"">>) and (BodyText == <<"">>) ->
++ throw(ignore);
++ true -> ok
++ end,
+
-+ Message_body = xml:get_tag_cdata(Body_xml),
-+ Message_subject =
-+ case xml:get_subtag(Packet, "subject") of
-+ false ->
-+ "";
-+ Subject_xml ->
-+ xml:get_tag_cdata(Subject_xml)
-+ end,
++ case Type of
++ groupchat when State#state.groupchat == send, Direction == to ->
++ ok;
++ groupchat when State#state.groupchat == send, Direction == from ->
++ throw(ignore);
++ groupchat when State#state.groupchat == none ->
++ throw(ignore);
++ _ ->
++ ok
++ end,
+
-+ OwnerName = stringprep:tolower(Owner#jid.user),
-+ PName = stringprep:tolower(Peer#jid.user),
-+ PServer = stringprep:tolower(Peer#jid.server),
-+ PResource = Peer#jid.resource,
-+
-+ #msg{timestamp=get_timestamp(),
-+ owner_name=OwnerName,
-+ peer_name=PName,
-+ peer_server=PServer,
-+ peer_resource=PResource,
-+ direction=Direction,
-+ type=Message_type,
-+ subject=Message_subject,
-+ body=Message_body}
-+ end.
++ #msg{timestamp = get_timestamp(),
++ owner_name = stringprep:tolower(Owner#jid.user),
++ peer_name = stringprep:tolower(Peer#jid.user),
++ peer_server = stringprep:tolower(Peer#jid.server),
++ peer_resource = Peer#jid.resource,
++ direction = Direction,
++ type = misc:atom_to_binary(Type),
++ subject = SubjectText,
++ body = BodyText};
++packet_parse(_, _, _, _, _) ->
++ ignore.
+
+% called from handle_cast({addlog, _}, _) -> true (log messages) | false (do not log messages)
+filter(Owner, Peer, State) ->
-+ OwnerStr = Owner#jid.luser++"@"++Owner#jid.lserver,
-+ OwnerServ = "@"++Owner#jid.lserver,
-+ PeerStr = Peer#jid.luser++"@"++Peer#jid.lserver,
-+ PeerServ = "@"++Peer#jid.lserver,
++ OwnerBin = << (Owner#jid.luser)/binary, "@", (Owner#jid.lserver)/binary >>,
++ OwnerServ = << "@", (Owner#jid.lserver)/binary >>,
++ PeerBin = << (Peer#jid.luser)/binary, "@", (Peer#jid.lserver)/binary >>,
++ PeerServ = << "@", (Peer#jid.lserver)/binary >>,
+
+ LogTo = case ets:match_object(ets_settings_table(State#state.vhost),
+ #user_settings{owner_name=Owner#jid.luser, _='_'}) of
+ [#user_settings{dolog_default=Default,
+ dolog_list=DLL,
+ donotlog_list=DNLL}] ->
-+ A = lists:member(PeerStr, DLL),
-+ B = lists:member(PeerStr, DNLL),
++
++ A = lists:member(PeerBin, DLL),
++ B = lists:member(PeerBin, DNLL),
+ if
+ A -> true;
+ B -> false;
+ true -> State#state.dolog_default
+ end;
+ _ -> State#state.dolog_default
-+ end,
-+
-+ lists:all(fun(O) -> O end,
-+ [not lists:member(OwnerStr, State#state.ignore_jids),
-+ not lists:member(PeerStr, State#state.ignore_jids),
++ end,
++ lists:all(fun(O) -> O end,
++ [not lists:member(OwnerBin, State#state.ignore_jids),
++ not lists:member(PeerBin, State#state.ignore_jids),
+ not lists:member(OwnerServ, State#state.ignore_jids),
+ not lists:member(PeerServ, State#state.ignore_jids),
+ LogTo]).
+ DateDiff = list_to_integer(Days)*24*60*60,
+ ?MYDEBUG("Purging tables older than ~s days", [Days]),
+ lists:foreach(fun(Date) ->
-+ {ok, [Year, Month, Day]} = regexp:split(Date, "[^0-9]+"),
-+ DateInSec = calendar:datetime_to_gregorian_seconds({{list_to_integer(Year), list_to_integer(Month), list_to_integer(Day)}, {0,0,1}}),
++ [Year, Month, Day] = ejabberd_regexp:split(iolist_to_binary(Date), <<"[^0-9]+">>),
++ DateInSec = calendar:datetime_to_gregorian_seconds({{binary_to_integer(Year), binary_to_integer(Month), binary_to_integer(Day)}, {0,0,1}}),
+ if
+ (DateNow - DateInSec) > DateDiff ->
+ gen_server:call(Proc, {delete_messages_at, Date});
-+ true ->
++ true ->
+ ?MYDEBUG("Skipping messages at ~p", [Date])
+ end
+ end, Dates).
+sort_stats(Stats) ->
+ % Stats = [{"2003-4-15",1}, {"2006-8-18",1}, ... ]
+ CFun = fun({TableName, Count}) ->
-+ {ok, [Year, Month, Day]} = regexp:split(TableName, "[^0-9]+"),
-+ { calendar:datetime_to_gregorian_seconds({{list_to_integer(Year), list_to_integer(Month), list_to_integer(Day)}, {0,0,1}}), Count }
++ [Year, Month, Day] = ejabberd_regexp:split(iolist_to_binary(TableName), <<"[^0-9]+">>),
++ { calendar:datetime_to_gregorian_seconds({{binary_to_integer(Year), binary_to_integer(Month), binary_to_integer(Day)}, {0,0,1}}), Count }
+ end,
+ % convert to [{63364377601,1}, {63360662401,1}, ... ]
+ CStats = lists:map(CFun, Stats),
+%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+user_messages_at_parse_query(VHost, Date, Msgs, Query) ->
-+ case lists:keysearch("delete", 1, Query) of
++ case lists:keysearch(<<"delete">>, 1, Query) of
+ {value, _} ->
+ PMsgs = lists:filter(
+ fun(Msg) ->
-+ ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg#msg.timestamp))),
-+ lists:member({"selected", ID}, Query)
++ ID = misc:encode_base64(term_to_binary(Msg#msg.timestamp)),
++ lists:member({<<"selected">>, ID}, Query)
+ end, Msgs),
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
+ gen_server:call(Proc, {delete_messages_by_user_at, PMsgs, Date}, ?CALL_TIMEOUT);
+ end.
+
+user_messages_parse_query(User, VHost, Query) ->
-+ case lists:keysearch("delete", 1, Query) of
++ case lists:keysearch(<<"delete">>, 1, Query) of
+ {value, _} ->
+ Dates = get_dates(VHost),
+ PDates = lists:filter(
+ fun(Date) ->
-+ ID = jlib:encode_base64(binary_to_list(term_to_binary(User++Date))),
-+ lists:member({"selected", ID}, Query)
++ ID = misc:encode_base64( << User/binary, (iolist_to_binary(Date))/binary >> ),
++ lists:member({<<"selected">>, ID}, Query)
+ end, Dates),
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
+ Rez = lists:foldl(
+ fun(Date, Acc) ->
+ lists:append(Acc,
+ [gen_server:call(Proc,
-+ {delete_all_messages_by_user_at, User, Date},
++ {delete_all_messages_by_user_at, User, iolist_to_binary(Date)},
+ ?CALL_TIMEOUT)])
+ end, [], PDates),
+ case lists:member(error, Rez) of
+ end.
+
+vhost_messages_parse_query(VHost, Query) ->
-+ case lists:keysearch("delete", 1, Query) of
++ case lists:keysearch(<<"delete">>, 1, Query) of
+ {value, _} ->
+ Dates = get_dates(VHost),
+ PDates = lists:filter(
+ fun(Date) ->
-+ ID = jlib:encode_base64(binary_to_list(term_to_binary(VHost++Date))),
-+ lists:member({"selected", ID}, Query)
++ ID = misc:encode_base64( << VHost/binary, (iolist_to_binary(Date))/binary >> ),
++ lists:member({<<"selected">>, ID}, Query)
+ end, Dates),
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
+ Rez = lists:foldl(fun(Date, Acc) ->
+ end.
+
+vhost_messages_at_parse_query(VHost, Date, Stats, Query) ->
-+ case lists:keysearch("delete", 1, Query) of
++ case lists:keysearch(<<"delete">>, 1, Query) of
+ {value, _} ->
+ PStats = lists:filter(
+ fun({User, _Count}) ->
-+ ID = jlib:encode_base64(binary_to_list(term_to_binary(User++VHost))),
-+ lists:member({"selected", ID}, Query)
++ ID = misc:encode_base64( << (iolist_to_binary(User))/binary, VHost/binary >> ),
++ lists:member({<<"selected">>, ID}, Query)
+ end, Stats),
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
+ Rez = lists:foldl(fun({User, _Count}, Acc) ->
+ lists:append(Acc, [gen_server:call(Proc,
+ {delete_all_messages_by_user_at,
-+ User, Date},
++ iolist_to_binary(User), iolist_to_binary(Date)},
+ ?CALL_TIMEOUT)])
+ end, [], PStats),
+ case lists:member(error, Rez) of
+ nothing
+ end.
+
-+copy_messages([#state{vhost=VHost}=State, From]) ->
-+ ?INFO_MSG("Going to copy messages from ~p for ~p", [From, VHost]),
-+
++copy_messages([#state{vhost=VHost}=State, From, DatesIn]) ->
+ {FromDBName, FromDBOpts} =
-+ case lists:keysearch(list_to_atom(From), 1, State#state.dbs) of
++ case lists:keysearch(misc:binary_to_atom(From), 1, State#state.dbs) of
+ {value, {FN, FO}} ->
+ {FN, FO};
+ false ->
+ FromDBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(FromDBName)),
+
+ {ok, _FromPid} = FromDBMod:start(VHost, FromDBOpts),
-+
-+ Dates = FromDBMod:get_dates(VHost),
++
++ Dates = case DatesIn of
++ [] -> FromDBMod:get_dates(VHost);
++ _ -> DatesIn
++ end,
++
+ DatesLength = length(Dates),
+
-+ lists:foldl(fun(Date, Acc) ->
-+ case copy_messages_int([FromDBMod, State#state.dbmod, VHost, Date]) of
-+ ok ->
-+ ?INFO_MSG("Copied messages at ~p (~p/~p)", [Date, Acc, DatesLength]);
-+ Value ->
-+ ?ERROR_MSG("Failed to copy messages at ~p (~p/~p): ~p", [Date, Acc, DatesLength, Value]),
-+ FromDBMod:stop(VHost),
-+ throw(error)
-+ end,
-+ Acc + 1
-+ end, 1, Dates),
-+ ?INFO_MSG("Copied messages from ~p", [From]),
-+ FromDBMod:stop(VHost);
-+copy_messages([#state{vhost=VHost}=State, From, Date]) ->
-+ {value, {FromDBName, FromDBOpts}} = lists:keysearch(list_to_atom(From), 1, State#state.dbs),
-+ FromDBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(FromDBName)),
-+ {ok, _FromPid} = FromDBMod:start(VHost, FromDBOpts),
-+ case catch copy_messages_int([FromDBMod, State#state.dbmod, VHost, Date]) of
-+ {'exit', Reason} ->
-+ ?ERROR_MSG("Failed to copy messages at ~p: ~p", [Date, Reason]);
-+ ok ->
-+ ?INFO_MSG("Copied messages at ~p", [Date]);
-+ Value ->
-+ ?ERROR_MSG("Failed to copy messages at ~p: ~p", [Date, Value])
-+ end,
++ catch lists:foldl(fun(Date, Acc) ->
++ case catch copy_messages_int([FromDBMod, State#state.dbmod, VHost, Date]) of
++ ok ->
++ ?INFO_MSG("Copied messages at ~p (~p/~p)", [Date, Acc, DatesLength]);
++ Value ->
++ ?ERROR_MSG("Failed to copy messages at ~p (~p/~p): ~p", [Date, Acc, DatesLength, Value]),
++ throw(error)
++ end,
++ Acc + 1
++ end, 1, Dates),
++ ?INFO_MSG("copy_messages from ~p finished", [From]),
+ FromDBMod:stop(VHost).
+
+copy_messages_int([FromDBMod, ToDBMod, VHost, Date]) ->
+
+copy_messages_int_tc([FromDBMod, ToDBMod, VHost, Date]) ->
+ ?INFO_MSG("Going to copy messages from ~p for ~p at ~p", [FromDBMod, VHost, Date]),
-+
++
+ ok = FromDBMod:rebuild_stats_at(VHost, Date),
+ catch mod_logdb:rebuild_stats_at(VHost, Date),
+ {ok, FromStats} = FromDBMod:get_vhost_stats_at(VHost, Date),
-+ ToStats = case mod_logdb:get_vhost_stats_at(VHost, Date) of
++ ToStats = case mod_logdb:get_vhost_stats_at(VHost, iolist_to_binary(Date)) of
+ {ok, Stats} -> Stats;
+ {error, _} -> []
+ end,
+ StatsLength = length(FromStats),
+
+ CopyFun = if
-+ % destination table is empty
-+ FromDBMod /= mod_logdb_mnesia_old, ToStats == [] ->
++ % destination table is empty
++ ToStats == [] ->
+ fun({User, _Count}, Acc) ->
+ {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date),
+ MAcc =
+ lists:foldl(fun(Msg, MFAcc) ->
-+ ok = ToDBMod:log_message(VHost, Msg),
++ MsgBinary = Msg#msg{owner_name=iolist_to_binary(User),
++ peer_name=iolist_to_binary(Msg#msg.peer_name),
++ peer_server=iolist_to_binary(Msg#msg.peer_server),
++ peer_resource=iolist_to_binary(Msg#msg.peer_resource),
++ type=iolist_to_binary(Msg#msg.type),
++ subject=iolist_to_binary(Msg#msg.subject),
++ body=iolist_to_binary(Msg#msg.body)},
++ ok = ToDBMod:log_message(VHost, MsgBinary),
+ MFAcc + 1
+ end, 0, Msgs),
+ NewAcc = Acc + 1,
+ %timer:sleep(100),
+ NewAcc
+ end;
-+ % destination table is not empty
-+ FromDBMod /= mod_logdb_mnesia_old, ToStats /= [] ->
++ % destination table is not empty
++ true ->
+ fun({User, _Count}, Acc) ->
+ {ok, ToMsgs} = ToDBMod:get_user_messages_at(User, VHost, Date),
+ lists:foreach(fun(#msg{timestamp=Tst}) when length(Tst) == 16 ->
+ lists:foldl(fun(#msg{timestamp=ToTimestamp} = Msg, MFAcc) ->
+ case ets:member(mod_logdb_temp, ToTimestamp) of
+ false ->
-+ ok = ToDBMod:log_message(VHost, Msg),
++ MsgBinary = Msg#msg{owner_name=iolist_to_binary(User),
++ peer_name=iolist_to_binary(Msg#msg.peer_name),
++ peer_server=iolist_to_binary(Msg#msg.peer_server),
++ peer_resource=iolist_to_binary(Msg#msg.peer_resource),
++ type=iolist_to_binary(Msg#msg.type),
++ subject=iolist_to_binary(Msg#msg.subject),
++ body=iolist_to_binary(Msg#msg.body)},
++ ok = ToDBMod:log_message(VHost, MsgBinary),
+ ets:insert(mod_logdb_temp, {ToTimestamp}),
+ MFAcc + 1;
+ true ->
+ ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
+ %timer:sleep(100),
+ NewAcc
-+ end;
-+ % copying from mod_logmnesia
-+ true ->
-+ fun({User, _Count}, Acc) ->
-+ ToStats =
-+ case ToDBMod:get_user_messages_at(User, VHost, Date) of
-+ {ok, []} ->
-+ ok;
-+ {ok, ToMsgs} ->
-+ lists:foreach(fun(#msg{timestamp=Tst}) when length(Tst) == 16 ->
-+ ets:insert(mod_logdb_temp, {Tst});
-+ % mysql, pgsql removes final zeros after decimal point
-+ (#msg{timestamp=Tst}) when length(Tst) < 15 ->
-+ {F, _} = string:to_float(Tst++".0"),
-+ [T] = io_lib:format("~.5f", [F]),
-+ ets:insert(mod_logdb_temp, {T})
-+ end, ToMsgs);
-+ {error, _} ->
-+ ok
-+ end,
-+ {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date),
-+
-+ MAcc =
-+ lists:foldl(
-+ fun({msg, TU, TS, TR, FU, FS, FR, Type, Subj, Body, Timest},
-+ MFAcc) ->
-+ [Timestamp] = if is_float(Timest) == true ->
-+ io_lib:format("~.5f", [Timest]);
-+ % early versions of mod_logmnesia
-+ is_integer(Timest) == true ->
-+ io_lib:format("~.5f", [Timest-719528*86400.0]);
-+ true ->
-+ ?ERROR_MSG("Incorrect timestamp ~p", [Timest]),
-+ throw(error)
-+ end,
-+ case ets:member(mod_logdb_temp, Timestamp) of
-+ false ->
-+ if
-+ % from
-+ TS == VHost ->
-+ TMsg = #msg{timestamp=Timestamp,
-+ owner_name=TU,
-+ peer_name=FU, peer_server=FS, peer_resource=FR,
-+ direction=from,
-+ type=Type,
-+ subject=Subj, body=Body},
-+ ok = ToDBMod:log_message(VHost, TMsg);
-+ true -> ok
-+ end,
-+ if
-+ % to
-+ FS == VHost ->
-+ FMsg = #msg{timestamp=Timestamp,
-+ owner_name=FU,
-+ peer_name=TU, peer_server=TS, peer_resource=TR,
-+ direction=to,
-+ type=Type,
-+ subject=Subj, body=Body},
-+ ok = ToDBMod:log_message(VHost, FMsg);
-+ true -> ok
-+ end,
-+ ets:insert(mod_logdb_temp, {Timestamp}),
-+ MFAcc + 1;
-+ true -> % not ets:member
-+ MFAcc
-+ end % case
-+ end, 0, Msgs), % foldl
-+ NewAcc = Acc + 1,
-+ ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
-+ %timer:sleep(100),
-+ NewAcc
-+ end % fun
-+ end, % if FromDBMod /= mod_logdb_mnesia_old
++ end
++ end,
+
+ if
+ FromStats == [] ->
+
+ ok.
+
-+list_to_bool(Num) ->
++list_to_bool(Num) when is_binary(Num) ->
++ list_to_bool(binary_to_list(Num));
++list_to_bool(Num) when is_list(Num) ->
+ case lists:member(Num, ["t", "true", "y", "yes", "1"]) of
+ true ->
+ true;
+list_to_string([]) ->
+ "";
+list_to_string(List) when is_list(List) ->
-+ Str = lists:flatmap(fun(Elm) -> Elm ++ "\n" end, List),
++ Str = lists:flatmap(fun(Elm) when is_binary(Elm) ->
++ binary_to_list(Elm) ++ "\n";
++ (Elm) when is_list(Elm) ->
++ Elm ++ "\n"
++ end, List),
+ lists:sublist(Str, length(Str)-1).
+
+string_to_list(null) ->
+string_to_list([]) ->
+ [];
+string_to_list(String) ->
-+ {ok, List} = regexp:split(String, "\n"),
-+ List.
++ ejabberd_regexp:split(iolist_to_binary(String), <<"\n">>).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-define(ITEMS_RESULT(Allow, LNode, Fallback),
+ case Allow of
-+ deny ->
-+ Fallback;
++ deny -> Fallback;
+ allow ->
+ case get_local_items(LServer, LNode,
-+ jlib:jid_to_string(To), Lang) of
-+ {result, Res} ->
-+ {result, Res};
-+ {error, Error} ->
-+ {error, Error}
++ jid:encode(To), Lang) of
++ {result, Res} -> {result, Res};
++ {error, Error} -> {error, Error}
+ end
+ end).
+
-+get_local_items(Acc, From, #jid{lserver = LServer} = To, "", Lang) ->
++get_local_items(Acc, From, #jid{lserver = LServer} = To,
++ <<"">>, Lang) ->
+ case gen_mod:is_loaded(LServer, mod_adhoc) of
-+ false ->
-+ Acc;
++ false -> Acc;
+ _ ->
+ Items = case Acc of
+ {result, Its} -> Its;
+ if
+ AllowUser == allow; AllowAdmin == allow ->
+ case get_local_items(LServer, [],
-+ jlib:jid_to_string(To), Lang) of
++ jid:encode(To), Lang) of
+ {result, Res} ->
+ {result, Items ++ Res};
+ {error, _Error} ->
+ {result, Items}
+ end
+ end;
-+get_local_items(Acc, From, #jid{lserver = LServer} = To, Node, Lang) ->
++get_local_items(Acc, From, #jid{lserver = LServer} = To,
++ Node, Lang) ->
+ case gen_mod:is_loaded(LServer, mod_adhoc) of
-+ false ->
-+ Acc;
++ false -> Acc;
+ _ ->
-+ LNode = string:tokens(Node, "/"),
++ LNode = tokenize(Node),
+ AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
++ Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang),
+ case LNode of
-+ ["mod_logdb"] ->
-+ ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
-+ ["mod_logdb_users"] ->
-+ ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
-+ ["mod_logdb_users", [$@ | _]] ->
-+ ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
-+ ["mod_logdb_users", _User] ->
-+ ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
-+ ["mod_logdb_settings"] ->
-+ ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
++ [<<"mod_logdb">>] ->
++ ?ITEMS_RESULT(AllowAdmin, LNode, {error, Err});
++ [<<"mod_logdb_users">>] ->
++ ?ITEMS_RESULT(AllowAdmin, LNode, {error, Err});
++ [<<"mod_logdb_users">>, <<$@, _/binary>>] ->
++ ?ITEMS_RESULT(AllowAdmin, LNode, {error, Err});
++ [<<"mod_logdb_users">>, _User] ->
++ ?ITEMS_RESULT(AllowAdmin, LNode, {error, Err});
++ [<<"mod_logdb_settings">>] ->
++ ?ITEMS_RESULT(AllowAdmin, LNode, {error, Err});
+ _ ->
+ Acc
+ end
+ end.
+
++-define(T(Lang, Text), translate:translate(Lang, Text)).
++
+-define(NODE(Name, Node),
-+ {xmlelement, "item",
-+ [{"jid", Server},
-+ {"name", translate:translate(Lang, Name)},
-+ {"node", Node}], []}).
++ #disco_item{jid = jid:make(Server),
++ node = Node,
++ name = ?T(Lang, Name)}).
++
++-define(NS_ADMINX(Sub),
++ <<(?NS_ADMIN)/binary, "#", Sub/binary>>).
++
++tokenize(Node) -> str:tokens(Node, <<"/#">>).
+
+get_local_items(_Host, [], Server, Lang) ->
+ {result,
-+ [?NODE("Messages logging engine", "mod_logdb")]
++ [?NODE(<<"Messages logging engine">>, <<"mod_logdb">>)]
+ };
-+get_local_items(_Host, ["mod_logdb"], Server, Lang) ->
++get_local_items(_Host, [<<"mod_logdb">>], Server, Lang) ->
+ {result,
-+ [?NODE("Messages logging engine users", "mod_logdb_users"),
-+ ?NODE("Messages logging engine settings", "mod_logdb_settings")]
++ [?NODE(<<"Messages logging engine users">>, <<"mod_logdb_users">>),
++ ?NODE(<<"Messages logging engine settings">>, <<"mod_logdb_settings">>)]
+ };
-+get_local_items(Host, ["mod_logdb_users"], Server, Lang) ->
-+ {result, get_all_vh_users(Host, Server, Lang)};
-+get_local_items(_Host, ["mod_logdb_users", [$@ | Diap]], Server, Lang) ->
-+ case catch ejabberd_auth:dirty_get_registered_users() of
-+ {'EXIT', _Reason} ->
-+ ?ERR_INTERNAL_SERVER_ERROR;
-+ Users ->
-+ SUsers = lists:sort([{S, U} || {U, S} <- Users]),
-+ case catch begin
-+ {ok, [S1, S2]} = regexp:split(Diap, "-"),
-+ N1 = list_to_integer(S1),
-+ N2 = list_to_integer(S2),
-+ Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
-+ lists:map(fun({S, U}) ->
-+ ?NODE(U ++ "@" ++ S, "mod_logdb_users/" ++ U ++ "@" ++ S)
-+ end, Sub)
-+ end of
-+ {'EXIT', _Reason} ->
-+ ?ERR_NOT_ACCEPTABLE;
-+ Res ->
-+ {result, Res}
-+ end
++get_local_items(Host, [<<"mod_logdb_users">>], Server, _Lang) ->
++ {result, get_all_vh_users(Host, Server)};
++get_local_items(Host, [<<"mod_logdb_users">>, <<$@, Diap/binary>>], Server, Lang) ->
++ Users = ejabberd_auth:get_vh_registered_users(Host),
++ SUsers = lists:sort([{S, U} || {U, S} <- Users]),
++ try
++ [S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
++ N1 = binary_to_integer(S1),
++ N2 = binary_to_integer(S2),
++ Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
++ {result, lists:map(fun({S, U}) ->
++ ?NODE(<< U/binary, "@", S/binary >>,
++ << (iolist_to_binary("mod_logdb_users/"))/binary, U/binary, "@", S/binary >>)
++ end, Sub)}
++ catch _:_ ->
++ xmpp:err_not_acceptable()
+ end;
-+get_local_items(_Host, ["mod_logdb_users", _User], _Server, _Lang) ->
++get_local_items(_Host, [<<"mod_logdb_users">>, _User], _Server, _Lang) ->
+ {result, []};
-+get_local_items(_Host, ["mod_logdb_settings"], _Server, _Lang) ->
++get_local_items(_Host, [<<"mod_logdb_settings">>], _Server, _Lang) ->
+ {result, []};
+get_local_items(_Host, Item, _Server, _Lang) ->
+ ?MYDEBUG("asked for items in ~p", [Item]),
-+ {error, ?ERR_ITEM_NOT_FOUND}.
++ {error, xmpp:err_item_not_found()}.
+
-+-define(INFO_RESULT(Allow, Feats),
++-define(INFO_RESULT(Allow, Feats, Lang),
+ case Allow of
-+ deny ->
-+ {error, ?ERR_FORBIDDEN};
-+ allow ->
-+ {result, Feats}
++ deny -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
++ allow -> {result, Feats}
+ end).
+
-+get_local_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
++get_local_features(Acc, From,
++ #jid{lserver = LServer} = _To, Node, Lang) ->
+ case gen_mod:is_loaded(LServer, mod_adhoc) of
+ false ->
+ Acc;
+ _ ->
-+ LNode = string:tokens(Node, "/"),
++ LNode = tokenize(Node),
+ AllowUser = acl:match_rule(LServer, mod_logdb, From),
+ AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
+ case LNode of
-+ ["mod_logdb"] when AllowUser == allow; AllowAdmin == allow ->
-+ ?INFO_RESULT(allow, [?NS_COMMANDS]);
-+ ["mod_logdb"] ->
-+ ?INFO_RESULT(deny, [?NS_COMMANDS]);
-+ ["mod_logdb_users"] ->
-+ ?INFO_RESULT(AllowAdmin, []);
-+ ["mod_logdb_users", [$@ | _]] ->
-+ ?INFO_RESULT(AllowAdmin, []);
-+ ["mod_logdb_users", _User] ->
-+ ?INFO_RESULT(AllowAdmin, [?NS_COMMANDS]);
-+ ["mod_logdb_settings"] ->
-+ ?INFO_RESULT(AllowAdmin, [?NS_COMMANDS]);
++ [<<"mod_logdb">>] when AllowUser == allow; AllowAdmin == allow ->
++ ?INFO_RESULT(allow, [?NS_COMMANDS], Lang);
++ [<<"mod_logdb">>] ->
++ ?INFO_RESULT(deny, [?NS_COMMANDS], Lang);
++ [<<"mod_logdb_users">>] ->
++ ?INFO_RESULT(AllowAdmin, [], Lang);
++ [<<"mod_logdb_users">>, [$@ | _]] ->
++ ?INFO_RESULT(AllowAdmin, [], Lang);
++ [<<"mod_logdb_users">>, _User] ->
++ ?INFO_RESULT(AllowAdmin, [?NS_COMMANDS], Lang);
++ [<<"mod_logdb_settings">>] ->
++ ?INFO_RESULT(AllowAdmin, [?NS_COMMANDS], Lang);
+ [] ->
+ Acc;
+ _ ->
-+ %?MYDEBUG("asked for ~p features: ~p", [LNode, Allow]),
+ Acc
+ end
+ end.
+
+-define(INFO_IDENTITY(Category, Type, Name, Lang),
-+ [{xmlelement, "identity",
-+ [{"category", Category},
-+ {"type", Type},
-+ {"name", translate:translate(Lang, Name)}], []}]).
++ [#identity{category = Category, type = Type, name = ?T(Lang, Name)}]).
+
+-define(INFO_COMMAND(Name, Lang),
-+ ?INFO_IDENTITY("automation", "command-node", Name, Lang)).
++ ?INFO_IDENTITY(<<"automation">>, <<"command-node">>,
++ Name, Lang)).
+
+get_local_identity(Acc, _From, _To, Node, Lang) ->
-+ LNode = string:tokens(Node, "/"),
++ LNode = tokenize(Node),
+ case LNode of
-+ ["mod_logdb"] ->
-+ ?INFO_COMMAND("Messages logging engine", Lang);
-+ ["mod_logdb_users"] ->
-+ ?INFO_COMMAND("Messages logging engine users", Lang);
-+ ["mod_logdb_users", [$@ | _]] ->
-+ Acc;
-+ ["mod_logdb_users", User] ->
++ [<<"mod_logdb">>] ->
++ ?INFO_COMMAND(<<"Messages logging engine">>, Lang);
++ [<<"mod_logdb_users">>] ->
++ ?INFO_COMMAND(<<"Messages logging engine users">>, Lang);
++ [<<"mod_logdb_users">>, User] ->
+ ?INFO_COMMAND(User, Lang);
-+ ["mod_logdb_settings"] ->
-+ ?INFO_COMMAND("Messages logging engine settings", Lang);
-+ [] ->
-+ Acc;
++ [<<"mod_logdb_settings">>] ->
++ ?INFO_COMMAND(<<"Messages logging engine settings">>, Lang);
+ _ ->
+ Acc
+ end.
+
-+%get_sm_items(Acc, From, To, Node, Lang) ->
-+% ?MYDEBUG("get_sm_items Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
-+% Acc.
-+
-+%get_sm_features(Acc, From, To, Node, Lang) ->
-+% ?MYDEBUG("get_sm_features Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
-+% Acc.
-+
-+%get_sm_identity(Acc, From, To, Node, Lang) ->
-+% ?MYDEBUG("get_sm_identity Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
-+% Acc.
-+
-+adhoc_local_items(Acc, From, #jid{lserver = LServer, server = Server} = To,
-+ Lang) ->
++adhoc_local_items(Acc, From,
++ #jid{lserver = LServer, server = Server} = To, Lang) ->
++ % TODO: case acl:match_rule(LServer, ???, From) of
+ Items = case Acc of
+ {result, Its} -> Its;
+ empty -> []
+ end,
-+ Nodes = recursively_get_local_items(LServer, "", Server, Lang),
++ Nodes = recursively_get_local_items(LServer,
++ <<"">>, Server, Lang),
+ Nodes1 = lists:filter(
-+ fun(N) ->
-+ Nd = xml:get_tag_attr_s("node", N),
++ fun(#disco_item{node = Nd}) ->
+ F = get_local_features([], From, To, Nd, Lang),
+ case F of
-+ {result, [?NS_COMMANDS]} ->
-+ true;
-+ _ ->
-+ false
++ {result, [?NS_COMMANDS]} -> true;
++ _ -> false
+ end
+ end, Nodes),
+ {result, Items ++ Nodes1}.
+
-+recursively_get_local_items(_LServer, "mod_logdb_users", _Server, _Lang) ->
++recursively_get_local_items(_LServer,
++ <<"mod_logdb_users">>, _Server, _Lang) ->
+ [];
-+recursively_get_local_items(LServer, Node, Server, Lang) ->
-+ LNode = string:tokens(Node, "/"),
-+ Items = case get_local_items(LServer, LNode, Server, Lang) of
-+ {result, Res} ->
-+ Res;
-+ {error, _Error} ->
-+ []
++recursively_get_local_items(LServer,
++ Node, Server, Lang) ->
++ LNode = tokenize(Node),
++ Items = case get_local_items(LServer, LNode,
++ Server, Lang) of
++ {result, Res} -> Res;
++ {error, _Error} -> []
+ end,
+ Nodes = lists:flatten(
+ lists:map(
-+ fun(N) ->
-+ S = xml:get_tag_attr_s("jid", N),
-+ Nd = xml:get_tag_attr_s("node", N),
-+ if (S /= Server) or (Nd == "") ->
++ fun(#disco_item{jid = #jid{server = S}, node = Nd} = Item) ->
++ if (S /= Server) or (Nd == <<"">>) ->
+ [];
+ true ->
-+ [N, recursively_get_local_items(
-+ LServer, Nd, Server, Lang)]
++ [Item, recursively_get_local_items(
++ LServer, Nd, Server, Lang)]
+ end
+ end, Items)),
+ Nodes.
+-define(COMMANDS_RESULT(Allow, From, To, Request),
+ case Allow of
+ deny ->
-+ {error, ?ERR_FORBIDDEN};
++ {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
+ allow ->
+ adhoc_local_commands(From, To, Request)
+ end).
+
+adhoc_local_commands(Acc, From, #jid{lserver = LServer} = To,
-+ #adhoc_request{node = Node} = Request) ->
-+ LNode = string:tokens(Node, "/"),
++ #adhoc_command{node = Node, lang = Lang} = Request) ->
++ LNode = tokenize(Node),
+ AllowUser = acl:match_rule(LServer, mod_logdb, From),
+ AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
+ case LNode of
-+ ["mod_logdb"] when AllowUser == allow; AllowAdmin == allow ->
++ [<<"mod_logdb">>] when AllowUser == allow; AllowAdmin == allow ->
+ ?COMMANDS_RESULT(allow, From, To, Request);
-+ ["mod_logdb_users", _User] when AllowAdmin == allow ->
++ [<<"mod_logdb_users">>, <<$@, _/binary>>] when AllowAdmin == allow ->
++ Acc;
++ [<<"mod_logdb_users">>, _User] when AllowAdmin == allow ->
+ ?COMMANDS_RESULT(allow, From, To, Request);
-+ ["mod_logdb_settings"] when AllowAdmin == allow ->
++ [<<"mod_logdb_settings">>] when AllowAdmin == allow ->
+ ?COMMANDS_RESULT(allow, From, To, Request);
+ _ ->
+ Acc
+ end.
+
+adhoc_local_commands(From, #jid{lserver = LServer} = _To,
-+ #adhoc_request{lang = Lang,
++ #adhoc_command{lang = Lang,
+ node = Node,
-+ sessionid = SessionID,
++ sid = SessionID,
+ action = Action,
+ xdata = XData} = Request) ->
-+ LNode = string:tokens(Node, "/"),
++ LNode = tokenize(Node),
+ %% If the "action" attribute is not present, it is
+ %% understood as "execute". If there was no <actions/>
+ %% element in the first response (which there isn't in our
+ %% case), "execute" and "complete" are equivalent.
-+ ActionIsExecute = lists:member(Action,
-+ ["", "execute", "complete"]),
-+ if Action == "cancel" ->
++ ActionIsExecute = Action == execute orelse Action == complete,
++ if Action == cancel ->
+ %% User cancels request
-+ adhoc:produce_response(
-+ Request,
-+ #adhoc_response{status = canceled});
-+ XData == false, ActionIsExecute ->
++ #adhoc_command{status = canceled, lang = Lang,
++ node = Node, sid = SessionID};
++ XData == undefined, ActionIsExecute ->
+ %% User requests form
-+ case get_form(LServer, LNode, From, Lang) of
++ case get_form(LServer, LNode, Lang) of
+ {result, Form} ->
-+ adhoc:produce_response(
++ xmpp_util:make_adhoc_response(
+ Request,
-+ #adhoc_response{status = executing,
-+ elements = Form});
++ #adhoc_command{status = executing,
++ xdata = Form});
+ {error, Error} ->
+ {error, Error}
+ end;
-+ XData /= false, ActionIsExecute ->
++ XData /= undefined, ActionIsExecute ->
+ %% User returns form.
-+ case jlib:parse_xdata_submit(XData) of
-+ invalid ->
-+ {error, ?ERR_BAD_REQUEST};
-+ Fields ->
-+ case set_form(From, LServer, LNode, Lang, Fields) of
-+ {result, _Res} ->
-+ adhoc:produce_response(
-+ #adhoc_response{lang = Lang,
-+ node = Node,
-+ sessionid = SessionID,
-+ status = completed});
-+ {error, Error} ->
-+ {error, Error}
-+ end
++ case catch set_form(From, LServer, LNode, Lang, XData) of
++ {result, Res} ->
++ xmpp_util:make_adhoc_response(
++ Request,
++ #adhoc_command{xdata = Res, status = completed});
++ {'EXIT', _} -> {error, xmpp:err_bad_request()};
++ {error, Error} -> {error, Error}
+ end;
-+ true ->
-+ {error, ?ERR_BAD_REQUEST}
++ true ->
++ {error, xmpp:err_bad_request(<<"Unexpected action">>, Lang)}
+ end.
+
-+-define(LISTLINE(Label, Value),
-+ {xmlelement, "option", [{"label", Label}],
-+ [{xmlelement, "value", [], [{xmlcdata, Value}]}]}).
-+-define(DEFVAL(Value), {xmlelement, "value", [], [{xmlcdata, Value}]}).
++-define(TVFIELD(Type, Var, Val),
++ #xdata_field{type = Type, var = Var, values = [Val]}).
++
++-define(HFIELD(),
++ ?TVFIELD(hidden, <<"FORM_TYPE">>, (?NS_ADMIN))).
+
+get_user_form(LUser, LServer, Lang) ->
-+ %From = jlib:jid_to_string(jlib:jid_remove_resource(Jid)),
++ ?MYDEBUG("get_user_form ~p ~p", [LUser, LServer]),
++ %From = jid:encode(jid:remove_resource(Jid)),
+ #user_settings{dolog_default=DLD,
+ dolog_list=DLL,
+ donotlog_list=DNLL} = get_user_settings(LUser, LServer),
-+ {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
-+ [{xmlelement, "title", [],
-+ [{xmlcdata,
-+ translate:translate(
-+ Lang, "Messages logging engine settings")}]},
-+ {xmlelement, "instructions", [],
-+ [{xmlcdata,
-+ translate:translate(
-+ Lang, "Set logging preferences")++ ": " ++ LUser ++ "@" ++ LServer}]},
-+ % default to log
-+ {xmlelement, "field", [{"type", "list-single"},
-+ {"label",
-+ translate:translate(Lang, "Default")},
-+ {"var", "dolog_default"}],
-+ [?DEFVAL(atom_to_list(DLD)),
-+ ?LISTLINE(translate:translate(Lang, "Log Messages"), "true"),
-+ ?LISTLINE(translate:translate(Lang, "Do Not Log Messages"), "false")
-+ ]},
-+ % do log list
-+ {xmlelement, "field", [{"type", "text-multi"},
-+ {"label",
-+ translate:translate(
-+ Lang, "Log Messages")},
-+ {"var", "dolog_list"}],
-+ [{xmlelement, "value", [], [{xmlcdata, list_to_string(DLL)}]}]},
-+ % do not log list
-+ {xmlelement, "field", [{"type", "text-multi"},
-+ {"label",
-+ translate:translate(
-+ Lang, "Do Not Log Messages")},
-+ {"var", "donotlog_list"}],
-+ [{xmlelement, "value", [], [{xmlcdata, list_to_string(DNLL)}]}]}
-+ ]}]}.
++ Fs = [
++ #xdata_field{
++ type = 'list-single',
++ label = ?T(Lang, <<"Default">>),
++ var = <<"dolog_default">>,
++ values = [misc:atom_to_binary(DLD)],
++ options = [#xdata_option{label = ?T(Lang, <<"Log Messages">>),
++ value = <<"true">>},
++ #xdata_option{label = ?T(Lang, <<"Do Not Log Messages">>),
++ value = <<"false">>}]},
++ #xdata_field{
++ type = 'text-multi',
++ label = ?T(Lang, <<"Log Messages">>),
++ var = <<"dolog_list">>,
++ values = DLL},
++ #xdata_field{
++ type = 'text-multi',
++ label = ?T(Lang, <<"Do Not Log Messages">>),
++ var = <<"donotlog_list">>,
++ values = DNLL}
++ ],
++ {result, #xdata{
++ title = ?T(Lang, <<"Messages logging engine settings">>),
++ type = form,
++ instructions = [<< (?T(Lang, <<"Set logging preferences">>))/binary,
++ (iolist_to_binary(": "))/binary,
++ LUser/binary, "@", LServer/binary >>],
++ fields = [?HFIELD()|
++ Fs]}}.
+
+get_settings_form(Host, Lang) ->
-+ #state{dbmod=DBMod,
-+ dbs=DBs,
++ ?MYDEBUG("get_settings_form ~p ~p", [Host, Lang]),
++ #state{dbmod=_DBMod,
++ dbs=_DBs,
+ dolog_default=DLD,
+ ignore_jids=IgnoreJids,
+ groupchat=GroupChat,
+ drop_messages_on_user_removal=MRemoval,
+ poll_users_settings=PollTime} = mod_logdb:get_module_settings(Host),
+
-+ Backends = lists:map(fun({Backend, _Opts}) ->
-+ ?LISTLINE(atom_to_list(Backend), atom_to_list(Backend))
-+ end, DBs),
-+ DB = lists:sublist(atom_to_list(DBMod), length(atom_to_list(?MODULE)) + 2, length(atom_to_list(DBMod))),
-+ DBsL = lists:append([?DEFVAL(DB)], Backends),
-+
+ PurgeDays =
+ case PurgeDaysT of
-+ never -> "never";
-+ Num when is_integer(Num) -> integer_to_list(Num);
-+ _ -> "unknown"
++ never -> <<"never">>;
++ Num when is_integer(Num) -> integer_to_binary(Num);
++ _ -> <<"unknown">>
+ end,
-+ {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
-+ [{xmlelement, "title", [],
-+ [{xmlcdata,
-+ translate:translate(
-+ Lang, "Messages logging engine settings") ++ " (run-time)"}]},
-+ {xmlelement, "instructions", [],
-+ [{xmlcdata,
-+ translate:translate(
-+ Lang, "Set run-time settings")}]},
-+ % backends
-+ {xmlelement, "field", [{"type", "list-single"},
-+ {"label",
-+ translate:translate(Lang, "Backend")},
-+ {"var", "backend"}],
-+ DBsL},
-+ % dbs
-+ {xmlelement, "field", [{"type", "text-multi"},
-+ {"label",
-+ translate:translate(
-+ Lang, "dbs")},
-+ {"var", "dbs"}],
-+ [{xmlelement, "value", [], [{xmlcdata, lists:flatten(io_lib:format("~p.",[DBs]))}]}]},
-+ % default to log
-+ {xmlelement, "field", [{"type", "list-single"},
-+ {"label",
-+ translate:translate(Lang, "Default")},
-+ {"var", "dolog_default"}],
-+ [?DEFVAL(atom_to_list(DLD)),
-+ ?LISTLINE(translate:translate(Lang, "Log Messages"), "true"),
-+ ?LISTLINE(translate:translate(Lang, "Do Not Log Messages"), "false")
-+ ]},
-+ % drop_messages_on_user_removal
-+ {xmlelement, "field", [{"type", "list-single"},
-+ {"label",
-+ translate:translate(Lang, "Drop messages on user removal")},
-+ {"var", "drop_messages_on_user_removal"}],
-+ [?DEFVAL(atom_to_list(MRemoval)),
-+ ?LISTLINE(translate:translate(Lang, "Drop"), "true"),
-+ ?LISTLINE(translate:translate(Lang, "Do not drop"), "false")
-+ ]},
-+ % groupchat
-+ {xmlelement, "field", [{"type", "list-single"},
-+ {"label",
-+ translate:translate(Lang, "Groupchat messages logging")},
-+ {"var", "groupchat"}],
-+ [?DEFVAL(atom_to_list(GroupChat)),
-+ ?LISTLINE("all", "all"),
-+ ?LISTLINE("none", "none"),
-+ ?LISTLINE("send", "send"),
-+ ?LISTLINE("half", "half")
-+ ]},
-+ % ignore_jids
-+ {xmlelement, "field", [{"type", "text-multi"},
-+ {"label",
-+ translate:translate(
-+ Lang, "Jids/Domains to ignore")},
-+ {"var", "ignore_list"}],
-+ [{xmlelement, "value", [], [{xmlcdata, list_to_string(IgnoreJids)}]}]},
-+ % purge older days
-+ {xmlelement, "field", [{"type", "text-single"},
-+ {"label",
-+ translate:translate(
-+ Lang, "Purge messages older than (days)")},
-+ {"var", "purge_older_days"}],
-+ [{xmlelement, "value", [], [{xmlcdata, PurgeDays}]}]},
-+ % poll users settings
-+ {xmlelement, "field", [{"type", "text-single"},
-+ {"label",
-+ translate:translate(
-+ Lang, "Poll users settings (seconds)")},
-+ {"var", "poll_users_settings"}],
-+ [{xmlelement, "value", [], [{xmlcdata, integer_to_list(PollTime)}]}]}
-+ ]}]}.
-+
-+get_form(_Host, ["mod_logdb"], #jid{luser = LUser, lserver = LServer} = _Jid, Lang) ->
-+ get_user_form(LUser, LServer, Lang);
-+get_form(_Host, ["mod_logdb_users", User], _JidFrom, Lang) ->
-+ #jid{luser=LUser, lserver=LServer} = jlib:string_to_jid(User),
++ Fs = [
++ #xdata_field{
++ type = 'list-single',
++ label = ?T(Lang, <<"Default">>),
++ var = <<"dolog_default">>,
++ values = [misc:atom_to_binary(DLD)],
++ options = [#xdata_option{label = ?T(Lang, <<"Log Messages">>),
++ value = <<"true">>},
++ #xdata_option{label = ?T(Lang, <<"Do Not Log Messages">>),
++ value = <<"false">>}]},
++ #xdata_field{
++ type = 'list-single',
++ label = ?T(Lang, <<"Drop messages on user removal">>),
++ var = <<"drop_messages_on_user_removal">>,
++ values = [misc:atom_to_binary(MRemoval)],
++ options = [#xdata_option{label = ?T(Lang, <<"Drop">>),
++ value = <<"true">>},
++ #xdata_option{label = ?T(Lang, <<"Do not drop">>),
++ value = <<"false">>}]},
++ #xdata_field{
++ type = 'list-single',
++ label = ?T(Lang, <<"Groupchat messages logging">>),
++ var = <<"groupchat">>,
++ values = [misc:atom_to_binary(GroupChat)],
++ options = [#xdata_option{label = ?T(Lang, <<"all">>),
++ value = <<"all">>},
++ #xdata_option{label = ?T(Lang, <<"none">>),
++ value = <<"none">>},
++ #xdata_option{label = ?T(Lang, <<"send">>),
++ value = <<"send">>}]},
++ #xdata_field{
++ type = 'text-multi',
++ label = ?T(Lang, <<"Jids/Domains to ignore">>),
++ var = <<"ignore_list">>,
++ values = IgnoreJids},
++ #xdata_field{
++ type = 'text-single',
++ label = ?T(Lang, <<"Purge messages older than (days)">>),
++ var = <<"purge_older_days">>,
++ values = [iolist_to_binary(PurgeDays)]},
++ #xdata_field{
++ type = 'text-single',
++ label = ?T(Lang, <<"Poll users settings (seconds)">>),
++ var = <<"poll_users_settings">>,
++ values = [integer_to_binary(PollTime)]}
++ ],
++ {result, #xdata{
++ title = ?T(Lang, <<"Messages logging engine settings (run-time)">>),
++ instructions = [?T(Lang, <<"Set run-time settings">>)],
++ type = form,
++ fields = [?HFIELD()|
++ Fs]}}.
++
++get_form(_Host, [<<"mod_logdb_users">>, User], Lang) ->
++ #jid{luser=LUser, lserver=LServer} = jid:decode(User),
+ get_user_form(LUser, LServer, Lang);
-+get_form(Host, ["mod_logdb_settings"], _JidFrom, Lang) ->
++get_form(Host, [<<"mod_logdb_settings">>], Lang) ->
+ get_settings_form(Host, Lang);
-+get_form(_Host, Command, _, _Lang) ->
++get_form(_Host, Command, _Lang) ->
+ ?MYDEBUG("asked for form ~p", [Command]),
-+ {error, ?ERR_SERVICE_UNAVAILABLE}.
++ {error, xmpp:err_service_unavailable()}.
+
++check_log_list([]) ->
++ ok;
++check_log_list([<<>>]) ->
++ ok;
+check_log_list([Head | Tail]) ->
-+ case lists:member($@, Head) of
-+ true -> ok;
-+ false -> throw(error)
++ case binary:match(Head, <<$@>>) of
++ nomatch -> throw(error);
++ {_, _} -> ok
+ end,
+ % this check for Head to be valid jid
-+ case jlib:string_to_jid(Head) of
-+ error ->
-+ throw(error);
-+ _ ->
-+ check_log_list(Tail)
-+ end;
-+check_log_list([]) ->
-+ ok.
++ case catch jid:decode(Head) of
++ {'EXIT', _Reason} -> throw(error);
++ _ -> check_log_list(Tail)
++ end.
+
++check_ignore_list([]) ->
++ ok;
++check_ignore_list([<<>>]) ->
++ ok;
++check_ignore_list([<<>> | Tail]) ->
++ check_ignore_list(Tail);
+check_ignore_list([Head | Tail]) ->
-+ case lists:member($@, Head) of
-+ true -> ok;
-+ false -> throw(error)
++ case binary:match(Head, <<$@>>) of
++ {_, _} -> ok;
++ nomatch -> throw(error)
+ end,
++ Jid2Test = case Head of
++ << $@, _Rest/binary >> -> << "a", Head/binary >>;
++ Jid -> Jid
++ end,
+ % this check for Head to be valid jid
-+ case jlib:string_to_jid(Head) of
-+ error ->
-+ % this check for Head to be valid domain "@domain.org"
-+ case lists:nth(1, Head) of
-+ $@ ->
-+ % TODO: this allows spaces and special characters in Head. May be change to nodeprep?
-+ case jlib:nameprep(lists:delete($@, Head)) of
-+ error -> throw(error);
-+ _ -> check_log_list(Tail)
-+ end;
-+ _ -> throw(error)
-+ end;
-+ _ ->
-+ check_ignore_list(Tail)
-+ end;
-+check_ignore_list([]) ->
-+ ok.
++ case catch jid:decode(Jid2Test) of
++ {'EXIT', _Reason} -> throw(error);
++ _ -> check_ignore_list(Tail)
++ end.
++
++get_value(Field, XData) -> hd(get_values(Field, XData)).
++
++get_values(Field, XData) ->
++ xmpp_util:get_xdata_values(Field, XData).
+
+parse_users_settings(XData) ->
-+ DLD = case lists:keysearch("dolog_default", 1, XData) of
-+ {value, {_, [String]}} when String == "true"; String == "false" ->
-+ list_to_bool(String);
-+ _ ->
-+ throw(bad_request)
-+ end,
-+ DLL = case lists:keysearch("dolog_list", 1, XData) of
-+ false ->
-+ throw(bad_request);
-+ {value, {_, [[]]}} ->
-+ [];
-+ {value, {_, List1}} ->
-+ case catch check_log_list(List1) of
-+ error ->
-+ throw(bad_request);
-+ ok ->
-+ List1
-+ end
-+ end,
-+ DNLL = case lists:keysearch("donotlog_list", 1, XData) of
-+ false ->
-+ throw(bad_request);
-+ {value, {_, [[]]}} ->
-+ [];
-+ {value, {_, List2}} ->
-+ case catch check_log_list(List2) of
-+ error ->
-+ throw(bad_request);
-+ ok ->
-+ List2
-+ end
++ DLD = case get_value(<<"dolog_default">>, XData) of
++ ValueDLD when ValueDLD == <<"true">>;
++ ValueDLD == <<"false">> ->
++ list_to_bool(ValueDLD);
++ _ -> throw(bad_request)
+ end,
++
++ ListDLL = get_values(<<"dolog_list">>, XData),
++ DLL = case catch check_log_list(ListDLL) of
++ ok -> ListDLL;
++ error -> throw(bad_request)
++ end,
++
++ ListDNLL = get_values(<<"donotlog_list">>, XData),
++ DNLL = case catch check_log_list(ListDNLL) of
++ ok -> ListDNLL;
++ error -> throw(bad_request)
++ end,
++
+ #user_settings{dolog_default=DLD,
+ dolog_list=DLL,
+ donotlog_list=DNLL}.
+
+parse_module_settings(XData) ->
-+ DLD = case lists:keysearch("dolog_default", 1, XData) of
-+ {value, {_, [Str1]}} when Str1 == "true"; Str1 == "false" ->
-+ list_to_bool(Str1);
-+ _ ->
-+ throw(bad_request)
++ DLD = case get_value(<<"dolog_default">>, XData) of
++ ValueDLD when ValueDLD == <<"true">>;
++ ValueDLD == <<"false">> ->
++ list_to_bool(ValueDLD);
++ _ -> throw(bad_request)
+ end,
-+ MRemoval = case lists:keysearch("drop_messages_on_user_removal", 1, XData) of
-+ {value, {_, [Str5]}} when Str5 == "true"; Str5 == "false" ->
-+ list_to_bool(Str5);
-+ _ ->
-+ throw(bad_request)
-+ end,
-+ GroupChat = case lists:keysearch("groupchat", 1, XData) of
-+ {value, {_, [Str2]}} when Str2 == "none";
-+ Str2 == "all";
-+ Str2 == "send";
-+ Str2 == "half" ->
-+ list_to_atom(Str2);
-+ _ ->
-+ throw(bad_request)
++ MRemoval = case get_value(<<"drop_messages_on_user_removal">>, XData) of
++ ValueMRemoval when ValueMRemoval == <<"true">>;
++ ValueMRemoval == <<"false">> ->
++ list_to_bool(ValueMRemoval);
++ _ -> throw(bad_request)
++ end,
++ GroupChat = case get_value(<<"groupchat">>, XData) of
++ ValueGroupChat when ValueGroupChat == <<"none">>;
++ ValueGroupChat == <<"all">>;
++ ValueGroupChat == <<"send">> ->
++ misc:binary_to_atom(ValueGroupChat);
++ _ -> throw(bad_request)
+ end,
-+ Ignore = case lists:keysearch("ignore_list", 1, XData) of
-+ {value, {_, List}} ->
-+ case catch check_ignore_list(List) of
-+ ok ->
-+ List;
-+ error ->
-+ throw(bad_request)
-+ end;
-+ _ ->
-+ throw(bad_request)
++ ListIgnore = get_values(<<"ignore_list">>, XData),
++ Ignore = case catch check_ignore_list(ListIgnore) of
++ ok -> ListIgnore;
++ error -> throw(bad_request)
+ end,
-+ Purge = case lists:keysearch("purge_older_days", 1, XData) of
-+ {value, {_, ["never"]}} ->
-+ never;
-+ {value, {_, [Str3]}} ->
-+ case catch list_to_integer(Str3) of
-+ {'EXIT', {badarg, _}} -> throw(bad_request);
-+ Int1 -> Int1
-+ end;
-+ _ ->
-+ throw(bad_request)
++ Purge = case get_value(<<"purge_older_days">>, XData) of
++ <<"never">> -> never;
++ ValuePurge ->
++ case catch binary_to_integer(ValuePurge) of
++ IntValuePurge when is_integer(IntValuePurge) -> IntValuePurge;
++ _ -> throw(bad_request)
++ end
+ end,
-+ Poll = case lists:keysearch("poll_users_settings", 1, XData) of
-+ {value, {_, [Str4]}} ->
-+ case catch list_to_integer(Str4) of
-+ {'EXIT', {badarg, _}} -> throw(bad_request);
-+ Int2 -> Int2
-+ end;
-+ _ ->
-+ throw(bad_request)
++ Poll = case catch binary_to_integer(get_value(<<"poll_users_settings">>, XData)) of
++ IntValuePoll when is_integer(IntValuePoll) -> IntValuePoll;
++ _ -> throw(bad_request)
+ end,
+ #state{dolog_default=DLD,
+ groupchat=GroupChat,
+ drop_messages_on_user_removal=MRemoval,
+ poll_users_settings=Poll}.
+
-+set_form(From, _Host, ["mod_logdb"], _Lang, XData) ->
-+ #jid{luser=LUser, lserver=LServer} = From,
++set_form(_From, _Host, [<<"mod_logdb_users">>, User], Lang, XData) ->
++ #jid{luser=LUser, lserver=LServer} = jid:decode(User),
++ Txt = "Parse user settings failed",
+ case catch parse_users_settings(XData) of
+ bad_request ->
-+ {error, ?ERR_BAD_REQUEST};
-+ UserSettings ->
-+ case mod_logdb:set_user_settings(LUser, LServer, UserSettings) of
-+ ok ->
-+ {result, []};
-+ error ->
-+ {error, ?ERR_INTERNAL_SERVER_ERROR}
-+ end
-+ end;
-+set_form(_From, _Host, ["mod_logdb_users", User], _Lang, XData) ->
-+ #jid{luser=LUser, lserver=LServer} = jlib:string_to_jid(User),
-+ case catch parse_users_settings(XData) of
-+ bad_request -> {error, ?ERR_BAD_REQUEST};
++ ?ERROR_MSG("Failed to set user form: bad_request", []),
++ {error, xmpp:err_bad_request(Txt, Lang)};
++ {'EXIT', Reason} ->
++ ?ERROR_MSG("Failed to set user form ~p", [Reason]),
++ {error, xmpp:err_bad_request(Txt, Lang)};
+ UserSettings ->
+ case mod_logdb:set_user_settings(LUser, LServer, UserSettings) of
+ ok ->
-+ {result, []};
++ {result, undefined};
+ error ->
-+ {error, ?ERR_INTERNAL_SERVER_ERROR}
++ {error, xmpp:err_internal_server_error()}
+ end
+ end;
-+set_form(_From, Host, ["mod_logdb_settings"], _Lang, XData) ->
++set_form(_From, Host, [<<"mod_logdb_settings">>], Lang, XData) ->
++ Txt = "Parse module settings failed",
+ case catch parse_module_settings(XData) of
-+ bad_request -> {error, ?ERR_BAD_REQUEST};
++ bad_request ->
++ ?ERROR_MSG("Failed to set settings form: bad_request", []),
++ {error, xmpp:err_bad_request(Txt, Lang)};
++ {'EXIT', Reason} ->
++ ?ERROR_MSG("Failed to set settings form ~p", [Reason]),
++ {error, xmpp:err_bad_request(Txt, Lang)};
+ Settings ->
+ case mod_logdb:set_module_settings(Host, Settings) of
+ ok ->
-+ {result, []};
++ {result, undefined};
+ error ->
-+ {error, ?ERR_INTERNAL_SERVER_ERROR}
++ {error, xmpp:err_internal_server_error()}
+ end
+ end;
+set_form(From, _Host, Node, _Lang, XData) ->
-+ User = jlib:jid_to_string(jlib:jid_remove_resource(From)),
++ User = jid:encode(jid:remove_resource(From)),
+ ?MYDEBUG("set form for ~p at ~p XData=~p", [User, Node, XData]),
-+ {error, ?ERR_SERVICE_UNAVAILABLE}.
++ {error, xmpp:err_service_unavailable()}.
+
-+%adhoc_sm_items(Acc, From, To, Request) ->
-+% ?MYDEBUG("adhoc_sm_items Acc=~p From=~p To=~p Request=~p", [Acc, From, To, Request]),
-+% Acc.
-+
-+%adhoc_sm_commands(Acc, From, To, Request) ->
-+% ?MYDEBUG("adhoc_sm_commands Acc=~p From=~p To=~p Request=~p", [Acc, From, To, Request]),
-+% Acc.
-+
-+get_all_vh_users(Host, Server, Lang) ->
++get_all_vh_users(Host, Server) ->
+ case catch ejabberd_auth:get_vh_registered_users(Host) of
+ {'EXIT', _Reason} ->
+ [];
+ case length(SUsers) of
+ N when N =< 100 ->
+ lists:map(fun({S, U}) ->
-+ ?NODE(U ++ "@" ++ S, "mod_logdb_users/" ++ U ++ "@" ++ S)
++ #disco_item{jid = jid:make(Server),
++ node = <<"mod_logdb_users/", U/binary, $@, S/binary>>,
++ name = << U/binary, "@", S/binary >>}
+ end, SUsers);
+ N ->
-+ NParts = trunc(math:sqrt(N * 0.618)) + 1,
++ NParts = trunc(math:sqrt(N * 6.17999999999999993783e-1)) + 1,
+ M = trunc(N / NParts) + 1,
+ lists:map(fun(K) ->
+ L = K + M - 1,
-+ Node =
-+ "@" ++ integer_to_list(K) ++
-+ "-" ++ integer_to_list(L),
++ Node = <<"@",
++ (integer_to_binary(K))/binary,
++ "-",
++ (integer_to_binary(L))/binary
++ >>,
+ {FS, FU} = lists:nth(K, SUsers),
+ {LS, LU} =
+ if L < N -> lists:nth(L, SUsers);
+ true -> lists:last(SUsers)
+ end,
+ Name =
-+ FU ++ "@" ++ FS ++
-+ " -- " ++
-+ LU ++ "@" ++ LS,
-+ ?NODE(Name, "mod_logdb_users/" ++ Node)
++ <<FU/binary, "@", FS/binary,
++ " -- ",
++ LU/binary, "@", LS/binary>>,
++ #disco_item{jid = jid:make(Host),
++ node = <<"mod_logdb_users/", Node/binary>>,
++ name = Name}
+ end, lists:seq(1, N, M))
+ end
+ end.
+%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+webadmin_menu(Acc, _Host, Lang) ->
-+ [{"messages", ?T("Users Messages")} | Acc].
++ [{<<"messages">>, ?T(<<"Users Messages">>)} | Acc].
+
+webadmin_user(Acc, User, Server, Lang) ->
+ Sett = get_user_settings(User, Server),
+ Log =
+ case Sett#user_settings.dolog_default of
+ false ->
-+ ?INPUTT("submit", "dolog", "Log Messages");
++ ?INPUTT(<<"submit">>, <<"dolog">>, <<"Log Messages">>);
+ true ->
-+ ?INPUTT("submit", "donotlog", "Do Not Log Messages");
++ ?INPUTT(<<"submit">>, <<"donotlog">>, <<"Do Not Log Messages">>);
+ _ -> []
+ end,
-+ Acc ++ [?XE("h3", [?ACT("messages/", "Messages"), ?C(" "), Log])].
++ Acc ++ [?XE(<<"h3">>, [?ACT(<<"messages/">>, <<"Messages">>), ?C(<<" ">>), Log])].
+
+webadmin_page(_, Host,
-+ #request{path = ["messages"],
++ #request{path = [<<"messages">>],
+ q = Query,
-+ lang = Lang}) when is_list(Host) ->
++ lang = Lang}) ->
+ Res = vhost_messages_stats(Host, Query, Lang),
+ {stop, Res};
+webadmin_page(_, Host,
-+ #request{path = ["messages", Date],
++ #request{path = [<<"messages">>, Date],
+ q = Query,
-+ lang = Lang}) when is_list(Host) ->
++ lang = Lang}) ->
+ Res = vhost_messages_stats_at(Host, Query, Lang, Date),
+ {stop, Res};
+webadmin_page(_, Host,
-+ #request{path = ["user", U, "messages"],
++ #request{path = [<<"user">>, U, <<"messages">>],
+ q = Query,
+ lang = Lang}) ->
+ Res = user_messages_stats(U, Host, Query, Lang),
+ {stop, Res};
+webadmin_page(_, Host,
-+ #request{path = ["user", U, "messages", Date],
++ #request{path = [<<"user">>, U, <<"messages">>, Date],
+ q = Query,
+ lang = Lang}) ->
+ Res = mod_logdb:user_messages_stats_at(U, Host, Query, Lang, Date),
+ {stop, Res};
-+webadmin_page(Acc, _, _) -> Acc.
++webadmin_page(Acc, _Host, _R) -> Acc.
+
-+user_parse_query(_, "dolog", User, Server, _Query) ->
++user_parse_query(_, <<"dolog">>, User, Server, _Query) ->
+ Sett = get_user_settings(User, Server),
+ % TODO: check returned value
+ set_user_settings(User, Server, Sett#user_settings{dolog_default=true}),
+ {stop, ok};
-+user_parse_query(_, "donotlog", User, Server, _Query) ->
++user_parse_query(_, <<"donotlog">>, User, Server, _Query) ->
+ Sett = get_user_settings(User, Server),
+ % TODO: check returned value
+ set_user_settings(User, Server, Sett#user_settings{dolog_default=false}),
+ case Value of
+ {'EXIT', CReason} ->
+ ?ERROR_MSG("Failed to get_vhost_stats: ~p", [CReason]),
-+ [?XC("h1", ?T("Error occupied while fetching list"))];
++ [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))];
+ {error, GReason} ->
+ ?ERROR_MSG("Failed to get_vhost_stats: ~p", [GReason]),
-+ [?XC("h1", ?T("Error occupied while fetching list"))];
++ [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))];
+ {ok, []} ->
-+ [?XC("h1", ?T("No logged messages for ") ++ Server)];
++ [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s">>), [Server])))];
+ {ok, Dates} ->
+ Fun = fun({Date, Count}) ->
-+ ID = jlib:encode_base64(binary_to_list(term_to_binary(Server++Date))),
-+ ?XE("tr",
-+ [?XE("td", [?INPUT("checkbox", "selected", ID)]),
-+ ?XE("td", [?AC(Date, Date)]),
-+ ?XC("td", integer_to_list(Count))
++ DateBin = iolist_to_binary(Date),
++ ID = misc:encode_base64( << Server/binary, DateBin/binary >> ),
++ ?XE(<<"tr">>,
++ [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
++ [?INPUT(<<"checkbox">>, <<"selected">>, ID)]),
++ ?XE(<<"td">>, [?AC(DateBin, DateBin)]),
++ ?XC(<<"td">>, integer_to_binary(Count))
+ ])
+ end,
-+ [?XC("h1", ?T("Logged messages for ") ++ Server)] ++
++
++ [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Logged messages for ~s">>), [Server])))] ++
+ case Res of
-+ ok -> [?CT("Submitted"), ?P];
-+ error -> [?CT("Bad format"), ?P];
++ ok -> [?CT(<<"Submitted">>), ?P];
++ error -> [?CT(<<"Bad format">>), ?P];
+ nothing -> []
+ end ++
-+ [?XAE("form", [{"action", ""}, {"method", "post"}],
-+ [?XE("table",
-+ [?XE("thead",
-+ [?XE("tr",
-+ [?X("td"),
-+ ?XCT("td", "Date"),
-+ ?XCT("td", "Count")
++ [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
++ [?XE(<<"table">>,
++ [?XE(<<"thead">>,
++ [?XE(<<"tr">>,
++ [?X(<<"td">>),
++ ?XCT(<<"td">>, <<"Date">>),
++ ?XCT(<<"td">>, <<"Count">>)
+ ])]),
-+ ?XE("tbody",
++ ?XE(<<"tbody">>,
+ lists:map(Fun, Dates)
+ )]),
+ ?BR,
-+ ?INPUTT("submit", "delete", "Delete Selected")
++ ?INPUTT(<<"submit">>, <<"delete">>, <<"Delete Selected">>)
+ ])]
+ end.
+
+ case Value of
+ {'EXIT', CReason} ->
+ ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [CReason]),
-+ [?XC("h1", ?T("Error occupied while fetching list"))];
++ [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))];
+ {error, GReason} ->
+ ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [GReason]),
-+ [?XC("h1", ?T("Error occupied while fetching list"))];
++ [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))];
+ {ok, []} ->
-+ [?XC("h1", ?T("No logged messages for ") ++ Server ++ ?T(" at ") ++ Date)];
-+ {ok, Users} ->
-+ Res = case catch vhost_messages_at_parse_query(Server, Date, Users, Query) of
++ [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s at ~s">>), [Server, Date])))];
++ {ok, Stats} ->
++ Res = case catch vhost_messages_at_parse_query(Server, Date, Stats, Query) of
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("~p", [Reason]),
+ error;
+ VResult -> VResult
+ end,
+ Fun = fun({User, Count}) ->
-+ ID = jlib:encode_base64(binary_to_list(term_to_binary(User++Server))),
-+ ?XE("tr",
-+ [?XE("td", [?INPUT("checkbox", "selected", ID)]),
-+ ?XE("td", [?AC("../user/"++User++"/messages/"++Date, User)]),
-+ ?XC("td", integer_to_list(Count))
++ UserBin = iolist_to_binary(User),
++ ID = misc:encode_base64( << UserBin/binary, Server/binary >> ),
++ ?XE(<<"tr">>,
++ [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
++ [?INPUT(<<"checkbox">>, <<"selected">>, ID)]),
++ ?XE(<<"td">>, [?AC(<< <<"../user/">>/binary, UserBin/binary, <<"/messages/">>/binary, Date/binary >>, UserBin)]),
++ ?XC(<<"td">>, integer_to_binary(Count))
+ ])
+ end,
-+ [?XC("h1", ?T("Logged messages for ") ++ Server ++ ?T(" at ") ++ Date)] ++
++ [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Logged messages for ~s at ~s">>), [Server, Date])))] ++
+ case Res of
-+ ok -> [?CT("Submitted"), ?P];
-+ error -> [?CT("Bad format"), ?P];
++ ok -> [?CT(<<"Submitted">>), ?P];
++ error -> [?CT(<<"Bad format">>), ?P];
+ nothing -> []
+ end ++
-+ [?XAE("form", [{"action", ""}, {"method", "post"}],
-+ [?XE("table",
-+ [?XE("thead",
-+ [?XE("tr",
-+ [?X("td"),
-+ ?XCT("td", "User"),
-+ ?XCT("td", "Count")
++ [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
++ [?XE(<<"table">>,
++ [?XE(<<"thead">>,
++ [?XE(<<"tr">>,
++ [?X(<<"td">>),
++ ?XCT(<<"td">>, <<"User">>),
++ ?XCT(<<"td">>, <<"Count">>)
+ ])]),
-+ ?XE("tbody",
-+ lists:map(Fun, Users)
++ ?XE(<<"tbody">>,
++ lists:map(Fun, Stats)
+ )]),
+ ?BR,
-+ ?INPUTT("submit", "delete", "Delete Selected")
++ ?INPUTT(<<"submit">>, <<"delete">>, <<"Delete Selected">>)
+ ])]
+ end.
+
+user_messages_stats(User, Server, Query, Lang) ->
-+ Jid = jlib:jid_to_string({User, Server, ""}),
++ Jid = jid:encode({User, Server, ""}),
+
+ Res = case catch user_messages_parse_query(User, Server, Query) of
+ {'EXIT', Reason} ->
+ case Value of
+ {'EXIT', CReason} ->
+ ?ERROR_MSG("Failed to get_user_stats: ~p", [CReason]),
-+ [?XC("h1", ?T("Error occupied while fetching days"))];
++ [?XC(<<"h1">>, ?T(<<"Error occupied while fetching days">>))];
+ {error, GReason} ->
+ ?ERROR_MSG("Failed to get_user_stats: ~p", [GReason]),
-+ [?XC("h1", ?T("Error occupied while fetching days"))];
++ [?XC(<<"h1">>, ?T(<<"Error occupied while fetching days">>))];
+ {ok, []} ->
-+ [?XC("h1", ?T("No logged messages for ") ++ Jid)];
++ [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s">>), [Jid])))];
+ {ok, Dates} ->
+ Fun = fun({Date, Count}) ->
-+ ID = jlib:encode_base64(binary_to_list(term_to_binary(User++Date))),
-+ ?XE("tr",
-+ [?XE("td", [?INPUT("checkbox", "selected", ID)]),
-+ ?XE("td", [?AC(Date, Date)]),
-+ ?XC("td", integer_to_list(Count))
++ DateBin = iolist_to_binary(Date),
++ ID = misc:encode_base64( << User/binary, DateBin/binary >> ),
++ ?XE(<<"tr">>,
++ [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
++ [?INPUT(<<"checkbox">>, <<"selected">>, ID)]),
++ ?XE(<<"td">>, [?AC(DateBin, DateBin)]),
++ ?XC(<<"td">>, iolist_to_binary(integer_to_list(Count)))
+ ])
-+ %[?AC(Date, Date ++ " (" ++ integer_to_list(Count) ++ ")"), ?BR]
+ end,
-+ [?XC("h1", ?T("Logged messages for ") ++ Jid)] ++
++ [?XC(<<"h1">>, list_to_binary(io_lib:format(?T("Logged messages for ~s"), [Jid])))] ++
+ case Res of
-+ ok -> [?CT("Submitted"), ?P];
-+ error -> [?CT("Bad format"), ?P];
++ ok -> [?CT(<<"Submitted">>), ?P];
++ error -> [?CT(<<"Bad format">>), ?P];
+ nothing -> []
+ end ++
-+ [?XAE("form", [{"action", ""}, {"method", "post"}],
-+ [?XE("table",
-+ [?XE("thead",
-+ [?XE("tr",
-+ [?X("td"),
-+ ?XCT("td", "Date"),
-+ ?XCT("td", "Count")
++ [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
++ [?XE(<<"table">>,
++ [?XE(<<"thead">>,
++ [?XE(<<"tr">>,
++ [?X(<<"td">>),
++ ?XCT(<<"td">>, <<"Date">>),
++ ?XCT(<<"td">>, <<"Count">>)
+ ])]),
-+ ?XE("tbody",
++ ?XE(<<"tbody">>,
+ lists:map(Fun, Dates)
+ )]),
+ ?BR,
-+ ?INPUTT("submit", "delete", "Delete Selected")
++ ?INPUTT(<<"submit">>, <<"delete">>, <<"Delete Selected">>)
+ ])]
+ end.
+
+ end.
+
+user_messages_stats_at(User, Server, Query, Lang, Date) ->
-+ Jid = jlib:jid_to_string({User, Server, ""}),
++ Jid = jid:encode({User, Server, ""}),
+
+ {Time, Value} = timer:tc(mod_logdb, get_user_messages_at, [User, Server, Date]),
+ ?INFO_MSG("get_user_messages_at(~p,~p,~p) elapsed ~p sec", [User, Server, Date, Time/1000000]),
+ case Value of
+ {'EXIT', CReason} ->
+ ?ERROR_MSG("Failed to get_user_messages_at: ~p", [CReason]),
-+ [?XC("h1", ?T("Error occupied while fetching messages"))];
++ [?XC(<<"h1">>, ?T(<<"Error occupied while fetching messages">>))];
+ {error, GReason} ->
+ ?ERROR_MSG("Failed to get_user_messages_at: ~p", [GReason]),
-+ [?XC("h1", ?T("Error occupied while fetching messages"))];
++ [?XC(<<"h1">>, ?T(<<"Error occupied while fetching messages">>))];
+ {ok, []} ->
-+ [?XC("h1", ?T("No logged messages for ") ++ Jid ++ ?T(" at ") ++ Date)];
++ [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s at ~s">>), [Jid, Date])))];
+ {ok, User_messages} ->
+ Res = case catch user_messages_at_parse_query(Server,
-+ Date,
-+ User_messages,
-+ Query) of
++ Date,
++ User_messages,
++ Query) of
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("~p", [Reason]),
+ error;
+ UR = ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]),
+ UserRoster =
+ lists:map(fun(Item) ->
-+ {jlib:jid_to_string(Item#roster.jid), Item#roster.name}
-+ end, UR),
++ {jid:encode(Item#roster.jid), Item#roster.name}
++ end, UR),
+
+ UniqUsers = lists:foldl(fun(#msg{peer_name=PName, peer_server=PServer}, List) ->
+ ToAdd = PName++"@"++PServer,
+ end, [], User_messages),
+
+ % Users to filter (sublist of UniqUsers)
-+ CheckedUsers = case lists:keysearch("filter", 1, Query) of
++ CheckedUsers = case lists:keysearch(<<"filter">>, 1, Query) of
+ {value, _} ->
+ lists:filter(fun(UFUser) ->
-+ ID = jlib:encode_base64(binary_to_list(term_to_binary(UFUser))),
-+ lists:member({"selected", ID}, Query)
++ ID = misc:encode_base64(term_to_binary(UFUser)),
++ lists:member({<<"selected">>, ID}, Query)
+ end, UniqUsers);
+ false -> []
+ end,
+
+ % UniqUsers in html (noone selected -> everyone selected)
+ Users = lists:map(fun(UHUser) ->
-+ ID = jlib:encode_base64(binary_to_list(term_to_binary(UHUser))),
++ ID = misc:encode_base64(term_to_binary(UHUser)),
+ Input = case lists:member(UHUser, CheckedUsers) of
-+ true -> [?INPUTC("checkbox", "selected", ID)];
-+ false when CheckedUsers == [] -> [?INPUTC("checkbox", "selected", ID)];
-+ false -> [?INPUT("checkbox", "selected", ID)]
++ true -> [?INPUTC(<<"checkbox">>, <<"selected">>, ID)];
++ false when CheckedUsers == [] -> [?INPUTC(<<"checkbox">>, <<"selected">>, ID)];
++ false -> [?INPUT(<<"checkbox">>, <<"selected">>, ID)]
+ end,
+ Nick =
+ case search_user_nick(UHUser, UserRoster) of
-+ nothing -> "";
-+ N -> " ("++ N ++")"
++ nothing -> <<"">>;
++ N -> iolist_to_binary( " ("++ N ++")" )
+ end,
-+ ?XE("tr",
-+ [?XE("td", Input),
-+ ?XC("td", UHUser++Nick)])
++ ?XE(<<"tr">>,
++ [?XE(<<"td">>, Input),
++ ?XC(<<"td">>, iolist_to_binary(UHUser++Nick))])
+ end, lists:sort(UniqUsers)),
+ % Messages to show (based on Users)
+ User_messages_filtered = case CheckedUsers of
+ peer_name=PName, peer_server=PServer, peer_resource=PRes,
+ type=Type,
+ body=Body}) ->
-+ TextRaw = case Subject of
-+ "" -> Body;
-+ _ -> [?T("Subject"),": ",Subject,"<br>", Body]
-+ end,
-+ ID = jlib:encode_base64(binary_to_list(term_to_binary(Timestamp))),
-+ % replace \n with <br>
-+ Text = lists:map(fun(10) -> "<br>";
-+ (A) -> A
-+ end, TextRaw),
++ Text = case Subject of
++ "" -> iolist_to_binary(Body);
++ _ -> iolist_to_binary([binary_to_list(?T(<<"Subject">>)) ++ ": " ++ Subject ++ "\n" ++ Body])
++ end,
+ Resource = case PRes of
+ [] -> [];
+ undefined -> [];
+ PName++"@"++PServer;
+ N -> N
+ end,
-+ ?XE("tr",
-+ [?XE("td", [?INPUT("checkbox", "selected", ID)]),
-+ ?XC("td", convert_timestamp(Timestamp)),
-+ ?XC("td", atom_to_list(Direction)++": "++UserNick),
-+ ?XC("td", Text)])
++ ID = misc:encode_base64(term_to_binary(Timestamp)),
++ ?XE(<<"tr">>,
++ [?XE(<<"td">>, [?INPUT(<<"checkbox">>, <<"selected">>, ID)]),
++ ?XC(<<"td">>, iolist_to_binary(convert_timestamp(Timestamp))),
++ ?XC(<<"td">>, iolist_to_binary(atom_to_list(Direction)++": "++UserNick)),
++ ?XE(<<"td">>, [?XC(<<"pre">>, Text)])])
+ end,
+ % Filtered user messages in html
+ Msgs = lists:map(Msgs_Fun, lists:sort(User_messages_filtered)),
+
-+ [?XC("h1", ?T("Logged messages for ") ++ Jid ++ ?T(" at ") ++ Date)] ++
++ [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Logged messages for ~s at ~s">>), [Jid, Date])))] ++
+ case Res of
-+ ok -> [?CT("Submitted"), ?P];
-+ error -> [?CT("Bad format"), ?P];
++ ok -> [?CT(<<"Submitted">>), ?P];
++ error -> [?CT(<<"Bad format">>), ?P];
+ nothing -> []
+ end ++
-+ [?XAE("form", [{"action", ""}, {"method", "post"}],
-+ [?XE("table",
-+ [?XE("thead",
-+ [?X("td"),
-+ ?XCT("td", "User")
++ [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
++ [?XE(<<"table">>,
++ [?XE(<<"thead">>,
++ [?X(<<"td">>),
++ ?XCT(<<"td">>, <<"User">>)
+ ]
+ ),
-+ ?XE("tbody",
++ ?XE(<<"tbody">>,
+ Users
+ )]),
-+ ?INPUTT("submit", "filter", "Filter Selected")
++ ?INPUTT(<<"submit">>, <<"filter">>, <<"Filter Selected">>)
+ ] ++
-+ [?XE("table",
-+ [?XE("thead",
-+ [?XE("tr",
-+ [?X("td"),
-+ ?XCT("td", "Date, Time"),
-+ ?XCT("td", "Direction: Jid"),
-+ ?XCT("td", "Body")
++ [?XE(<<"table">>,
++ [?XE(<<"thead">>,
++ [?XE(<<"tr">>,
++ [?X(<<"td">>),
++ ?XCT(<<"td">>, <<"Date, Time">>),
++ ?XCT(<<"td">>, <<"Direction: Jid">>),
++ ?XCT(<<"td">>, <<"Body">>)
+ ])]),
-+ ?XE("tbody",
++ ?XE(<<"tbody">>,
+ Msgs
+ )]),
-+ ?INPUTT("submit", "delete", "Delete Selected"),
++ ?INPUTT(<<"submit">>, <<"delete">>, <<"Delete Selected">>),
+ ?BR
+ ]
+ )]
+ end.
---- mod_logdb.hrl.orig 2009-11-22 13:06:23.000000000 +0200
-+++ mod_logdb.hrl 2009-02-05 20:12:58.000000000 +0200
-@@ -0,0 +1,35 @@
+diff --git a/src/mod_logdb.hrl b/src/mod_logdb.hrl
+new file mode 100644
+index 0000000000..49791f4e69
+--- /dev/null
++++ b/src/mod_logdb.hrl
+@@ -0,0 +1,33 @@
+%%%----------------------------------------------------------------------
+%%% File : mod_logdb.hrl
-+%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
++%%% Author : Oleg Palij (mailto:o.palij@gmail.com)
+%%% Purpose :
-+%%% Version : trunk
-+%%% Id : $Id$
-+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
++%%% Url : https://paleg.github.io/mod_logdb/
+%%%----------------------------------------------------------------------
+
+-define(logdb_debug, true).
+ donotlog_list=[]}).
+
+-define(INPUTC(Type, Name, Value),
-+ ?XA("input", [{"type", Type},
-+ {"name", Name},
-+ {"value", Value},
-+ {"checked", "true"}])).
---- mod_logdb_mnesia.erl.orig 2009-11-22 13:06:23.000000000 +0200
-+++ mod_logdb_mnesia.erl 2009-02-05 20:12:58.000000000 +0200
-@@ -0,0 +1,546 @@
++ ?XA(<<"input">>, [{<<"type">>, Type},
++ {<<"name">>, Name},
++ {<<"value">>, Value},
++ {<<"checked">>, <<"true">>}])).
+diff --git a/src/mod_logdb_mnesia.erl b/src/mod_logdb_mnesia.erl
+new file mode 100644
+index 0000000000..a08d5262c2
+--- /dev/null
++++ b/src/mod_logdb_mnesia.erl
+@@ -0,0 +1,553 @@
+%%%----------------------------------------------------------------------
+%%% File : mod_logdb_mnesia.erl
-+%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
++%%% Author : Oleg Palij (mailto:o.palij@gmail.com)
+%%% Purpose : mnesia backend for mod_logdb
-+%%% Version : trunk
-+%%% Id : $Id$
-+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
++%%% Url : https://paleg.github.io/mod_logdb/
+%%%----------------------------------------------------------------------
+
+-module(mod_logdb_mnesia).
+-author('o.palij@gmail.com').
+
+-include("mod_logdb.hrl").
-+-include("ejabberd.hrl").
-+-include("jlib.hrl").
++-include("logger.hrl").
+
+-behaviour(gen_logdb).
+-behaviour(gen_server).
-+
++
+% gen_server
+-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
+% gen_mod
+ get_dates/1,
+ get_users_settings/1, get_user_settings/2, set_user_settings/3,
+ drop_user/2]).
-+
++
+-define(PROCNAME, mod_logdb_mnesia).
+-define(CALL_TIMEOUT, 10000).
-+
++
+-record(state, {vhost}).
+
+-record(stats, {user, at, count}).
+ "logdb_".
+
+suffix(VHost) ->
-+ "_" ++ VHost.
++ "_" ++ binary_to_list(VHost).
+
+stats_table(VHost) ->
+ list_to_atom(prefix() ++ "stats" ++ suffix(VHost)).
+ {reply, Reply, State};
+handle_call({rebuild_stats_at, Date}, _From, #state{vhost=VHost}=State) ->
+ Reply = rebuild_stats_at_int(VHost, Date),
-+ {reply, Reply, State};
++ {reply, Reply, State};
+handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{vhost=VHost}=State) ->
+ Table = table_name(VHost, Date),
+ Fun = fun() ->
+% internals
+%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-+log_message_int(VHost, #msg{timestamp=Timestamp}=Msg) ->
++log_message_int(VHost, #msg{timestamp=Timestamp}=MsgBin) ->
+ Date = mod_logdb:convert_timestamp_brief(Timestamp),
+
++ Msg = #msg{timestamp = MsgBin#msg.timestamp,
++ owner_name = binary_to_list(MsgBin#msg.owner_name),
++ peer_name = binary_to_list(MsgBin#msg.peer_name),
++ peer_server = binary_to_list(MsgBin#msg.peer_server),
++ peer_resource = binary_to_list(MsgBin#msg.peer_resource),
++ direction = MsgBin#msg.direction,
++ type = binary_to_list(MsgBin#msg.type),
++ subject = binary_to_list(MsgBin#msg.subject),
++ body = binary_to_list(MsgBin#msg.body)},
++
+ ATable = table_name(VHost, Date),
+ Fun = fun() ->
+ mnesia:write_lock_table(ATable),
+ ?ERROR_MSG("Failed to log message: ~p", [CReason]),
+ error;
+ {atomic, ok} ->
-+ ?MYDEBUG("Created msg table for ~p at ~p", [VHost, Date]),
-+ log_message_int(VHost, Msg)
++ ?MYDEBUG("Created msg table for ~s at ~s", [VHost, Date]),
++ log_message_int(VHost, MsgBin)
+ end;
+ {aborted, TReason} ->
+ ?ERROR_MSG("Failed to log message: ~p", [TReason]),
+ error;
+ {atomic, _} ->
-+ ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
-+ Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
++ ?MYDEBUG("Logged ok for ~s, peer: ~s", [ [Msg#msg.owner_name, <<"@">>, VHost],
++ [Msg#msg.peer_name, <<"@">>, Msg#msg.peer_server] ]),
+ increment_user_stats(Msg#msg.owner_name, VHost, Date)
+ end.
+
+get_dates_int(VHost) ->
+ Tables = mnesia:system_info(tables),
+ lists:foldl(fun(ATable, Dates) ->
-+ Table = atom_to_list(ATable),
-+ case regexp:match(Table, VHost++"$") of
-+ {match, _, _} ->
-+ case regexp:match(Table,"_[0-9]+-[0-9]+-[0-9]+_") of
-+ {match, S, E} ->
-+ lists:append(Dates, [lists:sublist(Table,S+1,E-2)]);
++ Table = term_to_binary(ATable),
++ case ejabberd_regexp:run( Table, << VHost/binary, <<"$">>/binary >> ) of
++ match ->
++ case re:run(Table, "[0-9]+-[0-9]+-[0-9]+") of
++ {match, [{S, E}]} ->
++ lists:append(Dates, [lists:sublist(binary_to_list(Table), S+1, E)]);
+ nomatch ->
+ Dates
+ end;
+ case mnesia:transaction(fun() ->
+ mnesia:write_lock_table(Table),
+ mnesia:write_lock_table(STable),
++ % Delete all stats for VHost at Date
++ mnesia:foldl(DFun, [], STable),
+ % Calc stats for VHost at Date
+ case mnesia:foldl(CFun, [], Table) of
+ [] -> empty;
+ AStats ->
-+ % Delete all stats for VHost at Date
-+ mnesia:foldl(DFun, [], STable),
+ % Write new calc'ed stats
+ lists:foreach(fun({Owner, Count}) ->
+ WStat = #stats{user=Owner, at=Date, count=Count},
+ {type, bag},
+ {attributes, record_info(fields, msg)},
+ {record_name, msg}]).
---- mod_logdb_mysql.erl.orig 2009-11-22 13:06:23.000000000 +0200
-+++ mod_logdb_mysql.erl 2009-07-30 09:00:14.000000000 +0300
-@@ -0,0 +1,1053 @@
+diff --git a/src/mod_logdb_mysql.erl b/src/mod_logdb_mysql.erl
+new file mode 100644
+index 0000000000..21d65e6578
+--- /dev/null
++++ b/src/mod_logdb_mysql.erl
+@@ -0,0 +1,1050 @@
+%%%----------------------------------------------------------------------
+%%% File : mod_logdb_mysql.erl
-+%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
++%%% Author : Oleg Palij (mailto:o.palij@gmail.com)
+%%% Purpose : MySQL backend for mod_logdb
-+%%% Version : trunk
-+%%% Id : $Id$
-+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
++%%% Url : https://paleg.github.io/mod_logdb/
+%%%----------------------------------------------------------------------
+
+-module(mod_logdb_mysql).
+-author('o.palij@gmail.com').
+
+-include("mod_logdb.hrl").
-+-include("ejabberd.hrl").
-+-include("jlib.hrl").
++-include("logger.hrl").
+
+-behaviour(gen_logdb).
+-behaviour(gen_server).
+% replace "." with "_"
+escape_vhost(VHost) -> lists:map(fun(46) -> 95;
+ (A) -> A
-+ end, VHost).
++ end, binary_to_list(VHost)).
+prefix() ->
+ "`logdb_".
+
+resources_table(VHost) ->
+ prefix() ++ "resources" ++ suffix(VHost).
+
-+ets_users_table(VHost) -> list_to_atom("logdb_users_" ++ VHost).
-+ets_servers_table(VHost) -> list_to_atom("logdb_servers_" ++ VHost).
-+ets_resources_table(VHost) -> list_to_atom("logdb_resources_" ++ VHost).
++ets_users_table(VHost) -> list_to_atom("logdb_users_" ++ binary_to_list(VHost)).
++ets_servers_table(VHost) -> list_to_atom("logdb_servers_" ++ binary_to_list(VHost)).
++ets_resources_table(VHost) -> list_to_atom("logdb_resources_" ++ binary_to_list(VHost)).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+init([VHost, Opts]) ->
+ crypto:start(),
+
-+ Server = gen_mod:get_opt(server, Opts, "localhost"),
-+ Port = gen_mod:get_opt(port, Opts, 3306),
-+ DB = gen_mod:get_opt(db, Opts, "logdb"),
-+ User = gen_mod:get_opt(user, Opts, "root"),
-+ Password = gen_mod:get_opt(password, Opts, ""),
++ Server = gen_mod:get_opt(server, Opts, fun(A) -> A end, <<"localhost">>),
++ Port = gen_mod:get_opt(port, Opts, fun(A) -> A end, 3306),
++ DB = gen_mod:get_opt(db, Opts, fun(A) -> A end, <<"logdb">>),
++ User = gen_mod:get_opt(user, Opts, fun(A) -> A end, <<"root">>),
++ Password = gen_mod:get_opt(password, Opts, fun(A) -> A end, <<"">>),
+
+ St = #state{vhost=VHost,
+ server=Server, port=Port, db=DB,
+ ?MYDEBUG(Format, Argument)
+ end,
+ ?INFO_MSG("Opening mysql connection ~s@~s:~p/~s", [DBUser, Server, Port, DB]),
-+ mysql_conn:start(Server, Port, DBUser, Password, DB, LogFun).
++ p1_mysql_conn:start(binary_to_list(Server), Port,
++ binary_to_list(DBUser), binary_to_list(Password),
++ binary_to_list(DB), LogFun).
+
+close_mysql_connection(DBRef) ->
+ ?MYDEBUG("Closing ~p mysql connection", [DBRef]),
-+ mysql_conn:stop(DBRef).
++ catch p1_mysql_conn:stop(DBRef).
+
+handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
+ Date = convert_timestamp_brief(Msg#msg.timestamp),
+
+ Table = messages_table(VHost, Date),
-+ Owner_id = get_user_id(DBRef, VHost, Msg#msg.owner_name),
-+ Peer_name_id = get_user_id(DBRef, VHost, Msg#msg.peer_name),
-+ Peer_server_id = get_server_id(DBRef, VHost, Msg#msg.peer_server),
-+ Peer_resource_id = get_resource_id(DBRef, VHost, Msg#msg.peer_resource),
++ Owner_id = get_user_id(DBRef, VHost, binary_to_list(Msg#msg.owner_name)),
++ Peer_name_id = get_user_id(DBRef, VHost, binary_to_list(Msg#msg.peer_name)),
++ Peer_server_id = get_server_id(DBRef, VHost, binary_to_list(Msg#msg.peer_server)),
++ Peer_resource_id = get_resource_id(DBRef, VHost, binary_to_list(Msg#msg.peer_resource)),
+
+ Query = ["INSERT INTO ",Table," ",
+ "(owner_id,",
+ "'", Peer_server_id, "',",
+ "'", Peer_resource_id, "',",
+ "'", atom_to_list(Msg#msg.direction), "',",
-+ "'", Msg#msg.type, "',",
-+ "'", ejabberd_odbc:escape(Msg#msg.subject), "',",
-+ "'", ejabberd_odbc:escape(Msg#msg.body), "',",
++ "'", binary_to_list(Msg#msg.type), "',",
++ "'", binary_to_list( ejabberd_sql:escape(Msg#msg.subject) ), "',",
++ "'", binary_to_list( ejabberd_sql:escape(Msg#msg.body) ), "',",
+ "'", Msg#msg.timestamp, "');"],
+
+ Reply =
+ case sql_query_internal_silent(DBRef, Query) of
+ {updated, _} ->
-+ ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
-+ Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
++ ?MYDEBUG("Logged ok for ~s, peer: ~s", [ [Msg#msg.owner_name, <<"@">>, VHost],
++ [Msg#msg.peer_name, <<"@">>, Msg#msg.peer_server] ]),
+ increment_user_stats(DBRef, Msg#msg.owner_name, Owner_id, VHost, Peer_name_id, Peer_server_id, Date);
+ {error, Reason} ->
-+ case regexp:match(Reason, "#42S02") of
++ case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#42S02">>) of
+ % Table doesn't exist
-+ {match, _, _} ->
++ match ->
+ case create_msg_table(DBRef, VHost, Date) of
+ error ->
+ error;
+ ok ->
+ {updated, _} = sql_query_internal(DBRef, Query),
-+ increment_user_stats(DBRef, Msg#msg.owner_name, Owner_id, VHost, Peer_name_id, Peer_server_id, Date)
++ increment_user_stats(DBRef, binary_to_list(Msg#msg.owner_name), Owner_id, VHost, Peer_name_id, Peer_server_id, Date)
+ end;
+ _ ->
+ ?ERROR_MSG("Failed to log message: ~p", [Reason]),
+ Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
+ "FROM ",settings_table(VHost)," ",
+ "JOIN ",users_table(VHost)," ON user_id=owner_id;"],
-+ Reply =
++ Reply =
+ case sql_query_internal(DBRef, Query) of
+ {data, Result} ->
+ {ok, lists:map(fun([Owner, DoLogDef, DoLogL, DoNotLogL]) ->
+ ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
+ ok;
+ {error, Reason} ->
-+ case regexp:match(Reason, "#23000") of
++ case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>) of
+ % Already exists
-+ {match, _, _} ->
++ match ->
+ ok;
+ _ ->
+ ?ERROR_MSG("Failed setup user ~p@~p: ~p", [User, VHost, Reason]),
+get_dates_int(DBRef, VHost) ->
+ case sql_query_internal(DBRef, ["SHOW TABLES"]) of
+ {data, Tables} ->
++ Reg = "^" ++ lists:sublist(prefix(),2,length(prefix())) ++ ".*" ++ escape_vhost(VHost),
+ lists:foldl(fun([Table], Dates) ->
-+ Reg = lists:sublist(prefix(),2,length(prefix())) ++ ".*" ++ escape_vhost(VHost),
-+ case regexp:match(Table, Reg) of
-+ {match, 1, _} ->
-+ ?MYDEBUG("matched ~p against ~p", [Table, Reg]),
-+ case regexp:match(Table,"[0-9]+-[0-9]+-[0-9]+") of
-+ {match, S, E} ->
-+ lists:append(Dates, [lists:sublist(Table,S,E)]);
++ case re:run(Table, Reg) of
++ {match, _} ->
++ case re:run(Table, "[0-9]+-[0-9]+-[0-9]+") of
++ {match, [{S, E}]} ->
++ lists:append(Dates, [lists:sublist(Table, S+1, E)]);
+ nomatch ->
+ Dates
+ end;
-+
+ _ ->
+ Dates
+ end
+ rebuild_all_stats_int(State),
+ ok;
+ {error, Reason} ->
-+ case regexp:match(Reason, "#42S01") of
-+ {match, _, _} ->
++ case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#42S01">>) of
++ match ->
+ ?MYDEBUG("Stats table for ~p already exists", [VHost]),
+ CheckQuery = ["SHOW COLUMNS FROM ",SName," LIKE 'peer_%_id';"],
+ case sql_query_internal(DBRef, CheckQuery) of
+ NewId;
+ {error, Reason} ->
+ % this can be in clustered environment
-+ {match, _, _} = regexp:match(Reason, "#23000"),
++ match = ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>),
+ ?ERROR_MSG("Duplicate key name for ~p", [User]),
+ {ok, ClID} = get_user_id_from_db(DBRef, VHost, User),
+ ClID
+ Id;
+ {error, Reason} ->
+ % this can be in clustered environment
-+ {match, _, _} = regexp:match(Reason, "#23000"),
++ match = ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>),
+ ?ERROR_MSG("Duplicate key name for ~p", [Server]),
+ update_servers_from_db(DBRef, VHost),
+ [[Id1]] = ets:match(ets_servers_table(VHost), {Server, '$1'}),
+
+get_resource_id_from_db(DBRef, VHost, Resource) ->
+ SQuery = ["SELECT resource_id FROM ",resources_table(VHost)," ",
-+ "WHERE resource=\"",ejabberd_odbc:escape(Resource),"\";"],
++ "WHERE resource=\"",binary_to_list(ejabberd_sql:escape(iolist_to_binary(Resource))),"\";"],
+ case sql_query_internal(DBRef, SQuery) of
+ % no such resource in db
+ {data, []} ->
+ % no such resource in db
+ {ok, []} ->
+ IQuery = ["INSERT INTO ",resources_table(VHost)," ",
-+ "SET resource=\"",ejabberd_odbc:escape(Resource),"\";"],
++ "SET resource=\"",binary_to_list(ejabberd_sql:escape(iolist_to_binary(Resource))),"\";"],
+ case sql_query_internal_silent(DBRef, IQuery) of
+ {updated, _} ->
+ {ok, NewId} = get_resource_id_from_db(DBRef, VHost, Resource),
+ NewId;
+ {error, Reason} ->
+ % this can be in clustered environment
-+ {match, _, _} = regexp:match(Reason, "#23000"),
-+ ?ERROR_MSG("Duplicate key name for ~p", [Resource]),
++ match = ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>),
++ ?ERROR_MSG("Duplicate key name for ~s", [Resource]),
+ {ok, ClID} = get_resource_id_from_db(DBRef, VHost, Resource),
+ ClID
+ end;
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
-+% SQL internals
++% SQL internals
+%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+sql_query_internal(DBRef, Query) ->
+
+sql_query_internal_silent(DBRef, Query) ->
+ ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
-+ get_result(mysql_conn:fetch(DBRef, Query, self(), ?MYSQL_TIMEOUT)).
++ get_result(p1_mysql_conn:fetch(DBRef, Query, self(), ?MYSQL_TIMEOUT)).
+
+get_result({updated, MySQLRes}) ->
-+ {updated, mysql:get_result_affected_rows(MySQLRes)};
++ {updated, p1_mysql:get_result_affected_rows(MySQLRes)};
+get_result({data, MySQLRes}) ->
-+ {data, mysql:get_result_rows(MySQLRes)};
++ {data, p1_mysql:get_result_rows(MySQLRes)};
+get_result({error, "query timed out"}) ->
+ {error, "query timed out"};
+get_result({error, MySQLRes}) ->
-+ Reason = mysql:get_result_reason(MySQLRes),
++ Reason = p1_mysql:get_result_reason(MySQLRes),
+ {error, Reason}.
---- mod_logdb_mysql5.erl.orig 2009-11-22 13:06:23.000000000 +0200
-+++ mod_logdb_mysql5.erl 2009-07-30 09:00:14.000000000 +0300
+diff --git a/src/mod_logdb_mysql5.erl b/src/mod_logdb_mysql5.erl
+new file mode 100644
+index 0000000000..c05ab958e2
+--- /dev/null
++++ b/src/mod_logdb_mysql5.erl
@@ -0,0 +1,979 @@
+%%%----------------------------------------------------------------------
+%%% File : mod_logdb_mysql5.erl
-+%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
++%%% Author : Oleg Palij (mailto:o.palij@gmail.com)
+%%% Purpose : MySQL 5 backend for mod_logdb
-+%%% Version : trunk
-+%%% Id : $Id$
-+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
++%%% Url : https://paleg.github.io/mod_logdb/
+%%%----------------------------------------------------------------------
+
+-module(mod_logdb_mysql5).
+-author('o.palij@gmail.com').
+
+-include("mod_logdb.hrl").
-+-include("ejabberd.hrl").
-+-include("jlib.hrl").
++-include("logger.hrl").
+
+-behaviour(gen_logdb).
+-behaviour(gen_server).
+% replace "." with "_"
+escape_vhost(VHost) -> lists:map(fun(46) -> 95;
+ (A) -> A
-+ end, VHost).
++ end, binary_to_list(VHost)).
+prefix() ->
+ "`logdb_".
+
+init([VHost, Opts]) ->
+ crypto:start(),
+
-+ Server = gen_mod:get_opt(server, Opts, "localhost"),
-+ Port = gen_mod:get_opt(port, Opts, 3306),
-+ DB = gen_mod:get_opt(db, Opts, "logdb"),
-+ User = gen_mod:get_opt(user, Opts, "root"),
-+ Password = gen_mod:get_opt(password, Opts, ""),
++ Server = gen_mod:get_opt(server, Opts, fun(A) -> A end, <<"localhost">>),
++ Port = gen_mod:get_opt(port, Opts, fun(A) -> A end, 3306),
++ DB = gen_mod:get_opt(db, Opts, fun(A) -> A end, <<"logdb">>),
++ User = gen_mod:get_opt(user, Opts, fun(A) -> A end, <<"root">>),
++ Password = gen_mod:get_opt(password, Opts, fun(A) -> A end, <<"">>),
+
+ St = #state{vhost=VHost,
+ server=Server, port=Port, db=DB,
+ ?MYDEBUG(Format, Argument)
+ end,
+ ?INFO_MSG("Opening mysql connection ~s@~s:~p/~s", [DBUser, Server, Port, DB]),
-+ mysql_conn:start(Server, Port, DBUser, Password, DB, [65536, 131072], LogFun).
++ p1_mysql_conn:start(binary_to_list(Server), Port,
++ binary_to_list(DBUser), binary_to_list(Password),
++ binary_to_list(DB), LogFun).
+
+close_mysql_connection(DBRef) ->
+ ?MYDEBUG("Closing ~p mysql connection", [DBRef]),
-+ mysql_conn:stop(DBRef).
++ catch p1_mysql_conn:stop(DBRef).
+
+handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
+ Reply = rebuild_stats_at_int(DBRef, VHost, Date),
+ Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
+ "FROM ",settings_table(VHost)," ",
+ "JOIN ",users_table(VHost)," ON user_id=owner_id;"],
-+ Reply =
++ Reply =
+ case sql_query_internal(DBRef, Query) of
+ {data, Result} ->
+ {ok, lists:map(fun([Owner, DoLogDef, DoLogL, DoNotLogL]) ->
+ ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
+ ok;
+ {error, Reason} ->
-+ case regexp:match(Reason, "#23000") of
++ case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>) of
+ % Already exists
-+ {match, _, _} ->
++ match ->
+ ok;
+ _ ->
+ ?ERROR_MSG("Failed setup user ~p@~p: ~p", [User, VHost, Reason]),
+ Query = [ "CALL ",logmessage_name(VHost)," "
+ "('", TableName, "',",
+ "'", Date, "',",
-+ "'", Msg#msg.owner_name, "',",
-+ "'", Msg#msg.peer_name, "',",
-+ "'", Msg#msg.peer_server, "',",
-+ "'", ejabberd_odbc:escape(Msg#msg.peer_resource), "',",
++ "'", binary_to_list(Msg#msg.owner_name), "',",
++ "'", binary_to_list(Msg#msg.peer_name), "',",
++ "'", binary_to_list(Msg#msg.peer_server), "',",
++ "'", binary_to_list( ejabberd_sql:escape(Msg#msg.peer_resource) ), "',",
+ "'", atom_to_list(Msg#msg.direction), "',",
-+ "'", Msg#msg.type, "',",
-+ "'", ejabberd_odbc:escape(Msg#msg.subject), "',",
-+ "'", ejabberd_odbc:escape(Msg#msg.body), "',",
++ "'", binary_to_list(Msg#msg.type), "',",
++ "'", binary_to_list( ejabberd_sql:escape(Msg#msg.subject) ), "',",
++ "'", binary_to_list( ejabberd_sql:escape(Msg#msg.body) ), "',",
+ "'", Msg#msg.timestamp, "');"],
+
+ case sql_query_internal(DBRef, Query) of
+ {updated, _} ->
-+ ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
-+ Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
++ ?MYDEBUG("Logged ok for ~s, peer: ~s", [ [Msg#msg.owner_name, <<"@">>, VHost],
++ [Msg#msg.peer_name, <<"@">>, Msg#msg.peer_server] ]),
+ ok;
+ {error, _Reason} ->
+ error
+get_dates_int(DBRef, VHost) ->
+ case sql_query_internal(DBRef, ["SHOW TABLES"]) of
+ {data, Tables} ->
++ Reg = "^" ++ lists:sublist(prefix(),2,length(prefix())) ++ ".*" ++ escape_vhost(VHost),
+ lists:foldl(fun([Table], Dates) ->
-+ Reg = lists:sublist(prefix(),2,length(prefix())) ++ ".*" ++ escape_vhost(VHost),
-+ case regexp:match(Table, Reg) of
-+ {match, 1, _} ->
-+ case regexp:match(Table,"[0-9]+-[0-9]+-[0-9]+") of
-+ {match, S, E} ->
-+ lists:append(Dates, [lists:sublist(Table,S,E)]);
++ case re:run(Table, Reg) of
++ {match, _} ->
++ case re:run(Table, "[0-9]+-[0-9]+-[0-9]+") of
++ {match, [{S, E}]} ->
++ lists:append(Dates, [lists:sublist(Table, S+1, E)]);
+ nomatch ->
+ Dates
+ end;
+ Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]),
+ case Count of
+ {data, [["0"]]} ->
-+ {updated, _} = sql_query_internal(DBRef, ["DROP VIEW IF EXISTS ",view_table(VHost,Date),";"]),
+ {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]),
-+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE;"]),
++ sql_query_internal(DBRef, ["UNLOCK TABLES;"]),
++ {updated, _} = sql_query_internal(DBRef, ["DROP VIEW IF EXISTS ",view_table(VHost,Date),";"]),
++ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE, ",TempTable," WRITE;"]),
+ {updated, _} = sql_query_internal(DBRef, DQuery),
+ ok;
+ _ ->
+ rebuild_all_stats_int(State),
+ ok;
+ {error, Reason} ->
-+ case regexp:match(Reason, "#42S01") of
-+ {match, _, _} ->
++ case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#42S01">>) of
++ match ->
+ ?MYDEBUG("Stats table for ~p already exists", [VHost]),
+ CheckQuery = ["SHOW COLUMNS FROM ",SName," LIKE 'peer_%_id';"],
+ case sql_query_internal(DBRef, CheckQuery) of
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
-+% SQL internals
++% SQL internals
+%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+sql_query_internal(DBRef, Query) ->
+
+sql_query_internal_silent(DBRef, Query) ->
+ ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
-+ get_result(mysql_conn:fetch(DBRef, Query, self(), ?MYSQL_TIMEOUT)).
++ get_result(p1_mysql_conn:fetch(DBRef, Query, self(), ?MYSQL_TIMEOUT)).
+
+get_result({updated, MySQLRes}) ->
-+ {updated, mysql:get_result_affected_rows(MySQLRes)};
++ {updated, p1_mysql:get_result_affected_rows(MySQLRes)};
+get_result({data, MySQLRes}) ->
-+ {data, mysql:get_result_rows(MySQLRes)};
++ {data, p1_mysql:get_result_rows(MySQLRes)};
+get_result({error, "query timed out"}) ->
+ {error, "query timed out"};
+get_result({error, MySQLRes}) ->
-+ Reason = mysql:get_result_reason(MySQLRes),
++ Reason = p1_mysql:get_result_reason(MySQLRes),
+ {error, Reason}.
+
+get_user_id(DBRef, VHost, User) ->
+ DBIdNew;
+ {error, Reason} ->
+ % this can be in clustered environment
-+ {match, _, _} = regexp:match(Reason, "#23000"),
++ match = ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>),
+ ?ERROR_MSG("Duplicate key name for ~p", [User]),
+ {data, [[ClID]]} = sql_query_internal(DBRef, SQuery),
+ ClID
+ io_lib:format("
+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)
+BEGIN
-+ DECLARE ownerID MEDIUMINT UNSIGNED;
++ DECLARE ownerID MEDIUMINT UNSIGNED;
+ DECLARE peer_nameID MEDIUMINT UNSIGNED;
+ DECLARE peer_serverID MEDIUMINT UNSIGNED;
+ DECLARE peer_resourceID MEDIUMINT UNSIGNED;
+ SELECT user_id INTO @ownerID FROM ~s WHERE username=owner;
+ IF @ownerID IS NULL THEN
+ INSERT INTO ~s SET username=owner;
-+ SET @ownerID = LAST_INSERT_ID();
++ SET @ownerID = LAST_INSERT_ID();
+ END IF;
+
+ SELECT user_id INTO @peer_nameID FROM ~s WHERE username=peer_name;
+ END IF;
+ END IF;
+END;", [logmessage_name(VHost),UName,UName,UName,UName,SName,SName,RName,RName,UName,UName,SName,RName,StName,StName]).
---- mod_logdb_pgsql.erl.orig 2009-11-22 13:06:23.000000000 +0200
-+++ mod_logdb_pgsql.erl 2009-07-30 09:49:10.000000000 +0300
+diff --git a/src/mod_logdb_pgsql.erl b/src/mod_logdb_pgsql.erl
+new file mode 100644
+index 0000000000..202c6ed4a8
+--- /dev/null
++++ b/src/mod_logdb_pgsql.erl
@@ -0,0 +1,1104 @@
++% {ok, DBRef} = pgsql:connect([{host, "127.0.0.1"}, {database, "logdb"}, {user, "logdb"}, {password, "logdb"}, {port, 5432}, {as_binary, true}]).
++% Schema = "test".
++% 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);" ).
+%%%----------------------------------------------------------------------
+%%% File : mod_logdb_pgsql.erl
-+%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
++%%% Author : Oleg Palij (mailto:o.palij@gmail.com)
+%%% Purpose : Posgresql backend for mod_logdb
-+%%% Version : trunk
-+%%% Id : $Id$
-+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
++%%% Url : https://paleg.github.io/mod_logdb/
+%%%----------------------------------------------------------------------
+
+-module(mod_logdb_pgsql).
+-author('o.palij@gmail.com').
+
+-include("mod_logdb.hrl").
-+-include("ejabberd.hrl").
-+-include("jlib.hrl").
++-include("logger.hrl").
+
+-behaviour(gen_logdb).
+-behaviour(gen_server).
+% replace "." with "_"
+escape_vhost(VHost) -> lists:map(fun(46) -> 95;
+ (A) -> A
-+ end, VHost).
++ end, binary_to_list(VHost)).
+
+prefix(Schema) ->
+ Schema ++ ".\"" ++ "logdb_".
+%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+init([VHost, Opts]) ->
-+ Server = gen_mod:get_opt(server, Opts, "localhost"),
-+ DB = gen_mod:get_opt(db, Opts, "ejabberd_logdb"),
-+ User = gen_mod:get_opt(user, Opts, "root"),
-+ Port = gen_mod:get_opt(port, Opts, 5432),
-+ Password = gen_mod:get_opt(password, Opts, ""),
-+ Schema = gen_mod:get_opt(schema, Opts, "public"),
++ Server = gen_mod:get_opt(server, Opts, fun(A) -> A end, <<"localhost">>),
++ DB = gen_mod:get_opt(db, Opts, fun(A) -> A end, <<"ejabberd_logdb">>),
++ User = gen_mod:get_opt(user, Opts, fun(A) -> A end, <<"root">>),
++ Port = gen_mod:get_opt(port, Opts, fun(A) -> A end, 5432),
++ Password = gen_mod:get_opt(password, Opts, fun(A) -> A end, <<"">>),
++ Schema = binary_to_list(gen_mod:get_opt(schema, Opts, fun(A) -> A end, <<"public">>)),
+
-+ ?MYDEBUG("Starting pgsql backend for ~p", [VHost]),
++ ?MYDEBUG("Starting pgsql backend for ~s", [VHost]),
+
+ St = #state{vhost=VHost,
+ server=Server, port=Port, db=DB,
+ "('", TableName, "',",
+ "'", ViewName, "',",
+ "'", Date, "',",
-+ "'", Msg#msg.owner_name, "',",
-+ "'", Msg#msg.peer_name, "',",
-+ "'", Msg#msg.peer_server, "',",
-+ "'", ejabberd_odbc:escape(Msg#msg.peer_resource), "',",
++ "'", binary_to_list(Msg#msg.owner_name), "',",
++ "'", binary_to_list(Msg#msg.peer_name), "',",
++ "'", binary_to_list(Msg#msg.peer_server), "',",
++ "'", binary_to_list( ejabberd_sql:escape(Msg#msg.peer_resource) ), "',",
+ "'", atom_to_list(Msg#msg.direction), "',",
-+ "'", Msg#msg.type, "',",
-+ "'", ejabberd_odbc:escape(Msg#msg.subject), "',",
-+ "'", ejabberd_odbc:escape(Msg#msg.body), "',",
++ "'", binary_to_list(Msg#msg.type), "',",
++ "'", binary_to_list( ejabberd_sql:escape(Msg#msg.subject) ), "',",
++ "'", binary_to_list( ejabberd_sql:escape(Msg#msg.body) ), "',",
+ "'", Msg#msg.timestamp, "');"],
+
+ case sql_query_internal_silent(DBRef, Query) of
+ % TODO: change this
+ {data, [{"0"}]} ->
-+ ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
-+ Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
++ ?MYDEBUG("Logged ok for ~s, peer: ~s", [ [Msg#msg.owner_name, <<"@">>, VHost],
++ [Msg#msg.peer_name, <<"@">>, Msg#msg.peer_server] ]),
+ ok;
+ {error, _Reason} ->
+ error
+ dolog_list=string_to_list(DoLogL),
+ donotlog_list=string_to_list(DoNotLogL)}};
+ {error, Reason} ->
-+ ?ERROR_MSG("Failed to get_user_settings for ~p@~p: ~p", [User, VHost, Reason]),
++ ?ERROR_MSG("Failed to get_user_settings for ~s@~s: ~p", [User, VHost, Reason]),
+ error
+ end,
+ {reply, Reply, State};
+ case sql_query_internal(DBRef, Query) of
+ {data, Recs} ->
+ lists:foldl(fun({_Schema, Table, _Type, _Owner}, Dates) ->
-+ case regexp:match(Table,"[0-9]+-[0-9]+-[0-9]+") of
-+ {match, S, E} ->
-+ lists:append(Dates, [lists:sublist(Table,S,E)]);
++ case re:run(Table,"[0-9]+-[0-9]+-[0-9]+") of
++ {match, [{S, E}]} ->
++ lists:append(Dates, [lists:sublist(Table, S+1, E)]);
+ nomatch ->
+ Dates
+ end
+
+ case sql_transaction_internal(DBRef, Fun) of
+ {atomic, _} ->
-+ ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
++ ?INFO_MSG("Rebuilded stats for ~s at ~s", [VHost, Date]),
+ ok;
+ {aborted, Reason} ->
+ ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]),
+ {value, {code, "42P07"}} ->
+ exists;
+ _ ->
-+ ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
++ ?ERROR_MSG("Failed to create stats table for ~s: ~p", [VHost, Reason]),
+ error
+ end
+ end
+ end,
+ case sql_transaction_internal(DBRef, Fun) of
+ {atomic, created} ->
-+ ?MYDEBUG("Created stats table for ~p", [VHost]),
++ ?MYDEBUG("Created stats table for ~s", [VHost]),
+ rebuild_all_stats_int(State),
+ ok;
+ {atomic, exists} ->
-+ ?MYDEBUG("Stats table for ~p already exists", [VHost]),
-+ {match, F, L} = regexp:match(SName, "\".*\""),
-+ QTable = lists:sublist(SName, F+1, L-2),
++ ?MYDEBUG("Stats table for ~s already exists", [VHost]),
++ {match, [{F, L}]} = re:run(SName, "\".*\""),
++ QTable = lists:sublist(SName, F+2, L-2),
+ 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);"],
+ {data,[{OID}]} = sql_query_internal(DBRef, OIDQuery),
+ 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$';"],
+ ],
+ case sql_query_internal_silent(DBRef, Query) of
+ {updated, _} ->
-+ ?MYDEBUG("Created settings table for ~p", [VHost]),
++ ?MYDEBUG("Created settings table for ~s", [VHost]),
+ ok;
+ {error, Reason} ->
+ case lists:keysearch(code, 1, Reason) of
+ {value, {code, "42P07"}} ->
-+ ?MYDEBUG("Settings table for ~p already exists", [VHost]),
++ ?MYDEBUG("Settings table for ~s already exists", [VHost]),
+ ok;
+ _ ->
-+ ?ERROR_MSG("Failed to create settings table for ~p: ~p", [VHost, Reason]),
++ ?ERROR_MSG("Failed to create settings table for ~s: ~p", [VHost, Reason]),
+ error
+ end
+ end.
+ {value, {code, "42P07"}} ->
+ exists;
+ _ ->
-+ ?ERROR_MSG("Failed to create users table for ~p: ~p", [VHost, Reason]),
++ ?ERROR_MSG("Failed to create users table for ~s: ~p", [VHost, Reason]),
+ error
+ end
+ end
+ end,
+ case sql_transaction_internal(DBRef, Fun) of
+ {atomic, created} ->
-+ ?MYDEBUG("Created users table for ~p", [VHost]),
++ ?MYDEBUG("Created users table for ~s", [VHost]),
+ ok;
-+ {atomic, exists} ->
-+ ?MYDEBUG("Users table for ~p already exists", [VHost]),
++ {atomic, exists} ->
++ ?MYDEBUG("Users table for ~s already exists", [VHost]),
+ ok;
+ {aborted, _} -> error
+ end.
+ {value, {code, "42P07"}} ->
+ exists;
+ _ ->
-+ ?ERROR_MSG("Failed to create servers table for ~p: ~p", [VHost, Reason]),
++ ?ERROR_MSG("Failed to create servers table for ~s: ~p", [VHost, Reason]),
+ error
+ end
+ end
+ end,
+ case sql_transaction_internal(DBRef, Fun) of
+ {atomic, created} ->
-+ ?MYDEBUG("Created servers table for ~p", [VHost]),
++ ?MYDEBUG("Created servers table for ~s", [VHost]),
+ ok;
+ {atomic, exists} ->
-+ ?MYDEBUG("Servers table for ~p already exists", [VHost]),
++ ?MYDEBUG("Servers table for ~s already exists", [VHost]),
+ ok;
+ {aborted, _} -> error
+ end.
+ {value, {code, "42P07"}} ->
+ exists;
+ _ ->
-+ ?ERROR_MSG("Failed to create users table for ~p: ~p", [VHost, Reason]),
++ ?ERROR_MSG("Failed to create users table for ~s: ~p", [VHost, Reason]),
+ error
+ end
+ end
+ end,
+ case sql_transaction_internal(DBRef, Fun) of
+ {atomic, created} ->
-+ ?MYDEBUG("Created resources table for ~p", [VHost]),
++ ?MYDEBUG("Created resources table for ~s", [VHost]),
+ ok;
+ {atomic, exists} ->
-+ ?MYDEBUG("Resources table for ~p already exists", [VHost]),
++ ?MYDEBUG("Resources table for ~s already exists", [VHost]),
+ ok;
+ {aborted, _} -> error
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
-+% SQL internals
++% SQL internals
+%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% like do_transaction/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
+ {updated, 1};
+get_result({ok, ["CREATE FUNCTION"]}) ->
+ {updated, 1};
-+get_result({ok, [{"SELECT", _Rows, Recs}]}) ->
++get_result({ok, [{[$S, $E, $L, $E, $C, $T, $ | _Rest], _Rows, Recs}]}) ->
+ Fun = fun(Rec) ->
+ list_to_tuple(
+ lists:map(fun(Elem) when is_binary(Elem) ->
+get_result(Rez) ->
+ {error, undefined, Rez}.
+
---- mod_logdb_mnesia_old.erl.orig 2009-11-22 13:06:23.000000000 +0200
-+++ mod_logdb_mnesia_old.erl 2009-02-05 20:12:58.000000000 +0200
-@@ -0,0 +1,258 @@
-+%%%----------------------------------------------------------------------
-+%%% File : mod_logdb_mnesia_old.erl
-+%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
-+%%% Purpose : mod_logmnesia backend for mod_logdb (should be used only for copy_tables functionality)
-+%%% Version : trunk
-+%%% Id : $Id$
-+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
-+%%%----------------------------------------------------------------------
-+
-+-module(mod_logdb_mnesia_old).
-+-author('o.palij@gmail.com').
-+
-+-include("ejabberd.hrl").
-+-include("jlib.hrl").
-+
-+-behaviour(gen_logdb).
-+
-+-export([start/2, stop/1,
-+ log_message/2,
-+ rebuild_stats/1,
-+ rebuild_stats_at/2,
-+ rebuild_stats_at1/2,
-+ delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
-+ get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
-+ get_dates/1,
-+ get_users_settings/1, get_user_settings/2, set_user_settings/3,
-+ drop_user/2]).
-+
-+-record(stats, {user, server, table, count}).
-+-record(msg, {to_user, to_server, to_resource, from_user, from_server, from_resource, id, type, subject, body, timestamp}).
-+
-+tables_prefix() -> "messages_".
-+% stats_table should not start with tables_prefix(VHost) !
-+% i.e. lists:prefix(tables_prefix(VHost), atom_to_list(stats_table())) must be /= true
-+stats_table() -> list_to_atom("messages-stats").
-+% table name as atom from Date
-+-define(ATABLE(Date), list_to_atom(tables_prefix() ++ Date)).
-+-define(LTABLE(Date), tables_prefix() ++ Date).
-+
-+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-+%
-+% gen_logdb callbacks
-+%
-+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-+start(_Opts, _VHost) ->
-+ case mnesia:system_info(is_running) of
-+ yes ->
-+ ok = create_stats_table(),
-+ {ok, ok};
-+ no ->
-+ ?ERROR_MSG("Mnesia not running", []),
-+ error;
-+ Status ->
-+ ?ERROR_MSG("Mnesia status: ~p", [Status]),
-+ error
-+ end.
-+
-+stop(_VHost) ->
-+ ok.
-+
-+log_message(_VHost, _Msg) ->
-+ error.
-+
-+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-+%
-+% gen_logdb callbacks (maintaince)
-+%
-+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-+rebuild_stats(_VHost) ->
-+ ok.
-+
-+rebuild_stats_at(VHost, Date) ->
-+ Table = ?LTABLE(Date),
-+ {Time, Value}=timer:tc(?MODULE, rebuild_stats_at1, [VHost, Table]),
-+ ?INFO_MSG("rebuild_stats_at ~p elapsed ~p sec: ~p~n", [Date, Time/1000000, Value]),
-+ Value.
-+rebuild_stats_at1(VHost, Table) ->
-+ CFun = fun(Msg, Stats) ->
-+ To = Msg#msg.to_user ++ "@" ++ Msg#msg.to_server,
-+ Stats_to = if
-+ Msg#msg.to_server == VHost ->
-+ case lists:keysearch(To, 1, Stats) of
-+ {value, {Who_to, Count_to}} ->
-+ lists:keyreplace(To, 1, Stats, {Who_to, Count_to + 1});
-+ false ->
-+ lists:append(Stats, [{To, 1}])
-+ end;
-+ true ->
-+ Stats
-+ end,
-+ From = Msg#msg.from_user ++ "@" ++ Msg#msg.from_server,
-+ Stats_from = if
-+ Msg#msg.from_server == VHost ->
-+ case lists:keysearch(From, 1, Stats_to) of
-+ {value, {Who_from, Count_from}} ->
-+ lists:keyreplace(From, 1, Stats_to, {Who_from, Count_from + 1});
-+ false ->
-+ lists:append(Stats_to, [{From, 1}])
-+ end;
-+ true ->
-+ Stats_to
-+ end,
-+ Stats_from
-+ end,
-+ DFun = fun(#stats{table=STable, server=Server} = Stat, _Acc)
-+ when STable == Table, Server == VHost ->
-+ mnesia:delete_object(stats_table(), Stat, write);
-+ (_Stat, _Acc) -> ok
-+ end,
-+ case mnesia:transaction(fun() ->
-+ mnesia:write_lock_table(list_to_atom(Table)),
-+ mnesia:write_lock_table(stats_table()),
-+ % Calc stats for VHost at Date
-+ AStats = mnesia:foldl(CFun, [], list_to_atom(Table)),
-+ % Delete all stats for VHost at Date
-+ mnesia:foldl(DFun, [], stats_table()),
-+ % Write new calc'ed stats
-+ lists:foreach(fun({Who, Count}) ->
-+ Jid = jlib:string_to_jid(Who),
-+ JUser = Jid#jid.user,
-+ WStat = #stats{user=JUser, server=VHost, table=Table, count=Count},
-+ mnesia:write(stats_table(), WStat, write)
-+ end, AStats)
-+ end) of
-+ {aborted, Reason} ->
-+ ?ERROR_MSG("Failed to rebuild_stats_at for ~p at ~p: ~p", [VHost, Table, Reason]),
-+ error;
-+ {atomic, _} ->
-+ ok
-+ end.
+diff --git a/src/mod_roster.erl b/src/mod_roster.erl
+index 426589319c..6b51d3c381 100644
+--- a/src/mod_roster.erl
++++ b/src/mod_roster.erl
+@@ -65,6 +65,8 @@
+ -define(ROSTER_ITEM_CACHE, roster_item_cache).
+ -define(ROSTER_VERSION_CACHE, roster_version_cache).
+
++-include("mod_logdb.hrl").
+
-+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-+%
-+% gen_logdb callbacks (delete)
-+%
-+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-+delete_messages_by_user_at(_VHost, _Msgs, _Date) ->
-+ error.
+ -type c2s_state() :: ejabberd_c2s:state().
+ -export_type([subscription/0]).
+
+@@ -943,6 +945,14 @@ user_roster(User, Server, Query, Lang) ->
+ Query),
+ Items = get_roster(LUser, LServer),
+ SItems = lists:sort(Items),
+
-+delete_all_messages_by_user_at(_User, _VHost, _Date) ->
-+ error.
++ Settings = case gen_mod:is_loaded(Server, mod_logdb) of
++ true ->
++ mod_logdb:get_user_settings(User, Server);
++ false ->
++ []
++ end,
+
-+delete_messages_at(VHost, Date) ->
-+ Table = list_to_atom(tables_prefix() ++ Date),
+ FItems = case SItems of
+ [] -> [?CT(?T("None"))];
+ _ ->
+@@ -1000,7 +1010,33 @@ user_roster(User, Server, Query, Lang) ->
+ [?INPUTT(<<"submit">>,
+ <<"remove",
+ (ejabberd_web_admin:term_to_id(R#roster.jid))/binary>>,
+- ?T("Remove"))])])
++ ?T("Remove"))]),
++ case gen_mod:is_loaded(Server, mod_logdb) of
++ true ->
++ Peer = jid:encode(R#roster.jid),
++ A = lists:member(Peer, Settings#user_settings.dolog_list),
++ B = lists:member(Peer, Settings#user_settings.donotlog_list),
++ {Name, Value} =
++ if
++ A ->
++ {<<"donotlog">>, <<"Do Not Log Messages">>};
++ B ->
++ {<<"dolog">>, <<"Log Messages">>};
++ Settings#user_settings.dolog_default == true ->
++ {<<"donotlog">>, <<"Do Not Log Messages">>};
++ Settings#user_settings.dolog_default == false ->
++ {<<"dolog">>, <<"Log Messages">>}
++ end,
++
++ ?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
++ [?INPUTT(<<"submit">>,
++ <<Name/binary,
++ (ejabberd_web_admin:term_to_id(R#roster.jid))/binary>>,
++ Value)]);
++ false ->
++ ?X([])
++ end
++ ])
+ end,
+ SItems)))])]
+ end,
+@@ -1107,9 +1143,42 @@ user_roster_item_parse_query(User, Server, Items,
+ sub_els = [#roster_query{
+ items = [RosterItem]}]}),
+ throw(submitted);
+- false -> ok
+- end
+- end
++ false ->
++ case lists:keysearch(
++ <<"donotlog", (ejabberd_web_admin:term_to_id(JID))/binary>>, 1, Query) of
++ {value, _} ->
++ Peer = jid:encode(JID),
++ Settings = mod_logdb:get_user_settings(User, Server),
++ DNLL = case lists:member(Peer, Settings#user_settings.donotlog_list) of
++ false -> lists:append(Settings#user_settings.donotlog_list, [Peer]);
++ true -> Settings#user_settings.donotlog_list
++ end,
++ DLL = lists:delete(jid:encode(JID), Settings#user_settings.dolog_list),
++ Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
++ % TODO: check returned value
++ ok = mod_logdb:set_user_settings(User, Server, Sett),
++ throw(nothing);
++ false ->
++ case lists:keysearch(
++ <<"dolog", (ejabberd_web_admin:term_to_id(JID))/binary>>, 1, Query) of
++ {value, _} ->
++ Peer = jid:encode(JID),
++ Settings = mod_logdb:get_user_settings(User, Server),
++ DLL = case lists:member(Peer, Settings#user_settings.dolog_list) of
++ false -> lists:append(Settings#user_settings.dolog_list, [Peer]);
++ true -> Settings#user_settings.dolog_list
++ end,
++ DNLL = lists:delete(jid:encode(JID), Settings#user_settings.donotlog_list),
++ Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
++ % TODO: check returned value
++ ok = mod_logdb:set_user_settings(User, Server, Sett),
++ throw(nothing);
++ false ->
++ ok
++ end % dolog
++ end % donotlog
++ end % remove
++ end % validate
+ end,
+ Items),
+ nothing.
+
+From 5043114bc1a74caa522e8a1569b485ccc1808a79 Mon Sep 17 00:00:00 2001
+From: Oleh Palii <o.palij@gmail.com>
+Date: Sat, 31 Aug 2019 15:23:19 +0300
+Subject: [PATCH 2/3] mod_logdb 19.08 adaptation
+
+---
+ src/mod_logdb.erl | 187 +++++++++++++++++++++++----------------
+ src/mod_logdb_mysql.erl | 10 +--
+ src/mod_logdb_mysql5.erl | 10 +--
+ src/mod_logdb_pgsql.erl | 12 +--
+ 4 files changed, 125 insertions(+), 94 deletions(-)
+
+diff --git a/src/mod_logdb.erl b/src/mod_logdb.erl
+index bf0240d139..0b5c2ec687 100644
+--- a/src/mod_logdb.erl
++++ b/src/mod_logdb.erl
+@@ -26,6 +26,7 @@
+ -export([get_local_identity/5,
+ get_local_features/5,
+ get_local_items/5,
++ mod_options/1,
+ adhoc_local_items/4,
+ adhoc_local_commands/4
+ ]).
+@@ -56,6 +57,8 @@
+ user_messages_stats/4,
+ user_messages_stats_at/5]).
+
++-export([get_opt/3]).
+
-+ DFun = fun(#msg{to_server=To_server, from_server=From_server}=Msg, _Acc)
-+ when To_server == VHost; From_server == VHost ->
-+ mnesia:delete_object(Table, Msg, write);
-+ (_Msg, _Acc) -> ok
-+ end,
-+
-+ case mnesia:transaction(fun() ->
-+ mnesia:foldl(DFun, [], Table)
-+ end) of
-+ {aborted, Reason} ->
-+ ?ERROR_MSG("Failed to delete_messages_at for ~p at ~p: ~p", [VHost, Date, Reason]),
-+ error;
-+ {atomic, _} ->
-+ ok
+ -include("mod_logdb.hrl").
+ -include("xmpp.hrl").
+ -include("mod_roster.hrl").
+@@ -64,6 +67,7 @@
+ -include("ejabberd_web_admin.hrl").
+ -include("ejabberd_http.hrl").
+ -include("logger.hrl").
++-include("translate.hrl").
+
+ -define(PROCNAME, ejabberd_mod_logdb).
+ % gen_server call timeout
+@@ -73,6 +77,28 @@
+
+ ets_settings_table(VHost) -> list_to_atom("ets_logdb_settings_" ++ binary_to_list(VHost)).
+
++-spec tr(binary(), binary()) -> binary().
++tr(Lang, Text) ->
++ translate:translate(Lang, Text).
++
++mod_options(VHost) ->
++ [
++ {dbs, [{mnesia, []}]},
++ {vhosts, [{VHost, mnesia}]},
++ {ignore_jids, []},
++ {groupchat, none},
++ {drop_messages_on_user_removal, true},
++ {purge_older_days, never},
++ {dolog_default, true},
++ {poll_users_settings, 5}
++ ].
++
++get_opt(Opt, Opts, Default) ->
++ case lists:keyfind(Opt, 1, Opts) of
++ false -> Default;
++ {_, Result} -> Result
+ end.
+
-+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-+%
-+% gen_logdb callbacks (get)
-+%
-+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-+get_vhost_stats(_VHost) ->
-+ {error, "does not emplemented"}.
-+
-+get_vhost_stats_at(VHost, Date) ->
-+ Fun = fun() ->
-+ Pat = #stats{user='$1', server=VHost, table=tables_prefix()++Date, count = '$2'},
-+ mnesia:select(stats_table(), [{Pat, [], [['$1', '$2']]}])
-+ end,
-+ case mnesia:transaction(Fun) of
-+ {atomic, Result} ->
-+ RFun = fun([User, Count]) ->
-+ {User, Count}
-+ end,
-+ {ok, lists:reverse(lists:keysort(2, lists:map(RFun, Result)))};
-+ {aborted, Reason} -> {error, Reason}
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %
+ % gen_mod/gen_server callbacks
+@@ -88,7 +114,8 @@ start(VHost, Opts) ->
+ worker,
+ [?MODULE]},
+ % add child to ejabberd_sup
+- supervisor:start_child(ejabberd_gen_mod_sup, ChildSpec).
++ supervisor:start_child(ejabberd_gen_mod_sup, ChildSpec),
++ ok.
+
+ depends(_Host, _Opts) ->
+ [].
+@@ -106,14 +133,14 @@ start_link(VHost, Opts) ->
+
+ init([VHost, Opts]) ->
+ process_flag(trap_exit, true),
+- DBsRaw = gen_mod:get_opt(dbs, Opts, fun(A) -> A end, [{mnesia, []}]),
++ DBsRaw = gen_mod:get_opt(dbs, Opts),
+ DBs = case lists:keysearch(mnesia, 1, DBsRaw) of
+ false -> lists:append(DBsRaw, [{mnesia,[]}]);
+ {value, _} -> DBsRaw
+ end,
+- VHostDB = gen_mod:get_opt(vhosts, Opts, fun(A) -> A end, [{VHost, mnesia}]),
++ VHostDB = gen_mod:get_opt(vhosts, Opts),
+ % 10 is default because of using in clustered environment
+- PollUsersSettings = gen_mod:get_opt(poll_users_settings, Opts, fun(A) -> A end, 10),
++ PollUsersSettings = gen_mod:get_opt(poll_users_settings, Opts),
+
+ {DBName, DBOpts} =
+ case lists:keysearch(VHost, 1, VHostDB) of
+@@ -139,11 +166,11 @@ init([VHost, Opts]) ->
+ dbopts=DBOpts,
+ % dbs used for convert messages from one backend to other
+ dbs=DBs,
+- dolog_default=gen_mod:get_opt(dolog_default, Opts, fun(A) -> A end, true),
+- drop_messages_on_user_removal=gen_mod:get_opt(drop_messages_on_user_removal, Opts, fun(A) -> A end, true),
+- ignore_jids=gen_mod:get_opt(ignore_jids, Opts, fun(A) -> A end, []),
+- groupchat=gen_mod:get_opt(groupchat, Opts, fun(A) -> A end, none),
+- purge_older_days=gen_mod:get_opt(purge_older_days, Opts, fun(A) -> A end, never),
++ dolog_default=gen_mod:get_opt(dolog_default, Opts),
++ drop_messages_on_user_removal=gen_mod:get_opt(drop_messages_on_user_removal, Opts),
++ ignore_jids=gen_mod:get_opt(ignore_jids, Opts),
++ groupchat=gen_mod:get_opt(groupchat, Opts),
++ purge_older_days=gen_mod:get_opt(purge_older_days, Opts),
+ poll_users_settings=PollUsersSettings}}.
+
+ cleanup(#state{vhost=VHost} = _State) ->
+@@ -444,7 +471,7 @@ handle_info(scheduled_purging, #state{vhost=VHost, purge_older_days=Days} = Stat
+ % from timer:send_interval/2 (in start handle_info)
+ handle_info(poll_users_settings, #state{dbmod=DBMod, vhost=VHost}=State) ->
+ {ok, DoLog} = DBMod:get_users_settings(VHost),
+- ?MYDEBUG("DoLog=~p", [DoLog]),
++% ?MYDEBUG("DoLog=~p", [DoLog]),
+ true = ets:delete_all_objects(ets_settings_table(VHost)),
+ ets:insert(ets_settings_table(VHost), DoLog),
+ {noreply, State};
+@@ -654,8 +681,7 @@ sort_stats(Stats) ->
+ % return float seconds elapsed from "zero hour" as list
+ get_timestamp() ->
+ {MegaSec, Sec, MicroSec} = now(),
+- [List] = io_lib:format("~.5f", [MegaSec*1000000 + Sec + MicroSec/1000000]),
+- List.
++ io_lib:format("~.5f", [MegaSec*1000000 + Sec + MicroSec/1000000]).
+
+ % convert float seconds elapsed from "zero hour" to local time "%Y-%m-%d %H:%M:%S" string
+ convert_timestamp(Seconds) when is_list(Seconds) ->
+@@ -907,7 +933,7 @@ copy_messages_int_tc([FromDBMod, ToDBMod, VHost, Date]) ->
+ % mysql, pgsql removes final zeros after decimal point
+ (#msg{timestamp=Tst}) when length(Tst) < 16 ->
+ {F, _} = string:to_float(Tst++".0"),
+- [T] = io_lib:format("~.5f", [F]),
++ T = io_lib:format("~.5f", [F]),
+ ets:insert(mod_logdb_temp, {T})
+ end, ToMsgs),
+ {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date),
+@@ -992,16 +1018,25 @@ string_to_list(String) ->
+ % ad-hoc (copy/pasted from mod_configure.erl)
+ %
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++-spec get_permission_level(jid()) -> global | vhost.
++get_permission_level(JID) ->
++ case acl:match_rule(global, configure, JID) of
++ allow -> global;
++ deny -> vhost
+ end.
+
-+get_user_stats(_User, _VHost) ->
-+ {error, "does not emplemented"}.
-+
-+get_user_messages_at(User, VHost, Date) ->
-+ Table_name = tables_prefix() ++ Date,
-+ case mnesia:transaction(fun() ->
-+ Pat_to = #msg{to_user=User, to_server=VHost, _='_'},
-+ Pat_from = #msg{from_user=User, from_server=VHost, _='_'},
-+ mnesia:select(list_to_atom(Table_name),
-+ [{Pat_to, [], ['$_']},
-+ {Pat_from, [], ['$_']}])
-+ end) of
-+ {atomic, Result} ->
-+ Msgs = lists:map(fun(#msg{to_user=To_user, to_server=To_server, to_resource=To_res,
-+ from_user=From_user, from_server=From_server, from_resource=From_res,
-+ type=Type,
-+ subject=Subj,
-+ body=Body, timestamp=Timestamp} = _Msg) ->
-+ Subject = case Subj of
-+ "None" -> "";
-+ _ -> Subj
-+ end,
-+ {msg, To_user, To_server, To_res, From_user, From_server, From_res, Type, Subject, Body, Timestamp}
-+ end, Result),
-+ {ok, Msgs};
-+ {aborted, Reason} ->
-+ {error, Reason}
-+ end.
-+
-+get_dates(_VHost) ->
-+ Tables = mnesia:system_info(tables),
-+ MessagesTables =
-+ lists:filter(fun(Table) ->
-+ lists:prefix(tables_prefix(), atom_to_list(Table))
-+ end,
-+ Tables),
-+ lists:map(fun(Table) ->
-+ lists:sublist(atom_to_list(Table),
-+ length(tables_prefix())+1,
-+ length(atom_to_list(Table)))
-+ end,
-+ MessagesTables).
-+
-+get_users_settings(_VHost) ->
-+ {ok, []}.
-+get_user_settings(_User, _VHost) ->
-+ {ok, []}.
-+set_user_settings(_User, _VHost, _Set) ->
-+ ok.
-+drop_user(_User, _VHost) ->
-+ ok.
-+
-+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-+%
-+% internal
-+%
-+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-+% called from db_logon/2
-+create_stats_table() ->
-+ SName = stats_table(),
-+ case mnesia:create_table(SName,
-+ [{disc_only_copies, [node()]},
-+ {type, bag},
-+ {attributes, record_info(fields, stats)},
-+ {record_name, stats}
-+ ]) of
-+ {atomic, ok} ->
-+ ?INFO_MSG("Created stats table", []),
-+ ok;
-+ {aborted, {already_exists, _}} ->
-+ ok;
-+ {aborted, Reason} ->
-+ ?ERROR_MSG("Failed to create stats table: ~p", [Reason]),
-+ error
-+ end.
---- gen_logdb.erl.orig 2009-11-22 13:06:23.000000000 +0200
-+++ gen_logdb.erl 2009-07-22 16:43:26.000000000 +0300
-@@ -0,0 +1,164 @@
-+%%%----------------------------------------------------------------------
-+%%% File : gen_logdb.erl
-+%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
-+%%% Purpose : Describes generic behaviour for mod_logdb backends.
-+%%% Version : trunk
-+%%% Id : $Id$
-+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
-+%%%----------------------------------------------------------------------
-+
-+-module(gen_logdb).
-+-author('o.palij@gmail.com').
-+
-+-export([behaviour_info/1]).
-+
-+behaviour_info(callbacks) ->
-+ [
-+ % called from handle_info(start, _)
-+ % it should logon database and return reference to started instance
-+ % start(VHost, Opts) -> {ok, SPid} | error
-+ % Options - list of options to connect to db
-+ % Types: Options = list() -> [] |
-+ % [{user, "logdb"},
-+ % {pass, "1234"},
-+ % {db, "logdb"}] | ...
-+ % VHost = list() -> "jabber.example.org"
-+ {start, 2},
-+
-+ % called from cleanup/1
-+ % it should logoff database and do cleanup
-+ % stop(VHost)
-+ % Types: VHost = list() -> "jabber.example.org"
-+ {stop, 1},
-+
-+ % called from handle_call({addlog, _}, _, _)
-+ % it should log messages to database
-+ % log_message(VHost, Msg) -> ok | error
-+ % Types:
-+ % VHost = list() -> "jabber.example.org"
-+ % Msg = record() -> #msg
-+ {log_message, 2},
-+
-+ % called from ejabberdctl rebuild_stats
-+ % it should rebuild stats table (if used) for vhost
-+ % rebuild_stats(VHost)
-+ % Types:
-+ % VHost = list() -> "jabber.example.org"
-+ {rebuild_stats, 1},
-+
-+ % it should rebuild stats table (if used) for vhost at Date
-+ % rebuild_stats_at(VHost, Date)
-+ % Types:
-+ % VHost = list() -> "jabber.example.org"
-+ % Date = list() -> "2007-02-12"
-+ {rebuild_stats_at, 2},
-+
-+ % called from user_messages_at_parse_query/5
-+ % it should delete selected user messages at date
-+ % delete_messages_by_user_at(VHost, Msgs, Date) -> ok | error
-+ % Types:
-+ % VHost = list() -> "jabber.example.org"
-+ % Msgs = list() -> [ #msg1, msg2, ... ]
-+ % Date = list() -> "2007-02-12"
-+ {delete_messages_by_user_at, 3},
-+
-+ % called from user_messages_parse_query/4 | vhost_messages_at_parse_query/4
-+ % it should delete all user messages at date
-+ % delete_all_messages_by_user_at(User, VHost, Date) -> ok | error
-+ % Types:
-+ % User = list() -> "admin"
-+ % VHost = list() -> "jabber.example.org"
-+ % Date = list() -> "2007-02-12"
-+ {delete_all_messages_by_user_at, 3},
-+
-+ % called from vhost_messages_parse_query/3
-+ % it should delete messages for vhost at date and update stats
-+ % delete_messages_at(VHost, Date) -> ok | error
-+ % Types:
-+ % VHost = list() -> "jabber.example.org"
-+ % Date = list() -> "2007-02-12"
-+ {delete_messages_at, 2},
-+
-+ % called from ejabberd_web_admin:vhost_messages_stats/3
-+ % it should return sorted list of count of messages by dates for vhost
-+ % get_vhost_stats(VHost) -> {ok, [{Date1, Msgs_count1}, {Date2, Msgs_count2}, ... ]} |
-+ % {error, Reason}
-+ % Types:
-+ % VHost = list() -> "jabber.example.org"
-+ % DateN = list() -> "2007-02-12"
-+ % Msgs_countN = number() -> 241
-+ {get_vhost_stats, 1},
-+
-+ % called from ejabberd_web_admin:vhost_messages_stats_at/4
-+ % it should return sorted list of count of messages by users at date for vhost
-+ % get_vhost_stats_at(VHost, Date) -> {ok, [{User1, Msgs_count1}, {User2, Msgs_count2}, ....]} |
-+ % {error, Reason}
-+ % Types:
-+ % VHost = list() -> "jabber.example.org"
-+ % Date = list() -> "2007-02-12"
-+ % UserN = list() -> "admin"
-+ % Msgs_countN = number() -> 241
-+ {get_vhost_stats_at, 2},
-+
-+ % called from ejabberd_web_admin:user_messages_stats/4
-+ % it should return sorted list of count of messages by date for user at vhost
-+ % get_user_stats(User, VHost) -> {ok, [{Date1, Msgs_count1}, {Date2, Msgs_count2}, ...]} |
-+ % {error, Reason}
-+ % Types:
-+ % User = list() -> "admin"
-+ % VHost = list() -> "jabber.example.org"
-+ % DateN = list() -> "2007-02-12"
-+ % Msgs_countN = number() -> 241
-+ {get_user_stats, 2},
-+
-+ % called from ejabberd_web_admin:user_messages_stats_at/5
-+ % it should return all user messages at date
-+ % get_user_messages_at(User, VHost, Date) -> {ok, Msgs} | {error, Reason}
-+ % Types:
-+ % User = list() -> "admin"
-+ % VHost = list() -> "jabber.example.org"
-+ % Date = list() -> "2007-02-12"
-+ % Msgs = list() -> [ #msg1, msg2, ... ]
-+ {get_user_messages_at, 3},
-+
-+ % called from many places
-+ % it should return list of dates for vhost
-+ % get_dates(VHost) -> [Date1, Date2, ... ]
-+ % Types:
-+ % VHost = list() -> "jabber.example.org"
-+ % DateN = list() -> "2007-02-12"
-+ {get_dates, 1},
-+
-+ % called from start
-+ % it should return list with users settings for VHost in db
-+ % get_users_settings(VHost) -> [#user_settings1, #user_settings2, ... ] | error
-+ % Types:
-+ % VHost = list() -> "jabber.example.org"
-+ {get_users_settings, 1},
-+
-+ % called from many places
-+ % it should return User settings at VHost from db
-+ % get_user_settings(User, VHost) -> error | {ok, #user_settings}
-+ % Types:
-+ % User = list() -> "admin"
-+ % VHost = list() -> "jabber.example.org"
-+ {get_user_settings, 2},
-+
-+ % called from web admin
-+ % it should set User settings at VHost
-+ % set_user_settings(User, VHost, #user_settings) -> ok | error
-+ % Types:
-+ % User = list() -> "admin"
-+ % VHost = list() -> "jabber.example.org"
-+ {set_user_settings, 3},
-+
-+ % called from remove_user (ejabberd hook)
-+ % it should remove user messages and settings at VHost
-+ % drop_user(User, VHost) -> ok | error
-+ % Types:
-+ % User = list() -> "admin"
-+ % VHost = list() -> "jabber.example.org"
-+ {drop_user, 2}
-+ ];
-+behaviour_info(_) ->
-+ undefined.
---- mod_muc/mod_muc_room-2.1.0.erl 2009-11-22 12:30:40.000000000 +0200
-+++ mod_muc/mod_muc_room.erl 2009-11-22 12:33:43.000000000 +0200
-@@ -625,6 +625,12 @@
- {reply, {ok, NSD#state.config}, StateName, NSD};
- handle_sync_event({change_state, NewStateData}, _From, StateName, _StateData) ->
- {reply, {ok, NewStateData}, StateName, NewStateData};
-+handle_sync_event({get_jid_nick, Jid}, _From, StateName, StateData) ->
-+ R = case ?DICT:find(jlib:jid_tolower(Jid), StateData#state.users) of
-+ error -> [];
-+ {ok, {user, _, Nick, _, _}} -> Nick
-+ end,
-+ {reply, R, StateName, StateData};
- handle_sync_event(_Event, _From, StateName, StateData) ->
- Reply = ok,
- {reply, Reply, StateName, StateData}.
---- msgs/uk-2.1.0.msg 2009-11-22 12:30:40.000000000 +0200
-+++ msgs/uk.msg 2009-11-22 12:36:12.000000000 +0200
-@@ -369,3 +369,31 @@
- {"You need an x:data capable client to search","Для пошуку необхідний x:data-придатний клієнт"}.
- {"Your contact offline message queue is full. The message has been discarded.","Черга повідомлень, що не були доставлені, переповнена. Повідомлення не було збережено."}.
- {"Your messages to ~s are being blocked. To unblock them, visit ~s","Ваші повідомлення до ~s блокуються. Для розблокування відвідайте ~s"}.
-+% mod_logdb
-+{"Users Messages", "Повідомлення користувачів"}.
-+{"Date", "Дата"}.
-+{"Count", "Кількість"}.
-+{"Logged messages for ", "Збережені повідомлення для "}.
-+{" at ", " за "}.
-+{"No logged messages for ", "Відсутні повідомлення для "}.
-+{"Date, Time", "Дата, Час"}.
-+{"Direction: Jid", "Напрямок: Jid"}.
-+{"Subject", "Тема"}.
-+{"Body", "Текст"}.
-+{"Messages", "Повідомлення"}.
-+{"Filter Selected", "Відфільтрувати виділені"}.
-+{"Do Not Log Messages", "Не зберігати повідомлення"}.
-+{"Log Messages", "Зберігати повідомлення"}.
-+{"Messages logging engine", "Система збереження повідомлень"}.
-+{"Default", "За замовчуванням"}.
-+{"Set logging preferences", "Вкажіть налагоджування збереження повідомлень"}.
-+{"Messages logging engine users", "Користувачі системи збереження повідомлень"}.
-+{"Messages logging engine settings", "Налагоджування системи збереження повідомлень"}.
-+{"Set run-time settings", "Вкажіть поточні налагоджування"}.
-+{"Groupchat messages logging", "Збереження повідомлень типу groupchat"}.
-+{"Jids/Domains to ignore", "Ігнорувати наступні jids/домени"}.
-+{"Purge messages older than (days)", "Видаляти повідомлення старіші ніж (дні)"}.
-+{"Poll users settings (seconds)", "Оновлювати налагоджування користувачів кожні (секунд)"}.
-+{"Drop", "Видаляти"}.
-+{"Do not drop", "Не видаляти"}.
-+{"Drop messages on user removal", "Видаляти повідомлення під час видалення користувача"}.
---- msgs/ru-2.1.0.msg 2009-11-22 12:30:40.000000000 +0200
-+++ msgs/ru.msg 2009-11-22 12:35:52.000000000 +0200
-@@ -369,3 +369,31 @@
- {"You need an x:data capable client to search","Чтобы воспользоваться поиском, требуется x:data-совместимый клиент"}.
- {"Your contact offline message queue is full. The message has been discarded.","Очередь недоставленных сообщений Вашего адресата переполнена. Сообщение не было сохранено."}.
- {"Your messages to ~s are being blocked. To unblock them, visit ~s","Ваши сообщения к ~s блокируются. Для снятия блокировки перейдите по ссылке ~s"}.
-+% mod_logdb.erl
-+{"Users Messages", "Сообщения пользователей"}.
-+{"Date", "Дата"}.
-+{"Count", "Количество"}.
-+{"Logged messages for ", "Сохранённые cообщения для "}.
-+{" at ", " за "}.
-+{"No logged messages for ", "Отсутствуют сообщения для "}.
-+{"Date, Time", "Дата, Время"}.
-+{"Direction: Jid", "Направление: Jid"}.
-+{"Subject", "Тема"}.
-+{"Body", "Текст"}.
-+{"Messages", "Сообщения"}.
-+{"Filter Selected", "Отфильтровать выделенные"}.
-+{"Do Not Log Messages", "Не сохранять сообщения"}.
-+{"Log Messages", "Сохранять сообщения"}.
-+{"Messages logging engine", "Система логирования сообщений"}.
-+{"Default", "По умолчанию"}.
-+{"Set logging preferences", "Задайте настройки логирования"}.
-+{"Messages logging engine users", "Пользователи системы логирования сообщений"}.
-+{"Messages logging engine settings", "Настройки системы логирования сообщений"}.
-+{"Set run-time settings", "Задайте текущие настройки"}.
-+{"Groupchat messages logging", "Логирование сообщений типа groupchat"}.
-+{"Jids/Domains to ignore", "Игнорировать следующие jids/домены"}.
-+{"Purge messages older than (days)", "Удалять сообщения старее чем (дни)"}.
-+{"Poll users settings (seconds)", "Обновлять настройки пользователей через (секунд)"}.
-+{"Drop", "Удалять"}.
-+{"Do not drop", "Не удалять"}.
-+{"Drop messages on user removal", "Удалять сообщения при удалении пользователя"}.
---- msgs/pl-2.1.0.msg 2009-11-22 12:30:40.000000000 +0200
-+++ msgs/pl.msg 2009-11-22 12:35:07.000000000 +0200
-@@ -369,3 +369,27 @@
- {"You need an x:data capable client to search","Potrzebujesz klienta obsługującego x:data aby wyszukiwać"}.
- {"Your contact offline message queue is full. The message has been discarded.","Kolejka wiadomości offline adresata jest pełna. Wiadomość została odrzucona."}.
- {"Your messages to ~s are being blocked. To unblock them, visit ~s","Twoje wiadomości do użytkownika ~s są blokowane. Aby je odblokować, odwiedź ~s"}.
-+% mod_logdb
-+{"Users Messages", "Wiadomości użytkownika"}.
-+{"Date", "Data"}.
-+{"Count", "Liczba"}.
-+{"Logged messages for ", "Zapisane wiadomości dla "}.
-+{" at ", " o "}.
-+{"No logged messages for ", "Brak zapisanych wiadomości dla "}.
-+{"Date, Time", "Data, Godzina"}.
-+{"Direction: Jid", "Kierunek: Jid"}.
-+{"Subject", "Temat"}.
-+{"Body", "Treść"}.
-+{"Messages","Wiadomości"}.
-+{"Filter Selected", "Odfiltruj zaznaczone"}.
-+{"Do Not Log Messages", "Nie zapisuj wiadomości"}.
-+{"Log Messages", "Zapisuj wiadomości"}.
-+{"Messages logging engine", "System zapisywania historii rozmów"}.
-+{"Default", "Domyślne"}.
-+{"Set logging preferences", "Ustaw preferencje zapisywania"}.
-+{"Messages logging engine settings", "Ustawienia systemu logowania"}.
-+{"Set run-time settings", "Zapisz ustawienia systemu logowania"}.
-+{"Groupchat messages logging", "Zapisywanie rozmów z konferencji"}.
-+{"Jids/Domains to ignore", "JID/Domena która ma być ignorowana"}.
-+{"Purge messages older than (days)", "Usuń wiadomości starsze niż (w dniach)"}.
-+{"Poll users settings (seconds)", "Czas aktualizacji preferencji użytkowników (sekundy)"}.
---- msgs/nl-2.1.0.msg 2009-11-22 12:30:40.000000000 +0200
-+++ msgs/nl.msg 2009-11-22 12:34:41.000000000 +0200
-@@ -369,3 +369,15 @@
- {"You need an x:data capable client to search","U hebt een client nodig die x:data ondersteunt om te zoeken"}.
- {"Your contact offline message queue is full. The message has been discarded.","Te veel offline berichten voor dit contactpersoon. Het bericht is niet opgeslagen."}.
- {"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"}.
-+% mod_logdb
-+{"Users Messages", "Gebruikersberichten"}.
-+{"Date", "Datum"}.
-+{"Count", "Aantal"}.
-+{"Logged messages for ", "Gelogde berichten van "}.
-+{" at ", " op "}.
-+{"No logged messages for ", "Geen gelogde berichten van "}.
-+{"Date, Time", "Datum en tijd"}.
-+{"Direction: Jid", "Richting: Jabber ID"}.
-+{"Subject", "Onderwerp"}.
-+{"Body", "Berichtveld"}.
-+{"Messages", "Berichten"}.
---- ./mod_roster-2.1.0.erl 2009-11-22 12:30:40.000000000 +0200
-+++ mod_roster.erl 2009-11-22 12:40:40.000000000 +0200
-@@ -61,7 +61,7 @@
- -include("mod_roster.hrl").
- -include("web/ejabberd_http.hrl").
- -include("web/ejabberd_web_admin.hrl").
--
-+-include("mod_logdb.hrl").
+ -define(ITEMS_RESULT(Allow, LNode, Fallback),
+- case Allow of
+- deny -> Fallback;
+- allow ->
+- case get_local_items(LServer, LNode,
+- jid:encode(To), Lang) of
+- {result, Res} -> {result, Res};
+- {error, Error} -> {error, Error}
+- end
+- end).
++ case Allow of
++ deny -> Fallback;
++ allow ->
++ PermLev = get_permission_level(From),
++ case get_local_items({PermLev, LServer}, LNode,
++ jid:encode(To), Lang)
++ of
++ {result, Res} -> {result, Res};
++ {error, Error} -> {error, Error}
++ end
++ end).
- start(Host, Opts) ->
- IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
-@@ -937,6 +937,14 @@
- Res = user_roster_parse_query(User, Server, Items1, Query),
- Items = mnesia:dirty_index_read(roster, US, #roster.us),
- SItems = lists:sort(Items),
-+
-+ Settings = case gen_mod:is_loaded(Server, mod_logdb) of
-+ true ->
-+ mod_logdb:get_user_settings(User, Server);
-+ false ->
-+ []
-+ end,
-+
- FItems =
- case SItems of
- [] ->
-@@ -984,7 +992,33 @@
- [?INPUTT("submit",
- "remove" ++
- ejabberd_web_admin:term_to_id(R#roster.jid),
-- "Remove")])])
-+ "Remove")]),
-+ case gen_mod:is_loaded(Server, mod_logdb) of
-+ true ->
-+ Peer = jlib:jid_to_string(R#roster.jid),
-+ A = lists:member(Peer, Settings#user_settings.dolog_list),
-+ B = lists:member(Peer, Settings#user_settings.donotlog_list),
-+ {Name, Value} =
-+ if
-+ A ->
-+ {"donotlog", "Do Not Log Messages"};
-+ B ->
-+ {"dolog", "Log Messages"};
-+ Settings#user_settings.dolog_default == true ->
-+ {"donotlog", "Do Not Log Messages"};
-+ Settings#user_settings.dolog_default == false ->
-+ {"dolog", "Log Messages"}
-+ end,
-+
-+ ?XAE("td", [{"class", "valign"}],
-+ [?INPUTT("submit",
-+ Name ++
-+ ejabberd_web_admin:term_to_id(R#roster.jid),
-+ Value)]);
-+ false ->
-+ ?X([])
-+ end
-+ ])
- end, SItems))])]
- end,
- [?XC("h1", ?T("Roster of ") ++ us_to_list(US))] ++
-@@ -1084,11 +1118,42 @@
- {"subscription", "remove"}],
- []}]}}),
- throw(submitted);
-- false ->
-- ok
-- end
--
-- end
-+ false ->
-+ case lists:keysearch(
-+ "donotlog" ++ ejabberd_web_admin:term_to_id(JID), 1, Query) of
-+ {value, _} ->
-+ Peer = jlib:jid_to_string(JID),
-+ Settings = mod_logdb:get_user_settings(User, Server),
-+ DNLL = case lists:member(Peer, Settings#user_settings.donotlog_list) of
-+ false -> lists:append(Settings#user_settings.donotlog_list, [Peer]);
-+ true -> Settings#user_settings.donotlog_list
-+ end,
-+ DLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.dolog_list),
-+ Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
-+ % TODO: check returned value
-+ ok = mod_logdb:set_user_settings(User, Server, Sett),
-+ throw(nothing);
-+ false ->
-+ case lists:keysearch(
-+ "dolog" ++ ejabberd_web_admin:term_to_id(JID), 1, Query) of
-+ {value, _} ->
-+ Peer = jlib:jid_to_string(JID),
-+ Settings = mod_logdb:get_user_settings(User, Server),
-+ DLL = case lists:member(Peer, Settings#user_settings.dolog_list) of
-+ false -> lists:append(Settings#user_settings.dolog_list, [Peer]);
-+ true -> Settings#user_settings.dolog_list
-+ end,
-+ DNLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.donotlog_list),
-+ Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
-+ % TODO: check returned value
-+ ok = mod_logdb:set_user_settings(User, Server, Sett),
-+ throw(nothing);
-+ false ->
-+ ok
-+ end % dolog
-+ end % donotlog
-+ end % remove
-+ end % validate
- end, Items),
- nothing.
+ get_local_items(Acc, From, #jid{lserver = LServer} = To,
+ <<"">>, Lang) ->
+@@ -1051,15 +1086,13 @@ get_local_items(Acc, From, #jid{lserver = LServer} = To,
+ end
+ end.
---- ./mod_roster_odbc-2.1.0.erl 2009-11-22 12:30:40.000000000 +0200
-+++ mod_roster_odbc.erl 2009-11-22 12:42:38.000000000 +0200
-@@ -59,7 +59,7 @@
- -include("mod_roster.hrl").
- -include("web/ejabberd_http.hrl").
- -include("web/ejabberd_web_admin.hrl").
+--define(T(Lang, Text), translate:translate(Lang, Text)).
-
-+-include("mod_logdb.hrl").
+ -define(NODE(Name, Node),
+- #disco_item{jid = jid:make(Server),
+- node = Node,
+- name = ?T(Lang, Name)}).
++ #disco_item{jid = jid:make(Server),
++ node = Node,
++ name = tr(Lang, Name)}).
- start(Host, Opts) ->
- IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
-@@ -1038,6 +1038,14 @@
- Res = user_roster_parse_query(User, Server, Items1, Query),
- Items = get_roster(LUser, LServer),
- SItems = lists:sort(Items),
-+
-+ Settings = case gen_mod:is_loaded(Server, mod_logdb) of
-+ true ->
-+ mod_logdb:get_user_settings(User, Server);
-+ false ->
-+ []
-+ end,
-+
- FItems =
- case SItems of
- [] ->
-@@ -1085,7 +1093,33 @@
- [?INPUTT("submit",
- "remove" ++
- ejabberd_web_admin:term_to_id(R#roster.jid),
-- "Remove")])])
-+ "Remove")]),
-+ case gen_mod:is_loaded(Server, mod_logdb) of
-+ true ->
-+ Peer = jlib:jid_to_string(R#roster.jid),
-+ A = lists:member(Peer, Settings#user_settings.dolog_list),
-+ B = lists:member(Peer, Settings#user_settings.donotlog_list),
-+ {Name, Value} =
-+ if
-+ A ->
-+ {"donotlog", "Do Not Log Messages"};
-+ B ->
-+ {"dolog", "Log Messages"};
-+ Settings#user_settings.dolog_default == true ->
-+ {"donotlog", "Do Not Log Messages"};
-+ Settings#user_settings.dolog_default == false ->
-+ {"dolog", "Log Messages"}
-+ end,
-+
-+ ?XAE("td", [{"class", "valign"}],
-+ [?INPUTT("submit",
-+ Name ++
-+ ejabberd_web_admin:term_to_id(R#roster.jid),
-+ Value)]);
-+ false ->
-+ ?X([])
-+ end
-+ ])
- end, SItems))])]
- end,
- [?XC("h1", ?T("Roster of ") ++ us_to_list(US))] ++
-@@ -1185,11 +1219,42 @@
- {"subscription", "remove"}],
- []}]}}),
- throw(submitted);
-- false ->
-- ok
-- end
--
-- end
-+ false ->
-+ case lists:keysearch(
-+ "donotlog" ++ ejabberd_web_admin:term_to_id(JID), 1, Query) of
-+ {value, _} ->
-+ Peer = jlib:jid_to_string(JID),
-+ Settings = mod_logdb:get_user_settings(User, Server),
-+ DNLL = case lists:member(Peer, Settings#user_settings.donotlog_list) of
-+ false -> lists:append(Settings#user_settings.donotlog_list, [Peer]);
-+ true -> Settings#user_settings.donotlog_list
-+ end,
-+ DLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.dolog_list),
-+ Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
-+ % TODO: check returned value
-+ ok = mod_logdb:set_user_settings(User, Server, Sett),
-+ throw(nothing);
-+ false ->
-+ case lists:keysearch(
-+ "dolog" ++ ejabberd_web_admin:term_to_id(JID), 1, Query) of
-+ {value, _} ->
-+ Peer = jlib:jid_to_string(JID),
-+ Settings = mod_logdb:get_user_settings(User, Server),
-+ DLL = case lists:member(Peer, Settings#user_settings.dolog_list) of
-+ false -> lists:append(Settings#user_settings.dolog_list, [Peer]);
-+ true -> Settings#user_settings.dolog_list
-+ end,
-+ DNLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.donotlog_list),
-+ Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
-+ % TODO: check returned value
-+ ok = mod_logdb:set_user_settings(User, Server, Sett),
-+ throw(nothing);
-+ false ->
-+ ok
-+ end % dolog
-+ end % donotlog
-+ end % remove
-+ end % validate
- end, Items),
- nothing.
+ -define(NS_ADMINX(Sub),
+- <<(?NS_ADMIN)/binary, "#", Sub/binary>>).
++ <<(?NS_ADMIN)/binary, "#", Sub/binary>>).
+
+ tokenize(Node) -> str:tokens(Node, <<"/#">>).
+
+@@ -1098,10 +1131,10 @@ get_local_items(_Host, Item, _Server, _Lang) ->
+ {error, xmpp:err_item_not_found()}.
+
+ -define(INFO_RESULT(Allow, Feats, Lang),
+- case Allow of
+- deny -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
+- allow -> {result, Feats}
+- end).
++ case Allow of
++ deny -> {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)};
++ allow -> {result, Feats}
++ end).
+
+ get_local_features(Acc, From,
+ #jid{lserver = LServer} = _To, Node, Lang) ->
+@@ -1133,11 +1166,11 @@ get_local_features(Acc, From,
+ end.
+
+ -define(INFO_IDENTITY(Category, Type, Name, Lang),
+- [#identity{category = Category, type = Type, name = ?T(Lang, Name)}]).
++ [#identity{category = Category, type = Type, name = tr(Lang, Name)}]).
+
+ -define(INFO_COMMAND(Name, Lang),
+- ?INFO_IDENTITY(<<"automation">>, <<"command-node">>,
+- Name, Lang)).
++ ?INFO_IDENTITY(<<"automation">>, <<"command-node">>,
++ Name, Lang)).
+
+ get_local_identity(Acc, _From, _To, Node, Lang) ->
+ LNode = tokenize(Node),
+@@ -1198,10 +1231,8 @@ recursively_get_local_items(LServer,
+
+ -define(COMMANDS_RESULT(Allow, From, To, Request),
+ case Allow of
+- deny ->
+- {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
+- allow ->
+- adhoc_local_commands(From, To, Request)
++ deny -> {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)};
++ allow -> adhoc_local_commands(From, To, Request)
+ end).
+
+ adhoc_local_commands(Acc, From, #jid{lserver = LServer} = To,
+@@ -1278,28 +1309,28 @@ get_user_form(LUser, LServer, Lang) ->
+ Fs = [
+ #xdata_field{
+ type = 'list-single',
+- label = ?T(Lang, <<"Default">>),
++ label = tr(Lang, ?T("Default")),
+ var = <<"dolog_default">>,
+ values = [misc:atom_to_binary(DLD)],
+- options = [#xdata_option{label = ?T(Lang, <<"Log Messages">>),
++ options = [#xdata_option{label = tr(Lang, ?T("Log Messages")),
+ value = <<"true">>},
+- #xdata_option{label = ?T(Lang, <<"Do Not Log Messages">>),
++ #xdata_option{label = tr(Lang, ?T("Do Not Log Messages")),
+ value = <<"false">>}]},
+ #xdata_field{
+ type = 'text-multi',
+- label = ?T(Lang, <<"Log Messages">>),
++ label = tr(Lang, ?T("Log Messages")),
+ var = <<"dolog_list">>,
+ values = DLL},
+ #xdata_field{
+ type = 'text-multi',
+- label = ?T(Lang, <<"Do Not Log Messages">>),
++ label = tr(Lang, ?T("Do Not Log Messages")),
+ var = <<"donotlog_list">>,
+ values = DNLL}
+ ],
+ {result, #xdata{
+- title = ?T(Lang, <<"Messages logging engine settings">>),
++ title = tr(Lang, ?T("Messages logging engine settings")),
+ type = form,
+- instructions = [<< (?T(Lang, <<"Set logging preferences">>))/binary,
++ instructions = [<< (tr(Lang, ?T("Set logging preferences")))/binary,
+ (iolist_to_binary(": "))/binary,
+ LUser/binary, "@", LServer/binary >>],
+ fields = [?HFIELD()|
+@@ -1325,52 +1356,52 @@ get_settings_form(Host, Lang) ->
+ Fs = [
+ #xdata_field{
+ type = 'list-single',
+- label = ?T(Lang, <<"Default">>),
++ label = tr(Lang, ?T("Default")),
+ var = <<"dolog_default">>,
+ values = [misc:atom_to_binary(DLD)],
+- options = [#xdata_option{label = ?T(Lang, <<"Log Messages">>),
++ options = [#xdata_option{label = tr(Lang, ?T("Log Messages")),
+ value = <<"true">>},
+- #xdata_option{label = ?T(Lang, <<"Do Not Log Messages">>),
++ #xdata_option{label = tr(Lang, ?T("Do Not Log Messages")),
+ value = <<"false">>}]},
+ #xdata_field{
+ type = 'list-single',
+- label = ?T(Lang, <<"Drop messages on user removal">>),
++ label = tr(Lang, ?T("Drop messages on user removal")),
+ var = <<"drop_messages_on_user_removal">>,
+ values = [misc:atom_to_binary(MRemoval)],
+- options = [#xdata_option{label = ?T(Lang, <<"Drop">>),
++ options = [#xdata_option{label = tr(Lang, ?T("Drop")),
+ value = <<"true">>},
+- #xdata_option{label = ?T(Lang, <<"Do not drop">>),
++ #xdata_option{label = tr(Lang, ?T("Do not drop")),
+ value = <<"false">>}]},
+ #xdata_field{
+ type = 'list-single',
+- label = ?T(Lang, <<"Groupchat messages logging">>),
++ label = tr(Lang, ?T("Groupchat messages logging")),
+ var = <<"groupchat">>,
+ values = [misc:atom_to_binary(GroupChat)],
+- options = [#xdata_option{label = ?T(Lang, <<"all">>),
++ options = [#xdata_option{label = tr(Lang, ?T("all")),
+ value = <<"all">>},
+- #xdata_option{label = ?T(Lang, <<"none">>),
++ #xdata_option{label = tr(Lang, ?T("none")),
+ value = <<"none">>},
+- #xdata_option{label = ?T(Lang, <<"send">>),
++ #xdata_option{label = tr(Lang, ?T("send")),
+ value = <<"send">>}]},
+ #xdata_field{
+ type = 'text-multi',
+- label = ?T(Lang, <<"Jids/Domains to ignore">>),
++ label = tr(Lang, ?T("Jids/Domains to ignore")),
+ var = <<"ignore_list">>,
+ values = IgnoreJids},
+ #xdata_field{
+ type = 'text-single',
+- label = ?T(Lang, <<"Purge messages older than (days)">>),
++ label = tr(Lang, ?T("Purge messages older than (days)")),
+ var = <<"purge_older_days">>,
+ values = [iolist_to_binary(PurgeDays)]},
+ #xdata_field{
+ type = 'text-single',
+- label = ?T(Lang, <<"Poll users settings (seconds)">>),
++ label = tr(Lang, ?T("Poll users settings (seconds)")),
+ var = <<"poll_users_settings">>,
+ values = [integer_to_binary(PollTime)]}
+ ],
+ {result, #xdata{
+- title = ?T(Lang, <<"Messages logging engine settings (run-time)">>),
+- instructions = [?T(Lang, <<"Set run-time settings">>)],
++ title = tr(Lang, ?T("Messages logging engine settings (run-time)")),
++ instructions = [tr(Lang, ?T("Set run-time settings"))],
+ type = form,
+ fields = [?HFIELD()|
+ Fs]}}.
+@@ -1578,7 +1609,7 @@ get_all_vh_users(Host, Server) ->
+ %
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ webadmin_menu(Acc, _Host, Lang) ->
+- [{<<"messages">>, ?T(<<"Users Messages">>)} | Acc].
++ [{<<"messages">>, tr(Lang, ?T("Users Messages"))} | Acc].
+
+ webadmin_user(Acc, User, Server, Lang) ->
+ Sett = get_user_settings(User, Server),
+@@ -1649,12 +1680,12 @@ vhost_messages_stats(Server, Query, Lang) ->
+ case Value of
+ {'EXIT', CReason} ->
+ ?ERROR_MSG("Failed to get_vhost_stats: ~p", [CReason]),
+- [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))];
++ [?XC(<<"h1">>, tr(Lang, ?T("Error occupied while fetching list")))];
+ {error, GReason} ->
+ ?ERROR_MSG("Failed to get_vhost_stats: ~p", [GReason]),
+- [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))];
++ [?XC(<<"h1">>, tr(Lang, ?T("Error occupied while fetching list")))];
+ {ok, []} ->
+- [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s">>), [Server])))];
++ [?XC(<<"h1">>, list_to_binary(io_lib:format(tr(Lang, ?T("No logged messages for ~s")), [Server])))];
+ {ok, Dates} ->
+ Fun = fun({Date, Count}) ->
+ DateBin = iolist_to_binary(Date),
+@@ -1667,7 +1698,7 @@ vhost_messages_stats(Server, Query, Lang) ->
+ ])
+ end,
+
+- [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Logged messages for ~s">>), [Server])))] ++
++ [?XC(<<"h1">>, list_to_binary(io_lib:format(tr(Lang, ?T("Logged messages for ~s")), [Server])))] ++
+ case Res of
+ ok -> [?CT(<<"Submitted">>), ?P];
+ error -> [?CT(<<"Bad format">>), ?P];
+@@ -1696,12 +1727,12 @@ vhost_messages_stats_at(Server, Query, Lang, Date) ->
+ case Value of
+ {'EXIT', CReason} ->
+ ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [CReason]),
+- [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))];
++ [?XC(<<"h1">>, tr(Lang, ?T("Error occupied while fetching list")))];
+ {error, GReason} ->
+ ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [GReason]),
+- [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))];
++ [?XC(<<"h1">>, tr(Lang, ?T("Error occupied while fetching list")))];
+ {ok, []} ->
+- [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s at ~s">>), [Server, Date])))];
++ [?XC(<<"h1">>, list_to_binary(io_lib:format(tr(Lang, ?T("No logged messages for ~s at ~s")), [Server, Date])))];
+ {ok, Stats} ->
+ Res = case catch vhost_messages_at_parse_query(Server, Date, Stats, Query) of
+ {'EXIT', Reason} ->
+@@ -1719,7 +1750,7 @@ vhost_messages_stats_at(Server, Query, Lang, Date) ->
+ ?XC(<<"td">>, integer_to_binary(Count))
+ ])
+ end,
+- [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Logged messages for ~s at ~s">>), [Server, Date])))] ++
++ [?XC(<<"h1">>, list_to_binary(io_lib:format(tr(Lang, ?T("Logged messages for ~s at ~s")), [Server, Date])))] ++
+ case Res of
+ ok -> [?CT(<<"Submitted">>), ?P];
+ error -> [?CT(<<"Bad format">>), ?P];
+@@ -1757,12 +1788,12 @@ user_messages_stats(User, Server, Query, Lang) ->
+ case Value of
+ {'EXIT', CReason} ->
+ ?ERROR_MSG("Failed to get_user_stats: ~p", [CReason]),
+- [?XC(<<"h1">>, ?T(<<"Error occupied while fetching days">>))];
++ [?XC(<<"h1">>, tr(Lang, ?T("Error occupied while fetching days")))];
+ {error, GReason} ->
+ ?ERROR_MSG("Failed to get_user_stats: ~p", [GReason]),
+- [?XC(<<"h1">>, ?T(<<"Error occupied while fetching days">>))];
++ [?XC(<<"h1">>, tr(Lang,?T("Error occupied while fetching days")))];
+ {ok, []} ->
+- [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s">>), [Jid])))];
++ [?XC(<<"h1">>, list_to_binary(io_lib:format(tr(Lang, ?T("No logged messages for ~s")), [Jid])))];
+ {ok, Dates} ->
+ Fun = fun({Date, Count}) ->
+ DateBin = iolist_to_binary(Date),
+@@ -1814,12 +1845,12 @@ user_messages_stats_at(User, Server, Query, Lang, Date) ->
+ case Value of
+ {'EXIT', CReason} ->
+ ?ERROR_MSG("Failed to get_user_messages_at: ~p", [CReason]),
+- [?XC(<<"h1">>, ?T(<<"Error occupied while fetching messages">>))];
++ [?XC(<<"h1">>, tr(Lang, ?T("Error occupied while fetching messages")))];
+ {error, GReason} ->
+ ?ERROR_MSG("Failed to get_user_messages_at: ~p", [GReason]),
+- [?XC(<<"h1">>, ?T(<<"Error occupied while fetching messages">>))];
++ [?XC(<<"h1">>, tr(Lang, ?T("Error occupied while fetching messages")))];
+ {ok, []} ->
+- [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s at ~s">>), [Jid, Date])))];
++ [?XC(<<"h1">>, list_to_binary(io_lib:format(tr(Lang, ?T("No logged messages for ~s at ~s")), [Jid, Date])))];
+ {ok, User_messages} ->
+ Res = case catch user_messages_at_parse_query(Server,
+ Date,
+@@ -1888,7 +1919,7 @@ user_messages_stats_at(User, Server, Query, Lang, Date) ->
+ body=Body}) ->
+ Text = case Subject of
+ "" -> iolist_to_binary(Body);
+- _ -> iolist_to_binary([binary_to_list(?T(<<"Subject">>)) ++ ": " ++ Subject ++ "\n" ++ Body])
++ _ -> iolist_to_binary([binary_to_list(tr(Lang, ?T("Subject"))) ++ ": " ++ Subject ++ "\n" ++ Body])
+ end,
+ Resource = case PRes of
+ [] -> [];
+@@ -1915,7 +1946,7 @@ user_messages_stats_at(User, Server, Query, Lang, Date) ->
+ % Filtered user messages in html
+ Msgs = lists:map(Msgs_Fun, lists:sort(User_messages_filtered)),
+
+- [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Logged messages for ~s at ~s">>), [Jid, Date])))] ++
++ [?XC(<<"h1">>, list_to_binary(io_lib:format(tr(Lang, ?T("Logged messages for ~s at ~s")), [Jid, Date])))] ++
+ case Res of
+ ok -> [?CT(<<"Submitted">>), ?P];
+ error -> [?CT(<<"Bad format">>), ?P];
+diff --git a/src/mod_logdb_mysql.erl b/src/mod_logdb_mysql.erl
+index 21d65e6578..66b50acc86 100644
+--- a/src/mod_logdb_mysql.erl
++++ b/src/mod_logdb_mysql.erl
+@@ -94,11 +94,11 @@ stop(VHost) ->
+ init([VHost, Opts]) ->
+ crypto:start(),
+
+- Server = gen_mod:get_opt(server, Opts, fun(A) -> A end, <<"localhost">>),
+- Port = gen_mod:get_opt(port, Opts, fun(A) -> A end, 3306),
+- DB = gen_mod:get_opt(db, Opts, fun(A) -> A end, <<"logdb">>),
+- User = gen_mod:get_opt(user, Opts, fun(A) -> A end, <<"root">>),
+- Password = gen_mod:get_opt(password, Opts, fun(A) -> A end, <<"">>),
++ Server = mod_logdb:get_opt(server, Opts, <<"localhost">>),
++ Port = mod_logdb:get_opt(port, Opts, 3306),
++ DB = mod_logdb:get_opt(db, Opts, <<"logdb">>),
++ User = mod_logdb:get_opt(user, Opts, <<"root">>),
++ Password = mod_logdb:get_opt(password, Opts, <<"">>),
+
+ St = #state{vhost=VHost,
+ server=Server, port=Port, db=DB,
+diff --git a/src/mod_logdb_mysql5.erl b/src/mod_logdb_mysql5.erl
+index c05ab958e2..72fa72e72e 100644
+--- a/src/mod_logdb_mysql5.erl
++++ b/src/mod_logdb_mysql5.erl
+@@ -99,11 +99,11 @@ stop(VHost) ->
+ init([VHost, Opts]) ->
+ crypto:start(),
+
+- Server = gen_mod:get_opt(server, Opts, fun(A) -> A end, <<"localhost">>),
+- Port = gen_mod:get_opt(port, Opts, fun(A) -> A end, 3306),
+- DB = gen_mod:get_opt(db, Opts, fun(A) -> A end, <<"logdb">>),
+- User = gen_mod:get_opt(user, Opts, fun(A) -> A end, <<"root">>),
+- Password = gen_mod:get_opt(password, Opts, fun(A) -> A end, <<"">>),
++ Server = mod_logdb:get_opt(server, Opts, <<"localhost">>),
++ Port = mod_logdb:get_opt(port, Opts, 3306),
++ DB = mod_logdb:get_opt(db, Opts, <<"logdb">>),
++ User = mod_logdb:get_opt(user, Opts, <<"root">>),
++ Password = mod_logdb:get_opt(password, Opts, <<"">>),
+
+ St = #state{vhost=VHost,
+ server=Server, port=Port, db=DB,
+diff --git a/src/mod_logdb_pgsql.erl b/src/mod_logdb_pgsql.erl
+index 202c6ed4a8..7f74887b9d 100644
+--- a/src/mod_logdb_pgsql.erl
++++ b/src/mod_logdb_pgsql.erl
+@@ -101,12 +101,12 @@ stop(VHost) ->
+ %
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ init([VHost, Opts]) ->
+- Server = gen_mod:get_opt(server, Opts, fun(A) -> A end, <<"localhost">>),
+- DB = gen_mod:get_opt(db, Opts, fun(A) -> A end, <<"ejabberd_logdb">>),
+- User = gen_mod:get_opt(user, Opts, fun(A) -> A end, <<"root">>),
+- Port = gen_mod:get_opt(port, Opts, fun(A) -> A end, 5432),
+- Password = gen_mod:get_opt(password, Opts, fun(A) -> A end, <<"">>),
+- Schema = binary_to_list(gen_mod:get_opt(schema, Opts, fun(A) -> A end, <<"public">>)),
++ Server = mod_logdb:get_opt(server, Opts, <<"localhost">>),
++ Port = mod_logdb:get_opt(port, Opts, 5432),
++ DB = mod_logdb:get_opt(db, Opts, <<"logdb">>),
++ User = mod_logdb:get_opt(user, Opts, <<"root">>),
++ Password = mod_logdb:get_opt(password, Opts, <<"">>),
++ Schema = mod_logdb:get_opt(schema, Opts, <<"public">>),
+
+ ?MYDEBUG("Starting pgsql backend for ~s", [VHost]),
+
+
+From 55274ef5a3deb5979e0d97cdb48768eb472c36ec Mon Sep 17 00:00:00 2001
+From: Oleh Palii <o.palij@gmail.com>
+Date: Sat, 31 Aug 2019 22:43:11 +0300
+Subject: [PATCH 3/3] mod_logdb mod_opt_type fixes
+
+---
+ src/mod_logdb.erl | 33 ++++++++++++++++++++++-----------
+ 1 file changed, 22 insertions(+), 11 deletions(-)
+
+diff --git a/src/mod_logdb.erl b/src/mod_logdb.erl
+index 0b5c2ec687..0766241fec 100644
+--- a/src/mod_logdb.erl
++++ b/src/mod_logdb.erl
+@@ -220,24 +220,35 @@ get_commands_spec() ->
+ result = {res, rescode}}].
+
+ mod_opt_type(dbs) ->
+- fun (A) when is_list(A) -> A end;
++ econf:map(
++ econf:enum([mnesia, mysql, mysql5, pgsql]),
++ econf:map(
++ econf:enum([user, password, server, port, db, schema]),
++ econf:string()
++ )
++ );
+ mod_opt_type(vhosts) ->
+- fun (A) when is_list(A) -> A end;
++ econf:map(
++ econf:string(),
++ econf:enum([mnesia, mysql, mysql5, pgsql])
++ );
+ mod_opt_type(poll_users_settings) ->
+- fun (I) when is_integer(I) -> I end;
++ econf:non_neg_int();
+ mod_opt_type(groupchat) ->
+- fun (all) -> all;
+- (send) -> send;
+- (none) -> none
+- end;
++ econf:enum([all, send, none]);
+ mod_opt_type(dolog_default) ->
+- fun (B) when is_boolean(B) -> B end;
++ econf:bool();
++mod_opt_type(drop_messages_on_user_removal) ->
++ econf:bool();
+ mod_opt_type(ignore_jids) ->
+- fun (A) when is_list(A) -> A end;
++ econf:list(econf:string());
+ mod_opt_type(purge_older_days) ->
+- fun (I) when is_integer(I) -> I end;
++ econf:either(
++ never,
++ econf:non_neg_int()
++ );
+ mod_opt_type(_) ->
+- [dbs, vhosts, poll_users_settings, groupchat, dolog_default, ignore_jids, purge_older_days].
++ [dbs, vhosts, poll_users_settings, groupchat, dolog_default, drop_messages_on_user_removal, ignore_jids, purge_older_days].
+ handle_call({cleanup}, _From, State) ->
+ cleanup(State),