]>
Commit | Line | Data |
---|---|---|
dd02533f AM |
1 | From 9a2ed8d2b20ef052b71b065e686cc049d18999ac Mon Sep 17 00:00:00 2001 |
2 | From: Oleh Palii <o.palij@gmail.com> | |
3 | Date: Sat, 31 Aug 2019 11:04:57 +0300 | |
4 | Subject: [PATCH 1/3] apply mod_logdb to 19.08 | |
5 | ||
6 | --- | |
7 | priv/msgs/nl.msg | 14 + | |
8 | priv/msgs/pl.msg | 26 + | |
9 | priv/msgs/ru.msg | 30 + | |
10 | priv/msgs/uk.msg | 30 + | |
11 | rebar.config | 4 +- | |
12 | src/gen_logdb.erl | 162 ++++ | |
13 | src/mod_logdb.erl | 1951 ++++++++++++++++++++++++++++++++++++++ | |
14 | src/mod_logdb.hrl | 33 + | |
15 | src/mod_logdb_mnesia.erl | 553 +++++++++++ | |
16 | src/mod_logdb_mysql.erl | 1050 ++++++++++++++++++++ | |
17 | src/mod_logdb_mysql5.erl | 979 +++++++++++++++++++ | |
18 | src/mod_logdb_pgsql.erl | 1104 +++++++++++++++++++++ | |
19 | src/mod_roster.erl | 77 +- | |
20 | 13 files changed, 6007 insertions(+), 6 deletions(-) | |
21 | create mode 100644 src/gen_logdb.erl | |
22 | create mode 100644 src/mod_logdb.erl | |
23 | create mode 100644 src/mod_logdb.hrl | |
24 | create mode 100644 src/mod_logdb_mnesia.erl | |
25 | create mode 100644 src/mod_logdb_mysql.erl | |
26 | create mode 100644 src/mod_logdb_mysql5.erl | |
27 | create mode 100644 src/mod_logdb_pgsql.erl | |
28 | ||
046546ef | 29 | diff --git a/priv/msgs/nl.msg b/priv/msgs/nl.msg |
dd02533f | 30 | index 8009d529ff..fd3d3b0ebb 100644 |
046546ef AM |
31 | --- a/priv/msgs/nl.msg |
32 | +++ b/priv/msgs/nl.msg | |
08278fc0 AM |
33 | @@ -345,3 +345,17 @@ |
34 | {"Your contact offline message queue is full. The message has been discarded.","Te veel offline berichten voor dit contactpersoon. Het bericht is niet opgeslagen."}. | |
046546ef AM |
35 | {"Your Jabber account was successfully created.","Uw Jabber-account is succesvol gecreeerd."}. |
36 | {"Your Jabber account was successfully deleted.","Uw Jabber-account is succesvol verwijderd."}. | |
046546ef | 37 | +% mod_logdb |
dd02533f AM |
38 | +{"Users Messages", "Gebruikersberichten"}. |
39 | +{"Date", "Datum"}. | |
40 | +{"Count", "Aantal"}. | |
41 | +{"Logged messages for ~s", "Gelogde berichten van ~s"}. | |
42 | +{"Logged messages for ~s at ~s", "Gelogde berichten van ~s op ~s"}. | |
046546ef | 43 | +{" at ", " op "}. |
dd02533f AM |
44 | +{"No logged messages for ~s", "Geen gelogde berichten van ~s"}. |
45 | +{"No logged messages for ~s at ~s", "Geen gelogde berichten van ~s op ~s"}. | |
46 | +{"Date, Time", "Datum en tijd"}. | |
47 | +{"Direction: Jid", "Richting: Jabber ID"}. | |
48 | +{"Subject", "Onderwerp"}. | |
49 | +{"Body", "Berichtveld"}. | |
50 | +{"Messages", "Berichten"}. | |
046546ef | 51 | diff --git a/priv/msgs/pl.msg b/priv/msgs/pl.msg |
dd02533f | 52 | index 2ca75b259c..fffae5742e 100644 |
046546ef AM |
53 | --- a/priv/msgs/pl.msg |
54 | +++ b/priv/msgs/pl.msg | |
08278fc0 | 55 | @@ -444,3 +444,29 @@ |
9682d4d5 | 56 | {"You're not allowed to create nodes","Nie masz uprawnień do tworzenia węzłów"}. |
08278fc0 AM |
57 | {"Your Jabber account was successfully created.","Twoje konto zostało stworzone."}. |
58 | {"Your Jabber account was successfully deleted.","Twoje konto zostało usunięte."}. | |
046546ef AM |
59 | +% mod_logdb |
60 | +{"Users Messages", "Wiadomości użytkownika"}. | |
61 | +{"Date", "Data"}. | |
62 | +{"Count", "Liczba"}. | |
63 | +{"Logged messages for ~s", "Zapisane wiadomości dla ~s"}. | |
64 | +{"Logged messages for ~s at", "Zapisane wiadomości dla ~s o ~s"}. | |
65 | +{" at ", " o "}. | |
66 | +{"No logged messages for ~s", "Brak zapisanych wiadomości dla ~s"}. | |
67 | +{"No logged messages for ~s at ~s", "Brak zapisanych wiadomości dla ~s o ~s"}. | |
68 | +{"Date, Time", "Data, Godzina"}. | |
69 | +{"Direction: Jid", "Kierunek: Jid"}. | |
70 | +{"Subject", "Temat"}. | |
71 | +{"Body", "Treść"}. | |
72 | +{"Messages","Wiadomości"}. | |
73 | +{"Filter Selected", "Odfiltruj zaznaczone"}. | |
74 | +{"Do Not Log Messages", "Nie zapisuj wiadomości"}. | |
75 | +{"Log Messages", "Zapisuj wiadomości"}. | |
76 | +{"Messages logging engine", "System zapisywania historii rozmów"}. | |
77 | +{"Default", "Domyślne"}. | |
78 | +{"Set logging preferences", "Ustaw preferencje zapisywania"}. | |
79 | +{"Messages logging engine settings", "Ustawienia systemu logowania"}. | |
80 | +{"Set run-time settings", "Zapisz ustawienia systemu logowania"}. | |
81 | +{"Groupchat messages logging", "Zapisywanie rozmów z konferencji"}. | |
82 | +{"Jids/Domains to ignore", "JID/Domena która ma być ignorowana"}. | |
83 | +{"Purge messages older than (days)", "Usuń wiadomości starsze niż (w dniach)"}. | |
84 | +{"Poll users settings (seconds)", "Czas aktualizacji preferencji użytkowników (sekundy)"}. | |
85 | diff --git a/priv/msgs/ru.msg b/priv/msgs/ru.msg | |
dd02533f | 86 | index f7dff97ea1..42be5d4f15 100644 |
046546ef AM |
87 | --- a/priv/msgs/ru.msg |
88 | +++ b/priv/msgs/ru.msg | |
08278fc0 AM |
89 | @@ -507,3 +507,33 @@ |
90 | {"Your Jabber account was successfully created.","Ваш Jabber-аккаунт был успешно создан."}. | |
046546ef | 91 | {"Your Jabber account was successfully deleted.","Ваш Jabber-аккаунт был успешно удален."}. |
08278fc0 | 92 | {"Your subscription request and/or messages to ~s have been blocked. To unblock your subscription request, visit ~s","Ваши запросы на добавление в контакт-лист, а также сообщения к ~s блокируются. Для снятия блокировки перейдите по ссылке ~s"}. |
046546ef AM |
93 | +% mod_logdb.erl |
94 | +{"Users Messages", "Сообщения пользователей"}. | |
95 | +{"Date", "Дата"}. | |
96 | +{"Count", "Количество"}. | |
97 | +{"Logged messages for ~s", "Сохранённые cообщения для ~s"}. | |
98 | +{"Logged messages for ~s at ~s", "Сохранённые cообщения для ~s за ~s"}. | |
99 | +{" at ", " за "}. | |
100 | +{"No logged messages for ~s", "Отсутствуют сообщения для ~s"}. | |
101 | +{"No logged messages for ~s at ~s", "Отсутствуют сообщения для ~s за ~s"}. | |
102 | +{"Date, Time", "Дата, Время"}. | |
103 | +{"Direction: Jid", "Направление: Jid"}. | |
104 | +{"Subject", "Тема"}. | |
105 | +{"Body", "Текст"}. | |
106 | +{"Messages", "Сообщения"}. | |
107 | +{"Filter Selected", "Отфильтровать выделенные"}. | |
108 | +{"Do Not Log Messages", "Не сохранять сообщения"}. | |
109 | +{"Log Messages", "Сохранять сообщения"}. | |
110 | +{"Messages logging engine", "Система логирования сообщений"}. | |
111 | +{"Default", "По умолчанию"}. | |
112 | +{"Set logging preferences", "Задайте настройки логирования"}. | |
113 | +{"Messages logging engine users", "Пользователи системы логирования сообщений"}. | |
114 | +{"Messages logging engine settings", "Настройки системы логирования сообщений"}. | |
115 | +{"Set run-time settings", "Задайте текущие настройки"}. | |
116 | +{"Groupchat messages logging", "Логирование сообщений типа groupchat"}. | |
117 | +{"Jids/Domains to ignore", "Игнорировать следующие jids/домены"}. | |
118 | +{"Purge messages older than (days)", "Удалять сообщения старее чем (дни)"}. | |
119 | +{"Poll users settings (seconds)", "Обновлять настройки пользователей через (секунд)"}. | |
120 | +{"Drop", "Удалять"}. | |
121 | +{"Do not drop", "Не удалять"}. | |
122 | +{"Drop messages on user removal", "Удалять сообщения при удалении пользователя"}. | |
123 | diff --git a/priv/msgs/uk.msg b/priv/msgs/uk.msg | |
dd02533f | 124 | index 0fbc336d51..c0b90047fa 100644 |
046546ef AM |
125 | --- a/priv/msgs/uk.msg |
126 | +++ b/priv/msgs/uk.msg | |
08278fc0 AM |
127 | @@ -349,3 +349,33 @@ |
128 | {"Your contact offline message queue is full. The message has been discarded.","Черга повідомлень, що не були доставлені, переповнена. Повідомлення не було збережено."}. | |
046546ef AM |
129 | {"Your Jabber account was successfully created.","Ваш Jabber-акаунт було успішно створено."}. |
130 | {"Your Jabber account was successfully deleted.","Ваш Jabber-акаунт було успішно видалено."}. | |
046546ef AM |
131 | +% mod_logdb |
132 | +{"Users Messages", "Повідомлення користувачів"}. | |
133 | +{"Date", "Дата"}. | |
134 | +{"Count", "Кількість"}. | |
135 | +{"Logged messages for ~s", "Збережені повідомлення для ~s"}. | |
136 | +{"Logged messages for ~s at ~s", "Збережені повідомлення для ~s за ~s"}. | |
137 | +{" at ", " за "}. | |
138 | +{"No logged messages for ~s", "Відсутні повідомлення для ~s"}. | |
139 | +{"No logged messages for ~s at ~s", "Відсутні повідомлення для ~s за ~s"}. | |
140 | +{"Date, Time", "Дата, Час"}. | |
141 | +{"Direction: Jid", "Напрямок: Jid"}. | |
142 | +{"Subject", "Тема"}. | |
143 | +{"Body", "Текст"}. | |
144 | +{"Messages", "Повідомлення"}. | |
145 | +{"Filter Selected", "Відфільтрувати виділені"}. | |
146 | +{"Do Not Log Messages", "Не зберігати повідомлення"}. | |
147 | +{"Log Messages", "Зберігати повідомлення"}. | |
148 | +{"Messages logging engine", "Система збереження повідомлень"}. | |
149 | +{"Default", "За замовчуванням"}. | |
150 | +{"Set logging preferences", "Вкажіть налагоджування збереження повідомлень"}. | |
151 | +{"Messages logging engine users", "Користувачі системи збереження повідомлень"}. | |
152 | +{"Messages logging engine settings", "Налагоджування системи збереження повідомлень"}. | |
153 | +{"Set run-time settings", "Вкажіть поточні налагоджування"}. | |
154 | +{"Groupchat messages logging", "Збереження повідомлень типу groupchat"}. | |
155 | +{"Jids/Domains to ignore", "Ігнорувати наступні jids/домени"}. | |
156 | +{"Purge messages older than (days)", "Видаляти повідомлення старіші ніж (дні)"}. | |
157 | +{"Poll users settings (seconds)", "Оновлювати налагоджування користувачів кожні (секунд)"}. | |
158 | +{"Drop", "Видаляти"}. | |
159 | +{"Do not drop", "Не видаляти"}. | |
160 | +{"Drop messages on user removal", "Видаляти повідомлення під час видалення користувача"}. | |
3f23be8e | 161 | diff --git a/rebar.config b/rebar.config |
dd02533f | 162 | index e05fe84e6e..c3b87afd28 100644 |
3f23be8e AM |
163 | --- a/rebar.config |
164 | +++ b/rebar.config | |
dd02533f AM |
165 | @@ -35,8 +35,8 @@ |
166 | {mqtree, ".*", {git, "https://github.com/processone/mqtree", {tag, "1.0.4"}}}, | |
08278fc0 AM |
167 | {if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.29"}}}}, |
168 | {if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.30"}}}}, | |
3f23be8e | 169 | - {if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql", |
08278fc0 | 170 | - {tag, "1.0.11"}}}}, |
3f23be8e | 171 | + {if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/paleg/p1_mysql", |
dd02533f | 172 | + {tag, "1.0.11_multi"}}}}, |
3f23be8e | 173 | {if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql", |
08278fc0 | 174 | {tag, "1.1.8"}}}}, |
3f23be8e | 175 | {if_var_true, sqlite, {sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3", |
046546ef | 176 | diff --git a/src/gen_logdb.erl b/src/gen_logdb.erl |
0d78319d | 177 | new file mode 100644 |
dd02533f | 178 | index 0000000000..8bad112969 |
0d78319d | 179 | --- /dev/null |
046546ef | 180 | +++ b/src/gen_logdb.erl |
3f23be8e | 181 | @@ -0,0 +1,162 @@ |
0d78319d AM |
182 | +%%%---------------------------------------------------------------------- |
183 | +%%% File : gen_logdb.erl | |
3f23be8e | 184 | +%%% Author : Oleg Palij (mailto:o.palij@gmail.com) |
0d78319d | 185 | +%%% Purpose : Describes generic behaviour for mod_logdb backends. |
3f23be8e | 186 | +%%% Url : https://paleg.github.io/mod_logdb/ |
0d78319d AM |
187 | +%%%---------------------------------------------------------------------- |
188 | + | |
189 | +-module(gen_logdb). | |
190 | +-author('o.palij@gmail.com'). | |
191 | + | |
192 | +-export([behaviour_info/1]). | |
193 | + | |
194 | +behaviour_info(callbacks) -> | |
195 | + [ | |
196 | + % called from handle_info(start, _) | |
197 | + % it should logon database and return reference to started instance | |
198 | + % start(VHost, Opts) -> {ok, SPid} | error | |
199 | + % Options - list of options to connect to db | |
200 | + % Types: Options = list() -> [] | | |
201 | + % [{user, "logdb"}, | |
202 | + % {pass, "1234"}, | |
203 | + % {db, "logdb"}] | ... | |
204 | + % VHost = list() -> "jabber.example.org" | |
205 | + {start, 2}, | |
206 | + | |
207 | + % called from cleanup/1 | |
208 | + % it should logoff database and do cleanup | |
209 | + % stop(VHost) | |
210 | + % Types: VHost = list() -> "jabber.example.org" | |
211 | + {stop, 1}, | |
212 | + | |
213 | + % called from handle_call({addlog, _}, _, _) | |
214 | + % it should log messages to database | |
215 | + % log_message(VHost, Msg) -> ok | error | |
216 | + % Types: | |
217 | + % VHost = list() -> "jabber.example.org" | |
218 | + % Msg = record() -> #msg | |
219 | + {log_message, 2}, | |
220 | + | |
221 | + % called from ejabberdctl rebuild_stats | |
222 | + % it should rebuild stats table (if used) for vhost | |
223 | + % rebuild_stats(VHost) | |
224 | + % Types: | |
225 | + % VHost = list() -> "jabber.example.org" | |
226 | + {rebuild_stats, 1}, | |
227 | + | |
228 | + % it should rebuild stats table (if used) for vhost at Date | |
229 | + % rebuild_stats_at(VHost, Date) | |
230 | + % Types: | |
231 | + % VHost = list() -> "jabber.example.org" | |
232 | + % Date = list() -> "2007-02-12" | |
233 | + {rebuild_stats_at, 2}, | |
234 | + | |
235 | + % called from user_messages_at_parse_query/5 | |
236 | + % it should delete selected user messages at date | |
237 | + % delete_messages_by_user_at(VHost, Msgs, Date) -> ok | error | |
238 | + % Types: | |
239 | + % VHost = list() -> "jabber.example.org" | |
240 | + % Msgs = list() -> [ #msg1, msg2, ... ] | |
241 | + % Date = list() -> "2007-02-12" | |
242 | + {delete_messages_by_user_at, 3}, | |
243 | + | |
244 | + % called from user_messages_parse_query/4 | vhost_messages_at_parse_query/4 | |
245 | + % it should delete all user messages at date | |
246 | + % delete_all_messages_by_user_at(User, VHost, Date) -> ok | error | |
247 | + % Types: | |
248 | + % User = list() -> "admin" | |
249 | + % VHost = list() -> "jabber.example.org" | |
250 | + % Date = list() -> "2007-02-12" | |
251 | + {delete_all_messages_by_user_at, 3}, | |
252 | + | |
253 | + % called from vhost_messages_parse_query/3 | |
254 | + % it should delete messages for vhost at date and update stats | |
255 | + % delete_messages_at(VHost, Date) -> ok | error | |
256 | + % Types: | |
257 | + % VHost = list() -> "jabber.example.org" | |
258 | + % Date = list() -> "2007-02-12" | |
259 | + {delete_messages_at, 2}, | |
260 | + | |
261 | + % called from ejabberd_web_admin:vhost_messages_stats/3 | |
262 | + % it should return sorted list of count of messages by dates for vhost | |
263 | + % get_vhost_stats(VHost) -> {ok, [{Date1, Msgs_count1}, {Date2, Msgs_count2}, ... ]} | | |
264 | + % {error, Reason} | |
265 | + % Types: | |
266 | + % VHost = list() -> "jabber.example.org" | |
267 | + % DateN = list() -> "2007-02-12" | |
268 | + % Msgs_countN = number() -> 241 | |
269 | + {get_vhost_stats, 1}, | |
270 | + | |
271 | + % called from ejabberd_web_admin:vhost_messages_stats_at/4 | |
272 | + % it should return sorted list of count of messages by users at date for vhost | |
273 | + % get_vhost_stats_at(VHost, Date) -> {ok, [{User1, Msgs_count1}, {User2, Msgs_count2}, ....]} | | |
274 | + % {error, Reason} | |
275 | + % Types: | |
276 | + % VHost = list() -> "jabber.example.org" | |
277 | + % Date = list() -> "2007-02-12" | |
278 | + % UserN = list() -> "admin" | |
279 | + % Msgs_countN = number() -> 241 | |
280 | + {get_vhost_stats_at, 2}, | |
281 | + | |
282 | + % called from ejabberd_web_admin:user_messages_stats/4 | |
283 | + % it should return sorted list of count of messages by date for user at vhost | |
284 | + % get_user_stats(User, VHost) -> {ok, [{Date1, Msgs_count1}, {Date2, Msgs_count2}, ...]} | | |
285 | + % {error, Reason} | |
286 | + % Types: | |
287 | + % User = list() -> "admin" | |
288 | + % VHost = list() -> "jabber.example.org" | |
289 | + % DateN = list() -> "2007-02-12" | |
290 | + % Msgs_countN = number() -> 241 | |
291 | + {get_user_stats, 2}, | |
292 | + | |
293 | + % called from ejabberd_web_admin:user_messages_stats_at/5 | |
294 | + % it should return all user messages at date | |
295 | + % get_user_messages_at(User, VHost, Date) -> {ok, Msgs} | {error, Reason} | |
296 | + % Types: | |
297 | + % User = list() -> "admin" | |
298 | + % VHost = list() -> "jabber.example.org" | |
299 | + % Date = list() -> "2007-02-12" | |
300 | + % Msgs = list() -> [ #msg1, msg2, ... ] | |
301 | + {get_user_messages_at, 3}, | |
302 | + | |
303 | + % called from many places | |
304 | + % it should return list of dates for vhost | |
305 | + % get_dates(VHost) -> [Date1, Date2, ... ] | |
306 | + % Types: | |
307 | + % VHost = list() -> "jabber.example.org" | |
308 | + % DateN = list() -> "2007-02-12" | |
309 | + {get_dates, 1}, | |
310 | + | |
311 | + % called from start | |
312 | + % it should return list with users settings for VHost in db | |
313 | + % get_users_settings(VHost) -> [#user_settings1, #user_settings2, ... ] | error | |
314 | + % Types: | |
315 | + % VHost = list() -> "jabber.example.org" | |
316 | + {get_users_settings, 1}, | |
317 | + | |
318 | + % called from many places | |
319 | + % it should return User settings at VHost from db | |
320 | + % get_user_settings(User, VHost) -> error | {ok, #user_settings} | |
321 | + % Types: | |
322 | + % User = list() -> "admin" | |
323 | + % VHost = list() -> "jabber.example.org" | |
324 | + {get_user_settings, 2}, | |
325 | + | |
326 | + % called from web admin | |
327 | + % it should set User settings at VHost | |
328 | + % set_user_settings(User, VHost, #user_settings) -> ok | error | |
329 | + % Types: | |
330 | + % User = list() -> "admin" | |
331 | + % VHost = list() -> "jabber.example.org" | |
332 | + {set_user_settings, 3}, | |
333 | + | |
334 | + % called from remove_user (ejabberd hook) | |
335 | + % it should remove user messages and settings at VHost | |
336 | + % drop_user(User, VHost) -> ok | error | |
337 | + % Types: | |
338 | + % User = list() -> "admin" | |
339 | + % VHost = list() -> "jabber.example.org" | |
340 | + {drop_user, 2} | |
341 | + ]; | |
342 | +behaviour_info(_) -> | |
343 | + undefined. | |
046546ef | 344 | diff --git a/src/mod_logdb.erl b/src/mod_logdb.erl |
0d78319d | 345 | new file mode 100644 |
dd02533f | 346 | index 0000000000..bf0240d139 |
0d78319d | 347 | --- /dev/null |
046546ef | 348 | +++ b/src/mod_logdb.erl |
a815cc6c | 349 | @@ -0,0 +1,1951 @@ |
f7ce3e3a | 350 | +%%%---------------------------------------------------------------------- |
351 | +%%% File : mod_logdb.erl | |
3f23be8e | 352 | +%%% Author : Oleg Palij (mailto:o.palij@gmail.com) |
f7ce3e3a | 353 | +%%% Purpose : Frontend for log user messages to db |
3f23be8e | 354 | +%%% Url : https://paleg.github.io/mod_logdb/ |
f7ce3e3a | 355 | +%%%---------------------------------------------------------------------- |
356 | + | |
357 | +-module(mod_logdb). | |
358 | +-author('o.palij@gmail.com'). | |
f7ce3e3a | 359 | + |
360 | +-behaviour(gen_server). | |
361 | +-behaviour(gen_mod). | |
362 | + | |
363 | +% supervisor | |
364 | +-export([start_link/2]). | |
365 | +% gen_mod | |
3f23be8e AM |
366 | +-export([start/2, stop/1, |
367 | + mod_opt_type/1, | |
368 | + depends/2, reload/3]). | |
f7ce3e3a | 369 | +% gen_server |
3f23be8e AM |
370 | +-export([code_change/3, |
371 | + handle_call/3, handle_cast/2, handle_info/2, | |
372 | + init/1, terminate/2]). | |
f7ce3e3a | 373 | +% hooks |
3f23be8e | 374 | +-export([send_packet/1, receive_packet/1, offline_message/1, remove_user/2]). |
f7ce3e3a | 375 | +-export([get_local_identity/5, |
0d78319d | 376 | + get_local_features/5, |
f7ce3e3a | 377 | + get_local_items/5, |
378 | + adhoc_local_items/4, | |
379 | + adhoc_local_commands/4 | |
f7ce3e3a | 380 | + ]). |
381 | +% ejabberdctl | |
3f23be8e | 382 | +-export([rebuild_stats/1, |
f7ce3e3a | 383 | + copy_messages/1, copy_messages_ctl/3, copy_messages_int_tc/1]). |
384 | +% | |
385 | +-export([get_vhost_stats/1, get_vhost_stats_at/2, | |
386 | + get_user_stats/2, get_user_messages_at/3, | |
387 | + get_dates/1, | |
388 | + sort_stats/1, | |
389 | + convert_timestamp/1, convert_timestamp_brief/1, | |
390 | + get_user_settings/2, set_user_settings/3, | |
391 | + user_messages_at_parse_query/4, user_messages_parse_query/3, | |
392 | + vhost_messages_parse_query/2, vhost_messages_at_parse_query/4, | |
393 | + list_to_bool/1, bool_to_list/1, | |
394 | + list_to_string/1, string_to_list/1, | |
395 | + get_module_settings/1, set_module_settings/2, | |
396 | + purge_old_records/2]). | |
234c6b10 | 397 | +% webadmin hooks |
398 | +-export([webadmin_menu/3, | |
399 | + webadmin_user/4, | |
400 | + webadmin_page/3, | |
401 | + user_parse_query/5]). | |
402 | +% webadmin queries | |
403 | +-export([vhost_messages_stats/3, | |
404 | + vhost_messages_stats_at/4, | |
405 | + user_messages_stats/4, | |
406 | + user_messages_stats_at/5]). | |
f7ce3e3a | 407 | + |
408 | +-include("mod_logdb.hrl"). | |
3f23be8e | 409 | +-include("xmpp.hrl"). |
234c6b10 | 410 | +-include("mod_roster.hrl"). |
3f23be8e | 411 | +-include("ejabberd_commands.hrl"). |
f7ce3e3a | 412 | +-include("adhoc.hrl"). |
046546ef AM |
413 | +-include("ejabberd_web_admin.hrl"). |
414 | +-include("ejabberd_http.hrl"). | |
415 | +-include("logger.hrl"). | |
dd02533f | 416 | + |
f7ce3e3a | 417 | +-define(PROCNAME, ejabberd_mod_logdb). |
418 | +% gen_server call timeout | |
234c6b10 | 419 | +-define(CALL_TIMEOUT, 10000). |
f7ce3e3a | 420 | + |
234c6b10 | 421 | +-record(state, {vhost, dbmod, backendPid, monref, purgeRef, pollRef, dbopts, dbs, dolog_default, ignore_jids, groupchat, purge_older_days, poll_users_settings, drop_messages_on_user_removal}). |
f7ce3e3a | 422 | + |
046546ef | 423 | +ets_settings_table(VHost) -> list_to_atom("ets_logdb_settings_" ++ binary_to_list(VHost)). |
f7ce3e3a | 424 | + |
425 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
426 | +% | |
427 | +% gen_mod/gen_server callbacks | |
428 | +% | |
429 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
430 | +% ejabberd starts module | |
431 | +start(VHost, Opts) -> | |
432 | + ChildSpec = | |
433 | + {gen_mod:get_module_proc(VHost, ?PROCNAME), | |
434 | + {?MODULE, start_link, [VHost, Opts]}, | |
435 | + permanent, | |
436 | + 1000, | |
437 | + worker, | |
438 | + [?MODULE]}, | |
439 | + % add child to ejabberd_sup | |
3f23be8e AM |
440 | + supervisor:start_child(ejabberd_gen_mod_sup, ChildSpec). |
441 | + | |
442 | +depends(_Host, _Opts) -> | |
443 | + []. | |
444 | + | |
445 | +reload(_Host, _NewOpts, _OldOpts) -> | |
446 | + % TODO | |
447 | + ok. | |
f7ce3e3a | 448 | + |
449 | +% supervisor starts gen_server | |
450 | +start_link(VHost, Opts) -> | |
451 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
234c6b10 | 452 | + {ok, Pid} = gen_server:start_link({local, Proc}, ?MODULE, [VHost, Opts], []), |
453 | + Pid ! start, | |
454 | + {ok, Pid}. | |
f7ce3e3a | 455 | + |
456 | +init([VHost, Opts]) -> | |
457 | + process_flag(trap_exit, true), | |
046546ef AM |
458 | + DBsRaw = gen_mod:get_opt(dbs, Opts, fun(A) -> A end, [{mnesia, []}]), |
459 | + DBs = case lists:keysearch(mnesia, 1, DBsRaw) of | |
460 | + false -> lists:append(DBsRaw, [{mnesia,[]}]); | |
33af2120 | 461 | + {value, _} -> DBsRaw |
046546ef AM |
462 | + end, |
463 | + VHostDB = gen_mod:get_opt(vhosts, Opts, fun(A) -> A end, [{VHost, mnesia}]), | |
3f23be8e | 464 | + % 10 is default because of using in clustered environment |
046546ef | 465 | + PollUsersSettings = gen_mod:get_opt(poll_users_settings, Opts, fun(A) -> A end, 10), |
f7ce3e3a | 466 | + |
046546ef AM |
467 | + {DBName, DBOpts} = |
468 | + case lists:keysearch(VHost, 1, VHostDB) of | |
469 | + false -> | |
470 | + ?WARNING_MSG("There is no logging backend defined for '~s', switching to mnesia", [VHost]), | |
471 | + {mnesia, []}; | |
472 | + {value,{_, DBNameResult}} -> | |
473 | + case lists:keysearch(DBNameResult, 1, DBs) of | |
474 | + false -> | |
475 | + ?WARNING_MSG("There is no such logging backend '~s' defined for '~s', switching to mnesia", [DBNameResult, VHost]), | |
476 | + {mnesia, []}; | |
477 | + {value, {_, DBOptsResult}} -> | |
478 | + {DBNameResult, DBOptsResult} | |
479 | + end | |
480 | + end, | |
f7ce3e3a | 481 | + |
33af2120 | 482 | + ?MYDEBUG("Starting mod_logdb for '~s' with '~s' backend", [VHost, DBName]), |
f7ce3e3a | 483 | + |
484 | + DBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(DBName)), | |
485 | + | |
f7ce3e3a | 486 | + {ok, #state{vhost=VHost, |
487 | + dbmod=DBMod, | |
488 | + dbopts=DBOpts, | |
489 | + % dbs used for convert messages from one backend to other | |
490 | + dbs=DBs, | |
046546ef AM |
491 | + dolog_default=gen_mod:get_opt(dolog_default, Opts, fun(A) -> A end, true), |
492 | + drop_messages_on_user_removal=gen_mod:get_opt(drop_messages_on_user_removal, Opts, fun(A) -> A end, true), | |
493 | + ignore_jids=gen_mod:get_opt(ignore_jids, Opts, fun(A) -> A end, []), | |
494 | + groupchat=gen_mod:get_opt(groupchat, Opts, fun(A) -> A end, none), | |
495 | + purge_older_days=gen_mod:get_opt(purge_older_days, Opts, fun(A) -> A end, never), | |
f7ce3e3a | 496 | + poll_users_settings=PollUsersSettings}}. |
497 | + | |
26b6b0c9 | 498 | +cleanup(#state{vhost=VHost} = _State) -> |
f7ce3e3a | 499 | + ?MYDEBUG("Stopping ~s for ~p", [?MODULE, VHost]), |
500 | + | |
501 | + %ets:delete(ets_settings_table(VHost)), | |
502 | + | |
234c6b10 | 503 | + ejabberd_hooks:delete(remove_user, VHost, ?MODULE, remove_user, 90), |
f7ce3e3a | 504 | + ejabberd_hooks:delete(user_send_packet, VHost, ?MODULE, send_packet, 90), |
505 | + ejabberd_hooks:delete(user_receive_packet, VHost, ?MODULE, receive_packet, 90), | |
3f23be8e AM |
506 | + ejabberd_hooks:delete(offline_message_hook, VHost, ?MODULE, offline_message, 40), |
507 | + | |
046546ef AM |
508 | + ejabberd_hooks:delete(adhoc_local_commands, VHost, ?MODULE, adhoc_local_commands, 50), |
509 | + ejabberd_hooks:delete(adhoc_local_items, VHost, ?MODULE, adhoc_local_items, 50), | |
046546ef AM |
510 | + ejabberd_hooks:delete(disco_local_identity, VHost, ?MODULE, get_local_identity, 50), |
511 | + ejabberd_hooks:delete(disco_local_features, VHost, ?MODULE, get_local_features, 50), | |
512 | + ejabberd_hooks:delete(disco_local_items, VHost, ?MODULE, get_local_items, 50), | |
f7ce3e3a | 513 | + |
234c6b10 | 514 | + ejabberd_hooks:delete(webadmin_menu_host, VHost, ?MODULE, webadmin_menu, 70), |
515 | + ejabberd_hooks:delete(webadmin_user, VHost, ?MODULE, webadmin_user, 50), | |
516 | + ejabberd_hooks:delete(webadmin_page_host, VHost, ?MODULE, webadmin_page, 50), | |
517 | + ejabberd_hooks:delete(webadmin_user_parse_query, VHost, ?MODULE, user_parse_query, 50), | |
518 | + | |
f7ce3e3a | 519 | + ?MYDEBUG("Removed hooks for ~p", [VHost]), |
520 | + | |
3f23be8e | 521 | + ejabberd_commands:unregister_commands(get_commands_spec()), |
f7ce3e3a | 522 | + ?MYDEBUG("Unregistered commands for ~p", [VHost]). |
523 | + | |
524 | +stop(VHost) -> | |
525 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
526 | + %gen_server:call(Proc, {cleanup}), | |
527 | + %?MYDEBUG("Cleanup in stop finished!!!!", []), | |
528 | + %timer:sleep(10000), | |
3f23be8e AM |
529 | + ok = supervisor:terminate_child(ejabberd_gen_mod_sup, Proc), |
530 | + ok = supervisor:delete_child(ejabberd_gen_mod_sup, Proc). | |
531 | + | |
532 | +get_commands_spec() -> | |
533 | + [#ejabberd_commands{name = rebuild_stats, tags = [logdb], | |
534 | + desc = "Rebuild mod_logdb stats for given host", | |
535 | + module = ?MODULE, function = rebuild_stats, | |
536 | + args = [{host, binary}], | |
537 | + result = {res, rescode}}, | |
538 | + #ejabberd_commands{name = copy_messages, tags = [logdb], | |
539 | + desc = "Copy logdb messages from given backend to current backend for given host", | |
540 | + module = ?MODULE, function = copy_messages_ctl, | |
541 | + args = [{host, binary}, {backend, binary}, {date, binary}], | |
542 | + result = {res, rescode}}]. | |
f7ce3e3a | 543 | + |
bb18ce72 AM |
544 | +mod_opt_type(dbs) -> |
545 | + fun (A) when is_list(A) -> A end; | |
546 | +mod_opt_type(vhosts) -> | |
547 | + fun (A) when is_list(A) -> A end; | |
548 | +mod_opt_type(poll_users_settings) -> | |
549 | + fun (I) when is_integer(I) -> I end; | |
550 | +mod_opt_type(groupchat) -> | |
551 | + fun (all) -> all; | |
552 | + (send) -> send; | |
553 | + (none) -> none | |
554 | + end; | |
555 | +mod_opt_type(dolog_default) -> | |
556 | + fun (B) when is_boolean(B) -> B end; | |
557 | +mod_opt_type(ignore_jids) -> | |
558 | + fun (A) when is_list(A) -> A end; | |
559 | +mod_opt_type(purge_older_days) -> | |
560 | + fun (I) when is_integer(I) -> I end; | |
561 | +mod_opt_type(_) -> | |
562 | + [dbs, vhosts, poll_users_settings, groupchat, dolog_default, ignore_jids, purge_older_days]. | |
563 | + | |
f7ce3e3a | 564 | +handle_call({cleanup}, _From, State) -> |
565 | + cleanup(State), | |
566 | + ?MYDEBUG("Cleanup finished!!!!!", []), | |
567 | + {reply, ok, State}; | |
568 | +handle_call({get_dates}, _From, #state{dbmod=DBMod, vhost=VHost}=State) -> | |
569 | + Reply = DBMod:get_dates(VHost), | |
570 | + {reply, Reply, State}; | |
571 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
572 | +% ejabberd_web_admin callbacks | |
573 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
574 | +handle_call({delete_messages_by_user_at, PMsgs, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) -> | |
046546ef | 575 | + Reply = DBMod:delete_messages_by_user_at(VHost, PMsgs, binary_to_list(Date)), |
f7ce3e3a | 576 | + {reply, Reply, State}; |
577 | +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) -> | |
046546ef | 578 | + Reply = DBMod:delete_all_messages_by_user_at(binary_to_list(User), VHost, binary_to_list(Date)), |
f7ce3e3a | 579 | + {reply, Reply, State}; |
580 | +handle_call({delete_messages_at, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) -> | |
581 | + Reply = DBMod:delete_messages_at(VHost, Date), | |
582 | + {reply, Reply, State}; | |
583 | +handle_call({get_vhost_stats}, _From, #state{dbmod=DBMod, vhost=VHost}=State) -> | |
584 | + Reply = DBMod:get_vhost_stats(VHost), | |
585 | + {reply, Reply, State}; | |
586 | +handle_call({get_vhost_stats_at, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) -> | |
046546ef | 587 | + Reply = DBMod:get_vhost_stats_at(VHost, binary_to_list(Date)), |
f7ce3e3a | 588 | + {reply, Reply, State}; |
589 | +handle_call({get_user_stats, User}, _From, #state{dbmod=DBMod, vhost=VHost}=State) -> | |
046546ef | 590 | + Reply = DBMod:get_user_stats(binary_to_list(User), VHost), |
f7ce3e3a | 591 | + {reply, Reply, State}; |
592 | +handle_call({get_user_messages_at, User, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) -> | |
046546ef | 593 | + Reply = DBMod:get_user_messages_at(binary_to_list(User), VHost, binary_to_list(Date)), |
f7ce3e3a | 594 | + {reply, Reply, State}; |
595 | +handle_call({get_user_settings, User}, _From, #state{dbmod=_DBMod, vhost=VHost}=State) -> | |
596 | + Reply = case ets:match_object(ets_settings_table(VHost), | |
597 | + #user_settings{owner_name=User, _='_'}) of | |
598 | + [Set] -> Set; | |
599 | + _ -> #user_settings{owner_name=User, | |
600 | + dolog_default=State#state.dolog_default, | |
601 | + dolog_list=[], | |
602 | + donotlog_list=[]} | |
603 | + end, | |
604 | + {reply, Reply, State}; | |
605 | +% TODO: remove User ?? | |
606 | +handle_call({set_user_settings, User, GSet}, _From, #state{dbmod=DBMod, vhost=VHost}=State) -> | |
607 | + Set = GSet#user_settings{owner_name=User}, | |
608 | + Reply = | |
609 | + case ets:match_object(ets_settings_table(VHost), | |
610 | + #user_settings{owner_name=User, _='_'}) of | |
611 | + [Set] -> | |
f7ce3e3a | 612 | + ok; |
613 | + _ -> | |
046546ef | 614 | + case DBMod:set_user_settings(binary_to_list(User), VHost, Set) of |
f7ce3e3a | 615 | + error -> |
616 | + error; | |
617 | + ok -> | |
618 | + true = ets:insert(ets_settings_table(VHost), Set), | |
619 | + ok | |
620 | + end | |
621 | + end, | |
622 | + {reply, Reply, State}; | |
623 | +handle_call({get_module_settings}, _From, State) -> | |
624 | + {reply, State, State}; | |
625 | +handle_call({set_module_settings, #state{purge_older_days=PurgeDays, | |
626 | + poll_users_settings=PollSec} = Settings}, | |
627 | + _From, | |
628 | + #state{purgeRef=PurgeRefOld, | |
629 | + pollRef=PollRefOld, | |
630 | + purge_older_days=PurgeDaysOld, | |
631 | + poll_users_settings=PollSecOld} = State) -> | |
632 | + PurgeRef = if | |
633 | + PurgeDays == never, PurgeDaysOld /= never -> | |
634 | + {ok, cancel} = timer:cancel(PurgeRefOld), | |
635 | + disabled; | |
636 | + is_integer(PurgeDays), PurgeDaysOld == never -> | |
637 | + set_purge_timer(PurgeDays); | |
638 | + true -> | |
639 | + PurgeRefOld | |
640 | + end, | |
641 | + | |
642 | + PollRef = if | |
643 | + PollSec == PollSecOld -> | |
644 | + PollRefOld; | |
645 | + PollSec == 0, PollSecOld /= 0 -> | |
646 | + {ok, cancel} = timer:cancel(PollRefOld), | |
647 | + disabled; | |
648 | + is_integer(PollSec), PollSecOld == 0 -> | |
649 | + set_poll_timer(PollSec); | |
650 | + is_integer(PollSec), PollSecOld /= 0 -> | |
651 | + {ok, cancel} = timer:cancel(PollRefOld), | |
652 | + set_poll_timer(PollSec) | |
653 | + end, | |
654 | + | |
655 | + NewState = State#state{dolog_default=Settings#state.dolog_default, | |
656 | + ignore_jids=Settings#state.ignore_jids, | |
657 | + groupchat=Settings#state.groupchat, | |
234c6b10 | 658 | + drop_messages_on_user_removal=Settings#state.drop_messages_on_user_removal, |
f7ce3e3a | 659 | + purge_older_days=PurgeDays, |
660 | + poll_users_settings=PollSec, | |
661 | + purgeRef=PurgeRef, | |
662 | + pollRef=PollRef}, | |
663 | + {reply, ok, NewState}; | |
664 | +handle_call(Msg, _From, State) -> | |
665 | + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]), | |
666 | + {noreply, State}. | |
667 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
668 | +% end ejabberd_web_admin callbacks | |
669 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
670 | + | |
671 | +% ejabberd_hooks call | |
672 | +handle_cast({addlog, Direction, Owner, Peer, Packet}, #state{dbmod=DBMod, vhost=VHost}=State) -> | |
673 | + case filter(Owner, Peer, State) of | |
674 | + true -> | |
675 | + case catch packet_parse(Owner, Peer, Packet, Direction, State) of | |
676 | + ignore -> | |
677 | + ok; | |
678 | + {'EXIT', Reason} -> | |
679 | + ?ERROR_MSG("Failed to parse: ~p", [Reason]); | |
680 | + Msg -> | |
681 | + DBMod:log_message(VHost, Msg) | |
682 | + end; | |
683 | + false -> | |
684 | + ok | |
685 | + end, | |
686 | + {noreply, State}; | |
234c6b10 | 687 | +handle_cast({remove_user, User}, #state{dbmod=DBMod, vhost=VHost}=State) -> |
688 | + case State#state.drop_messages_on_user_removal of | |
689 | + true -> | |
046546ef | 690 | + DBMod:drop_user(binary_to_list(User), VHost), |
234c6b10 | 691 | + ?INFO_MSG("Launched ~s@~s removal", [User, VHost]); |
692 | + false -> | |
693 | + ?INFO_MSG("Message removing is disabled. Keeping messages for ~s@~s", [User, VHost]) | |
694 | + end, | |
695 | + {noreply, State}; | |
f7ce3e3a | 696 | +% ejabberdctl rebuild_stats/3 |
697 | +handle_cast({rebuild_stats}, #state{dbmod=DBMod, vhost=VHost}=State) -> | |
f7ce3e3a | 698 | + DBMod:rebuild_stats(VHost), |
699 | + {noreply, State}; | |
700 | +handle_cast({copy_messages, Backend}, State) -> | |
3f23be8e | 701 | + spawn(?MODULE, copy_messages, [[State, Backend, []]]), |
f7ce3e3a | 702 | + {noreply, State}; |
703 | +handle_cast({copy_messages, Backend, Date}, State) -> | |
3f23be8e | 704 | + spawn(?MODULE, copy_messages, [[State, Backend, [binary_to_list(Date)]]]), |
f7ce3e3a | 705 | + {noreply, State}; |
706 | +handle_cast(Msg, State) -> | |
707 | + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]), | |
708 | + {noreply, State}. | |
709 | + | |
710 | +% return: disabled | timer reference | |
711 | +set_purge_timer(PurgeDays) -> | |
712 | + case PurgeDays of | |
713 | + never -> disabled; | |
714 | + Days when is_integer(Days) -> | |
715 | + {ok, Ref1} = timer:send_interval(timer:hours(24), scheduled_purging), | |
716 | + Ref1 | |
717 | + end. | |
718 | + | |
719 | +% return: disabled | timer reference | |
720 | +set_poll_timer(PollSec) -> | |
721 | + if | |
722 | + PollSec > 0 -> | |
723 | + {ok, Ref2} = timer:send_interval(timer:seconds(PollSec), poll_users_settings), | |
724 | + Ref2; | |
725 | + % db polling disabled | |
726 | + PollSec == 0 -> | |
727 | + disabled; | |
728 | + true -> | |
729 | + {ok, Ref3} = timer:send_interval(timer:seconds(10), poll_users_settings), | |
730 | + Ref3 | |
731 | + end. | |
732 | + | |
733 | +% actual starting of logging | |
734 | +% from timer:send_after (in init) | |
735 | +handle_info(start, #state{dbmod=DBMod, vhost=VHost}=State) -> | |
736 | + case DBMod:start(VHost, State#state.dbopts) of | |
234c6b10 | 737 | + {error,{already_started,_}} -> |
738 | + ?MYDEBUG("backend module already started - trying to stop it", []), | |
739 | + DBMod:stop(VHost), | |
740 | + {stop, already_started, State}; | |
741 | + {error, Reason} -> | |
f7ce3e3a | 742 | + timer:sleep(30000), |
234c6b10 | 743 | + ?ERROR_MSG("Failed to start: ~p", [Reason]), |
f7ce3e3a | 744 | + {stop, db_connection_failed, State}; |
745 | + {ok, SPid} -> | |
f7ce3e3a | 746 | + ?INFO_MSG("~p connection established", [DBMod]), |
0d78319d | 747 | + |
f7ce3e3a | 748 | + MonRef = erlang:monitor(process, SPid), |
749 | + | |
750 | + ets:new(ets_settings_table(VHost), [named_table,public,set,{keypos, #user_settings.owner_name}]), | |
046546ef AM |
751 | + DoLog = case DBMod:get_users_settings(VHost) of |
752 | + {ok, Settings} -> [Sett#user_settings{owner_name = iolist_to_binary(Sett#user_settings.owner_name)} || Sett <- Settings]; | |
753 | + {error, _Reason} -> [] | |
754 | + end, | |
f7ce3e3a | 755 | + ets:insert(ets_settings_table(VHost), DoLog), |
756 | + | |
757 | + TrefPurge = set_purge_timer(State#state.purge_older_days), | |
758 | + TrefPoll = set_poll_timer(State#state.poll_users_settings), | |
759 | + | |
234c6b10 | 760 | + ejabberd_hooks:add(remove_user, VHost, ?MODULE, remove_user, 90), |
f7ce3e3a | 761 | + ejabberd_hooks:add(user_send_packet, VHost, ?MODULE, send_packet, 90), |
762 | + ejabberd_hooks:add(user_receive_packet, VHost, ?MODULE, receive_packet, 90), | |
3f23be8e | 763 | + ejabberd_hooks:add(offline_message_hook, VHost, ?MODULE, offline_message, 40), |
f7ce3e3a | 764 | + |
3f23be8e | 765 | + ejabberd_hooks:add(adhoc_local_commands, VHost, ?MODULE, adhoc_local_commands, 50), |
046546ef | 766 | + ejabberd_hooks:add(disco_local_items, VHost, ?MODULE, get_local_items, 50), |
046546ef | 767 | + ejabberd_hooks:add(disco_local_identity, VHost, ?MODULE, get_local_identity, 50), |
3f23be8e | 768 | + ejabberd_hooks:add(disco_local_features, VHost, ?MODULE, get_local_features, 50), |
046546ef | 769 | + ejabberd_hooks:add(adhoc_local_items, VHost, ?MODULE, adhoc_local_items, 50), |
f7ce3e3a | 770 | + |
234c6b10 | 771 | + ejabberd_hooks:add(webadmin_menu_host, VHost, ?MODULE, webadmin_menu, 70), |
772 | + ejabberd_hooks:add(webadmin_user, VHost, ?MODULE, webadmin_user, 50), | |
773 | + ejabberd_hooks:add(webadmin_page_host, VHost, ?MODULE, webadmin_page, 50), | |
774 | + ejabberd_hooks:add(webadmin_user_parse_query, VHost, ?MODULE, user_parse_query, 50), | |
775 | + | |
f7ce3e3a | 776 | + ?MYDEBUG("Added hooks for ~p", [VHost]), |
777 | + | |
3f23be8e | 778 | + ejabberd_commands:register_commands(get_commands_spec()), |
f7ce3e3a | 779 | + ?MYDEBUG("Registered commands for ~p", [VHost]), |
780 | + | |
781 | + NewState=State#state{monref = MonRef, backendPid=SPid, purgeRef=TrefPurge, pollRef=TrefPoll}, | |
782 | + {noreply, NewState}; | |
783 | + Rez -> | |
784 | + ?ERROR_MSG("Rez=~p", [Rez]), | |
785 | + timer:sleep(30000), | |
786 | + {stop, db_connection_failed, State} | |
787 | + end; | |
788 | +% from timer:send_interval/2 (in start handle_info) | |
789 | +handle_info(scheduled_purging, #state{vhost=VHost, purge_older_days=Days} = State) -> | |
790 | + ?MYDEBUG("Starting scheduled purging of old records for ~p", [VHost]), | |
791 | + spawn(?MODULE, purge_old_records, [VHost, integer_to_list(Days)]), | |
792 | + {noreply, State}; | |
793 | +% from timer:send_interval/2 (in start handle_info) | |
794 | +handle_info(poll_users_settings, #state{dbmod=DBMod, vhost=VHost}=State) -> | |
795 | + {ok, DoLog} = DBMod:get_users_settings(VHost), | |
796 | + ?MYDEBUG("DoLog=~p", [DoLog]), | |
797 | + true = ets:delete_all_objects(ets_settings_table(VHost)), | |
798 | + ets:insert(ets_settings_table(VHost), DoLog), | |
799 | + {noreply, State}; | |
800 | +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) -> | |
801 | + {stop, db_connection_dropped, State}; | |
802 | +handle_info({fetch_result, _, _}, State) -> | |
803 | + ?MYDEBUG("Got timed out mysql fetch result", []), | |
804 | + {noreply, State}; | |
805 | +handle_info(Info, State) -> | |
806 | + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]), | |
807 | + {noreply, State}. | |
808 | + | |
809 | +terminate(db_connection_failed, _State) -> | |
810 | + ok; | |
811 | +terminate(db_connection_dropped, State) -> | |
234c6b10 | 812 | + ?MYDEBUG("Got terminate with db_connection_dropped", []), |
f7ce3e3a | 813 | + cleanup(State), |
814 | + ok; | |
234c6b10 | 815 | +terminate(Reason, #state{monref=undefined} = State) -> |
816 | + ?MYDEBUG("Got terminate with undefined monref.~nReason: ~p", [Reason]), | |
f7ce3e3a | 817 | + cleanup(State), |
818 | + ok; | |
819 | +terminate(Reason, #state{dbmod=DBMod, vhost=VHost, monref=MonRef, backendPid=Pid} = State) -> | |
820 | + ?INFO_MSG("Reason: ~p", [Reason]), | |
821 | + case erlang:is_process_alive(Pid) of | |
822 | + true -> | |
823 | + erlang:demonitor(MonRef, [flush]), | |
824 | + DBMod:stop(VHost); | |
825 | + false -> | |
826 | + ok | |
827 | + end, | |
828 | + cleanup(State), | |
829 | + ok. | |
830 | + | |
831 | +code_change(_OldVsn, State, _Extra) -> | |
832 | + {ok, State}. | |
833 | + | |
834 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
bb18ce72 | 835 | +% |
f7ce3e3a | 836 | +% ejabberd_hooks callbacks |
bb18ce72 | 837 | +% |
f7ce3e3a | 838 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
839 | +% TODO: change to/from to list as sql stores it as list | |
3f23be8e AM |
840 | +send_packet({Pkt, #{jid := Owner} = C2SState}) -> |
841 | + VHost = Owner#jid.lserver, | |
842 | + Peer = xmpp:get_to(Pkt), | |
843 | + %?MYDEBUG("send_packet. Peer=~p, Owner=~p", [Peer, Owner]), | |
844 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
845 | + gen_server:cast(Proc, {addlog, to, Owner, Peer, Pkt}), | |
846 | + {Pkt, C2SState}. | |
847 | + | |
848 | +receive_packet({Pkt, #{jid := Owner} = C2SState}) -> | |
f7ce3e3a | 849 | + VHost = Owner#jid.lserver, |
3f23be8e AM |
850 | + Peer = xmpp:get_from(Pkt), |
851 | + %?MYDEBUG("receive_packet. Pkt=~p", [Pkt]), | |
f7ce3e3a | 852 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), |
3f23be8e AM |
853 | + gen_server:cast(Proc, {addlog, from, Owner, Peer, Pkt}), |
854 | + {Pkt, C2SState}. | |
f7ce3e3a | 855 | + |
3f23be8e | 856 | +offline_message({_Action, #message{from = Peer, to = Owner} = Pkt} = Acc) -> |
f7ce3e3a | 857 | + VHost = Owner#jid.lserver, |
3f23be8e | 858 | + %?MYDEBUG("offline_message. Pkt=~p", [Pkt]), |
f7ce3e3a | 859 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), |
3f23be8e AM |
860 | + gen_server:cast(Proc, {addlog, from, Owner, Peer, Pkt}), |
861 | + Acc. | |
f7ce3e3a | 862 | + |
234c6b10 | 863 | +remove_user(User, Server) -> |
3f23be8e AM |
864 | + LUser = jid:nodeprep(User), |
865 | + LServer = jid:nameprep(Server), | |
234c6b10 | 866 | + Proc = gen_mod:get_module_proc(LServer, ?PROCNAME), |
867 | + gen_server:cast(Proc, {remove_user, LUser}). | |
868 | + | |
f7ce3e3a | 869 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
870 | +% | |
871 | +% ejabberdctl | |
872 | +% | |
873 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
3f23be8e | 874 | +rebuild_stats(VHost) -> |
f7ce3e3a | 875 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), |
876 | + gen_server:cast(Proc, {rebuild_stats}), | |
3f23be8e | 877 | + ok. |
f7ce3e3a | 878 | + |
3f23be8e | 879 | +copy_messages_ctl(VHost, Backend, <<"all">>) -> |
f7ce3e3a | 880 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), |
881 | + gen_server:cast(Proc, {copy_messages, Backend}), | |
3f23be8e AM |
882 | + ok; |
883 | +copy_messages_ctl(VHost, Backend, Date) -> | |
f7ce3e3a | 884 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), |
885 | + gen_server:cast(Proc, {copy_messages, Backend, Date}), | |
3f23be8e AM |
886 | + ok. |
887 | + | |
f7ce3e3a | 888 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
889 | +% | |
890 | +% misc operations | |
891 | +% | |
892 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
893 | + | |
894 | +% handle_cast({addlog, E}, _) | |
895 | +% raw packet -> #msg | |
3f23be8e AM |
896 | +packet_parse(_Owner, _Peer, #message{type = error}, _Direction, _State) -> |
897 | + ignore; | |
898 | +packet_parse(_Owner, _Peer, #message{meta = #{sm_copy := true}}, _Direction, _State) -> | |
899 | + ignore; | |
900 | +packet_parse(_Owner, _Peer, #message{meta = #{from_offline := true}}, _Direction, _State) -> | |
901 | + ignore; | |
902 | +packet_parse(Owner, Peer, #message{body = Body, subject = Subject, type = Type}, Direction, State) -> | |
903 | + %?MYDEBUG("Owner=~p, Peer=~p, Direction=~p", [Owner, Peer, Direction]), | |
904 | + %?MYDEBUG("Body=~p, Subject=~p, Type=~p", [Body, Subject, Type]), | |
905 | + SubjectText = xmpp:get_text(Subject), | |
906 | + BodyText = xmpp:get_text(Body), | |
907 | + if (SubjectText == <<"">>) and (BodyText == <<"">>) -> | |
908 | + throw(ignore); | |
909 | + true -> ok | |
910 | + end, | |
f7ce3e3a | 911 | + |
3f23be8e AM |
912 | + case Type of |
913 | + groupchat when State#state.groupchat == send, Direction == to -> | |
914 | + ok; | |
915 | + groupchat when State#state.groupchat == send, Direction == from -> | |
916 | + throw(ignore); | |
917 | + groupchat when State#state.groupchat == none -> | |
918 | + throw(ignore); | |
919 | + _ -> | |
920 | + ok | |
921 | + end, | |
f7ce3e3a | 922 | + |
3f23be8e AM |
923 | + #msg{timestamp = get_timestamp(), |
924 | + owner_name = stringprep:tolower(Owner#jid.user), | |
925 | + peer_name = stringprep:tolower(Peer#jid.user), | |
926 | + peer_server = stringprep:tolower(Peer#jid.server), | |
927 | + peer_resource = Peer#jid.resource, | |
928 | + direction = Direction, | |
929 | + type = misc:atom_to_binary(Type), | |
930 | + subject = SubjectText, | |
931 | + body = BodyText}; | |
932 | +packet_parse(_, _, _, _, _) -> | |
933 | + ignore. | |
f7ce3e3a | 934 | + |
935 | +% called from handle_cast({addlog, _}, _) -> true (log messages) | false (do not log messages) | |
936 | +filter(Owner, Peer, State) -> | |
046546ef AM |
937 | + OwnerBin = << (Owner#jid.luser)/binary, "@", (Owner#jid.lserver)/binary >>, |
938 | + OwnerServ = << "@", (Owner#jid.lserver)/binary >>, | |
939 | + PeerBin = << (Peer#jid.luser)/binary, "@", (Peer#jid.lserver)/binary >>, | |
940 | + PeerServ = << "@", (Peer#jid.lserver)/binary >>, | |
f7ce3e3a | 941 | + |
942 | + LogTo = case ets:match_object(ets_settings_table(State#state.vhost), | |
943 | + #user_settings{owner_name=Owner#jid.luser, _='_'}) of | |
944 | + [#user_settings{dolog_default=Default, | |
945 | + dolog_list=DLL, | |
946 | + donotlog_list=DNLL}] -> | |
046546ef AM |
947 | + |
948 | + A = lists:member(PeerBin, DLL), | |
949 | + B = lists:member(PeerBin, DNLL), | |
f7ce3e3a | 950 | + if |
951 | + A -> true; | |
952 | + B -> false; | |
953 | + Default == true -> true; | |
954 | + Default == false -> false; | |
955 | + true -> State#state.dolog_default | |
956 | + end; | |
957 | + _ -> State#state.dolog_default | |
bb18ce72 | 958 | + end, |
0d78319d | 959 | + lists:all(fun(O) -> O end, |
046546ef AM |
960 | + [not lists:member(OwnerBin, State#state.ignore_jids), |
961 | + not lists:member(PeerBin, State#state.ignore_jids), | |
f7ce3e3a | 962 | + not lists:member(OwnerServ, State#state.ignore_jids), |
963 | + not lists:member(PeerServ, State#state.ignore_jids), | |
964 | + LogTo]). | |
965 | + | |
966 | +purge_old_records(VHost, Days) -> | |
967 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
968 | + | |
234c6b10 | 969 | + Dates = ?MODULE:get_dates(VHost), |
f7ce3e3a | 970 | + DateNow = calendar:datetime_to_gregorian_seconds({date(), {0,0,1}}), |
971 | + DateDiff = list_to_integer(Days)*24*60*60, | |
972 | + ?MYDEBUG("Purging tables older than ~s days", [Days]), | |
973 | + lists:foreach(fun(Date) -> | |
046546ef AM |
974 | + [Year, Month, Day] = ejabberd_regexp:split(iolist_to_binary(Date), <<"[^0-9]+">>), |
975 | + DateInSec = calendar:datetime_to_gregorian_seconds({{binary_to_integer(Year), binary_to_integer(Month), binary_to_integer(Day)}, {0,0,1}}), | |
f7ce3e3a | 976 | + if |
977 | + (DateNow - DateInSec) > DateDiff -> | |
978 | + gen_server:call(Proc, {delete_messages_at, Date}); | |
0d78319d | 979 | + true -> |
f7ce3e3a | 980 | + ?MYDEBUG("Skipping messages at ~p", [Date]) |
981 | + end | |
982 | + end, Dates). | |
983 | + | |
984 | +% called from get_vhost_stats/2, get_user_stats/3 | |
985 | +sort_stats(Stats) -> | |
986 | + % Stats = [{"2003-4-15",1}, {"2006-8-18",1}, ... ] | |
987 | + CFun = fun({TableName, Count}) -> | |
046546ef AM |
988 | + [Year, Month, Day] = ejabberd_regexp:split(iolist_to_binary(TableName), <<"[^0-9]+">>), |
989 | + { calendar:datetime_to_gregorian_seconds({{binary_to_integer(Year), binary_to_integer(Month), binary_to_integer(Day)}, {0,0,1}}), Count } | |
f7ce3e3a | 990 | + end, |
991 | + % convert to [{63364377601,1}, {63360662401,1}, ... ] | |
992 | + CStats = lists:map(CFun, Stats), | |
993 | + % sort by date | |
994 | + SortedStats = lists:reverse(lists:keysort(1, CStats)), | |
995 | + % convert to [{"2007-12-9",1}, {"2007-10-27",1}, ... ] sorted list | |
996 | + [{mod_logdb:convert_timestamp_brief(TableSec), Count} || {TableSec, Count} <- SortedStats]. | |
997 | + | |
998 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
999 | +% | |
1000 | +% Date/Time operations | |
1001 | +% | |
1002 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
1003 | +% return float seconds elapsed from "zero hour" as list | |
1004 | +get_timestamp() -> | |
1005 | + {MegaSec, Sec, MicroSec} = now(), | |
dd02533f AM |
1006 | + [List] = io_lib:format("~.5f", [MegaSec*1000000 + Sec + MicroSec/1000000]), |
1007 | + List. | |
f7ce3e3a | 1008 | + |
1009 | +% convert float seconds elapsed from "zero hour" to local time "%Y-%m-%d %H:%M:%S" string | |
1010 | +convert_timestamp(Seconds) when is_list(Seconds) -> | |
1011 | + case string:to_float(Seconds++".0") of | |
1012 | + {F,_} when is_float(F) -> convert_timestamp(F); | |
1013 | + _ -> erlang:error(badarg, [Seconds]) | |
1014 | + end; | |
1015 | +convert_timestamp(Seconds) when is_float(Seconds) -> | |
1016 | + GregSec = trunc(Seconds + 719528*86400), | |
1017 | + UnivDT = calendar:gregorian_seconds_to_datetime(GregSec), | |
1018 | + {{Year, Month, Day},{Hour, Minute, Sec}} = calendar:universal_time_to_local_time(UnivDT), | |
1019 | + integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day) ++ " " ++ integer_to_list(Hour) ++ ":" ++ integer_to_list(Minute) ++ ":" ++ integer_to_list(Sec). | |
1020 | + | |
1021 | +% convert float seconds elapsed from "zero hour" to local time "%Y-%m-%d" string | |
1022 | +convert_timestamp_brief(Seconds) when is_list(Seconds) -> | |
1023 | + convert_timestamp_brief(list_to_float(Seconds)); | |
1024 | +convert_timestamp_brief(Seconds) when is_float(Seconds) -> | |
1025 | + GregSec = trunc(Seconds + 719528*86400), | |
1026 | + UnivDT = calendar:gregorian_seconds_to_datetime(GregSec), | |
1027 | + {{Year, Month, Day},{_Hour, _Minute, _Sec}} = calendar:universal_time_to_local_time(UnivDT), | |
1028 | + integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day); | |
1029 | +convert_timestamp_brief(Seconds) when is_integer(Seconds) -> | |
1030 | + {{Year, Month, Day},{_Hour, _Minute, _Sec}} = calendar:gregorian_seconds_to_datetime(Seconds), | |
1031 | + integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day). | |
1032 | + | |
1033 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
1034 | +% | |
1035 | +% DB operations (get) | |
1036 | +% | |
1037 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
1038 | +get_vhost_stats(VHost) -> | |
1039 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
1040 | + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT). | |
1041 | + | |
1042 | +get_vhost_stats_at(VHost, Date) -> | |
1043 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
1044 | + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT). | |
1045 | + | |
1046 | +get_user_stats(User, VHost) -> | |
1047 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
1048 | + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT). | |
1049 | + | |
1050 | +get_user_messages_at(User, VHost, Date) -> | |
1051 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
1052 | + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT). | |
1053 | + | |
1054 | +get_dates(VHost) -> | |
1055 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
1056 | + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT). | |
1057 | + | |
1058 | +get_user_settings(User, VHost) -> | |
1059 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
1060 | + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT). | |
1061 | + | |
1062 | +set_user_settings(User, VHost, Set) -> | |
1063 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
1064 | + gen_server:call(Proc, {set_user_settings, User, Set}). | |
1065 | + | |
1066 | +get_module_settings(VHost) -> | |
1067 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
1068 | + gen_server:call(Proc, {get_module_settings}). | |
1069 | + | |
1070 | +set_module_settings(VHost, Settings) -> | |
1071 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
1072 | + gen_server:call(Proc, {set_module_settings, Settings}). | |
1073 | + | |
1074 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
1075 | +% | |
1076 | +% Web admin callbacks (delete) | |
1077 | +% | |
1078 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
1079 | +user_messages_at_parse_query(VHost, Date, Msgs, Query) -> | |
046546ef | 1080 | + case lists:keysearch(<<"delete">>, 1, Query) of |
f7ce3e3a | 1081 | + {value, _} -> |
1082 | + PMsgs = lists:filter( | |
1083 | + fun(Msg) -> | |
3f23be8e | 1084 | + ID = misc:encode_base64(term_to_binary(Msg#msg.timestamp)), |
046546ef | 1085 | + lists:member({<<"selected">>, ID}, Query) |
f7ce3e3a | 1086 | + end, Msgs), |
1087 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
1088 | + gen_server:call(Proc, {delete_messages_by_user_at, PMsgs, Date}, ?CALL_TIMEOUT); | |
1089 | + false -> | |
1090 | + nothing | |
1091 | + end. | |
1092 | + | |
1093 | +user_messages_parse_query(User, VHost, Query) -> | |
046546ef | 1094 | + case lists:keysearch(<<"delete">>, 1, Query) of |
f7ce3e3a | 1095 | + {value, _} -> |
234c6b10 | 1096 | + Dates = get_dates(VHost), |
f7ce3e3a | 1097 | + PDates = lists:filter( |
1098 | + fun(Date) -> | |
3f23be8e | 1099 | + ID = misc:encode_base64( << User/binary, (iolist_to_binary(Date))/binary >> ), |
046546ef | 1100 | + lists:member({<<"selected">>, ID}, Query) |
f7ce3e3a | 1101 | + end, Dates), |
1102 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
1103 | + Rez = lists:foldl( | |
1104 | + fun(Date, Acc) -> | |
1105 | + lists:append(Acc, | |
1106 | + [gen_server:call(Proc, | |
046546ef | 1107 | + {delete_all_messages_by_user_at, User, iolist_to_binary(Date)}, |
f7ce3e3a | 1108 | + ?CALL_TIMEOUT)]) |
1109 | + end, [], PDates), | |
1110 | + case lists:member(error, Rez) of | |
1111 | + true -> | |
1112 | + error; | |
1113 | + false -> | |
1114 | + nothing | |
1115 | + end; | |
1116 | + false -> | |
1117 | + nothing | |
1118 | + end. | |
1119 | + | |
1120 | +vhost_messages_parse_query(VHost, Query) -> | |
046546ef | 1121 | + case lists:keysearch(<<"delete">>, 1, Query) of |
f7ce3e3a | 1122 | + {value, _} -> |
234c6b10 | 1123 | + Dates = get_dates(VHost), |
f7ce3e3a | 1124 | + PDates = lists:filter( |
1125 | + fun(Date) -> | |
3f23be8e | 1126 | + ID = misc:encode_base64( << VHost/binary, (iolist_to_binary(Date))/binary >> ), |
046546ef | 1127 | + lists:member({<<"selected">>, ID}, Query) |
f7ce3e3a | 1128 | + end, Dates), |
1129 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
1130 | + Rez = lists:foldl(fun(Date, Acc) -> | |
1131 | + lists:append(Acc, [gen_server:call(Proc, | |
1132 | + {delete_messages_at, Date}, | |
1133 | + ?CALL_TIMEOUT)]) | |
1134 | + end, [], PDates), | |
1135 | + case lists:member(error, Rez) of | |
1136 | + true -> | |
1137 | + error; | |
1138 | + false -> | |
1139 | + nothing | |
1140 | + end; | |
1141 | + false -> | |
1142 | + nothing | |
1143 | + end. | |
1144 | + | |
1145 | +vhost_messages_at_parse_query(VHost, Date, Stats, Query) -> | |
046546ef | 1146 | + case lists:keysearch(<<"delete">>, 1, Query) of |
f7ce3e3a | 1147 | + {value, _} -> |
1148 | + PStats = lists:filter( | |
1149 | + fun({User, _Count}) -> | |
3f23be8e | 1150 | + ID = misc:encode_base64( << (iolist_to_binary(User))/binary, VHost/binary >> ), |
046546ef | 1151 | + lists:member({<<"selected">>, ID}, Query) |
f7ce3e3a | 1152 | + end, Stats), |
1153 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
1154 | + Rez = lists:foldl(fun({User, _Count}, Acc) -> | |
1155 | + lists:append(Acc, [gen_server:call(Proc, | |
1156 | + {delete_all_messages_by_user_at, | |
046546ef | 1157 | + iolist_to_binary(User), iolist_to_binary(Date)}, |
f7ce3e3a | 1158 | + ?CALL_TIMEOUT)]) |
1159 | + end, [], PStats), | |
1160 | + case lists:member(error, Rez) of | |
1161 | + true -> | |
1162 | + error; | |
1163 | + false -> | |
1164 | + ok | |
1165 | + end; | |
1166 | + false -> | |
1167 | + nothing | |
1168 | + end. | |
1169 | + | |
3f23be8e | 1170 | +copy_messages([#state{vhost=VHost}=State, From, DatesIn]) -> |
f7ce3e3a | 1171 | + {FromDBName, FromDBOpts} = |
3f23be8e | 1172 | + case lists:keysearch(misc:binary_to_atom(From), 1, State#state.dbs) of |
f7ce3e3a | 1173 | + {value, {FN, FO}} -> |
1174 | + {FN, FO}; | |
1175 | + false -> | |
1176 | + ?ERROR_MSG("Failed to find record for ~p in dbs", [From]), | |
1177 | + throw(error) | |
1178 | + end, | |
1179 | + | |
1180 | + FromDBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(FromDBName)), | |
1181 | + | |
1182 | + {ok, _FromPid} = FromDBMod:start(VHost, FromDBOpts), | |
0d78319d | 1183 | + |
3f23be8e AM |
1184 | + Dates = case DatesIn of |
1185 | + [] -> FromDBMod:get_dates(VHost); | |
1186 | + _ -> DatesIn | |
1187 | + end, | |
1188 | + | |
f7ce3e3a | 1189 | + DatesLength = length(Dates), |
1190 | + | |
3f23be8e AM |
1191 | + catch lists:foldl(fun(Date, Acc) -> |
1192 | + case catch copy_messages_int([FromDBMod, State#state.dbmod, VHost, Date]) of | |
1193 | + ok -> | |
1194 | + ?INFO_MSG("Copied messages at ~p (~p/~p)", [Date, Acc, DatesLength]); | |
1195 | + Value -> | |
1196 | + ?ERROR_MSG("Failed to copy messages at ~p (~p/~p): ~p", [Date, Acc, DatesLength, Value]), | |
1197 | + throw(error) | |
1198 | + end, | |
1199 | + Acc + 1 | |
1200 | + end, 1, Dates), | |
1201 | + ?INFO_MSG("copy_messages from ~p finished", [From]), | |
f7ce3e3a | 1202 | + FromDBMod:stop(VHost). |
1203 | + | |
1204 | +copy_messages_int([FromDBMod, ToDBMod, VHost, Date]) -> | |
1205 | + ets:new(mod_logdb_temp, [named_table, set, public]), | |
1206 | + {Time, Value} = timer:tc(?MODULE, copy_messages_int_tc, [[FromDBMod, ToDBMod, VHost, Date]]), | |
1207 | + ets:delete_all_objects(mod_logdb_temp), | |
1208 | + ets:delete(mod_logdb_temp), | |
1209 | + ?INFO_MSG("copy_messages at ~p elapsed ~p sec", [Date, Time/1000000]), | |
1210 | + Value. | |
1211 | + | |
1212 | +copy_messages_int_tc([FromDBMod, ToDBMod, VHost, Date]) -> | |
1213 | + ?INFO_MSG("Going to copy messages from ~p for ~p at ~p", [FromDBMod, VHost, Date]), | |
0d78319d | 1214 | + |
3f23be8e | 1215 | + ok = FromDBMod:rebuild_stats_at(VHost, Date), |
f7ce3e3a | 1216 | + catch mod_logdb:rebuild_stats_at(VHost, Date), |
3f23be8e AM |
1217 | + {ok, FromStats} = FromDBMod:get_vhost_stats_at(VHost, Date), |
1218 | + ToStats = case mod_logdb:get_vhost_stats_at(VHost, iolist_to_binary(Date)) of | |
f7ce3e3a | 1219 | + {ok, Stats} -> Stats; |
1220 | + {error, _} -> [] | |
1221 | + end, | |
1222 | + | |
1223 | + FromStatsS = lists:keysort(1, FromStats), | |
1224 | + ToStatsS = lists:keysort(1, ToStats), | |
1225 | + | |
1226 | + StatsLength = length(FromStats), | |
1227 | + | |
1228 | + CopyFun = if | |
3f23be8e AM |
1229 | + % destination table is empty |
1230 | + ToStats == [] -> | |
f7ce3e3a | 1231 | + fun({User, _Count}, Acc) -> |
3f23be8e | 1232 | + {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date), |
f7ce3e3a | 1233 | + MAcc = |
1234 | + lists:foldl(fun(Msg, MFAcc) -> | |
3f23be8e AM |
1235 | + MsgBinary = Msg#msg{owner_name=iolist_to_binary(User), |
1236 | + peer_name=iolist_to_binary(Msg#msg.peer_name), | |
1237 | + peer_server=iolist_to_binary(Msg#msg.peer_server), | |
1238 | + peer_resource=iolist_to_binary(Msg#msg.peer_resource), | |
1239 | + type=iolist_to_binary(Msg#msg.type), | |
1240 | + subject=iolist_to_binary(Msg#msg.subject), | |
1241 | + body=iolist_to_binary(Msg#msg.body)}, | |
1242 | + ok = ToDBMod:log_message(VHost, MsgBinary), | |
f7ce3e3a | 1243 | + MFAcc + 1 |
1244 | + end, 0, Msgs), | |
1245 | + NewAcc = Acc + 1, | |
1246 | + ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]), | |
1247 | + %timer:sleep(100), | |
1248 | + NewAcc | |
1249 | + end; | |
3f23be8e AM |
1250 | + % destination table is not empty |
1251 | + true -> | |
f7ce3e3a | 1252 | + fun({User, _Count}, Acc) -> |
3f23be8e | 1253 | + {ok, ToMsgs} = ToDBMod:get_user_messages_at(User, VHost, Date), |
f7ce3e3a | 1254 | + lists:foreach(fun(#msg{timestamp=Tst}) when length(Tst) == 16 -> |
1255 | + ets:insert(mod_logdb_temp, {Tst}); | |
1256 | + % mysql, pgsql removes final zeros after decimal point | |
1257 | + (#msg{timestamp=Tst}) when length(Tst) < 16 -> | |
1258 | + {F, _} = string:to_float(Tst++".0"), | |
1259 | + [T] = io_lib:format("~.5f", [F]), | |
1260 | + ets:insert(mod_logdb_temp, {T}) | |
1261 | + end, ToMsgs), | |
3f23be8e | 1262 | + {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date), |
f7ce3e3a | 1263 | + MAcc = |
1264 | + lists:foldl(fun(#msg{timestamp=ToTimestamp} = Msg, MFAcc) -> | |
1265 | + case ets:member(mod_logdb_temp, ToTimestamp) of | |
1266 | + false -> | |
3f23be8e AM |
1267 | + MsgBinary = Msg#msg{owner_name=iolist_to_binary(User), |
1268 | + peer_name=iolist_to_binary(Msg#msg.peer_name), | |
1269 | + peer_server=iolist_to_binary(Msg#msg.peer_server), | |
1270 | + peer_resource=iolist_to_binary(Msg#msg.peer_resource), | |
1271 | + type=iolist_to_binary(Msg#msg.type), | |
1272 | + subject=iolist_to_binary(Msg#msg.subject), | |
1273 | + body=iolist_to_binary(Msg#msg.body)}, | |
1274 | + ok = ToDBMod:log_message(VHost, MsgBinary), | |
f7ce3e3a | 1275 | + ets:insert(mod_logdb_temp, {ToTimestamp}), |
1276 | + MFAcc + 1; | |
1277 | + true -> | |
1278 | + MFAcc | |
1279 | + end | |
1280 | + end, 0, Msgs), | |
1281 | + NewAcc = Acc + 1, | |
1282 | + ets:delete_all_objects(mod_logdb_temp), | |
1283 | + ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]), | |
1284 | + %timer:sleep(100), | |
1285 | + NewAcc | |
3f23be8e AM |
1286 | + end |
1287 | + end, | |
f7ce3e3a | 1288 | + |
1289 | + if | |
1290 | + FromStats == [] -> | |
1291 | + ?INFO_MSG("No messages were found at ~p", [Date]); | |
1292 | + FromStatsS == ToStatsS -> | |
1293 | + ?INFO_MSG("Stats are equal at ~p", [Date]); | |
1294 | + FromStatsS /= ToStatsS -> | |
1295 | + lists:foldl(CopyFun, 0, FromStats), | |
3f23be8e | 1296 | + ok = ToDBMod:rebuild_stats_at(VHost, Date) |
f7ce3e3a | 1297 | + %timer:sleep(1000) |
1298 | + end, | |
1299 | + | |
1300 | + ok. | |
1301 | + | |
046546ef AM |
1302 | +list_to_bool(Num) when is_binary(Num) -> |
1303 | + list_to_bool(binary_to_list(Num)); | |
1304 | +list_to_bool(Num) when is_list(Num) -> | |
f7ce3e3a | 1305 | + case lists:member(Num, ["t", "true", "y", "yes", "1"]) of |
1306 | + true -> | |
1307 | + true; | |
1308 | + false -> | |
1309 | + case lists:member(Num, ["f", "false", "n", "no", "0"]) of | |
1310 | + true -> | |
1311 | + false; | |
1312 | + false -> | |
1313 | + error | |
1314 | + end | |
1315 | + end. | |
1316 | + | |
1317 | +bool_to_list(true) -> | |
1318 | + "TRUE"; | |
1319 | +bool_to_list(false) -> | |
1320 | + "FALSE". | |
1321 | + | |
1322 | +list_to_string([]) -> | |
1323 | + ""; | |
1324 | +list_to_string(List) when is_list(List) -> | |
046546ef AM |
1325 | + Str = lists:flatmap(fun(Elm) when is_binary(Elm) -> |
1326 | + binary_to_list(Elm) ++ "\n"; | |
1327 | + (Elm) when is_list(Elm) -> | |
1328 | + Elm ++ "\n" | |
1329 | + end, List), | |
f7ce3e3a | 1330 | + lists:sublist(Str, length(Str)-1). |
1331 | + | |
1332 | +string_to_list(null) -> | |
1333 | + []; | |
1334 | +string_to_list([]) -> | |
1335 | + []; | |
1336 | +string_to_list(String) -> | |
046546ef | 1337 | + ejabberd_regexp:split(iolist_to_binary(String), <<"\n">>). |
f7ce3e3a | 1338 | + |
1339 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
1340 | +% | |
1341 | +% ad-hoc (copy/pasted from mod_configure.erl) | |
1342 | +% | |
1343 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
1344 | +-define(ITEMS_RESULT(Allow, LNode, Fallback), | |
1345 | + case Allow of | |
3f23be8e | 1346 | + deny -> Fallback; |
f7ce3e3a | 1347 | + allow -> |
1348 | + case get_local_items(LServer, LNode, | |
3f23be8e AM |
1349 | + jid:encode(To), Lang) of |
1350 | + {result, Res} -> {result, Res}; | |
1351 | + {error, Error} -> {error, Error} | |
f7ce3e3a | 1352 | + end |
1353 | + end). | |
1354 | + | |
3f23be8e AM |
1355 | +get_local_items(Acc, From, #jid{lserver = LServer} = To, |
1356 | + <<"">>, Lang) -> | |
f7ce3e3a | 1357 | + case gen_mod:is_loaded(LServer, mod_adhoc) of |
3f23be8e | 1358 | + false -> Acc; |
f7ce3e3a | 1359 | + _ -> |
1360 | + Items = case Acc of | |
1361 | + {result, Its} -> Its; | |
1362 | + empty -> [] | |
1363 | + end, | |
1364 | + AllowUser = acl:match_rule(LServer, mod_logdb, From), | |
1365 | + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From), | |
1366 | + if | |
1367 | + AllowUser == allow; AllowAdmin == allow -> | |
1368 | + case get_local_items(LServer, [], | |
3f23be8e | 1369 | + jid:encode(To), Lang) of |
f7ce3e3a | 1370 | + {result, Res} -> |
1371 | + {result, Items ++ Res}; | |
1372 | + {error, _Error} -> | |
1373 | + {result, Items} | |
1374 | + end; | |
1375 | + true -> | |
1376 | + {result, Items} | |
1377 | + end | |
1378 | + end; | |
3f23be8e AM |
1379 | +get_local_items(Acc, From, #jid{lserver = LServer} = To, |
1380 | + Node, Lang) -> | |
f7ce3e3a | 1381 | + case gen_mod:is_loaded(LServer, mod_adhoc) of |
3f23be8e | 1382 | + false -> Acc; |
f7ce3e3a | 1383 | + _ -> |
3f23be8e | 1384 | + LNode = tokenize(Node), |
f7ce3e3a | 1385 | + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From), |
3f23be8e | 1386 | + Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang), |
f7ce3e3a | 1387 | + case LNode of |
046546ef | 1388 | + [<<"mod_logdb">>] -> |
3f23be8e | 1389 | + ?ITEMS_RESULT(AllowAdmin, LNode, {error, Err}); |
046546ef | 1390 | + [<<"mod_logdb_users">>] -> |
3f23be8e | 1391 | + ?ITEMS_RESULT(AllowAdmin, LNode, {error, Err}); |
046546ef | 1392 | + [<<"mod_logdb_users">>, <<$@, _/binary>>] -> |
3f23be8e | 1393 | + ?ITEMS_RESULT(AllowAdmin, LNode, {error, Err}); |
046546ef | 1394 | + [<<"mod_logdb_users">>, _User] -> |
3f23be8e | 1395 | + ?ITEMS_RESULT(AllowAdmin, LNode, {error, Err}); |
046546ef | 1396 | + [<<"mod_logdb_settings">>] -> |
3f23be8e | 1397 | + ?ITEMS_RESULT(AllowAdmin, LNode, {error, Err}); |
f7ce3e3a | 1398 | + _ -> |
1399 | + Acc | |
1400 | + end | |
1401 | + end. | |
1402 | + | |
dd02533f | 1403 | +-define(T(Lang, Text), translate:translate(Lang, Text)). |
046546ef | 1404 | + |
f7ce3e3a | 1405 | +-define(NODE(Name, Node), |
3f23be8e AM |
1406 | + #disco_item{jid = jid:make(Server), |
1407 | + node = Node, | |
dd02533f | 1408 | + name = ?T(Lang, Name)}). |
3f23be8e AM |
1409 | + |
1410 | +-define(NS_ADMINX(Sub), | |
1411 | + <<(?NS_ADMIN)/binary, "#", Sub/binary>>). | |
1412 | + | |
1413 | +tokenize(Node) -> str:tokens(Node, <<"/#">>). | |
f7ce3e3a | 1414 | + |
1415 | +get_local_items(_Host, [], Server, Lang) -> | |
1416 | + {result, | |
046546ef | 1417 | + [?NODE(<<"Messages logging engine">>, <<"mod_logdb">>)] |
f7ce3e3a | 1418 | + }; |
046546ef | 1419 | +get_local_items(_Host, [<<"mod_logdb">>], Server, Lang) -> |
f7ce3e3a | 1420 | + {result, |
046546ef AM |
1421 | + [?NODE(<<"Messages logging engine users">>, <<"mod_logdb_users">>), |
1422 | + ?NODE(<<"Messages logging engine settings">>, <<"mod_logdb_settings">>)] | |
f7ce3e3a | 1423 | + }; |
3f23be8e AM |
1424 | +get_local_items(Host, [<<"mod_logdb_users">>], Server, _Lang) -> |
1425 | + {result, get_all_vh_users(Host, Server)}; | |
046546ef | 1426 | +get_local_items(Host, [<<"mod_logdb_users">>, <<$@, Diap/binary>>], Server, Lang) -> |
3f23be8e AM |
1427 | + Users = ejabberd_auth:get_vh_registered_users(Host), |
1428 | + SUsers = lists:sort([{S, U} || {U, S} <- Users]), | |
1429 | + try | |
1430 | + [S1, S2] = ejabberd_regexp:split(Diap, <<"-">>), | |
1431 | + N1 = binary_to_integer(S1), | |
1432 | + N2 = binary_to_integer(S2), | |
1433 | + Sub = lists:sublist(SUsers, N1, N2 - N1 + 1), | |
1434 | + {result, lists:map(fun({S, U}) -> | |
1435 | + ?NODE(<< U/binary, "@", S/binary >>, | |
1436 | + << (iolist_to_binary("mod_logdb_users/"))/binary, U/binary, "@", S/binary >>) | |
1437 | + end, Sub)} | |
1438 | + catch _:_ -> | |
1439 | + xmpp:err_not_acceptable() | |
f7ce3e3a | 1440 | + end; |
046546ef | 1441 | +get_local_items(_Host, [<<"mod_logdb_users">>, _User], _Server, _Lang) -> |
f7ce3e3a | 1442 | + {result, []}; |
046546ef | 1443 | +get_local_items(_Host, [<<"mod_logdb_settings">>], _Server, _Lang) -> |
f7ce3e3a | 1444 | + {result, []}; |
1445 | +get_local_items(_Host, Item, _Server, _Lang) -> | |
1446 | + ?MYDEBUG("asked for items in ~p", [Item]), | |
3f23be8e | 1447 | + {error, xmpp:err_item_not_found()}. |
f7ce3e3a | 1448 | + |
3f23be8e | 1449 | +-define(INFO_RESULT(Allow, Feats, Lang), |
f7ce3e3a | 1450 | + case Allow of |
3f23be8e | 1451 | + deny -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)}; |
046546ef | 1452 | + allow -> {result, Feats} |
f7ce3e3a | 1453 | + end). |
1454 | + | |
3f23be8e AM |
1455 | +get_local_features(Acc, From, |
1456 | + #jid{lserver = LServer} = _To, Node, Lang) -> | |
f7ce3e3a | 1457 | + case gen_mod:is_loaded(LServer, mod_adhoc) of |
1458 | + false -> | |
1459 | + Acc; | |
1460 | + _ -> | |
3f23be8e | 1461 | + LNode = tokenize(Node), |
f7ce3e3a | 1462 | + AllowUser = acl:match_rule(LServer, mod_logdb, From), |
1463 | + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From), | |
1464 | + case LNode of | |
046546ef | 1465 | + [<<"mod_logdb">>] when AllowUser == allow; AllowAdmin == allow -> |
3f23be8e | 1466 | + ?INFO_RESULT(allow, [?NS_COMMANDS], Lang); |
046546ef | 1467 | + [<<"mod_logdb">>] -> |
3f23be8e | 1468 | + ?INFO_RESULT(deny, [?NS_COMMANDS], Lang); |
046546ef | 1469 | + [<<"mod_logdb_users">>] -> |
3f23be8e | 1470 | + ?INFO_RESULT(AllowAdmin, [], Lang); |
046546ef | 1471 | + [<<"mod_logdb_users">>, [$@ | _]] -> |
3f23be8e | 1472 | + ?INFO_RESULT(AllowAdmin, [], Lang); |
046546ef | 1473 | + [<<"mod_logdb_users">>, _User] -> |
3f23be8e | 1474 | + ?INFO_RESULT(AllowAdmin, [?NS_COMMANDS], Lang); |
046546ef | 1475 | + [<<"mod_logdb_settings">>] -> |
3f23be8e | 1476 | + ?INFO_RESULT(AllowAdmin, [?NS_COMMANDS], Lang); |
f7ce3e3a | 1477 | + [] -> |
1478 | + Acc; | |
1479 | + _ -> | |
f7ce3e3a | 1480 | + Acc |
1481 | + end | |
1482 | + end. | |
1483 | + | |
1484 | +-define(INFO_IDENTITY(Category, Type, Name, Lang), | |
dd02533f | 1485 | + [#identity{category = Category, type = Type, name = ?T(Lang, Name)}]). |
f7ce3e3a | 1486 | + |
1487 | +-define(INFO_COMMAND(Name, Lang), | |
046546ef AM |
1488 | + ?INFO_IDENTITY(<<"automation">>, <<"command-node">>, |
1489 | + Name, Lang)). | |
f7ce3e3a | 1490 | + |
1491 | +get_local_identity(Acc, _From, _To, Node, Lang) -> | |
3f23be8e | 1492 | + LNode = tokenize(Node), |
f7ce3e3a | 1493 | + case LNode of |
046546ef AM |
1494 | + [<<"mod_logdb">>] -> |
1495 | + ?INFO_COMMAND(<<"Messages logging engine">>, Lang); | |
1496 | + [<<"mod_logdb_users">>] -> | |
1497 | + ?INFO_COMMAND(<<"Messages logging engine users">>, Lang); | |
046546ef | 1498 | + [<<"mod_logdb_users">>, User] -> |
f7ce3e3a | 1499 | + ?INFO_COMMAND(User, Lang); |
046546ef AM |
1500 | + [<<"mod_logdb_settings">>] -> |
1501 | + ?INFO_COMMAND(<<"Messages logging engine settings">>, Lang); | |
f7ce3e3a | 1502 | + _ -> |
1503 | + Acc | |
1504 | + end. | |
1505 | + | |
3f23be8e AM |
1506 | +adhoc_local_items(Acc, From, |
1507 | + #jid{lserver = LServer, server = Server} = To, Lang) -> | |
1508 | + % TODO: case acl:match_rule(LServer, ???, From) of | |
f7ce3e3a | 1509 | + Items = case Acc of |
1510 | + {result, Its} -> Its; | |
1511 | + empty -> [] | |
1512 | + end, | |
3f23be8e AM |
1513 | + Nodes = recursively_get_local_items(LServer, |
1514 | + <<"">>, Server, Lang), | |
f7ce3e3a | 1515 | + Nodes1 = lists:filter( |
3f23be8e | 1516 | + fun(#disco_item{node = Nd}) -> |
f7ce3e3a | 1517 | + F = get_local_features([], From, To, Nd, Lang), |
1518 | + case F of | |
3f23be8e AM |
1519 | + {result, [?NS_COMMANDS]} -> true; |
1520 | + _ -> false | |
f7ce3e3a | 1521 | + end |
1522 | + end, Nodes), | |
1523 | + {result, Items ++ Nodes1}. | |
1524 | + | |
3f23be8e AM |
1525 | +recursively_get_local_items(_LServer, |
1526 | + <<"mod_logdb_users">>, _Server, _Lang) -> | |
f7ce3e3a | 1527 | + []; |
3f23be8e AM |
1528 | +recursively_get_local_items(LServer, |
1529 | + Node, Server, Lang) -> | |
1530 | + LNode = tokenize(Node), | |
1531 | + Items = case get_local_items(LServer, LNode, | |
1532 | + Server, Lang) of | |
1533 | + {result, Res} -> Res; | |
1534 | + {error, _Error} -> [] | |
f7ce3e3a | 1535 | + end, |
1536 | + Nodes = lists:flatten( | |
1537 | + lists:map( | |
3f23be8e AM |
1538 | + fun(#disco_item{jid = #jid{server = S}, node = Nd} = Item) -> |
1539 | + if (S /= Server) or (Nd == <<"">>) -> | |
f7ce3e3a | 1540 | + []; |
1541 | + true -> | |
3f23be8e AM |
1542 | + [Item, recursively_get_local_items( |
1543 | + LServer, Nd, Server, Lang)] | |
f7ce3e3a | 1544 | + end |
1545 | + end, Items)), | |
1546 | + Nodes. | |
1547 | + | |
1548 | +-define(COMMANDS_RESULT(Allow, From, To, Request), | |
1549 | + case Allow of | |
1550 | + deny -> | |
3f23be8e | 1551 | + {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)}; |
f7ce3e3a | 1552 | + allow -> |
1553 | + adhoc_local_commands(From, To, Request) | |
1554 | + end). | |
1555 | + | |
1556 | +adhoc_local_commands(Acc, From, #jid{lserver = LServer} = To, | |
3f23be8e AM |
1557 | + #adhoc_command{node = Node, lang = Lang} = Request) -> |
1558 | + LNode = tokenize(Node), | |
f7ce3e3a | 1559 | + AllowUser = acl:match_rule(LServer, mod_logdb, From), |
1560 | + AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From), | |
1561 | + case LNode of | |
046546ef | 1562 | + [<<"mod_logdb">>] when AllowUser == allow; AllowAdmin == allow -> |
f7ce3e3a | 1563 | + ?COMMANDS_RESULT(allow, From, To, Request); |
3f23be8e AM |
1564 | + [<<"mod_logdb_users">>, <<$@, _/binary>>] when AllowAdmin == allow -> |
1565 | + Acc; | |
046546ef | 1566 | + [<<"mod_logdb_users">>, _User] when AllowAdmin == allow -> |
f7ce3e3a | 1567 | + ?COMMANDS_RESULT(allow, From, To, Request); |
046546ef | 1568 | + [<<"mod_logdb_settings">>] when AllowAdmin == allow -> |
f7ce3e3a | 1569 | + ?COMMANDS_RESULT(allow, From, To, Request); |
1570 | + _ -> | |
1571 | + Acc | |
1572 | + end. | |
1573 | + | |
1574 | +adhoc_local_commands(From, #jid{lserver = LServer} = _To, | |
3f23be8e | 1575 | + #adhoc_command{lang = Lang, |
f7ce3e3a | 1576 | + node = Node, |
3f23be8e | 1577 | + sid = SessionID, |
f7ce3e3a | 1578 | + action = Action, |
1579 | + xdata = XData} = Request) -> | |
3f23be8e | 1580 | + LNode = tokenize(Node), |
f7ce3e3a | 1581 | + %% If the "action" attribute is not present, it is |
1582 | + %% understood as "execute". If there was no <actions/> | |
1583 | + %% element in the first response (which there isn't in our | |
1584 | + %% case), "execute" and "complete" are equivalent. | |
3f23be8e AM |
1585 | + ActionIsExecute = Action == execute orelse Action == complete, |
1586 | + if Action == cancel -> | |
f7ce3e3a | 1587 | + %% User cancels request |
3f23be8e AM |
1588 | + #adhoc_command{status = canceled, lang = Lang, |
1589 | + node = Node, sid = SessionID}; | |
1590 | + XData == undefined, ActionIsExecute -> | |
f7ce3e3a | 1591 | + %% User requests form |
3f23be8e | 1592 | + case get_form(LServer, LNode, Lang) of |
f7ce3e3a | 1593 | + {result, Form} -> |
3f23be8e | 1594 | + xmpp_util:make_adhoc_response( |
f7ce3e3a | 1595 | + Request, |
3f23be8e AM |
1596 | + #adhoc_command{status = executing, |
1597 | + xdata = Form}); | |
f7ce3e3a | 1598 | + {error, Error} -> |
1599 | + {error, Error} | |
1600 | + end; | |
3f23be8e | 1601 | + XData /= undefined, ActionIsExecute -> |
f7ce3e3a | 1602 | + %% User returns form. |
3f23be8e AM |
1603 | + case catch set_form(From, LServer, LNode, Lang, XData) of |
1604 | + {result, Res} -> | |
1605 | + xmpp_util:make_adhoc_response( | |
1606 | + Request, | |
1607 | + #adhoc_command{xdata = Res, status = completed}); | |
1608 | + {'EXIT', _} -> {error, xmpp:err_bad_request()}; | |
1609 | + {error, Error} -> {error, Error} | |
f7ce3e3a | 1610 | + end; |
046546ef | 1611 | + true -> |
3f23be8e | 1612 | + {error, xmpp:err_bad_request(<<"Unexpected action">>, Lang)} |
f7ce3e3a | 1613 | + end. |
1614 | + | |
3f23be8e AM |
1615 | +-define(TVFIELD(Type, Var, Val), |
1616 | + #xdata_field{type = Type, var = Var, values = [Val]}). | |
1617 | + | |
1618 | +-define(HFIELD(), | |
1619 | + ?TVFIELD(hidden, <<"FORM_TYPE">>, (?NS_ADMIN))). | |
f7ce3e3a | 1620 | + |
1621 | +get_user_form(LUser, LServer, Lang) -> | |
3f23be8e AM |
1622 | + ?MYDEBUG("get_user_form ~p ~p", [LUser, LServer]), |
1623 | + %From = jid:encode(jid:remove_resource(Jid)), | |
f7ce3e3a | 1624 | + #user_settings{dolog_default=DLD, |
1625 | + dolog_list=DLL, | |
1626 | + donotlog_list=DNLL} = get_user_settings(LUser, LServer), | |
3f23be8e AM |
1627 | + Fs = [ |
1628 | + #xdata_field{ | |
1629 | + type = 'list-single', | |
dd02533f | 1630 | + label = ?T(Lang, <<"Default">>), |
3f23be8e AM |
1631 | + var = <<"dolog_default">>, |
1632 | + values = [misc:atom_to_binary(DLD)], | |
dd02533f | 1633 | + options = [#xdata_option{label = ?T(Lang, <<"Log Messages">>), |
3f23be8e | 1634 | + value = <<"true">>}, |
dd02533f | 1635 | + #xdata_option{label = ?T(Lang, <<"Do Not Log Messages">>), |
3f23be8e AM |
1636 | + value = <<"false">>}]}, |
1637 | + #xdata_field{ | |
1638 | + type = 'text-multi', | |
dd02533f | 1639 | + label = ?T(Lang, <<"Log Messages">>), |
3f23be8e AM |
1640 | + var = <<"dolog_list">>, |
1641 | + values = DLL}, | |
1642 | + #xdata_field{ | |
1643 | + type = 'text-multi', | |
dd02533f | 1644 | + label = ?T(Lang, <<"Do Not Log Messages">>), |
3f23be8e AM |
1645 | + var = <<"donotlog_list">>, |
1646 | + values = DNLL} | |
1647 | + ], | |
1648 | + {result, #xdata{ | |
dd02533f | 1649 | + title = ?T(Lang, <<"Messages logging engine settings">>), |
3f23be8e | 1650 | + type = form, |
dd02533f | 1651 | + instructions = [<< (?T(Lang, <<"Set logging preferences">>))/binary, |
3f23be8e AM |
1652 | + (iolist_to_binary(": "))/binary, |
1653 | + LUser/binary, "@", LServer/binary >>], | |
1654 | + fields = [?HFIELD()| | |
1655 | + Fs]}}. | |
f7ce3e3a | 1656 | + |
1657 | +get_settings_form(Host, Lang) -> | |
3f23be8e | 1658 | + ?MYDEBUG("get_settings_form ~p ~p", [Host, Lang]), |
046546ef AM |
1659 | + #state{dbmod=_DBMod, |
1660 | + dbs=_DBs, | |
f7ce3e3a | 1661 | + dolog_default=DLD, |
1662 | + ignore_jids=IgnoreJids, | |
1663 | + groupchat=GroupChat, | |
1664 | + purge_older_days=PurgeDaysT, | |
234c6b10 | 1665 | + drop_messages_on_user_removal=MRemoval, |
f7ce3e3a | 1666 | + poll_users_settings=PollTime} = mod_logdb:get_module_settings(Host), |
1667 | + | |
f7ce3e3a | 1668 | + PurgeDays = |
1669 | + case PurgeDaysT of | |
046546ef AM |
1670 | + never -> <<"never">>; |
1671 | + Num when is_integer(Num) -> integer_to_binary(Num); | |
1672 | + _ -> <<"unknown">> | |
f7ce3e3a | 1673 | + end, |
3f23be8e AM |
1674 | + Fs = [ |
1675 | + #xdata_field{ | |
1676 | + type = 'list-single', | |
dd02533f | 1677 | + label = ?T(Lang, <<"Default">>), |
3f23be8e AM |
1678 | + var = <<"dolog_default">>, |
1679 | + values = [misc:atom_to_binary(DLD)], | |
dd02533f | 1680 | + options = [#xdata_option{label = ?T(Lang, <<"Log Messages">>), |
3f23be8e | 1681 | + value = <<"true">>}, |
dd02533f | 1682 | + #xdata_option{label = ?T(Lang, <<"Do Not Log Messages">>), |
3f23be8e AM |
1683 | + value = <<"false">>}]}, |
1684 | + #xdata_field{ | |
1685 | + type = 'list-single', | |
dd02533f | 1686 | + label = ?T(Lang, <<"Drop messages on user removal">>), |
3f23be8e AM |
1687 | + var = <<"drop_messages_on_user_removal">>, |
1688 | + values = [misc:atom_to_binary(MRemoval)], | |
dd02533f | 1689 | + options = [#xdata_option{label = ?T(Lang, <<"Drop">>), |
3f23be8e | 1690 | + value = <<"true">>}, |
dd02533f | 1691 | + #xdata_option{label = ?T(Lang, <<"Do not drop">>), |
3f23be8e AM |
1692 | + value = <<"false">>}]}, |
1693 | + #xdata_field{ | |
1694 | + type = 'list-single', | |
dd02533f | 1695 | + label = ?T(Lang, <<"Groupchat messages logging">>), |
3f23be8e AM |
1696 | + var = <<"groupchat">>, |
1697 | + values = [misc:atom_to_binary(GroupChat)], | |
dd02533f | 1698 | + options = [#xdata_option{label = ?T(Lang, <<"all">>), |
3f23be8e | 1699 | + value = <<"all">>}, |
dd02533f | 1700 | + #xdata_option{label = ?T(Lang, <<"none">>), |
3f23be8e | 1701 | + value = <<"none">>}, |
dd02533f | 1702 | + #xdata_option{label = ?T(Lang, <<"send">>), |
3f23be8e AM |
1703 | + value = <<"send">>}]}, |
1704 | + #xdata_field{ | |
1705 | + type = 'text-multi', | |
dd02533f | 1706 | + label = ?T(Lang, <<"Jids/Domains to ignore">>), |
3f23be8e AM |
1707 | + var = <<"ignore_list">>, |
1708 | + values = IgnoreJids}, | |
1709 | + #xdata_field{ | |
1710 | + type = 'text-single', | |
dd02533f | 1711 | + label = ?T(Lang, <<"Purge messages older than (days)">>), |
3f23be8e AM |
1712 | + var = <<"purge_older_days">>, |
1713 | + values = [iolist_to_binary(PurgeDays)]}, | |
1714 | + #xdata_field{ | |
1715 | + type = 'text-single', | |
dd02533f | 1716 | + label = ?T(Lang, <<"Poll users settings (seconds)">>), |
3f23be8e AM |
1717 | + var = <<"poll_users_settings">>, |
1718 | + values = [integer_to_binary(PollTime)]} | |
1719 | + ], | |
1720 | + {result, #xdata{ | |
dd02533f AM |
1721 | + title = ?T(Lang, <<"Messages logging engine settings (run-time)">>), |
1722 | + instructions = [?T(Lang, <<"Set run-time settings">>)], | |
3f23be8e AM |
1723 | + type = form, |
1724 | + fields = [?HFIELD()| | |
1725 | + Fs]}}. | |
1726 | + | |
1727 | +get_form(_Host, [<<"mod_logdb_users">>, User], Lang) -> | |
1728 | + #jid{luser=LUser, lserver=LServer} = jid:decode(User), | |
f7ce3e3a | 1729 | + get_user_form(LUser, LServer, Lang); |
3f23be8e | 1730 | +get_form(Host, [<<"mod_logdb_settings">>], Lang) -> |
f7ce3e3a | 1731 | + get_settings_form(Host, Lang); |
3f23be8e | 1732 | +get_form(_Host, Command, _Lang) -> |
f7ce3e3a | 1733 | + ?MYDEBUG("asked for form ~p", [Command]), |
3f23be8e | 1734 | + {error, xmpp:err_service_unavailable()}. |
f7ce3e3a | 1735 | + |
046546ef AM |
1736 | +check_log_list([]) -> |
1737 | + ok; | |
1738 | +check_log_list([<<>>]) -> | |
1739 | + ok; | |
f7ce3e3a | 1740 | +check_log_list([Head | Tail]) -> |
046546ef AM |
1741 | + case binary:match(Head, <<$@>>) of |
1742 | + nomatch -> throw(error); | |
1743 | + {_, _} -> ok | |
f7ce3e3a | 1744 | + end, |
1745 | + % this check for Head to be valid jid | |
3f23be8e AM |
1746 | + case catch jid:decode(Head) of |
1747 | + {'EXIT', _Reason} -> throw(error); | |
1748 | + _ -> check_log_list(Tail) | |
046546ef | 1749 | + end. |
f7ce3e3a | 1750 | + |
046546ef AM |
1751 | +check_ignore_list([]) -> |
1752 | + ok; | |
1753 | +check_ignore_list([<<>>]) -> | |
1754 | + ok; | |
1755 | +check_ignore_list([<<>> | Tail]) -> | |
1756 | + check_ignore_list(Tail); | |
f7ce3e3a | 1757 | +check_ignore_list([Head | Tail]) -> |
046546ef AM |
1758 | + case binary:match(Head, <<$@>>) of |
1759 | + {_, _} -> ok; | |
1760 | + nomatch -> throw(error) | |
f7ce3e3a | 1761 | + end, |
3f23be8e AM |
1762 | + Jid2Test = case Head of |
1763 | + << $@, _Rest/binary >> -> << "a", Head/binary >>; | |
1764 | + Jid -> Jid | |
1765 | + end, | |
f7ce3e3a | 1766 | + % this check for Head to be valid jid |
3f23be8e AM |
1767 | + case catch jid:decode(Jid2Test) of |
1768 | + {'EXIT', _Reason} -> throw(error); | |
1769 | + _ -> check_ignore_list(Tail) | |
046546ef | 1770 | + end. |
f7ce3e3a | 1771 | + |
3f23be8e AM |
1772 | +get_value(Field, XData) -> hd(get_values(Field, XData)). |
1773 | + | |
1774 | +get_values(Field, XData) -> | |
1775 | + xmpp_util:get_xdata_values(Field, XData). | |
1776 | + | |
f7ce3e3a | 1777 | +parse_users_settings(XData) -> |
3f23be8e AM |
1778 | + DLD = case get_value(<<"dolog_default">>, XData) of |
1779 | + ValueDLD when ValueDLD == <<"true">>; | |
1780 | + ValueDLD == <<"false">> -> | |
1781 | + list_to_bool(ValueDLD); | |
1782 | + _ -> throw(bad_request) | |
f7ce3e3a | 1783 | + end, |
3f23be8e AM |
1784 | + |
1785 | + ListDLL = get_values(<<"dolog_list">>, XData), | |
1786 | + DLL = case catch check_log_list(ListDLL) of | |
1787 | + ok -> ListDLL; | |
1788 | + error -> throw(bad_request) | |
1789 | + end, | |
1790 | + | |
1791 | + ListDNLL = get_values(<<"donotlog_list">>, XData), | |
1792 | + DNLL = case catch check_log_list(ListDNLL) of | |
1793 | + ok -> ListDNLL; | |
1794 | + error -> throw(bad_request) | |
1795 | + end, | |
1796 | + | |
f7ce3e3a | 1797 | + #user_settings{dolog_default=DLD, |
1798 | + dolog_list=DLL, | |
1799 | + donotlog_list=DNLL}. | |
1800 | + | |
1801 | +parse_module_settings(XData) -> | |
3f23be8e AM |
1802 | + DLD = case get_value(<<"dolog_default">>, XData) of |
1803 | + ValueDLD when ValueDLD == <<"true">>; | |
1804 | + ValueDLD == <<"false">> -> | |
1805 | + list_to_bool(ValueDLD); | |
1806 | + _ -> throw(bad_request) | |
234c6b10 | 1807 | + end, |
3f23be8e AM |
1808 | + MRemoval = case get_value(<<"drop_messages_on_user_removal">>, XData) of |
1809 | + ValueMRemoval when ValueMRemoval == <<"true">>; | |
1810 | + ValueMRemoval == <<"false">> -> | |
1811 | + list_to_bool(ValueMRemoval); | |
1812 | + _ -> throw(bad_request) | |
1813 | + end, | |
1814 | + GroupChat = case get_value(<<"groupchat">>, XData) of | |
1815 | + ValueGroupChat when ValueGroupChat == <<"none">>; | |
1816 | + ValueGroupChat == <<"all">>; | |
1817 | + ValueGroupChat == <<"send">> -> | |
1818 | + misc:binary_to_atom(ValueGroupChat); | |
1819 | + _ -> throw(bad_request) | |
f7ce3e3a | 1820 | + end, |
3f23be8e AM |
1821 | + ListIgnore = get_values(<<"ignore_list">>, XData), |
1822 | + Ignore = case catch check_ignore_list(ListIgnore) of | |
1823 | + ok -> ListIgnore; | |
1824 | + error -> throw(bad_request) | |
f7ce3e3a | 1825 | + end, |
3f23be8e AM |
1826 | + Purge = case get_value(<<"purge_older_days">>, XData) of |
1827 | + <<"never">> -> never; | |
1828 | + ValuePurge -> | |
1829 | + case catch binary_to_integer(ValuePurge) of | |
1830 | + IntValuePurge when is_integer(IntValuePurge) -> IntValuePurge; | |
1831 | + _ -> throw(bad_request) | |
1832 | + end | |
f7ce3e3a | 1833 | + end, |
3f23be8e AM |
1834 | + Poll = case catch binary_to_integer(get_value(<<"poll_users_settings">>, XData)) of |
1835 | + IntValuePoll when is_integer(IntValuePoll) -> IntValuePoll; | |
1836 | + _ -> throw(bad_request) | |
f7ce3e3a | 1837 | + end, |
1838 | + #state{dolog_default=DLD, | |
1839 | + groupchat=GroupChat, | |
1840 | + ignore_jids=Ignore, | |
1841 | + purge_older_days=Purge, | |
234c6b10 | 1842 | + drop_messages_on_user_removal=MRemoval, |
f7ce3e3a | 1843 | + poll_users_settings=Poll}. |
1844 | + | |
3f23be8e AM |
1845 | +set_form(_From, _Host, [<<"mod_logdb_users">>, User], Lang, XData) -> |
1846 | + #jid{luser=LUser, lserver=LServer} = jid:decode(User), | |
1847 | + Txt = "Parse user settings failed", | |
f7ce3e3a | 1848 | + case catch parse_users_settings(XData) of |
1849 | + bad_request -> | |
3f23be8e AM |
1850 | + ?ERROR_MSG("Failed to set user form: bad_request", []), |
1851 | + {error, xmpp:err_bad_request(Txt, Lang)}; | |
046546ef | 1852 | + {'EXIT', Reason} -> |
3f23be8e AM |
1853 | + ?ERROR_MSG("Failed to set user form ~p", [Reason]), |
1854 | + {error, xmpp:err_bad_request(Txt, Lang)}; | |
f7ce3e3a | 1855 | + UserSettings -> |
1856 | + case mod_logdb:set_user_settings(LUser, LServer, UserSettings) of | |
1857 | + ok -> | |
3f23be8e | 1858 | + {result, undefined}; |
f7ce3e3a | 1859 | + error -> |
3f23be8e | 1860 | + {error, xmpp:err_internal_server_error()} |
f7ce3e3a | 1861 | + end |
1862 | + end; | |
3f23be8e AM |
1863 | +set_form(_From, Host, [<<"mod_logdb_settings">>], Lang, XData) -> |
1864 | + Txt = "Parse module settings failed", | |
f7ce3e3a | 1865 | + case catch parse_module_settings(XData) of |
3f23be8e AM |
1866 | + bad_request -> |
1867 | + ?ERROR_MSG("Failed to set settings form: bad_request", []), | |
1868 | + {error, xmpp:err_bad_request(Txt, Lang)}; | |
046546ef | 1869 | + {'EXIT', Reason} -> |
3f23be8e AM |
1870 | + ?ERROR_MSG("Failed to set settings form ~p", [Reason]), |
1871 | + {error, xmpp:err_bad_request(Txt, Lang)}; | |
f7ce3e3a | 1872 | + Settings -> |
1873 | + case mod_logdb:set_module_settings(Host, Settings) of | |
1874 | + ok -> | |
3f23be8e | 1875 | + {result, undefined}; |
f7ce3e3a | 1876 | + error -> |
3f23be8e | 1877 | + {error, xmpp:err_internal_server_error()} |
f7ce3e3a | 1878 | + end |
1879 | + end; | |
1880 | +set_form(From, _Host, Node, _Lang, XData) -> | |
3f23be8e | 1881 | + User = jid:encode(jid:remove_resource(From)), |
f7ce3e3a | 1882 | + ?MYDEBUG("set form for ~p at ~p XData=~p", [User, Node, XData]), |
3f23be8e | 1883 | + {error, xmpp:err_service_unavailable()}. |
f7ce3e3a | 1884 | + |
3f23be8e | 1885 | +get_all_vh_users(Host, Server) -> |
f7ce3e3a | 1886 | + case catch ejabberd_auth:get_vh_registered_users(Host) of |
1887 | + {'EXIT', _Reason} -> | |
1888 | + []; | |
1889 | + Users -> | |
1890 | + SUsers = lists:sort([{S, U} || {U, S} <- Users]), | |
1891 | + case length(SUsers) of | |
1892 | + N when N =< 100 -> | |
1893 | + lists:map(fun({S, U}) -> | |
3f23be8e AM |
1894 | + #disco_item{jid = jid:make(Server), |
1895 | + node = <<"mod_logdb_users/", U/binary, $@, S/binary>>, | |
1896 | + name = << U/binary, "@", S/binary >>} | |
1897 | + end, SUsers); | |
f7ce3e3a | 1898 | + N -> |
3f23be8e | 1899 | + NParts = trunc(math:sqrt(N * 6.17999999999999993783e-1)) + 1, |
f7ce3e3a | 1900 | + M = trunc(N / NParts) + 1, |
1901 | + lists:map(fun(K) -> | |
1902 | + L = K + M - 1, | |
046546ef | 1903 | + Node = <<"@", |
3f23be8e | 1904 | + (integer_to_binary(K))/binary, |
046546ef | 1905 | + "-", |
3f23be8e | 1906 | + (integer_to_binary(L))/binary |
046546ef | 1907 | + >>, |
f7ce3e3a | 1908 | + {FS, FU} = lists:nth(K, SUsers), |
1909 | + {LS, LU} = | |
1910 | + if L < N -> lists:nth(L, SUsers); | |
1911 | + true -> lists:last(SUsers) | |
1912 | + end, | |
1913 | + Name = | |
046546ef AM |
1914 | + <<FU/binary, "@", FS/binary, |
1915 | + " -- ", | |
1916 | + LU/binary, "@", LS/binary>>, | |
3f23be8e AM |
1917 | + #disco_item{jid = jid:make(Host), |
1918 | + node = <<"mod_logdb_users/", Node/binary>>, | |
1919 | + name = Name} | |
f7ce3e3a | 1920 | + end, lists:seq(1, N, M)) |
1921 | + end | |
1922 | + end. | |
f7ce3e3a | 1923 | + |
1924 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
1925 | +% | |
234c6b10 | 1926 | +% webadmin hooks |
f7ce3e3a | 1927 | +% |
1928 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
234c6b10 | 1929 | +webadmin_menu(Acc, _Host, Lang) -> |
dd02533f | 1930 | + [{<<"messages">>, ?T(<<"Users Messages">>)} | Acc]. |
234c6b10 | 1931 | + |
1932 | +webadmin_user(Acc, User, Server, Lang) -> | |
1933 | + Sett = get_user_settings(User, Server), | |
1934 | + Log = | |
1935 | + case Sett#user_settings.dolog_default of | |
1936 | + false -> | |
046546ef | 1937 | + ?INPUTT(<<"submit">>, <<"dolog">>, <<"Log Messages">>); |
234c6b10 | 1938 | + true -> |
046546ef | 1939 | + ?INPUTT(<<"submit">>, <<"donotlog">>, <<"Do Not Log Messages">>); |
234c6b10 | 1940 | + _ -> [] |
1941 | + end, | |
046546ef | 1942 | + Acc ++ [?XE(<<"h3">>, [?ACT(<<"messages/">>, <<"Messages">>), ?C(<<" ">>), Log])]. |
f7ce3e3a | 1943 | + |
234c6b10 | 1944 | +webadmin_page(_, Host, |
046546ef | 1945 | + #request{path = [<<"messages">>], |
234c6b10 | 1946 | + q = Query, |
046546ef | 1947 | + lang = Lang}) -> |
234c6b10 | 1948 | + Res = vhost_messages_stats(Host, Query, Lang), |
1949 | + {stop, Res}; | |
1950 | +webadmin_page(_, Host, | |
046546ef | 1951 | + #request{path = [<<"messages">>, Date], |
234c6b10 | 1952 | + q = Query, |
046546ef | 1953 | + lang = Lang}) -> |
234c6b10 | 1954 | + Res = vhost_messages_stats_at(Host, Query, Lang, Date), |
1955 | + {stop, Res}; | |
1956 | +webadmin_page(_, Host, | |
046546ef | 1957 | + #request{path = [<<"user">>, U, <<"messages">>], |
234c6b10 | 1958 | + q = Query, |
1959 | + lang = Lang}) -> | |
1960 | + Res = user_messages_stats(U, Host, Query, Lang), | |
1961 | + {stop, Res}; | |
1962 | +webadmin_page(_, Host, | |
046546ef | 1963 | + #request{path = [<<"user">>, U, <<"messages">>, Date], |
234c6b10 | 1964 | + q = Query, |
1965 | + lang = Lang}) -> | |
1966 | + Res = mod_logdb:user_messages_stats_at(U, Host, Query, Lang, Date), | |
1967 | + {stop, Res}; | |
046546ef | 1968 | +webadmin_page(Acc, _Host, _R) -> Acc. |
234c6b10 | 1969 | + |
046546ef | 1970 | +user_parse_query(_, <<"dolog">>, User, Server, _Query) -> |
234c6b10 | 1971 | + Sett = get_user_settings(User, Server), |
1972 | + % TODO: check returned value | |
1973 | + set_user_settings(User, Server, Sett#user_settings{dolog_default=true}), | |
1974 | + {stop, ok}; | |
046546ef | 1975 | +user_parse_query(_, <<"donotlog">>, User, Server, _Query) -> |
234c6b10 | 1976 | + Sett = get_user_settings(User, Server), |
1977 | + % TODO: check returned value | |
1978 | + set_user_settings(User, Server, Sett#user_settings{dolog_default=false}), | |
1979 | + {stop, ok}; | |
1980 | +user_parse_query(Acc, _Action, _User, _Server, _Query) -> | |
1981 | + Acc. | |
f7ce3e3a | 1982 | + |
1983 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
1984 | +% | |
234c6b10 | 1985 | +% webadmin funcs |
f7ce3e3a | 1986 | +% |
1987 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
234c6b10 | 1988 | +vhost_messages_stats(Server, Query, Lang) -> |
1989 | + Res = case catch vhost_messages_parse_query(Server, Query) of | |
1990 | + {'EXIT', Reason} -> | |
1991 | + ?ERROR_MSG("~p", [Reason]), | |
1992 | + error; | |
1993 | + VResult -> VResult | |
1994 | + end, | |
1995 | + {Time, Value} = timer:tc(mod_logdb, get_vhost_stats, [Server]), | |
1996 | + ?INFO_MSG("get_vhost_stats(~p) elapsed ~p sec", [Server, Time/1000000]), | |
1997 | + %case get_vhost_stats(Server) of | |
1998 | + case Value of | |
1999 | + {'EXIT', CReason} -> | |
2000 | + ?ERROR_MSG("Failed to get_vhost_stats: ~p", [CReason]), | |
dd02533f | 2001 | + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))]; |
234c6b10 | 2002 | + {error, GReason} -> |
2003 | + ?ERROR_MSG("Failed to get_vhost_stats: ~p", [GReason]), | |
dd02533f | 2004 | + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))]; |
234c6b10 | 2005 | + {ok, []} -> |
dd02533f | 2006 | + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s">>), [Server])))]; |
234c6b10 | 2007 | + {ok, Dates} -> |
2008 | + Fun = fun({Date, Count}) -> | |
046546ef | 2009 | + DateBin = iolist_to_binary(Date), |
3f23be8e | 2010 | + ID = misc:encode_base64( << Server/binary, DateBin/binary >> ), |
046546ef AM |
2011 | + ?XE(<<"tr">>, |
2012 | + [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}], | |
2013 | + [?INPUT(<<"checkbox">>, <<"selected">>, ID)]), | |
2014 | + ?XE(<<"td">>, [?AC(DateBin, DateBin)]), | |
2015 | + ?XC(<<"td">>, integer_to_binary(Count)) | |
234c6b10 | 2016 | + ]) |
2017 | + end, | |
046546ef | 2018 | + |
dd02533f | 2019 | + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Logged messages for ~s">>), [Server])))] ++ |
234c6b10 | 2020 | + case Res of |
046546ef AM |
2021 | + ok -> [?CT(<<"Submitted">>), ?P]; |
2022 | + error -> [?CT(<<"Bad format">>), ?P]; | |
234c6b10 | 2023 | + nothing -> [] |
2024 | + end ++ | |
046546ef AM |
2025 | + [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], |
2026 | + [?XE(<<"table">>, | |
2027 | + [?XE(<<"thead">>, | |
2028 | + [?XE(<<"tr">>, | |
2029 | + [?X(<<"td">>), | |
2030 | + ?XCT(<<"td">>, <<"Date">>), | |
2031 | + ?XCT(<<"td">>, <<"Count">>) | |
234c6b10 | 2032 | + ])]), |
046546ef | 2033 | + ?XE(<<"tbody">>, |
234c6b10 | 2034 | + lists:map(Fun, Dates) |
2035 | + )]), | |
2036 | + ?BR, | |
046546ef | 2037 | + ?INPUTT(<<"submit">>, <<"delete">>, <<"Delete Selected">>) |
234c6b10 | 2038 | + ])] |
f7ce3e3a | 2039 | + end. |
2040 | + | |
234c6b10 | 2041 | +vhost_messages_stats_at(Server, Query, Lang, Date) -> |
2042 | + {Time, Value} = timer:tc(mod_logdb, get_vhost_stats_at, [Server, Date]), | |
2043 | + ?INFO_MSG("get_vhost_stats_at(~p,~p) elapsed ~p sec", [Server, Date, Time/1000000]), | |
2044 | + %case get_vhost_stats_at(Server, Date) of | |
2045 | + case Value of | |
2046 | + {'EXIT', CReason} -> | |
2047 | + ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [CReason]), | |
dd02533f | 2048 | + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))]; |
234c6b10 | 2049 | + {error, GReason} -> |
2050 | + ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [GReason]), | |
dd02533f | 2051 | + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))]; |
234c6b10 | 2052 | + {ok, []} -> |
dd02533f | 2053 | + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s at ~s">>), [Server, Date])))]; |
046546ef AM |
2054 | + {ok, Stats} -> |
2055 | + Res = case catch vhost_messages_at_parse_query(Server, Date, Stats, Query) of | |
234c6b10 | 2056 | + {'EXIT', Reason} -> |
2057 | + ?ERROR_MSG("~p", [Reason]), | |
2058 | + error; | |
2059 | + VResult -> VResult | |
2060 | + end, | |
2061 | + Fun = fun({User, Count}) -> | |
046546ef | 2062 | + UserBin = iolist_to_binary(User), |
3f23be8e | 2063 | + ID = misc:encode_base64( << UserBin/binary, Server/binary >> ), |
046546ef | 2064 | + ?XE(<<"tr">>, |
bb18ce72 | 2065 | + [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}], |
046546ef AM |
2066 | + [?INPUT(<<"checkbox">>, <<"selected">>, ID)]), |
2067 | + ?XE(<<"td">>, [?AC(<< <<"../user/">>/binary, UserBin/binary, <<"/messages/">>/binary, Date/binary >>, UserBin)]), | |
2068 | + ?XC(<<"td">>, integer_to_binary(Count)) | |
234c6b10 | 2069 | + ]) |
2070 | + end, | |
dd02533f | 2071 | + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Logged messages for ~s at ~s">>), [Server, Date])))] ++ |
234c6b10 | 2072 | + case Res of |
046546ef AM |
2073 | + ok -> [?CT(<<"Submitted">>), ?P]; |
2074 | + error -> [?CT(<<"Bad format">>), ?P]; | |
234c6b10 | 2075 | + nothing -> [] |
2076 | + end ++ | |
046546ef AM |
2077 | + [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], |
2078 | + [?XE(<<"table">>, | |
2079 | + [?XE(<<"thead">>, | |
2080 | + [?XE(<<"tr">>, | |
2081 | + [?X(<<"td">>), | |
2082 | + ?XCT(<<"td">>, <<"User">>), | |
2083 | + ?XCT(<<"td">>, <<"Count">>) | |
234c6b10 | 2084 | + ])]), |
046546ef AM |
2085 | + ?XE(<<"tbody">>, |
2086 | + lists:map(Fun, Stats) | |
234c6b10 | 2087 | + )]), |
2088 | + ?BR, | |
046546ef | 2089 | + ?INPUTT(<<"submit">>, <<"delete">>, <<"Delete Selected">>) |
234c6b10 | 2090 | + ])] |
2091 | + end. | |
2092 | + | |
2093 | +user_messages_stats(User, Server, Query, Lang) -> | |
3f23be8e | 2094 | + Jid = jid:encode({User, Server, ""}), |
234c6b10 | 2095 | + |
2096 | + Res = case catch user_messages_parse_query(User, Server, Query) of | |
2097 | + {'EXIT', Reason} -> | |
2098 | + ?ERROR_MSG("~p", [Reason]), | |
2099 | + error; | |
2100 | + VResult -> VResult | |
f7ce3e3a | 2101 | + end, |
234c6b10 | 2102 | + |
2103 | + {Time, Value} = timer:tc(mod_logdb, get_user_stats, [User, Server]), | |
2104 | + ?INFO_MSG("get_user_stats(~p,~p) elapsed ~p sec", [User, Server, Time/1000000]), | |
2105 | + | |
2106 | + case Value of | |
2107 | + {'EXIT', CReason} -> | |
2108 | + ?ERROR_MSG("Failed to get_user_stats: ~p", [CReason]), | |
dd02533f | 2109 | + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching days">>))]; |
234c6b10 | 2110 | + {error, GReason} -> |
2111 | + ?ERROR_MSG("Failed to get_user_stats: ~p", [GReason]), | |
dd02533f | 2112 | + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching days">>))]; |
234c6b10 | 2113 | + {ok, []} -> |
dd02533f | 2114 | + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s">>), [Jid])))]; |
234c6b10 | 2115 | + {ok, Dates} -> |
2116 | + Fun = fun({Date, Count}) -> | |
046546ef | 2117 | + DateBin = iolist_to_binary(Date), |
3f23be8e | 2118 | + ID = misc:encode_base64( << User/binary, DateBin/binary >> ), |
046546ef AM |
2119 | + ?XE(<<"tr">>, |
2120 | + [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}], | |
2121 | + [?INPUT(<<"checkbox">>, <<"selected">>, ID)]), | |
2122 | + ?XE(<<"td">>, [?AC(DateBin, DateBin)]), | |
2123 | + ?XC(<<"td">>, iolist_to_binary(integer_to_list(Count))) | |
234c6b10 | 2124 | + ]) |
234c6b10 | 2125 | + end, |
046546ef | 2126 | + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T("Logged messages for ~s"), [Jid])))] ++ |
234c6b10 | 2127 | + case Res of |
046546ef AM |
2128 | + ok -> [?CT(<<"Submitted">>), ?P]; |
2129 | + error -> [?CT(<<"Bad format">>), ?P]; | |
234c6b10 | 2130 | + nothing -> [] |
2131 | + end ++ | |
046546ef AM |
2132 | + [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], |
2133 | + [?XE(<<"table">>, | |
2134 | + [?XE(<<"thead">>, | |
2135 | + [?XE(<<"tr">>, | |
2136 | + [?X(<<"td">>), | |
2137 | + ?XCT(<<"td">>, <<"Date">>), | |
2138 | + ?XCT(<<"td">>, <<"Count">>) | |
234c6b10 | 2139 | + ])]), |
046546ef | 2140 | + ?XE(<<"tbody">>, |
234c6b10 | 2141 | + lists:map(Fun, Dates) |
2142 | + )]), | |
2143 | + ?BR, | |
046546ef | 2144 | + ?INPUTT(<<"submit">>, <<"delete">>, <<"Delete Selected">>) |
234c6b10 | 2145 | + ])] |
2146 | + end. | |
2147 | + | |
2148 | +search_user_nick(User, List) -> | |
2149 | + case lists:keysearch(User, 1, List) of | |
2150 | + {value,{User, []}} -> | |
2151 | + nothing; | |
2152 | + {value,{User, Nick}} -> | |
2153 | + Nick; | |
2154 | + false -> | |
2155 | + nothing | |
2156 | + end. | |
2157 | + | |
2158 | +user_messages_stats_at(User, Server, Query, Lang, Date) -> | |
3f23be8e | 2159 | + Jid = jid:encode({User, Server, ""}), |
234c6b10 | 2160 | + |
2161 | + {Time, Value} = timer:tc(mod_logdb, get_user_messages_at, [User, Server, Date]), | |
2162 | + ?INFO_MSG("get_user_messages_at(~p,~p,~p) elapsed ~p sec", [User, Server, Date, Time/1000000]), | |
2163 | + case Value of | |
2164 | + {'EXIT', CReason} -> | |
2165 | + ?ERROR_MSG("Failed to get_user_messages_at: ~p", [CReason]), | |
dd02533f | 2166 | + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching messages">>))]; |
234c6b10 | 2167 | + {error, GReason} -> |
2168 | + ?ERROR_MSG("Failed to get_user_messages_at: ~p", [GReason]), | |
dd02533f | 2169 | + [?XC(<<"h1">>, ?T(<<"Error occupied while fetching messages">>))]; |
234c6b10 | 2170 | + {ok, []} -> |
dd02533f | 2171 | + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s at ~s">>), [Jid, Date])))]; |
234c6b10 | 2172 | + {ok, User_messages} -> |
2173 | + Res = case catch user_messages_at_parse_query(Server, | |
046546ef AM |
2174 | + Date, |
2175 | + User_messages, | |
2176 | + Query) of | |
234c6b10 | 2177 | + {'EXIT', Reason} -> |
2178 | + ?ERROR_MSG("~p", [Reason]), | |
2179 | + error; | |
2180 | + VResult -> VResult | |
2181 | + end, | |
2182 | + | |
2183 | + UR = ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]), | |
2184 | + UserRoster = | |
2185 | + lists:map(fun(Item) -> | |
3f23be8e | 2186 | + {jid:encode(Item#roster.jid), Item#roster.name} |
046546ef | 2187 | + end, UR), |
234c6b10 | 2188 | + |
2189 | + UniqUsers = lists:foldl(fun(#msg{peer_name=PName, peer_server=PServer}, List) -> | |
2190 | + ToAdd = PName++"@"++PServer, | |
2191 | + case lists:member(ToAdd, List) of | |
2192 | + true -> List; | |
2193 | + false -> lists:append([ToAdd], List) | |
2194 | + end | |
2195 | + end, [], User_messages), | |
2196 | + | |
2197 | + % Users to filter (sublist of UniqUsers) | |
046546ef | 2198 | + CheckedUsers = case lists:keysearch(<<"filter">>, 1, Query) of |
234c6b10 | 2199 | + {value, _} -> |
2200 | + lists:filter(fun(UFUser) -> | |
3f23be8e | 2201 | + ID = misc:encode_base64(term_to_binary(UFUser)), |
046546ef | 2202 | + lists:member({<<"selected">>, ID}, Query) |
234c6b10 | 2203 | + end, UniqUsers); |
2204 | + false -> [] | |
2205 | + end, | |
2206 | + | |
2207 | + % UniqUsers in html (noone selected -> everyone selected) | |
2208 | + Users = lists:map(fun(UHUser) -> | |
3f23be8e | 2209 | + ID = misc:encode_base64(term_to_binary(UHUser)), |
234c6b10 | 2210 | + Input = case lists:member(UHUser, CheckedUsers) of |
046546ef AM |
2211 | + true -> [?INPUTC(<<"checkbox">>, <<"selected">>, ID)]; |
2212 | + false when CheckedUsers == [] -> [?INPUTC(<<"checkbox">>, <<"selected">>, ID)]; | |
2213 | + false -> [?INPUT(<<"checkbox">>, <<"selected">>, ID)] | |
234c6b10 | 2214 | + end, |
2215 | + Nick = | |
2216 | + case search_user_nick(UHUser, UserRoster) of | |
046546ef AM |
2217 | + nothing -> <<"">>; |
2218 | + N -> iolist_to_binary( " ("++ N ++")" ) | |
234c6b10 | 2219 | + end, |
046546ef AM |
2220 | + ?XE(<<"tr">>, |
2221 | + [?XE(<<"td">>, Input), | |
2222 | + ?XC(<<"td">>, iolist_to_binary(UHUser++Nick))]) | |
234c6b10 | 2223 | + end, lists:sort(UniqUsers)), |
2224 | + % Messages to show (based on Users) | |
2225 | + User_messages_filtered = case CheckedUsers of | |
2226 | + [] -> User_messages; | |
2227 | + _ -> lists:filter(fun(#msg{peer_name=PName, peer_server=PServer}) -> | |
2228 | + lists:member(PName++"@"++PServer, CheckedUsers) | |
2229 | + end, User_messages) | |
2230 | + end, | |
2231 | + | |
2232 | + Msgs_Fun = fun(#msg{timestamp=Timestamp, | |
2233 | + subject=Subject, | |
2234 | + direction=Direction, | |
2235 | + peer_name=PName, peer_server=PServer, peer_resource=PRes, | |
2236 | + type=Type, | |
2237 | + body=Body}) -> | |
046546ef AM |
2238 | + Text = case Subject of |
2239 | + "" -> iolist_to_binary(Body); | |
dd02533f | 2240 | + _ -> iolist_to_binary([binary_to_list(?T(<<"Subject">>)) ++ ": " ++ Subject ++ "\n" ++ Body]) |
046546ef | 2241 | + end, |
234c6b10 | 2242 | + Resource = case PRes of |
2243 | + [] -> []; | |
2244 | + undefined -> []; | |
2245 | + R -> "/" ++ R | |
2246 | + end, | |
2247 | + UserNick = | |
2248 | + case search_user_nick(PName++"@"++PServer, UserRoster) of | |
2249 | + nothing when PServer == Server -> | |
2250 | + PName; | |
2251 | + nothing when Type == "groupchat", Direction == from -> | |
2252 | + PName++"@"++PServer++Resource; | |
2253 | + nothing -> | |
2254 | + PName++"@"++PServer; | |
2255 | + N -> N | |
2256 | + end, | |
3f23be8e | 2257 | + ID = misc:encode_base64(term_to_binary(Timestamp)), |
046546ef AM |
2258 | + ?XE(<<"tr">>, |
2259 | + [?XE(<<"td">>, [?INPUT(<<"checkbox">>, <<"selected">>, ID)]), | |
2260 | + ?XC(<<"td">>, iolist_to_binary(convert_timestamp(Timestamp))), | |
2261 | + ?XC(<<"td">>, iolist_to_binary(atom_to_list(Direction)++": "++UserNick)), | |
2262 | + ?XE(<<"td">>, [?XC(<<"pre">>, Text)])]) | |
234c6b10 | 2263 | + end, |
2264 | + % Filtered user messages in html | |
2265 | + Msgs = lists:map(Msgs_Fun, lists:sort(User_messages_filtered)), | |
2266 | + | |
dd02533f | 2267 | + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Logged messages for ~s at ~s">>), [Jid, Date])))] ++ |
234c6b10 | 2268 | + case Res of |
046546ef AM |
2269 | + ok -> [?CT(<<"Submitted">>), ?P]; |
2270 | + error -> [?CT(<<"Bad format">>), ?P]; | |
234c6b10 | 2271 | + nothing -> [] |
2272 | + end ++ | |
046546ef AM |
2273 | + [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], |
2274 | + [?XE(<<"table">>, | |
2275 | + [?XE(<<"thead">>, | |
2276 | + [?X(<<"td">>), | |
2277 | + ?XCT(<<"td">>, <<"User">>) | |
234c6b10 | 2278 | + ] |
2279 | + ), | |
046546ef | 2280 | + ?XE(<<"tbody">>, |
234c6b10 | 2281 | + Users |
2282 | + )]), | |
046546ef | 2283 | + ?INPUTT(<<"submit">>, <<"filter">>, <<"Filter Selected">>) |
234c6b10 | 2284 | + ] ++ |
046546ef AM |
2285 | + [?XE(<<"table">>, |
2286 | + [?XE(<<"thead">>, | |
2287 | + [?XE(<<"tr">>, | |
2288 | + [?X(<<"td">>), | |
2289 | + ?XCT(<<"td">>, <<"Date, Time">>), | |
2290 | + ?XCT(<<"td">>, <<"Direction: Jid">>), | |
2291 | + ?XCT(<<"td">>, <<"Body">>) | |
234c6b10 | 2292 | + ])]), |
046546ef | 2293 | + ?XE(<<"tbody">>, |
234c6b10 | 2294 | + Msgs |
2295 | + )]), | |
046546ef | 2296 | + ?INPUTT(<<"submit">>, <<"delete">>, <<"Delete Selected">>), |
234c6b10 | 2297 | + ?BR |
2298 | + ] | |
2299 | + )] | |
2300 | + end. | |
046546ef | 2301 | diff --git a/src/mod_logdb.hrl b/src/mod_logdb.hrl |
0d78319d | 2302 | new file mode 100644 |
dd02533f | 2303 | index 0000000000..49791f4e69 |
0d78319d | 2304 | --- /dev/null |
046546ef | 2305 | +++ b/src/mod_logdb.hrl |
3f23be8e | 2306 | @@ -0,0 +1,33 @@ |
234c6b10 | 2307 | +%%%---------------------------------------------------------------------- |
2308 | +%%% File : mod_logdb.hrl | |
3f23be8e | 2309 | +%%% Author : Oleg Palij (mailto:o.palij@gmail.com) |
234c6b10 | 2310 | +%%% Purpose : |
3f23be8e | 2311 | +%%% Url : https://paleg.github.io/mod_logdb/ |
234c6b10 | 2312 | +%%%---------------------------------------------------------------------- |
2313 | + | |
2314 | +-define(logdb_debug, true). | |
2315 | + | |
2316 | +-ifdef(logdb_debug). | |
2317 | +-define(MYDEBUG(Format, Args), io:format("D(~p:~p:~p) : "++Format++"~n", | |
2318 | + [calendar:local_time(),?MODULE,?LINE]++Args)). | |
2319 | +-else. | |
2320 | +-define(MYDEBUG(_F,_A),[]). | |
2321 | +-endif. | |
2322 | + | |
2323 | +-record(msg, {timestamp, | |
2324 | + owner_name, | |
2325 | + peer_name, peer_server, peer_resource, | |
2326 | + direction, | |
2327 | + type, subject, | |
2328 | + body}). | |
2329 | + | |
2330 | +-record(user_settings, {owner_name, | |
2331 | + dolog_default, | |
2332 | + dolog_list=[], | |
2333 | + donotlog_list=[]}). | |
2334 | + | |
2335 | +-define(INPUTC(Type, Name, Value), | |
046546ef AM |
2336 | + ?XA(<<"input">>, [{<<"type">>, Type}, |
2337 | + {<<"name">>, Name}, | |
2338 | + {<<"value">>, Value}, | |
2339 | + {<<"checked">>, <<"true">>}])). | |
2340 | diff --git a/src/mod_logdb_mnesia.erl b/src/mod_logdb_mnesia.erl | |
0d78319d | 2341 | new file mode 100644 |
dd02533f | 2342 | index 0000000000..a08d5262c2 |
0d78319d | 2343 | --- /dev/null |
046546ef | 2344 | +++ b/src/mod_logdb_mnesia.erl |
a815cc6c | 2345 | @@ -0,0 +1,553 @@ |
234c6b10 | 2346 | +%%%---------------------------------------------------------------------- |
2347 | +%%% File : mod_logdb_mnesia.erl | |
3f23be8e | 2348 | +%%% Author : Oleg Palij (mailto:o.palij@gmail.com) |
234c6b10 | 2349 | +%%% Purpose : mnesia backend for mod_logdb |
3f23be8e | 2350 | +%%% Url : https://paleg.github.io/mod_logdb/ |
234c6b10 | 2351 | +%%%---------------------------------------------------------------------- |
2352 | + | |
2353 | +-module(mod_logdb_mnesia). | |
2354 | +-author('o.palij@gmail.com'). | |
2355 | + | |
2356 | +-include("mod_logdb.hrl"). | |
046546ef | 2357 | +-include("logger.hrl"). |
234c6b10 | 2358 | + |
2359 | +-behaviour(gen_logdb). | |
2360 | +-behaviour(gen_server). | |
0d78319d | 2361 | + |
234c6b10 | 2362 | +% gen_server |
2363 | +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]). | |
2364 | +% gen_mod | |
bb18ce72 | 2365 | +-export([start/2, stop/1]). |
234c6b10 | 2366 | +% gen_logdb |
2367 | +-export([log_message/2, | |
2368 | + rebuild_stats/1, | |
2369 | + rebuild_stats_at/2, | |
2370 | + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2, | |
2371 | + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3, | |
2372 | + get_dates/1, | |
2373 | + get_users_settings/1, get_user_settings/2, set_user_settings/3, | |
2374 | + drop_user/2]). | |
0d78319d | 2375 | + |
234c6b10 | 2376 | +-define(PROCNAME, mod_logdb_mnesia). |
2377 | +-define(CALL_TIMEOUT, 10000). | |
0d78319d | 2378 | + |
234c6b10 | 2379 | +-record(state, {vhost}). |
2380 | + | |
2381 | +-record(stats, {user, at, count}). | |
2382 | + | |
2383 | +prefix() -> | |
2384 | + "logdb_". | |
2385 | + | |
2386 | +suffix(VHost) -> | |
046546ef | 2387 | + "_" ++ binary_to_list(VHost). |
234c6b10 | 2388 | + |
2389 | +stats_table(VHost) -> | |
2390 | + list_to_atom(prefix() ++ "stats" ++ suffix(VHost)). | |
2391 | + | |
2392 | +table_name(VHost, Date) -> | |
2393 | + list_to_atom(prefix() ++ "messages_" ++ Date ++ suffix(VHost)). | |
2394 | + | |
2395 | +settings_table(VHost) -> | |
2396 | + list_to_atom(prefix() ++ "settings" ++ suffix(VHost)). | |
2397 | + | |
2398 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
2399 | +% | |
2400 | +% gen_mod callbacks | |
2401 | +% | |
2402 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
2403 | +start(VHost, Opts) -> | |
2404 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
2405 | + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []). | |
2406 | + | |
2407 | +stop(VHost) -> | |
2408 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
2409 | + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT). | |
2410 | + | |
2411 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
2412 | +% | |
2413 | +% gen_server callbacks | |
2414 | +% | |
2415 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
2416 | +init([VHost, _Opts]) -> | |
2417 | + case mnesia:system_info(is_running) of | |
2418 | + yes -> | |
2419 | + ok = create_stats_table(VHost), | |
2420 | + ok = create_settings_table(VHost), | |
2421 | + {ok, #state{vhost=VHost}}; | |
2422 | + no -> | |
2423 | + ?ERROR_MSG("Mnesia not running", []), | |
2424 | + {stop, db_connection_failed}; | |
2425 | + Status -> | |
2426 | + ?ERROR_MSG("Mnesia status: ~p", [Status]), | |
2427 | + {stop, db_connection_failed} | |
2428 | + end. | |
2429 | + | |
2430 | +handle_call({log_message, Msg}, _From, #state{vhost=VHost}=State) -> | |
2431 | + {reply, log_message_int(VHost, Msg), State}; | |
2432 | +handle_call({rebuild_stats}, _From, #state{vhost=VHost}=State) -> | |
2433 | + {atomic, ok} = delete_nonexistent_stats(VHost), | |
2434 | + Reply = | |
2435 | + lists:foreach(fun(Date) -> | |
2436 | + rebuild_stats_at_int(VHost, Date) | |
2437 | + end, get_dates_int(VHost)), | |
f7ce3e3a | 2438 | + {reply, Reply, State}; |
234c6b10 | 2439 | +handle_call({rebuild_stats_at, Date}, _From, #state{vhost=VHost}=State) -> |
2440 | + Reply = rebuild_stats_at_int(VHost, Date), | |
0d78319d | 2441 | + {reply, Reply, State}; |
234c6b10 | 2442 | +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{vhost=VHost}=State) -> |
f7ce3e3a | 2443 | + Table = table_name(VHost, Date), |
234c6b10 | 2444 | + Fun = fun() -> |
2445 | + lists:foreach( | |
2446 | + fun(Msg) -> | |
2447 | + mnesia:write_lock_table(stats_table(VHost)), | |
2448 | + mnesia:write_lock_table(Table), | |
2449 | + mnesia:delete_object(Table, Msg, write) | |
2450 | + end, Msgs) | |
2451 | + end, | |
2452 | + DRez = case mnesia:transaction(Fun) of | |
f7ce3e3a | 2453 | + {aborted, Reason} -> |
234c6b10 | 2454 | + ?ERROR_MSG("Failed to delete_messages_by_user_at at ~p for ~p: ~p", [Date, VHost, Reason]), |
f7ce3e3a | 2455 | + error; |
2456 | + _ -> | |
2457 | + ok | |
234c6b10 | 2458 | + end, |
f7ce3e3a | 2459 | + Reply = |
2460 | + case rebuild_stats_at_int(VHost, Date) of | |
2461 | + error -> | |
2462 | + error; | |
2463 | + ok -> | |
2464 | + DRez | |
2465 | + end, | |
2466 | + {reply, Reply, State}; | |
234c6b10 | 2467 | +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{vhost=VHost}=State) -> |
2468 | + {reply, delete_all_messages_by_user_at_int(User, VHost, Date), State}; | |
f7ce3e3a | 2469 | +handle_call({delete_messages_at, Date}, _From, #state{vhost=VHost}=State) -> |
2470 | + Reply = | |
2471 | + case mnesia:delete_table(table_name(VHost, Date)) of | |
2472 | + {atomic, ok} -> | |
2473 | + delete_stats_by_vhost_at_int(VHost, Date); | |
2474 | + {aborted, Reason} -> | |
2475 | + ?ERROR_MSG("Failed to delete_messages_at for ~p at ~p", [VHost, Date, Reason]), | |
2476 | + error | |
2477 | + end, | |
2478 | + {reply, Reply, State}; | |
2479 | +handle_call({get_vhost_stats}, _From, #state{vhost=VHost}=State) -> | |
2480 | + Fun = fun(#stats{at=Date, count=Count}, Stats) -> | |
2481 | + case lists:keysearch(Date, 1, Stats) of | |
2482 | + false -> | |
2483 | + lists:append(Stats, [{Date, Count}]); | |
2484 | + {value, {_, TempCount}} -> | |
2485 | + lists:keyreplace(Date, 1, Stats, {Date, TempCount+Count}) | |
2486 | + end | |
2487 | + end, | |
2488 | + Reply = | |
2489 | + case mnesia:transaction(fun() -> | |
2490 | + mnesia:foldl(Fun, [], stats_table(VHost)) | |
2491 | + end) of | |
2492 | + {atomic, Result} -> {ok, mod_logdb:sort_stats(Result)}; | |
2493 | + {aborted, Reason} -> {error, Reason} | |
2494 | + end, | |
2495 | + {reply, Reply, State}; | |
2496 | +handle_call({get_vhost_stats_at, Date}, _From, #state{vhost=VHost}=State) -> | |
2497 | + Fun = fun() -> | |
2498 | + Pat = #stats{user='$1', at=Date, count='$2'}, | |
2499 | + mnesia:select(stats_table(VHost), [{Pat, [], [['$1', '$2']]}]) | |
2500 | + end, | |
2501 | + Reply = | |
2502 | + case mnesia:transaction(Fun) of | |
2503 | + {atomic, Result} -> | |
2504 | + {ok, lists:reverse(lists:keysort(2, [{User, Count} || [User, Count] <- Result]))}; | |
2505 | + {aborted, Reason} -> | |
2506 | + {error, Reason} | |
2507 | + end, | |
2508 | + {reply, Reply, State}; | |
2509 | +handle_call({get_user_stats, User}, _From, #state{vhost=VHost}=State) -> | |
234c6b10 | 2510 | + {reply, get_user_stats_int(User, VHost), State}; |
f7ce3e3a | 2511 | +handle_call({get_user_messages_at, User, Date}, _From, #state{vhost=VHost}=State) -> |
2512 | + Reply = | |
2513 | + case mnesia:transaction(fun() -> | |
2514 | + Pat = #msg{owner_name=User, _='_'}, | |
2515 | + mnesia:select(table_name(VHost, Date), | |
2516 | + [{Pat, [], ['$_']}]) | |
2517 | + end) of | |
2518 | + {atomic, Result} -> {ok, Result}; | |
2519 | + {aborted, Reason} -> | |
2520 | + {error, Reason} | |
2521 | + end, | |
2522 | + {reply, Reply, State}; | |
2523 | +handle_call({get_dates}, _From, #state{vhost=VHost}=State) -> | |
2524 | + {reply, get_dates_int(VHost), State}; | |
2525 | +handle_call({get_users_settings}, _From, #state{vhost=VHost}=State) -> | |
2526 | + Reply = mnesia:dirty_match_object(settings_table(VHost), #user_settings{_='_'}), | |
2527 | + {reply, {ok, Reply}, State}; | |
2528 | +handle_call({get_user_settings, User}, _From, #state{vhost=VHost}=State) -> | |
2529 | + Reply = | |
2530 | + case mnesia:dirty_match_object(settings_table(VHost), #user_settings{owner_name=User, _='_'}) of | |
2531 | + [] -> []; | |
2532 | + [Setting] -> | |
2533 | + Setting | |
2534 | + end, | |
2535 | + {reply, Reply, State}; | |
2536 | +handle_call({set_user_settings, _User, Set}, _From, #state{vhost=VHost}=State) -> | |
2537 | + ?MYDEBUG("~p~n~p", [settings_table(VHost), Set]), | |
2538 | + Reply = mnesia:dirty_write(settings_table(VHost), Set), | |
234c6b10 | 2539 | + ?MYDEBUG("~p", [Reply]), |
2540 | + {reply, Reply, State}; | |
2541 | +handle_call({drop_user, User}, _From, #state{vhost=VHost}=State) -> | |
2542 | + {ok, Dates} = get_user_stats_int(User, VHost), | |
2543 | + MDResult = lists:map(fun({Date, _}) -> | |
2544 | + delete_all_messages_by_user_at_int(User, VHost, Date) | |
2545 | + end, Dates), | |
2546 | + SDResult = delete_user_settings_int(User, VHost), | |
2547 | + Reply = | |
2548 | + case lists:all(fun(Result) when Result == ok -> | |
2549 | + true; | |
2550 | + (Result) when Result == error -> | |
2551 | + false | |
2552 | + end, lists:append(MDResult, [SDResult])) of | |
2553 | + true -> | |
2554 | + ok; | |
2555 | + false -> | |
2556 | + error | |
2557 | + end, | |
f7ce3e3a | 2558 | + {reply, Reply, State}; |
2559 | +handle_call({stop}, _From, State) -> | |
2560 | + {stop, normal, ok, State}; | |
2561 | +handle_call(Msg, _From, State) -> | |
2562 | + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]), | |
2563 | + {noreply, State}. | |
2564 | + | |
2565 | +handle_cast(Msg, State) -> | |
2566 | + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]), | |
2567 | + {noreply, State}. | |
2568 | + | |
2569 | +handle_info(Info, State) -> | |
2570 | + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]), | |
2571 | + {noreply, State}. | |
2572 | + | |
2573 | +terminate(_Reason, _State) -> | |
2574 | + ok. | |
2575 | + | |
2576 | +code_change(_OldVsn, State, _Extra) -> | |
2577 | + {ok, State}. | |
2578 | + | |
2579 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
2580 | +% | |
2581 | +% gen_logdb callbacks | |
2582 | +% | |
2583 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
2584 | +log_message(VHost, Msg) -> | |
2585 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
2586 | + gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT). | |
2587 | +rebuild_stats(VHost) -> | |
2588 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
2589 | + gen_server:call(Proc, {rebuild_stats}, ?CALL_TIMEOUT). | |
2590 | +rebuild_stats_at(VHost, Date) -> | |
2591 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
2592 | + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT). | |
2593 | +delete_messages_by_user_at(VHost, Msgs, Date) -> | |
2594 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
2595 | + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT). | |
2596 | +delete_all_messages_by_user_at(User, VHost, Date) -> | |
2597 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
2598 | + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT). | |
2599 | +delete_messages_at(VHost, Date) -> | |
2600 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
2601 | + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT). | |
2602 | +get_vhost_stats(VHost) -> | |
2603 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
2604 | + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT). | |
2605 | +get_vhost_stats_at(VHost, Date) -> | |
2606 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
2607 | + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT). | |
2608 | +get_user_stats(User, VHost) -> | |
2609 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
2610 | + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT). | |
2611 | +get_user_messages_at(User, VHost, Date) -> | |
2612 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
2613 | + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT). | |
2614 | +get_dates(VHost) -> | |
2615 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
2616 | + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT). | |
2617 | +get_user_settings(User, VHost) -> | |
2618 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
2619 | + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT). | |
2620 | +get_users_settings(VHost) -> | |
2621 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
2622 | + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT). | |
2623 | +set_user_settings(User, VHost, Set) -> | |
2624 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
2625 | + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT). | |
234c6b10 | 2626 | +drop_user(User, VHost) -> |
2627 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
2628 | + gen_server:call(Proc, {drop_user, User}, ?CALL_TIMEOUT). | |
f7ce3e3a | 2629 | + |
2630 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
2631 | +% | |
2632 | +% internals | |
2633 | +% | |
2634 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
046546ef | 2635 | +log_message_int(VHost, #msg{timestamp=Timestamp}=MsgBin) -> |
f7ce3e3a | 2636 | + Date = mod_logdb:convert_timestamp_brief(Timestamp), |
2637 | + | |
046546ef AM |
2638 | + Msg = #msg{timestamp = MsgBin#msg.timestamp, |
2639 | + owner_name = binary_to_list(MsgBin#msg.owner_name), | |
2640 | + peer_name = binary_to_list(MsgBin#msg.peer_name), | |
2641 | + peer_server = binary_to_list(MsgBin#msg.peer_server), | |
2642 | + peer_resource = binary_to_list(MsgBin#msg.peer_resource), | |
2643 | + direction = MsgBin#msg.direction, | |
2644 | + type = binary_to_list(MsgBin#msg.type), | |
2645 | + subject = binary_to_list(MsgBin#msg.subject), | |
2646 | + body = binary_to_list(MsgBin#msg.body)}, | |
2647 | + | |
f7ce3e3a | 2648 | + ATable = table_name(VHost, Date), |
2649 | + Fun = fun() -> | |
2650 | + mnesia:write_lock_table(ATable), | |
2651 | + mnesia:write(ATable, Msg, write) | |
2652 | + end, | |
2653 | + % log message, increment stats for both users | |
2654 | + case mnesia:transaction(Fun) of | |
2655 | + % if table does not exists - create it and try to log message again | |
2656 | + {aborted,{no_exists, _Table}} -> | |
2657 | + case create_msg_table(VHost, Date) of | |
2658 | + {aborted, CReason} -> | |
2659 | + ?ERROR_MSG("Failed to log message: ~p", [CReason]), | |
2660 | + error; | |
2661 | + {atomic, ok} -> | |
046546ef AM |
2662 | + ?MYDEBUG("Created msg table for ~s at ~s", [VHost, Date]), |
2663 | + log_message_int(VHost, MsgBin) | |
f7ce3e3a | 2664 | + end; |
2665 | + {aborted, TReason} -> | |
2666 | + ?ERROR_MSG("Failed to log message: ~p", [TReason]), | |
2667 | + error; | |
2668 | + {atomic, _} -> | |
046546ef AM |
2669 | + ?MYDEBUG("Logged ok for ~s, peer: ~s", [ [Msg#msg.owner_name, <<"@">>, VHost], |
2670 | + [Msg#msg.peer_name, <<"@">>, Msg#msg.peer_server] ]), | |
f7ce3e3a | 2671 | + increment_user_stats(Msg#msg.owner_name, VHost, Date) |
2672 | + end. | |
2673 | + | |
2674 | +increment_user_stats(Owner, VHost, Date) -> | |
2675 | + Fun = fun() -> | |
2676 | + Pat = #stats{user=Owner, at=Date, count='$1'}, | |
2677 | + mnesia:write_lock_table(stats_table(VHost)), | |
2678 | + case mnesia:select(stats_table(VHost), [{Pat, [], ['$_']}]) of | |
2679 | + [] -> | |
2680 | + mnesia:write(stats_table(VHost), | |
2681 | + #stats{user=Owner, | |
2682 | + at=Date, | |
2683 | + count=1}, | |
2684 | + write); | |
2685 | + [Stats] -> | |
2686 | + mnesia:delete_object(stats_table(VHost), | |
2687 | + #stats{user=Owner, | |
2688 | + at=Date, | |
2689 | + count=Stats#stats.count}, | |
2690 | + write), | |
2691 | + New = Stats#stats{count = Stats#stats.count+1}, | |
2692 | + if | |
2693 | + New#stats.count > 0 -> mnesia:write(stats_table(VHost), | |
2694 | + New, | |
2695 | + write); | |
2696 | + true -> ok | |
2697 | + end | |
2698 | + end | |
2699 | + end, | |
2700 | + case mnesia:transaction(Fun) of | |
2701 | + {aborted, Reason} -> | |
2702 | + ?ERROR_MSG("Failed to update stats for ~s@~s: ~p", [Owner, VHost, Reason]), | |
2703 | + error; | |
2704 | + {atomic, _} -> | |
2705 | + ?MYDEBUG("Updated stats for ~s@~s", [Owner, VHost]), | |
2706 | + ok | |
2707 | + end. | |
2708 | + | |
2709 | +get_dates_int(VHost) -> | |
2710 | + Tables = mnesia:system_info(tables), | |
2711 | + lists:foldl(fun(ATable, Dates) -> | |
046546ef AM |
2712 | + Table = term_to_binary(ATable), |
2713 | + case ejabberd_regexp:run( Table, << VHost/binary, <<"$">>/binary >> ) of | |
0d78319d | 2714 | + match -> |
3f23be8e | 2715 | + case re:run(Table, "[0-9]+-[0-9]+-[0-9]+") of |
0d78319d | 2716 | + {match, [{S, E}]} -> |
3f23be8e | 2717 | + lists:append(Dates, [lists:sublist(binary_to_list(Table), S+1, E)]); |
f7ce3e3a | 2718 | + nomatch -> |
2719 | + Dates | |
2720 | + end; | |
2721 | + nomatch -> | |
2722 | + Dates | |
2723 | + end | |
2724 | + end, [], Tables). | |
2725 | + | |
2726 | +rebuild_stats_at_int(VHost, Date) -> | |
2727 | + Table = table_name(VHost, Date), | |
2728 | + STable = stats_table(VHost), | |
2729 | + CFun = fun(Msg, Stats) -> | |
2730 | + Owner = Msg#msg.owner_name, | |
2731 | + case lists:keysearch(Owner, 1, Stats) of | |
2732 | + {value, {_, Count}} -> | |
2733 | + lists:keyreplace(Owner, 1, Stats, {Owner, Count + 1}); | |
2734 | + false -> | |
2735 | + lists:append(Stats, [{Owner, 1}]) | |
2736 | + end | |
2737 | + end, | |
2738 | + DFun = fun(#stats{at=SDate} = Stat, _Acc) | |
2739 | + when SDate == Date -> | |
2740 | + mnesia:delete_object(stats_table(VHost), Stat, write); | |
2741 | + (_Stat, _Acc) -> ok | |
2742 | + end, | |
2743 | + % TODO: Maybe unregister hooks ? | |
2744 | + case mnesia:transaction(fun() -> | |
2745 | + mnesia:write_lock_table(Table), | |
2746 | + mnesia:write_lock_table(STable), | |
046546ef AM |
2747 | + % Delete all stats for VHost at Date |
2748 | + mnesia:foldl(DFun, [], STable), | |
f7ce3e3a | 2749 | + % Calc stats for VHost at Date |
2750 | + case mnesia:foldl(CFun, [], Table) of | |
2751 | + [] -> empty; | |
2752 | + AStats -> | |
f7ce3e3a | 2753 | + % Write new calc'ed stats |
2754 | + lists:foreach(fun({Owner, Count}) -> | |
2755 | + WStat = #stats{user=Owner, at=Date, count=Count}, | |
2756 | + mnesia:write(stats_table(VHost), WStat, write) | |
2757 | + end, AStats), | |
2758 | + ok | |
2759 | + end | |
2760 | + end) of | |
2761 | + {aborted, Reason} -> | |
2762 | + ?ERROR_MSG("Failed to rebuild_stats_at for ~p at ~p: ~p", [VHost, Date, Reason]), | |
2763 | + error; | |
2764 | + {atomic, ok} -> | |
2765 | + ok; | |
2766 | + {atomic, empty} -> | |
2767 | + {atomic,ok} = mnesia:delete_table(Table), | |
2768 | + ?MYDEBUG("Dropped table at ~p", [Date]), | |
2769 | + ok | |
2770 | + end. | |
2771 | + | |
2772 | +delete_nonexistent_stats(VHost) -> | |
2773 | + Dates = get_dates_int(VHost), | |
2774 | + mnesia:transaction(fun() -> | |
2775 | + mnesia:foldl(fun(#stats{at=Date} = Stat, _Acc) -> | |
2776 | + case lists:member(Date, Dates) of | |
2777 | + false -> mnesia:delete_object(Stat); | |
2778 | + true -> ok | |
2779 | + end | |
2780 | + end, ok, stats_table(VHost)) | |
2781 | + end). | |
2782 | + | |
2783 | +delete_stats_by_vhost_at_int(VHost, Date) -> | |
2784 | + StatsDelete = fun(#stats{at=SDate} = Stat, _Acc) | |
2785 | + when SDate == Date -> | |
2786 | + mnesia:delete_object(stats_table(VHost), Stat, write), | |
2787 | + ok; | |
2788 | + (_Msg, _Acc) -> ok | |
2789 | + end, | |
2790 | + case mnesia:transaction(fun() -> | |
2791 | + mnesia:write_lock_table(stats_table(VHost)), | |
2792 | + mnesia:foldl(StatsDelete, ok, stats_table(VHost)) | |
2793 | + end) of | |
2794 | + {aborted, Reason} -> | |
2795 | + ?ERROR_MSG("Failed to update stats at ~p for ~p: ~p", [Date, VHost, Reason]), | |
2796 | + rebuild_stats_at_int(VHost, Date); | |
2797 | + _ -> | |
2798 | + ?INFO_MSG("Updated stats at ~p for ~p", [Date, VHost]), | |
2799 | + ok | |
2800 | + end. | |
2801 | + | |
234c6b10 | 2802 | +get_user_stats_int(User, VHost) -> |
2803 | + case mnesia:transaction(fun() -> | |
2804 | + Pat = #stats{user=User, at='$1', count='$2'}, | |
2805 | + mnesia:select(stats_table(VHost), [{Pat, [], [['$1', '$2']]}]) | |
2806 | + end) of | |
2807 | + {atomic, Result} -> | |
2808 | + {ok, mod_logdb:sort_stats([{Date, Count} || [Date, Count] <- Result])}; | |
2809 | + {aborted, Reason} -> | |
2810 | + {error, Reason} | |
2811 | + end. | |
2812 | + | |
2813 | +delete_all_messages_by_user_at_int(User, VHost, Date) -> | |
2814 | + Table = table_name(VHost, Date), | |
2815 | + MsgDelete = fun(#msg{owner_name=Owner} = Msg, _Acc) | |
2816 | + when Owner == User -> | |
2817 | + mnesia:delete_object(Table, Msg, write), | |
2818 | + ok; | |
2819 | + (_Msg, _Acc) -> ok | |
2820 | + end, | |
2821 | + DRez = case mnesia:transaction(fun() -> | |
2822 | + mnesia:foldl(MsgDelete, ok, Table) | |
2823 | + end) of | |
2824 | + {aborted, Reason} -> | |
2825 | + ?ERROR_MSG("Failed to delete_all_messages_by_user_at for ~p@~p at ~p: ~p", [User, VHost, Date, Reason]), | |
2826 | + error; | |
2827 | + _ -> | |
2828 | + ok | |
2829 | + end, | |
2830 | + case rebuild_stats_at_int(VHost, Date) of | |
2831 | + error -> | |
2832 | + error; | |
2833 | + ok -> | |
2834 | + DRez | |
2835 | + end. | |
2836 | + | |
2837 | +delete_user_settings_int(User, VHost) -> | |
2838 | + STable = settings_table(VHost), | |
2839 | + case mnesia:dirty_match_object(STable, #user_settings{owner_name=User, _='_'}) of | |
2840 | + [] -> | |
2841 | + ok; | |
2842 | + [UserSettings] -> | |
2843 | + mnesia:dirty_delete_object(STable, UserSettings) | |
2844 | + end. | |
2845 | + | |
f7ce3e3a | 2846 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
2847 | +% | |
2848 | +% tables internals | |
2849 | +% | |
2850 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
2851 | +create_stats_table(VHost) -> | |
2852 | + SName = stats_table(VHost), | |
2853 | + case mnesia:create_table(SName, | |
2854 | + [{disc_only_copies, [node()]}, | |
2855 | + {type, bag}, | |
2856 | + {attributes, record_info(fields, stats)}, | |
2857 | + {record_name, stats} | |
2858 | + ]) of | |
2859 | + {atomic, ok} -> | |
2860 | + ?MYDEBUG("Created stats table for ~p", [VHost]), | |
2861 | + lists:foreach(fun(Date) -> | |
2862 | + rebuild_stats_at_int(VHost, Date) | |
2863 | + end, get_dates_int(VHost)), | |
2864 | + ok; | |
2865 | + {aborted, {already_exists, _}} -> | |
2866 | + ?MYDEBUG("Stats table for ~p already exists", [VHost]), | |
2867 | + ok; | |
2868 | + {aborted, Reason} -> | |
2869 | + ?ERROR_MSG("Failed to create stats table: ~p", [Reason]), | |
2870 | + error | |
2871 | + end. | |
2872 | + | |
2873 | +create_settings_table(VHost) -> | |
2874 | + SName = settings_table(VHost), | |
2875 | + case mnesia:create_table(SName, | |
2876 | + [{disc_copies, [node()]}, | |
2877 | + {type, set}, | |
2878 | + {attributes, record_info(fields, user_settings)}, | |
2879 | + {record_name, user_settings} | |
2880 | + ]) of | |
2881 | + {atomic, ok} -> | |
2882 | + ?MYDEBUG("Created settings table for ~p", [VHost]), | |
2883 | + ok; | |
2884 | + {aborted, {already_exists, _}} -> | |
2885 | + ?MYDEBUG("Settings table for ~p already exists", [VHost]), | |
2886 | + ok; | |
2887 | + {aborted, Reason} -> | |
2888 | + ?ERROR_MSG("Failed to create settings table: ~p", [Reason]), | |
2889 | + error | |
2890 | + end. | |
2891 | + | |
2892 | +create_msg_table(VHost, Date) -> | |
2893 | + mnesia:create_table( | |
2894 | + table_name(VHost, Date), | |
2895 | + [{disc_only_copies, [node()]}, | |
2896 | + {type, bag}, | |
2897 | + {attributes, record_info(fields, msg)}, | |
2898 | + {record_name, msg}]). | |
046546ef | 2899 | diff --git a/src/mod_logdb_mysql.erl b/src/mod_logdb_mysql.erl |
0d78319d | 2900 | new file mode 100644 |
dd02533f | 2901 | index 0000000000..21d65e6578 |
0d78319d | 2902 | --- /dev/null |
046546ef | 2903 | +++ b/src/mod_logdb_mysql.erl |
a815cc6c | 2904 | @@ -0,0 +1,1050 @@ |
0d78319d AM |
2905 | +%%%---------------------------------------------------------------------- |
2906 | +%%% File : mod_logdb_mysql.erl | |
3f23be8e | 2907 | +%%% Author : Oleg Palij (mailto:o.palij@gmail.com) |
0d78319d | 2908 | +%%% Purpose : MySQL backend for mod_logdb |
3f23be8e | 2909 | +%%% Url : https://paleg.github.io/mod_logdb/ |
0d78319d AM |
2910 | +%%%---------------------------------------------------------------------- |
2911 | + | |
2912 | +-module(mod_logdb_mysql). | |
2913 | +-author('o.palij@gmail.com'). | |
2914 | + | |
2915 | +-include("mod_logdb.hrl"). | |
046546ef | 2916 | +-include("logger.hrl"). |
0d78319d AM |
2917 | + |
2918 | +-behaviour(gen_logdb). | |
2919 | +-behaviour(gen_server). | |
2920 | + | |
2921 | +% gen_server | |
2922 | +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]). | |
2923 | +% gen_mod | |
bb18ce72 | 2924 | +-export([start/2, stop/1]). |
0d78319d AM |
2925 | +% gen_logdb |
2926 | +-export([log_message/2, | |
2927 | + rebuild_stats/1, | |
2928 | + rebuild_stats_at/2, | |
2929 | + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2, | |
2930 | + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3, | |
2931 | + get_dates/1, | |
2932 | + get_users_settings/1, get_user_settings/2, set_user_settings/3, | |
2933 | + drop_user/2]). | |
2934 | + | |
2935 | +% gen_server call timeout | |
2936 | +-define(CALL_TIMEOUT, 30000). | |
2937 | +-define(MYSQL_TIMEOUT, 60000). | |
2938 | +-define(INDEX_SIZE, integer_to_list(170)). | |
2939 | +-define(PROCNAME, mod_logdb_mysql). | |
2940 | + | |
2941 | +-import(mod_logdb, [list_to_bool/1, bool_to_list/1, | |
2942 | + list_to_string/1, string_to_list/1, | |
2943 | + convert_timestamp_brief/1]). | |
2944 | + | |
2945 | +-record(state, {dbref, vhost, server, port, db, user, password}). | |
2946 | + | |
2947 | +% replace "." with "_" | |
2948 | +escape_vhost(VHost) -> lists:map(fun(46) -> 95; | |
2949 | + (A) -> A | |
046546ef | 2950 | + end, binary_to_list(VHost)). |
0d78319d AM |
2951 | +prefix() -> |
2952 | + "`logdb_". | |
2953 | + | |
2954 | +suffix(VHost) -> | |
2955 | + "_" ++ escape_vhost(VHost) ++ "`". | |
2956 | + | |
2957 | +messages_table(VHost, Date) -> | |
2958 | + prefix() ++ "messages_" ++ Date ++ suffix(VHost). | |
2959 | + | |
2960 | +stats_table(VHost) -> | |
2961 | + prefix() ++ "stats" ++ suffix(VHost). | |
2962 | + | |
2963 | +temp_table(VHost) -> | |
2964 | + prefix() ++ "temp" ++ suffix(VHost). | |
2965 | + | |
2966 | +settings_table(VHost) -> | |
2967 | + prefix() ++ "settings" ++ suffix(VHost). | |
2968 | + | |
2969 | +users_table(VHost) -> | |
2970 | + prefix() ++ "users" ++ suffix(VHost). | |
2971 | +servers_table(VHost) -> | |
2972 | + prefix() ++ "servers" ++ suffix(VHost). | |
2973 | +resources_table(VHost) -> | |
2974 | + prefix() ++ "resources" ++ suffix(VHost). | |
2975 | + | |
046546ef AM |
2976 | +ets_users_table(VHost) -> list_to_atom("logdb_users_" ++ binary_to_list(VHost)). |
2977 | +ets_servers_table(VHost) -> list_to_atom("logdb_servers_" ++ binary_to_list(VHost)). | |
2978 | +ets_resources_table(VHost) -> list_to_atom("logdb_resources_" ++ binary_to_list(VHost)). | |
0d78319d AM |
2979 | + |
2980 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
2981 | +% | |
2982 | +% gen_mod callbacks | |
2983 | +% | |
2984 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
2985 | +start(VHost, Opts) -> | |
2986 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
2987 | + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []). | |
2988 | + | |
2989 | +stop(VHost) -> | |
2990 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
2991 | + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT). | |
2992 | + | |
2993 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
2994 | +% | |
2995 | +% gen_server callbacks | |
2996 | +% | |
2997 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
2998 | +init([VHost, Opts]) -> | |
2999 | + crypto:start(), | |
3000 | + | |
046546ef AM |
3001 | + Server = gen_mod:get_opt(server, Opts, fun(A) -> A end, <<"localhost">>), |
3002 | + Port = gen_mod:get_opt(port, Opts, fun(A) -> A end, 3306), | |
3003 | + DB = gen_mod:get_opt(db, Opts, fun(A) -> A end, <<"logdb">>), | |
3004 | + User = gen_mod:get_opt(user, Opts, fun(A) -> A end, <<"root">>), | |
3005 | + Password = gen_mod:get_opt(password, Opts, fun(A) -> A end, <<"">>), | |
0d78319d AM |
3006 | + |
3007 | + St = #state{vhost=VHost, | |
3008 | + server=Server, port=Port, db=DB, | |
3009 | + user=User, password=Password}, | |
3010 | + | |
3011 | + case open_mysql_connection(St) of | |
3012 | + {ok, DBRef} -> | |
3013 | + State = St#state{dbref=DBRef}, | |
3014 | + ok = create_stats_table(State), | |
3015 | + ok = create_settings_table(State), | |
3016 | + ok = create_users_table(State), | |
3017 | + % clear ets cache every ... | |
3018 | + timer:send_interval(timer:hours(12), clear_ets_tables), | |
3019 | + ok = create_servers_table(State), | |
3020 | + ok = create_resources_table(State), | |
3021 | + erlang:monitor(process, DBRef), | |
3022 | + {ok, State}; | |
3023 | + {error, Reason} -> | |
3024 | + ?ERROR_MSG("MySQL connection failed: ~p~n", [Reason]), | |
3025 | + {stop, db_connection_failed} | |
3026 | + end. | |
3027 | + | |
3028 | +open_mysql_connection(#state{server=Server, port=Port, db=DB, | |
3029 | + user=DBUser, password=Password} = _State) -> | |
3030 | + LogFun = fun(debug, _Format, _Argument) -> | |
3031 | + %?MYDEBUG(Format, Argument); | |
3032 | + ok; | |
3033 | + (error, Format, Argument) -> | |
3034 | + ?ERROR_MSG(Format, Argument); | |
3035 | + (Level, Format, Argument) -> | |
3036 | + ?MYDEBUG("MySQL (~p)~n", [Level]), | |
3037 | + ?MYDEBUG(Format, Argument) | |
3038 | + end, | |
3039 | + ?INFO_MSG("Opening mysql connection ~s@~s:~p/~s", [DBUser, Server, Port, DB]), | |
046546ef AM |
3040 | + p1_mysql_conn:start(binary_to_list(Server), Port, |
3041 | + binary_to_list(DBUser), binary_to_list(Password), | |
3042 | + binary_to_list(DB), LogFun). | |
0d78319d AM |
3043 | + |
3044 | +close_mysql_connection(DBRef) -> | |
3045 | + ?MYDEBUG("Closing ~p mysql connection", [DBRef]), | |
046546ef | 3046 | + catch p1_mysql_conn:stop(DBRef). |
0d78319d AM |
3047 | + |
3048 | +handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> | |
3049 | + Date = convert_timestamp_brief(Msg#msg.timestamp), | |
3050 | + | |
3051 | + Table = messages_table(VHost, Date), | |
046546ef AM |
3052 | + Owner_id = get_user_id(DBRef, VHost, binary_to_list(Msg#msg.owner_name)), |
3053 | + Peer_name_id = get_user_id(DBRef, VHost, binary_to_list(Msg#msg.peer_name)), | |
3054 | + Peer_server_id = get_server_id(DBRef, VHost, binary_to_list(Msg#msg.peer_server)), | |
3055 | + Peer_resource_id = get_resource_id(DBRef, VHost, binary_to_list(Msg#msg.peer_resource)), | |
0d78319d AM |
3056 | + |
3057 | + Query = ["INSERT INTO ",Table," ", | |
3058 | + "(owner_id,", | |
3059 | + "peer_name_id,", | |
3060 | + "peer_server_id,", | |
3061 | + "peer_resource_id,", | |
3062 | + "direction,", | |
3063 | + "type,", | |
3064 | + "subject,", | |
3065 | + "body,", | |
3066 | + "timestamp) ", | |
3067 | + "VALUES ", | |
3068 | + "('", Owner_id, "',", | |
3069 | + "'", Peer_name_id, "',", | |
3070 | + "'", Peer_server_id, "',", | |
3071 | + "'", Peer_resource_id, "',", | |
3072 | + "'", atom_to_list(Msg#msg.direction), "',", | |
046546ef | 3073 | + "'", binary_to_list(Msg#msg.type), "',", |
bb18ce72 AM |
3074 | + "'", binary_to_list( ejabberd_sql:escape(Msg#msg.subject) ), "',", |
3075 | + "'", binary_to_list( ejabberd_sql:escape(Msg#msg.body) ), "',", | |
0d78319d AM |
3076 | + "'", Msg#msg.timestamp, "');"], |
3077 | + | |
3078 | + Reply = | |
3079 | + case sql_query_internal_silent(DBRef, Query) of | |
3080 | + {updated, _} -> | |
046546ef AM |
3081 | + ?MYDEBUG("Logged ok for ~s, peer: ~s", [ [Msg#msg.owner_name, <<"@">>, VHost], |
3082 | + [Msg#msg.peer_name, <<"@">>, Msg#msg.peer_server] ]), | |
0d78319d AM |
3083 | + increment_user_stats(DBRef, Msg#msg.owner_name, Owner_id, VHost, Peer_name_id, Peer_server_id, Date); |
3084 | + {error, Reason} -> | |
046546ef | 3085 | + case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#42S02">>) of |
0d78319d AM |
3086 | + % Table doesn't exist |
3087 | + match -> | |
3088 | + case create_msg_table(DBRef, VHost, Date) of | |
3089 | + error -> | |
3090 | + error; | |
3091 | + ok -> | |
3092 | + {updated, _} = sql_query_internal(DBRef, Query), | |
046546ef | 3093 | + increment_user_stats(DBRef, binary_to_list(Msg#msg.owner_name), Owner_id, VHost, Peer_name_id, Peer_server_id, Date) |
0d78319d AM |
3094 | + end; |
3095 | + _ -> | |
3096 | + ?ERROR_MSG("Failed to log message: ~p", [Reason]), | |
3097 | + error | |
3098 | + end | |
3099 | + end, | |
3100 | + {reply, Reply, State}; | |
3101 | +handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> | |
3102 | + Reply = rebuild_stats_at_int(DBRef, VHost, Date), | |
3103 | + {reply, Reply, State}; | |
3104 | +handle_call({delete_messages_by_user_at, [], _Date}, _From, State) -> | |
3105 | + {reply, error, State}; | |
3106 | +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> | |
3107 | + Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) -> | |
3108 | + ["\"",Timestamp,"\"",","] | |
3109 | + end, Msgs), | |
3110 | + | |
3111 | + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]), | |
3112 | + | |
3113 | + Query = ["DELETE FROM ",messages_table(VHost, Date)," ", | |
3114 | + "WHERE timestamp IN (", Temp1], | |
f7ce3e3a | 3115 | + |
3116 | + Reply = | |
3117 | + case sql_query_internal(DBRef, Query) of | |
3118 | + {updated, Aff} -> | |
3119 | + ?MYDEBUG("Aff=~p", [Aff]), | |
3120 | + rebuild_stats_at_int(DBRef, VHost, Date); | |
3121 | + {error, _} -> | |
3122 | + error | |
3123 | + end, | |
3124 | + {reply, Reply, State}; | |
3125 | +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> | |
234c6b10 | 3126 | + ok = delete_all_messages_by_user_at_int(DBRef, User, VHost, Date), |
3127 | + ok = delete_stats_by_user_at_int(DBRef, User, VHost, Date), | |
3128 | + {reply, ok, State}; | |
f7ce3e3a | 3129 | +handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> |
3130 | + Reply = | |
3131 | + case sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Date),";"]) of | |
3132 | + {updated, _} -> | |
3133 | + Query = ["DELETE FROM ",stats_table(VHost)," " | |
3134 | + "WHERE at=\"",Date,"\";"], | |
3135 | + case sql_query_internal(DBRef, Query) of | |
3136 | + {updated, _} -> | |
3137 | + ok; | |
3138 | + {error, _} -> | |
3139 | + error | |
3140 | + end; | |
3141 | + {error, _} -> | |
3142 | + error | |
3143 | + end, | |
3144 | + {reply, Reply, State}; | |
3145 | +handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> | |
3146 | + SName = stats_table(VHost), | |
3147 | + Query = ["SELECT at, sum(count) ", | |
3148 | + "FROM ",SName," ", | |
3149 | + "GROUP BY at ", | |
3150 | + "ORDER BY DATE(at) DESC;" | |
3151 | + ], | |
3152 | + Reply = | |
3153 | + case sql_query_internal(DBRef, Query) of | |
3154 | + {data, Result} -> | |
3155 | + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]}; | |
3156 | + {error, Reason} -> | |
3157 | + % TODO: Duplicate error message ? | |
3158 | + {error, Reason} | |
3159 | + end, | |
3160 | + {reply, Reply, State}; | |
3161 | +handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> | |
3162 | + SName = stats_table(VHost), | |
234c6b10 | 3163 | + Query = ["SELECT username, sum(count) AS allcount ", |
f7ce3e3a | 3164 | + "FROM ",SName," ", |
3165 | + "JOIN ",users_table(VHost)," ON owner_id=user_id " | |
234c6b10 | 3166 | + "WHERE at=\"",Date,"\" " |
3167 | + "GROUP BY username ", | |
3168 | + "ORDER BY allcount DESC;" | |
f7ce3e3a | 3169 | + ], |
3170 | + Reply = | |
3171 | + case sql_query_internal(DBRef, Query) of | |
3172 | + {data, Result} -> | |
3173 | + {ok, lists:reverse( | |
3174 | + lists:keysort(2, | |
3175 | + [ {User, list_to_integer(Count)} || [User, Count] <- Result]))}; | |
3176 | + {error, Reason} -> | |
3177 | + % TODO: | |
3178 | + {error, Reason} | |
3179 | + end, | |
3180 | + {reply, Reply, State}; | |
3181 | +handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> | |
234c6b10 | 3182 | + {reply, get_user_stats_int(DBRef, User, VHost), State}; |
f7ce3e3a | 3183 | +handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> |
3184 | + TName = messages_table(VHost, Date), | |
3185 | + UName = users_table(VHost), | |
3186 | + SName = servers_table(VHost), | |
3187 | + RName = resources_table(VHost), | |
3188 | + Query = ["SELECT users.username,", | |
3189 | + "servers.server,", | |
3190 | + "resources.resource,", | |
3191 | + "messages.direction," | |
3192 | + "messages.type," | |
3193 | + "messages.subject," | |
3194 | + "messages.body," | |
3195 | + "messages.timestamp " | |
3196 | + "FROM ",TName," AS messages " | |
3197 | + "JOIN ",UName," AS users ON peer_name_id=user_id ", | |
3198 | + "JOIN ",SName," AS servers ON peer_server_id=server_id ", | |
3199 | + "JOIN ",RName," AS resources ON peer_resource_id=resource_id ", | |
3200 | + "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\" ", | |
3201 | + "ORDER BY timestamp ASC;"], | |
3202 | + Reply = | |
3203 | + case sql_query_internal(DBRef, Query) of | |
3204 | + {data, Result} -> | |
3205 | + Fun = fun([Peer_name, Peer_server, Peer_resource, | |
3206 | + Direction, | |
3207 | + Type, | |
3208 | + Subject, Body, | |
3209 | + Timestamp]) -> | |
3210 | + #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource, | |
3211 | + direction=list_to_atom(Direction), | |
3212 | + type=Type, | |
3213 | + subject=Subject, body=Body, | |
3214 | + timestamp=Timestamp} | |
3215 | + end, | |
3216 | + {ok, lists:map(Fun, Result)}; | |
3217 | + {error, Reason} -> | |
3218 | + {error, Reason} | |
3219 | + end, | |
3220 | + {reply, Reply, State}; | |
3221 | +handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> | |
3222 | + SName = stats_table(VHost), | |
3223 | + Query = ["SELECT at ", | |
3224 | + "FROM ",SName," ", | |
3225 | + "GROUP BY at ", | |
3226 | + "ORDER BY DATE(at) DESC;" | |
3227 | + ], | |
3228 | + Reply = | |
3229 | + case sql_query_internal(DBRef, Query) of | |
3230 | + {data, Result} -> | |
3231 | + [ Date || [Date] <- Result ]; | |
3232 | + {error, Reason} -> | |
3233 | + {error, Reason} | |
3234 | + end, | |
3235 | + {reply, Reply, State}; | |
3236 | +handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> | |
3237 | + Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ", | |
3238 | + "FROM ",settings_table(VHost)," ", | |
3239 | + "JOIN ",users_table(VHost)," ON user_id=owner_id;"], | |
0d78319d | 3240 | + Reply = |
f7ce3e3a | 3241 | + case sql_query_internal(DBRef, Query) of |
3242 | + {data, Result} -> | |
3243 | + {ok, lists:map(fun([Owner, DoLogDef, DoLogL, DoNotLogL]) -> | |
3244 | + #user_settings{owner_name=Owner, | |
3245 | + dolog_default=list_to_bool(DoLogDef), | |
3246 | + dolog_list=string_to_list(DoLogL), | |
3247 | + donotlog_list=string_to_list(DoNotLogL) | |
3248 | + } | |
3249 | + end, Result)}; | |
3250 | + {error, _} -> | |
3251 | + error | |
3252 | + end, | |
3253 | + {reply, Reply, State}; | |
3254 | +handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> | |
3255 | + Query = ["SELECT dolog_default,dolog_list,donotlog_list FROM ",settings_table(VHost)," ", | |
3256 | + "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\";"], | |
3257 | + Reply = | |
3258 | + case sql_query_internal(DBRef, Query) of | |
3259 | + {data, []} -> | |
3260 | + {ok, []}; | |
3261 | + {data, [[Owner, DoLogDef, DoLogL, DoNotLogL]]} -> | |
3262 | + {ok, #user_settings{owner_name=Owner, | |
3263 | + dolog_default=list_to_bool(DoLogDef), | |
3264 | + dolog_list=string_to_list(DoLogL), | |
3265 | + donotlog_list=string_to_list(DoNotLogL)}}; | |
3266 | + {error, _} -> | |
3267 | + error | |
3268 | + end, | |
3269 | + {reply, Reply, State}; | |
3270 | +handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef, | |
3271 | + dolog_list=DoLogL, | |
3272 | + donotlog_list=DoNotLogL}}, | |
3273 | + _From, #state{dbref=DBRef, vhost=VHost} = State) -> | |
3274 | + User_id = get_user_id(DBRef, VHost, User), | |
3275 | + | |
3276 | + Query = ["UPDATE ",settings_table(VHost)," ", | |
3277 | + "SET dolog_default=",bool_to_list(DoLogDef),", ", | |
3278 | + "dolog_list='",list_to_string(DoLogL),"', ", | |
3279 | + "donotlog_list='",list_to_string(DoNotLogL),"' ", | |
3280 | + "WHERE owner_id=\"",User_id,"\";"], | |
3281 | + | |
3282 | + Reply = | |
3283 | + case sql_query_internal(DBRef, Query) of | |
3284 | + {updated, 0} -> | |
3285 | + IQuery = ["INSERT INTO ",settings_table(VHost)," ", | |
3286 | + "(owner_id, dolog_default, dolog_list, donotlog_list) ", | |
3287 | + "VALUES ", | |
3288 | + "('",User_id,"', ",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"], | |
3289 | + case sql_query_internal_silent(DBRef, IQuery) of | |
3290 | + {updated, _} -> | |
3291 | + ?MYDEBUG("New settings for ~s@~s", [User, VHost]), | |
3292 | + ok; | |
3293 | + {error, Reason} -> | |
046546ef | 3294 | + case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>) of |
f7ce3e3a | 3295 | + % Already exists |
0d78319d | 3296 | + match -> |
f7ce3e3a | 3297 | + ok; |
3298 | + _ -> | |
3299 | + ?ERROR_MSG("Failed setup user ~p@~p: ~p", [User, VHost, Reason]), | |
3300 | + error | |
3301 | + end | |
3302 | + end; | |
3303 | + {updated, 1} -> | |
3304 | + ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]), | |
3305 | + ok; | |
3306 | + {error, _} -> | |
3307 | + error | |
3308 | + end, | |
3309 | + {reply, Reply, State}; | |
3310 | +handle_call({stop}, _From, #state{vhost=VHost}=State) -> | |
3311 | + ets:delete(ets_users_table(VHost)), | |
3312 | + ets:delete(ets_servers_table(VHost)), | |
3313 | + ?MYDEBUG("Stoping mysql backend for ~p", [VHost]), | |
3314 | + {stop, normal, ok, State}; | |
3315 | +handle_call(Msg, _From, State) -> | |
3316 | + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]), | |
3317 | + {noreply, State}. | |
3318 | + | |
234c6b10 | 3319 | +handle_cast({rebuild_stats}, State) -> |
3320 | + rebuild_all_stats_int(State), | |
3321 | + {noreply, State}; | |
3322 | +handle_cast({drop_user, User}, #state{vhost=VHost} = State) -> | |
3323 | + Fun = fun() -> | |
3324 | + {ok, DBRef} = open_mysql_connection(State), | |
3325 | + {ok, Dates} = get_user_stats_int(DBRef, User, VHost), | |
3326 | + MDResult = lists:map(fun({Date, _}) -> | |
3327 | + delete_all_messages_by_user_at_int(DBRef, User, VHost, Date) | |
3328 | + end, Dates), | |
3329 | + StDResult = delete_all_stats_by_user_int(DBRef, User, VHost), | |
3330 | + SDResult = delete_user_settings_int(DBRef, User, VHost), | |
3331 | + case lists:all(fun(Result) when Result == ok -> | |
3332 | + true; | |
3333 | + (Result) when Result == error -> | |
3334 | + false | |
3335 | + end, lists:append([MDResult, [StDResult], [SDResult]])) of | |
3336 | + true -> | |
3337 | + ?INFO_MSG("Removed ~s@~s", [User, VHost]); | |
3338 | + false -> | |
3339 | + ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost]) | |
3340 | + end, | |
3341 | + close_mysql_connection(DBRef) | |
3342 | + end, | |
3343 | + spawn(Fun), | |
3344 | + {noreply, State}; | |
f7ce3e3a | 3345 | +handle_cast(Msg, State) -> |
3346 | + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]), | |
3347 | + {noreply, State}. | |
3348 | + | |
3349 | +handle_info(clear_ets_tables, State) -> | |
3350 | + ets:delete_all_objects(ets_users_table(State#state.vhost)), | |
3351 | + ets:delete_all_objects(ets_resources_table(State#state.vhost)), | |
3352 | + {noreply, State}; | |
3353 | +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) -> | |
3354 | + {stop, connection_dropped, State}; | |
3355 | +handle_info(Info, State) -> | |
3356 | + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]), | |
3357 | + {noreply, State}. | |
3358 | + | |
234c6b10 | 3359 | +terminate(_Reason, #state{dbref=DBRef}=_State) -> |
3360 | + close_mysql_connection(DBRef), | |
f7ce3e3a | 3361 | + ok. |
3362 | + | |
3363 | +code_change(_OldVsn, State, _Extra) -> | |
3364 | + {ok, State}. | |
3365 | + | |
3366 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
3367 | +% | |
3368 | +% gen_logdb callbacks | |
3369 | +% | |
3370 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
3371 | +log_message(VHost, Msg) -> | |
3372 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
3373 | + gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT). | |
3374 | +rebuild_stats(VHost) -> | |
3375 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
234c6b10 | 3376 | + gen_server:cast(Proc, {rebuild_stats}). |
f7ce3e3a | 3377 | +rebuild_stats_at(VHost, Date) -> |
3378 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
3379 | + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT). | |
3380 | +delete_messages_by_user_at(VHost, Msgs, Date) -> | |
3381 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
3382 | + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT). | |
3383 | +delete_all_messages_by_user_at(User, VHost, Date) -> | |
3384 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
3385 | + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT). | |
3386 | +delete_messages_at(VHost, Date) -> | |
3387 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
3388 | + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT). | |
3389 | +get_vhost_stats(VHost) -> | |
3390 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
3391 | + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT). | |
3392 | +get_vhost_stats_at(VHost, Date) -> | |
3393 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
3394 | + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT). | |
3395 | +get_user_stats(User, VHost) -> | |
3396 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
3397 | + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT). | |
3398 | +get_user_messages_at(User, VHost, Date) -> | |
3399 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
3400 | + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT). | |
3401 | +get_dates(VHost) -> | |
3402 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
3403 | + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT). | |
3404 | +get_users_settings(VHost) -> | |
3405 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
3406 | + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT). | |
3407 | +get_user_settings(User, VHost) -> | |
3408 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
3409 | + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT). | |
3410 | +set_user_settings(User, VHost, Set) -> | |
3411 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
3412 | + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT). | |
234c6b10 | 3413 | +drop_user(User, VHost) -> |
3414 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
3415 | + gen_server:cast(Proc, {drop_user, User}). | |
f7ce3e3a | 3416 | + |
3417 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
3418 | +% | |
3419 | +% internals | |
3420 | +% | |
3421 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
234c6b10 | 3422 | +increment_user_stats(DBRef, User_name, User_id, VHost, PNameID, PServerID, Date) -> |
f7ce3e3a | 3423 | + SName = stats_table(VHost), |
3424 | + UQuery = ["UPDATE ",SName," ", | |
3425 | + "SET count=count+1 ", | |
234c6b10 | 3426 | + "WHERE owner_id=\"",User_id,"\" AND peer_name_id=\"",PNameID,"\" AND peer_server_id=\"",PServerID,"\" AND at=\"",Date,"\";"], |
f7ce3e3a | 3427 | + |
3428 | + case sql_query_internal(DBRef, UQuery) of | |
3429 | + {updated, 0} -> | |
3430 | + IQuery = ["INSERT INTO ",SName," ", | |
234c6b10 | 3431 | + "(owner_id, peer_name_id, peer_server_id, at, count) ", |
f7ce3e3a | 3432 | + "VALUES ", |
234c6b10 | 3433 | + "('",User_id,"', '",PNameID,"', '",PServerID,"', '",Date,"', '1');"], |
f7ce3e3a | 3434 | + case sql_query_internal(DBRef, IQuery) of |
3435 | + {updated, _} -> | |
3436 | + ?MYDEBUG("New stats for ~s@~s at ~s", [User_name, VHost, Date]), | |
3437 | + ok; | |
3438 | + {error, _} -> | |
3439 | + error | |
3440 | + end; | |
3441 | + {updated, _} -> | |
3442 | + ?MYDEBUG("Updated stats for ~s@~s at ~s", [User_name, VHost, Date]), | |
3443 | + ok; | |
3444 | + {error, _} -> | |
3445 | + error | |
3446 | + end. | |
3447 | + | |
3448 | +get_dates_int(DBRef, VHost) -> | |
3449 | + case sql_query_internal(DBRef, ["SHOW TABLES"]) of | |
3450 | + {data, Tables} -> | |
3f23be8e | 3451 | + Reg = "^" ++ lists:sublist(prefix(),2,length(prefix())) ++ ".*" ++ escape_vhost(VHost), |
f7ce3e3a | 3452 | + lists:foldl(fun([Table], Dates) -> |
0d78319d | 3453 | + case re:run(Table, Reg) of |
3f23be8e AM |
3454 | + {match, _} -> |
3455 | + case re:run(Table, "[0-9]+-[0-9]+-[0-9]+") of | |
0d78319d | 3456 | + {match, [{S, E}]} -> |
3f23be8e | 3457 | + lists:append(Dates, [lists:sublist(Table, S+1, E)]); |
f7ce3e3a | 3458 | + nomatch -> |
3459 | + Dates | |
3460 | + end; | |
234c6b10 | 3461 | + _ -> |
f7ce3e3a | 3462 | + Dates |
3463 | + end | |
3464 | + end, [], Tables); | |
3465 | + {error, _} -> | |
3466 | + [] | |
3467 | + end. | |
3468 | + | |
234c6b10 | 3469 | +rebuild_all_stats_int(#state{vhost=VHost}=State) -> |
3470 | + Fun = fun() -> | |
3471 | + {ok, DBRef} = open_mysql_connection(State), | |
3472 | + ok = delete_nonexistent_stats(DBRef, VHost), | |
3473 | + case lists:filter(fun(Date) -> | |
3474 | + case catch rebuild_stats_at_int(DBRef, VHost, Date) of | |
3475 | + ok -> false; | |
3476 | + error -> true; | |
3477 | + {'EXIT', _} -> true | |
3478 | + end | |
3479 | + end, get_dates_int(DBRef, VHost)) of | |
3480 | + [] -> ok; | |
3481 | + FTables -> | |
3482 | + ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]), | |
3483 | + error | |
3484 | + end, | |
3485 | + close_mysql_connection(DBRef) | |
3486 | + end, | |
3487 | + spawn(Fun). | |
f7ce3e3a | 3488 | + |
234c6b10 | 3489 | +rebuild_stats_at_int(DBRef, VHost, Date) -> |
3490 | + TempTable = temp_table(VHost), | |
3491 | + Fun = fun() -> | |
3492 | + Table = messages_table(VHost, Date), | |
3493 | + STable = stats_table(VHost), | |
f7ce3e3a | 3494 | + |
234c6b10 | 3495 | + DQuery = [ "DELETE FROM ",STable," ", |
3496 | + "WHERE at='",Date,"';"], | |
f7ce3e3a | 3497 | + |
234c6b10 | 3498 | + ok = create_temp_table(DBRef, TempTable), |
3499 | + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," WRITE, ",TempTable," WRITE;"]), | |
3500 | + SQuery = ["INSERT INTO ",TempTable," ", | |
3501 | + "(owner_id,peer_name_id,peer_server_id,at,count) ", | |
3502 | + "SELECT owner_id,peer_name_id,peer_server_id,\"",Date,"\",count(*) ", | |
3503 | + "FROM ",Table," GROUP BY owner_id,peer_name_id,peer_server_id;"], | |
3504 | + case sql_query_internal(DBRef, SQuery) of | |
3505 | + {updated, 0} -> | |
3506 | + Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]), | |
3507 | + case Count of | |
3508 | + {data, [["0"]]} -> | |
3509 | + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]), | |
3510 | + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE;"]), | |
3511 | + {updated, _} = sql_query_internal(DBRef, DQuery), | |
3512 | + ok; | |
3513 | + _ -> | |
3514 | + ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]), | |
3515 | + error | |
3516 | + end; | |
3517 | + {updated, _} -> | |
3518 | + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE, ",TempTable," WRITE;"]), | |
3519 | + {updated, _} = sql_query_internal(DBRef, DQuery), | |
3520 | + SQuery1 = ["INSERT INTO ",STable," ", | |
3521 | + "(owner_id,peer_name_id,peer_server_id,at,count) ", | |
3522 | + "SELECT owner_id,peer_name_id,peer_server_id,at,count ", | |
3523 | + "FROM ",TempTable,";"], | |
3524 | + case sql_query_internal(DBRef, SQuery1) of | |
3525 | + {updated, _} -> ok; | |
3526 | + {error, _} -> error | |
3527 | + end; | |
3528 | + {error, _} -> error | |
3529 | + end | |
3530 | + end, | |
f7ce3e3a | 3531 | + |
234c6b10 | 3532 | + case catch apply(Fun, []) of |
3533 | + ok -> | |
3534 | + ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]), | |
3535 | + ok; | |
3536 | + error -> | |
3537 | + error; | |
3538 | + {'EXIT', Reason} -> | |
3539 | + ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]), | |
3540 | + error | |
3541 | + end, | |
3542 | + sql_query_internal(DBRef, ["UNLOCK TABLES;"]), | |
3543 | + sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]), | |
3544 | + ok. | |
f7ce3e3a | 3545 | + |
3546 | + | |
3547 | +delete_nonexistent_stats(DBRef, VHost) -> | |
3548 | + Dates = get_dates_int(DBRef, VHost), | |
3549 | + STable = stats_table(VHost), | |
3550 | + | |
3551 | + Temp = lists:flatmap(fun(Date) -> | |
3552 | + ["\"",Date,"\"",","] | |
3553 | + end, Dates), | |
3554 | + | |
234c6b10 | 3555 | + case Temp of |
3556 | + [] -> | |
3557 | + ok; | |
3558 | + _ -> | |
3559 | + % replace last "," with ");" | |
3560 | + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]), | |
3561 | + Query = ["DELETE FROM ",STable," ", | |
3562 | + "WHERE at NOT IN (", Temp1], | |
3563 | + case sql_query_internal(DBRef, Query) of | |
3564 | + {updated, _} -> | |
3565 | + ok; | |
3566 | + {error, _} -> | |
3567 | + error | |
3568 | + end | |
3569 | + end. | |
f7ce3e3a | 3570 | + |
234c6b10 | 3571 | +get_user_stats_int(DBRef, User, VHost) -> |
3572 | + SName = stats_table(VHost), | |
3573 | + Query = ["SELECT at, sum(count) as allcount ", | |
3574 | + "FROM ",SName," ", | |
3575 | + "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\" ", | |
3576 | + "GROUP BY at " | |
3577 | + "ORDER BY DATE(at) DESC;" | |
3578 | + ], | |
f7ce3e3a | 3579 | + case sql_query_internal(DBRef, Query) of |
234c6b10 | 3580 | + {data, Result} -> |
3581 | + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result]}; | |
3582 | + {error, Result} -> | |
3583 | + {error, Result} | |
3584 | + end. | |
3585 | + | |
3586 | +delete_all_messages_by_user_at_int(DBRef, User, VHost, Date) -> | |
3587 | + DQuery = ["DELETE FROM ",messages_table(VHost, Date)," ", | |
3588 | + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"], | |
3589 | + case sql_query_internal(DBRef, DQuery) of | |
f7ce3e3a | 3590 | + {updated, _} -> |
234c6b10 | 3591 | + ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]), |
f7ce3e3a | 3592 | + ok; |
3593 | + {error, _} -> | |
3594 | + error | |
3595 | + end. | |
3596 | + | |
234c6b10 | 3597 | +delete_all_stats_by_user_int(DBRef, User, VHost) -> |
3598 | + SQuery = ["DELETE FROM ",stats_table(VHost)," ", | |
3599 | + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"], | |
3600 | + case sql_query_internal(DBRef, SQuery) of | |
3601 | + {updated, _} -> | |
3602 | + ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]), | |
3603 | + ok; | |
3604 | + {error, _} -> error | |
3605 | + end. | |
3606 | + | |
3607 | +delete_stats_by_user_at_int(DBRef, User, VHost, Date) -> | |
3608 | + SQuery = ["DELETE FROM ",stats_table(VHost)," ", | |
3609 | + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\") ", | |
3610 | + "AND at=\"",Date,"\";"], | |
3611 | + case sql_query_internal(DBRef, SQuery) of | |
3612 | + {updated, _} -> | |
3613 | + ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]), | |
3614 | + ok; | |
3615 | + {error, _} -> error | |
3616 | + end. | |
3617 | + | |
3618 | +delete_user_settings_int(DBRef, User, VHost) -> | |
3619 | + Query = ["DELETE FROM ",settings_table(VHost)," ", | |
3620 | + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"], | |
3621 | + case sql_query_internal(DBRef, Query) of | |
3622 | + {updated, _} -> | |
3623 | + ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]), | |
3624 | + ok; | |
3625 | + {error, Reason} -> | |
3626 | + ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]), | |
3627 | + error | |
3628 | + end. | |
3629 | + | |
f7ce3e3a | 3630 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
3631 | +% | |
3632 | +% tables internals | |
3633 | +% | |
3634 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
234c6b10 | 3635 | +create_temp_table(DBRef, Name) -> |
3636 | + Query = ["CREATE TABLE ",Name," (", | |
3637 | + "owner_id MEDIUMINT UNSIGNED, ", | |
3638 | + "peer_name_id MEDIUMINT UNSIGNED, ", | |
3639 | + "peer_server_id MEDIUMINT UNSIGNED, ", | |
3640 | + "at VARCHAR(11), ", | |
3641 | + "count INT(11) ", | |
3642 | + ") ENGINE=MyISAM CHARACTER SET utf8;" | |
3643 | + ], | |
3644 | + case sql_query_internal(DBRef, Query) of | |
3645 | + {updated, _} -> ok; | |
3646 | + {error, _Reason} -> error | |
3647 | + end. | |
3648 | + | |
3649 | +create_stats_table(#state{dbref=DBRef, vhost=VHost}=State) -> | |
f7ce3e3a | 3650 | + SName = stats_table(VHost), |
3651 | + Query = ["CREATE TABLE ",SName," (", | |
3652 | + "owner_id MEDIUMINT UNSIGNED, ", | |
234c6b10 | 3653 | + "peer_name_id MEDIUMINT UNSIGNED, ", |
3654 | + "peer_server_id MEDIUMINT UNSIGNED, ", | |
f7ce3e3a | 3655 | + "at varchar(20), ", |
3656 | + "count int(11), ", | |
234c6b10 | 3657 | + "INDEX(owner_id, peer_name_id, peer_server_id), ", |
f7ce3e3a | 3658 | + "INDEX(at)" |
3659 | + ") ENGINE=InnoDB CHARACTER SET utf8;" | |
3660 | + ], | |
3661 | + case sql_query_internal_silent(DBRef, Query) of | |
3662 | + {updated, _} -> | |
234c6b10 | 3663 | + ?INFO_MSG("Created stats table for ~p", [VHost]), |
3664 | + rebuild_all_stats_int(State), | |
f7ce3e3a | 3665 | + ok; |
3666 | + {error, Reason} -> | |
046546ef | 3667 | + case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#42S01">>) of |
0d78319d | 3668 | + match -> |
f7ce3e3a | 3669 | + ?MYDEBUG("Stats table for ~p already exists", [VHost]), |
234c6b10 | 3670 | + CheckQuery = ["SHOW COLUMNS FROM ",SName," LIKE 'peer_%_id';"], |
3671 | + case sql_query_internal(DBRef, CheckQuery) of | |
3672 | + {data, Elems} when length(Elems) == 2 -> | |
3673 | + ?MYDEBUG("Stats table structure is ok", []), | |
3674 | + ok; | |
3675 | + _ -> | |
3676 | + ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []), | |
3677 | + case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of | |
3678 | + {updated, _} -> | |
3679 | + ?INFO_MSG("Successfully dropped ~p", [SName]); | |
3680 | + _ -> | |
3681 | + ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName]) | |
3682 | + end, | |
3683 | + error | |
3684 | + end; | |
f7ce3e3a | 3685 | + _ -> |
3686 | + ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]), | |
3687 | + error | |
3688 | + end | |
3689 | + end. | |
3690 | + | |
234c6b10 | 3691 | +create_settings_table(#state{dbref=DBRef, vhost=VHost}) -> |
f7ce3e3a | 3692 | + SName = settings_table(VHost), |
3693 | + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (", | |
3694 | + "owner_id MEDIUMINT UNSIGNED PRIMARY KEY, ", | |
3695 | + "dolog_default TINYINT(1) NOT NULL DEFAULT 1, ", | |
3696 | + "dolog_list TEXT, ", | |
3697 | + "donotlog_list TEXT ", | |
3698 | + ") ENGINE=InnoDB CHARACTER SET utf8;" | |
3699 | + ], | |
3700 | + case sql_query_internal(DBRef, Query) of | |
3701 | + {updated, _} -> | |
3702 | + ?MYDEBUG("Created settings table for ~p", [VHost]), | |
3703 | + ok; | |
3704 | + {error, _} -> | |
3705 | + error | |
3706 | + end. | |
3707 | + | |
234c6b10 | 3708 | +create_users_table(#state{dbref=DBRef, vhost=VHost}) -> |
f7ce3e3a | 3709 | + SName = users_table(VHost), |
3710 | + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (", | |
3711 | + "username TEXT NOT NULL, ", | |
3712 | + "user_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ", | |
3713 | + "UNIQUE INDEX(username(",?INDEX_SIZE,")) ", | |
3714 | + ") ENGINE=InnoDB CHARACTER SET utf8;" | |
3715 | + ], | |
3716 | + case sql_query_internal(DBRef, Query) of | |
3717 | + {updated, _} -> | |
3718 | + ?MYDEBUG("Created users table for ~p", [VHost]), | |
3719 | + ets:new(ets_users_table(VHost), [named_table, set, public]), | |
3720 | + %update_users_from_db(DBRef, VHost), | |
3721 | + ok; | |
3722 | + {error, _} -> | |
3723 | + error | |
3724 | + end. | |
3725 | + | |
234c6b10 | 3726 | +create_servers_table(#state{dbref=DBRef, vhost=VHost}) -> |
f7ce3e3a | 3727 | + SName = servers_table(VHost), |
3728 | + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (", | |
3729 | + "server TEXT NOT NULL, ", | |
3730 | + "server_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ", | |
3731 | + "UNIQUE INDEX(server(",?INDEX_SIZE,")) ", | |
3732 | + ") ENGINE=InnoDB CHARACTER SET utf8;" | |
3733 | + ], | |
3734 | + case sql_query_internal(DBRef, Query) of | |
3735 | + {updated, _} -> | |
3736 | + ?MYDEBUG("Created servers table for ~p", [VHost]), | |
3737 | + ets:new(ets_servers_table(VHost), [named_table, set, public]), | |
3738 | + update_servers_from_db(DBRef, VHost), | |
3739 | + ok; | |
3740 | + {error, _} -> | |
3741 | + error | |
3742 | + end. | |
3743 | + | |
234c6b10 | 3744 | +create_resources_table(#state{dbref=DBRef, vhost=VHost}) -> |
f7ce3e3a | 3745 | + RName = resources_table(VHost), |
3746 | + Query = ["CREATE TABLE IF NOT EXISTS ",RName," (", | |
3747 | + "resource TEXT NOT NULL, ", | |
3748 | + "resource_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ", | |
3749 | + "UNIQUE INDEX(resource(",?INDEX_SIZE,")) ", | |
3750 | + ") ENGINE=InnoDB CHARACTER SET utf8;" | |
3751 | + ], | |
3752 | + case sql_query_internal(DBRef, Query) of | |
3753 | + {updated, _} -> | |
3754 | + ?MYDEBUG("Created resources table for ~p", [VHost]), | |
3755 | + ets:new(ets_resources_table(VHost), [named_table, set, public]), | |
3756 | + ok; | |
3757 | + {error, _} -> | |
3758 | + error | |
3759 | + end. | |
3760 | + | |
3761 | +create_msg_table(DBRef, VHost, Date) -> | |
3762 | + TName = messages_table(VHost, Date), | |
3763 | + Query = ["CREATE TABLE ",TName," (", | |
3764 | + "owner_id MEDIUMINT UNSIGNED, ", | |
3765 | + "peer_name_id MEDIUMINT UNSIGNED, ", | |
3766 | + "peer_server_id MEDIUMINT UNSIGNED, ", | |
3767 | + "peer_resource_id MEDIUMINT(8) UNSIGNED, ", | |
3768 | + "direction ENUM('to', 'from'), ", | |
3769 | + "type ENUM('chat','error','groupchat','headline','normal') NOT NULL, ", | |
3770 | + "subject TEXT, ", | |
3771 | + "body TEXT, ", | |
3772 | + "timestamp DOUBLE, ", | |
234c6b10 | 3773 | + "INDEX search_i (owner_id, peer_name_id, peer_server_id, peer_resource_id), ", |
f7ce3e3a | 3774 | + "FULLTEXT (body) " |
3775 | + ") ENGINE=MyISAM CHARACTER SET utf8;" | |
3776 | + ], | |
3777 | + case sql_query_internal(DBRef, Query) of | |
3778 | + {updated, _MySQLRes} -> | |
3779 | + ?MYDEBUG("Created msg table for ~p at ~p", [VHost, Date]), | |
3780 | + ok; | |
3781 | + {error, _} -> | |
3782 | + error | |
3783 | + end. | |
3784 | + | |
3785 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
3786 | +% | |
3787 | +% internal ets cache (users, servers, resources) | |
3788 | +% | |
3789 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
3790 | +update_servers_from_db(DBRef, VHost) -> | |
3791 | + ?INFO_MSG("Reading servers from db for ~p", [VHost]), | |
3792 | + SQuery = ["SELECT server, server_id FROM ",servers_table(VHost),";"], | |
3793 | + {data, Result} = sql_query_internal(DBRef, SQuery), | |
3794 | + true = ets:delete_all_objects(ets_servers_table(VHost)), | |
3795 | + true = ets:insert(ets_servers_table(VHost), [ {Server, Server_id} || [Server, Server_id] <- Result]). | |
3796 | + | |
3797 | +%update_users_from_db(DBRef, VHost) -> | |
3798 | +% ?INFO_MSG("Reading users from db for ~p", [VHost]), | |
3799 | +% SQuery = ["SELECT username, user_id FROM ",users_table(VHost),";"], | |
3800 | +% {data, Result} = sql_query_internal(DBRef, SQuery), | |
3801 | +% true = ets:delete_all_objects(ets_users_table(VHost)), | |
3802 | +% true = ets:insert(ets_users_table(VHost), [ {Username, User_id} || [Username, User_id] <- Result]). | |
3803 | + | |
3804 | +%get_user_name(DBRef, VHost, User_id) -> | |
3805 | +% case ets:match(ets_users_table(VHost), {'$1', User_id}) of | |
3806 | +% [[User]] -> User; | |
3807 | +% % this can be in clustered environment | |
3808 | +% [] -> | |
3809 | +% %update_users_from_db(DBRef, VHost), | |
3810 | +% SQuery = ["SELECT username FROM ",users_table(VHost)," ", | |
3811 | +% "WHERE user_id=\"",User_id,"\";"], | |
3812 | +% {data, [[Name]]} = sql_query_internal(DBRef, SQuery), | |
3813 | +% % cache {user, id} pair | |
3814 | +% ets:insert(ets_users_table(VHost), {Name, User_id}), | |
3815 | +% Name | |
3816 | +% end. | |
3817 | + | |
3818 | +%get_server_name(DBRef, VHost, Server_id) -> | |
3819 | +% case ets:match(ets_servers_table(VHost), {'$1', Server_id}) of | |
3820 | +% [[Server]] -> Server; | |
3821 | + % this can be in clustered environment | |
3822 | +% [] -> | |
3823 | +% update_servers_from_db(DBRef, VHost), | |
3824 | +% [[Server1]] = ets:match(ets_servers_table(VHost), {'$1', Server_id}), | |
3825 | +% Server1 | |
3826 | +% end. | |
3827 | + | |
3828 | +get_user_id_from_db(DBRef, VHost, User) -> | |
3829 | + SQuery = ["SELECT user_id FROM ",users_table(VHost)," ", | |
3830 | + "WHERE username=\"",User,"\";"], | |
3831 | + case sql_query_internal(DBRef, SQuery) of | |
3832 | + % no such user in db | |
3833 | + {data, []} -> | |
3834 | + {ok, []}; | |
3835 | + {data, [[DBId]]} -> | |
3836 | + % cache {user, id} pair | |
3837 | + ets:insert(ets_users_table(VHost), {User, DBId}), | |
3838 | + {ok, DBId} | |
3839 | + end. | |
3840 | +get_user_id(DBRef, VHost, User) -> | |
3841 | + % Look at ets | |
3842 | + case ets:match(ets_users_table(VHost), {User, '$1'}) of | |
3843 | + [] -> | |
3844 | + % Look at db | |
3845 | + case get_user_id_from_db(DBRef, VHost, User) of | |
3846 | + % no such user in db | |
3847 | + {ok, []} -> | |
3848 | + IQuery = ["INSERT INTO ",users_table(VHost)," ", | |
3849 | + "SET username=\"",User,"\";"], | |
3850 | + case sql_query_internal_silent(DBRef, IQuery) of | |
3851 | + {updated, _} -> | |
3852 | + {ok, NewId} = get_user_id_from_db(DBRef, VHost, User), | |
3853 | + NewId; | |
3854 | + {error, Reason} -> | |
3855 | + % this can be in clustered environment | |
046546ef | 3856 | + match = ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>), |
f7ce3e3a | 3857 | + ?ERROR_MSG("Duplicate key name for ~p", [User]), |
3858 | + {ok, ClID} = get_user_id_from_db(DBRef, VHost, User), | |
3859 | + ClID | |
3860 | + end; | |
3861 | + {ok, DBId} -> | |
3862 | + DBId | |
3863 | + end; | |
3864 | + [[EtsId]] -> EtsId | |
3865 | + end. | |
3866 | + | |
3867 | +get_server_id(DBRef, VHost, Server) -> | |
3868 | + case ets:match(ets_servers_table(VHost), {Server, '$1'}) of | |
3869 | + [] -> | |
3870 | + IQuery = ["INSERT INTO ",servers_table(VHost)," ", | |
3871 | + "SET server=\"",Server,"\";"], | |
3872 | + case sql_query_internal_silent(DBRef, IQuery) of | |
3873 | + {updated, _} -> | |
3874 | + SQuery = ["SELECT server_id FROM ",servers_table(VHost)," ", | |
3875 | + "WHERE server=\"",Server,"\";"], | |
3876 | + {data, [[Id]]} = sql_query_internal(DBRef, SQuery), | |
3877 | + ets:insert(ets_servers_table(VHost), {Server, Id}), | |
3878 | + Id; | |
3879 | + {error, Reason} -> | |
3880 | + % this can be in clustered environment | |
046546ef | 3881 | + match = ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>), |
f7ce3e3a | 3882 | + ?ERROR_MSG("Duplicate key name for ~p", [Server]), |
3883 | + update_servers_from_db(DBRef, VHost), | |
3884 | + [[Id1]] = ets:match(ets_servers_table(VHost), {Server, '$1'}), | |
3885 | + Id1 | |
3886 | + end; | |
3887 | + [[Id]] -> Id | |
3888 | + end. | |
3889 | + | |
3890 | +get_resource_id_from_db(DBRef, VHost, Resource) -> | |
3891 | + SQuery = ["SELECT resource_id FROM ",resources_table(VHost)," ", | |
bb18ce72 | 3892 | + "WHERE resource=\"",binary_to_list(ejabberd_sql:escape(iolist_to_binary(Resource))),"\";"], |
f7ce3e3a | 3893 | + case sql_query_internal(DBRef, SQuery) of |
3894 | + % no such resource in db | |
3895 | + {data, []} -> | |
3896 | + {ok, []}; | |
3897 | + {data, [[DBId]]} -> | |
3898 | + % cache {resource, id} pair | |
3899 | + ets:insert(ets_resources_table(VHost), {Resource, DBId}), | |
3900 | + {ok, DBId} | |
3901 | + end. | |
3902 | +get_resource_id(DBRef, VHost, Resource) -> | |
3903 | + % Look at ets | |
3904 | + case ets:match(ets_resources_table(VHost), {Resource, '$1'}) of | |
3905 | + [] -> | |
3906 | + % Look at db | |
3907 | + case get_resource_id_from_db(DBRef, VHost, Resource) of | |
3908 | + % no such resource in db | |
3909 | + {ok, []} -> | |
3910 | + IQuery = ["INSERT INTO ",resources_table(VHost)," ", | |
bb18ce72 | 3911 | + "SET resource=\"",binary_to_list(ejabberd_sql:escape(iolist_to_binary(Resource))),"\";"], |
f7ce3e3a | 3912 | + case sql_query_internal_silent(DBRef, IQuery) of |
3913 | + {updated, _} -> | |
3914 | + {ok, NewId} = get_resource_id_from_db(DBRef, VHost, Resource), | |
3915 | + NewId; | |
3916 | + {error, Reason} -> | |
3917 | + % this can be in clustered environment | |
046546ef AM |
3918 | + match = ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>), |
3919 | + ?ERROR_MSG("Duplicate key name for ~s", [Resource]), | |
f7ce3e3a | 3920 | + {ok, ClID} = get_resource_id_from_db(DBRef, VHost, Resource), |
3921 | + ClID | |
3922 | + end; | |
3923 | + {ok, DBId} -> | |
3924 | + DBId | |
3925 | + end; | |
3926 | + [[EtsId]] -> EtsId | |
3927 | + end. | |
3928 | + | |
3929 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
3930 | +% | |
0d78319d | 3931 | +% SQL internals |
f7ce3e3a | 3932 | +% |
3933 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
f7ce3e3a | 3934 | +sql_query_internal(DBRef, Query) -> |
3935 | + case sql_query_internal_silent(DBRef, Query) of | |
3936 | + {error, Reason} -> | |
3937 | + ?ERROR_MSG("~p while ~p", [Reason, lists:append(Query)]), | |
3938 | + {error, Reason}; | |
3939 | + Rez -> Rez | |
3940 | + end. | |
3941 | + | |
3942 | +sql_query_internal_silent(DBRef, Query) -> | |
3943 | + ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]), | |
046546ef | 3944 | + get_result(p1_mysql_conn:fetch(DBRef, Query, self(), ?MYSQL_TIMEOUT)). |
f7ce3e3a | 3945 | + |
3946 | +get_result({updated, MySQLRes}) -> | |
046546ef | 3947 | + {updated, p1_mysql:get_result_affected_rows(MySQLRes)}; |
f7ce3e3a | 3948 | +get_result({data, MySQLRes}) -> |
046546ef | 3949 | + {data, p1_mysql:get_result_rows(MySQLRes)}; |
f7ce3e3a | 3950 | +get_result({error, "query timed out"}) -> |
3951 | + {error, "query timed out"}; | |
3952 | +get_result({error, MySQLRes}) -> | |
046546ef | 3953 | + Reason = p1_mysql:get_result_reason(MySQLRes), |
f7ce3e3a | 3954 | + {error, Reason}. |
046546ef | 3955 | diff --git a/src/mod_logdb_mysql5.erl b/src/mod_logdb_mysql5.erl |
0d78319d | 3956 | new file mode 100644 |
dd02533f | 3957 | index 0000000000..c05ab958e2 |
0d78319d | 3958 | --- /dev/null |
046546ef | 3959 | +++ b/src/mod_logdb_mysql5.erl |
a815cc6c | 3960 | @@ -0,0 +1,979 @@ |
f7ce3e3a | 3961 | +%%%---------------------------------------------------------------------- |
3962 | +%%% File : mod_logdb_mysql5.erl | |
3f23be8e | 3963 | +%%% Author : Oleg Palij (mailto:o.palij@gmail.com) |
f7ce3e3a | 3964 | +%%% Purpose : MySQL 5 backend for mod_logdb |
3f23be8e | 3965 | +%%% Url : https://paleg.github.io/mod_logdb/ |
f7ce3e3a | 3966 | +%%%---------------------------------------------------------------------- |
3967 | + | |
3968 | +-module(mod_logdb_mysql5). | |
3969 | +-author('o.palij@gmail.com'). | |
f7ce3e3a | 3970 | + |
3971 | +-include("mod_logdb.hrl"). | |
046546ef | 3972 | +-include("logger.hrl"). |
f7ce3e3a | 3973 | + |
3974 | +-behaviour(gen_logdb). | |
3975 | +-behaviour(gen_server). | |
3976 | + | |
3977 | +% gen_server | |
3978 | +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]). | |
3979 | +% gen_mod | |
bb18ce72 | 3980 | +-export([start/2, stop/1]). |
f7ce3e3a | 3981 | +% gen_logdb |
3982 | +-export([log_message/2, | |
3983 | + rebuild_stats/1, | |
3984 | + rebuild_stats_at/2, | |
3985 | + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2, | |
3986 | + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3, | |
3987 | + get_dates/1, | |
234c6b10 | 3988 | + get_users_settings/1, get_user_settings/2, set_user_settings/3, |
3989 | + drop_user/2]). | |
f7ce3e3a | 3990 | + |
3991 | +% gen_server call timeout | |
234c6b10 | 3992 | +-define(CALL_TIMEOUT, 30000). |
3993 | +-define(MYSQL_TIMEOUT, 60000). | |
f7ce3e3a | 3994 | +-define(INDEX_SIZE, integer_to_list(170)). |
3995 | +-define(PROCNAME, mod_logdb_mysql5). | |
3996 | + | |
3997 | +-import(mod_logdb, [list_to_bool/1, bool_to_list/1, | |
3998 | + list_to_string/1, string_to_list/1, | |
3999 | + convert_timestamp_brief/1]). | |
4000 | + | |
234c6b10 | 4001 | +-record(state, {dbref, vhost, server, port, db, user, password}). |
f7ce3e3a | 4002 | + |
4003 | +% replace "." with "_" | |
4004 | +escape_vhost(VHost) -> lists:map(fun(46) -> 95; | |
4005 | + (A) -> A | |
046546ef | 4006 | + end, binary_to_list(VHost)). |
f7ce3e3a | 4007 | +prefix() -> |
4008 | + "`logdb_". | |
4009 | + | |
4010 | +suffix(VHost) -> | |
4011 | + "_" ++ escape_vhost(VHost) ++ "`". | |
4012 | + | |
4013 | +messages_table(VHost, Date) -> | |
4014 | + prefix() ++ "messages_" ++ Date ++ suffix(VHost). | |
4015 | + | |
4016 | +% TODO: this needs to be redone to unify view name in stored procedure and in delete_messages_at/2 | |
4017 | +view_table(VHost, Date) -> | |
4018 | + Table = messages_table(VHost, Date), | |
4019 | + TablewoQ = lists:sublist(Table, 2, length(Table) - 2), | |
4020 | + lists:append(["`v_", TablewoQ, "`"]). | |
4021 | + | |
4022 | +stats_table(VHost) -> | |
4023 | + prefix() ++ "stats" ++ suffix(VHost). | |
4024 | + | |
234c6b10 | 4025 | +temp_table(VHost) -> |
4026 | + prefix() ++ "temp" ++ suffix(VHost). | |
4027 | + | |
f7ce3e3a | 4028 | +settings_table(VHost) -> |
4029 | + prefix() ++ "settings" ++ suffix(VHost). | |
4030 | + | |
4031 | +users_table(VHost) -> | |
4032 | + prefix() ++ "users" ++ suffix(VHost). | |
4033 | +servers_table(VHost) -> | |
4034 | + prefix() ++ "servers" ++ suffix(VHost). | |
4035 | +resources_table(VHost) -> | |
4036 | + prefix() ++ "resources" ++ suffix(VHost). | |
4037 | + | |
234c6b10 | 4038 | +logmessage_name(VHost) -> |
4039 | + prefix() ++ "logmessage" ++ suffix(VHost). | |
4040 | + | |
f7ce3e3a | 4041 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
4042 | +% | |
4043 | +% gen_mod callbacks | |
4044 | +% | |
4045 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
4046 | +start(VHost, Opts) -> | |
4047 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
4048 | + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []). | |
4049 | + | |
4050 | +stop(VHost) -> | |
4051 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
4052 | + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT). | |
4053 | + | |
4054 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
4055 | +% | |
4056 | +% gen_server callbacks | |
4057 | +% | |
4058 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
4059 | +init([VHost, Opts]) -> | |
4060 | + crypto:start(), | |
4061 | + | |
046546ef AM |
4062 | + Server = gen_mod:get_opt(server, Opts, fun(A) -> A end, <<"localhost">>), |
4063 | + Port = gen_mod:get_opt(port, Opts, fun(A) -> A end, 3306), | |
4064 | + DB = gen_mod:get_opt(db, Opts, fun(A) -> A end, <<"logdb">>), | |
4065 | + User = gen_mod:get_opt(user, Opts, fun(A) -> A end, <<"root">>), | |
4066 | + Password = gen_mod:get_opt(password, Opts, fun(A) -> A end, <<"">>), | |
f7ce3e3a | 4067 | + |
234c6b10 | 4068 | + St = #state{vhost=VHost, |
4069 | + server=Server, port=Port, db=DB, | |
4070 | + user=User, password=Password}, | |
4071 | + | |
4072 | + case open_mysql_connection(St) of | |
f7ce3e3a | 4073 | + {ok, DBRef} -> |
234c6b10 | 4074 | + State = St#state{dbref=DBRef}, |
4075 | + ok = create_internals(State), | |
4076 | + ok = create_stats_table(State), | |
4077 | + ok = create_settings_table(State), | |
4078 | + ok = create_users_table(State), | |
4079 | + ok = create_servers_table(State), | |
4080 | + ok = create_resources_table(State), | |
f7ce3e3a | 4081 | + erlang:monitor(process, DBRef), |
234c6b10 | 4082 | + {ok, State}; |
f7ce3e3a | 4083 | + {error, Reason} -> |
4084 | + ?ERROR_MSG("MySQL connection failed: ~p~n", [Reason]), | |
4085 | + {stop, db_connection_failed} | |
4086 | + end. | |
4087 | + | |
234c6b10 | 4088 | +open_mysql_connection(#state{server=Server, port=Port, db=DB, |
4089 | + user=DBUser, password=Password} = _State) -> | |
4090 | + LogFun = fun(debug, _Format, _Argument) -> | |
4091 | + %?MYDEBUG(Format, Argument); | |
4092 | + ok; | |
4093 | + (error, Format, Argument) -> | |
4094 | + ?ERROR_MSG(Format, Argument); | |
4095 | + (Level, Format, Argument) -> | |
4096 | + ?MYDEBUG("MySQL (~p)~n", [Level]), | |
4097 | + ?MYDEBUG(Format, Argument) | |
4098 | + end, | |
26b6b0c9 | 4099 | + ?INFO_MSG("Opening mysql connection ~s@~s:~p/~s", [DBUser, Server, Port, DB]), |
046546ef AM |
4100 | + p1_mysql_conn:start(binary_to_list(Server), Port, |
4101 | + binary_to_list(DBUser), binary_to_list(Password), | |
4102 | + binary_to_list(DB), LogFun). | |
f7ce3e3a | 4103 | + |
234c6b10 | 4104 | +close_mysql_connection(DBRef) -> |
4105 | + ?MYDEBUG("Closing ~p mysql connection", [DBRef]), | |
046546ef | 4106 | + catch p1_mysql_conn:stop(DBRef). |
f7ce3e3a | 4107 | + |
f7ce3e3a | 4108 | +handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> |
4109 | + Reply = rebuild_stats_at_int(DBRef, VHost, Date), | |
4110 | + {reply, Reply, State}; | |
4111 | +handle_call({delete_messages_by_user_at, [], _Date}, _From, State) -> | |
4112 | + {reply, error, State}; | |
4113 | +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> | |
4114 | + Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) -> | |
4115 | + ["\"",Timestamp,"\"",","] | |
4116 | + end, Msgs), | |
4117 | + | |
4118 | + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]), | |
4119 | + | |
4120 | + Query = ["DELETE FROM ",messages_table(VHost, Date)," ", | |
4121 | + "WHERE timestamp IN (", Temp1], | |
4122 | + | |
4123 | + Reply = | |
4124 | + case sql_query_internal(DBRef, Query) of | |
4125 | + {updated, Aff} -> | |
4126 | + ?MYDEBUG("Aff=~p", [Aff]), | |
4127 | + rebuild_stats_at_int(DBRef, VHost, Date); | |
4128 | + {error, _} -> | |
4129 | + error | |
4130 | + end, | |
4131 | + {reply, Reply, State}; | |
4132 | +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> | |
234c6b10 | 4133 | + ok = delete_all_messages_by_user_at_int(DBRef, User, VHost, Date), |
4134 | + ok = delete_stats_by_user_at_int(DBRef, User, VHost, Date), | |
4135 | + {reply, ok, State}; | |
f7ce3e3a | 4136 | +handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> |
4137 | + Fun = fun() -> | |
4138 | + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Date),";"]), | |
4139 | + TQuery = ["DELETE FROM ",stats_table(VHost)," " | |
4140 | + "WHERE at=\"",Date,"\";"], | |
4141 | + {updated, _} = sql_query_internal(DBRef, TQuery), | |
4142 | + VQuery = ["DROP VIEW IF EXISTS ",view_table(VHost,Date),";"], | |
234c6b10 | 4143 | + {updated, _} = sql_query_internal(DBRef, VQuery), |
4144 | + ok | |
f7ce3e3a | 4145 | + end, |
4146 | + Reply = | |
234c6b10 | 4147 | + case catch apply(Fun, []) of |
4148 | + ok -> | |
f7ce3e3a | 4149 | + ok; |
234c6b10 | 4150 | + {'EXIT', _} -> |
f7ce3e3a | 4151 | + error |
4152 | + end, | |
4153 | + {reply, Reply, State}; | |
4154 | +handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> | |
4155 | + SName = stats_table(VHost), | |
4156 | + Query = ["SELECT at, sum(count) ", | |
4157 | + "FROM ",SName," ", | |
4158 | + "GROUP BY at ", | |
4159 | + "ORDER BY DATE(at) DESC;" | |
4160 | + ], | |
4161 | + Reply = | |
4162 | + case sql_query_internal(DBRef, Query) of | |
4163 | + {data, Result} -> | |
4164 | + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]}; | |
4165 | + {error, Reason} -> | |
4166 | + % TODO: Duplicate error message ? | |
4167 | + {error, Reason} | |
4168 | + end, | |
4169 | + {reply, Reply, State}; | |
4170 | +handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> | |
4171 | + SName = stats_table(VHost), | |
234c6b10 | 4172 | + Query = ["SELECT username, sum(count) as allcount ", |
f7ce3e3a | 4173 | + "FROM ",SName," ", |
4174 | + "JOIN ",users_table(VHost)," ON owner_id=user_id " | |
4175 | + "WHERE at=\"",Date,"\" ", | |
234c6b10 | 4176 | + "GROUP BY username ", |
4177 | + "ORDER BY allcount DESC;" | |
f7ce3e3a | 4178 | + ], |
4179 | + Reply = | |
4180 | + case sql_query_internal(DBRef, Query) of | |
4181 | + {data, Result} -> | |
4182 | + {ok, [ {User, list_to_integer(Count)} || [User, Count] <- Result ]}; | |
4183 | + {error, Reason} -> | |
4184 | + {error, Reason} | |
4185 | + end, | |
4186 | + {reply, Reply, State}; | |
4187 | +handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> | |
234c6b10 | 4188 | + {reply, get_user_stats_int(DBRef, User, VHost), State}; |
f7ce3e3a | 4189 | +handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> |
4190 | + Query = ["SELECT peer_name,", | |
4191 | + "peer_server,", | |
4192 | + "peer_resource,", | |
4193 | + "direction," | |
4194 | + "type," | |
4195 | + "subject," | |
4196 | + "body," | |
4197 | + "timestamp " | |
4198 | + "FROM ",view_table(VHost, Date)," " | |
4199 | + "WHERE owner_name=\"",User,"\";"], | |
4200 | + Reply = | |
4201 | + case sql_query_internal(DBRef, Query) of | |
4202 | + {data, Result} -> | |
4203 | + Fun = fun([Peer_name, Peer_server, Peer_resource, | |
4204 | + Direction, | |
4205 | + Type, | |
4206 | + Subject, Body, | |
4207 | + Timestamp]) -> | |
4208 | + #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource, | |
4209 | + direction=list_to_atom(Direction), | |
4210 | + type=Type, | |
4211 | + subject=Subject, body=Body, | |
4212 | + timestamp=Timestamp} | |
4213 | + end, | |
4214 | + {ok, lists:map(Fun, Result)}; | |
4215 | + {error, Reason} -> | |
4216 | + {error, Reason} | |
4217 | + end, | |
4218 | + {reply, Reply, State}; | |
4219 | +handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> | |
4220 | + SName = stats_table(VHost), | |
4221 | + Query = ["SELECT at ", | |
4222 | + "FROM ",SName," ", | |
4223 | + "GROUP BY at ", | |
4224 | + "ORDER BY DATE(at) DESC;" | |
4225 | + ], | |
4226 | + Reply = | |
4227 | + case sql_query_internal(DBRef, Query) of | |
4228 | + {data, Result} -> | |
4229 | + [ Date || [Date] <- Result ]; | |
4230 | + {error, Reason} -> | |
4231 | + {error, Reason} | |
4232 | + end, | |
4233 | + {reply, Reply, State}; | |
4234 | +handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> | |
4235 | + Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ", | |
4236 | + "FROM ",settings_table(VHost)," ", | |
4237 | + "JOIN ",users_table(VHost)," ON user_id=owner_id;"], | |
0d78319d | 4238 | + Reply = |
f7ce3e3a | 4239 | + case sql_query_internal(DBRef, Query) of |
4240 | + {data, Result} -> | |
4241 | + {ok, lists:map(fun([Owner, DoLogDef, DoLogL, DoNotLogL]) -> | |
4242 | + #user_settings{owner_name=Owner, | |
4243 | + dolog_default=list_to_bool(DoLogDef), | |
4244 | + dolog_list=string_to_list(DoLogL), | |
4245 | + donotlog_list=string_to_list(DoNotLogL) | |
4246 | + } | |
4247 | + end, Result)}; | |
4248 | + {error, _} -> | |
4249 | + error | |
4250 | + end, | |
4251 | + {reply, Reply, State}; | |
4252 | +handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) -> | |
4253 | + Query = ["SELECT dolog_default,dolog_list,donotlog_list FROM ",settings_table(VHost)," ", | |
4254 | + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"], | |
4255 | + Reply = | |
4256 | + case sql_query_internal(DBRef, Query) of | |
4257 | + {data, []} -> | |
4258 | + {ok, []}; | |
4259 | + {data, [[Owner, DoLogDef, DoLogL, DoNotLogL]]} -> | |
4260 | + {ok, #user_settings{owner_name=Owner, | |
4261 | + dolog_default=list_to_bool(DoLogDef), | |
4262 | + dolog_list=string_to_list(DoLogL), | |
4263 | + donotlog_list=string_to_list(DoNotLogL)}}; | |
4264 | + {error, _} -> | |
4265 | + error | |
4266 | + end, | |
4267 | + {reply, Reply, State}; | |
4268 | +handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef, | |
4269 | + dolog_list=DoLogL, | |
4270 | + donotlog_list=DoNotLogL}}, | |
4271 | + _From, #state{dbref=DBRef, vhost=VHost} = State) -> | |
4272 | + User_id = get_user_id(DBRef, VHost, User), | |
4273 | + Query = ["UPDATE ",settings_table(VHost)," ", | |
4274 | + "SET dolog_default=",bool_to_list(DoLogDef),", ", | |
4275 | + "dolog_list='",list_to_string(DoLogL),"', ", | |
4276 | + "donotlog_list='",list_to_string(DoNotLogL),"' ", | |
4277 | + "WHERE owner_id=",User_id,";"], | |
4278 | + | |
4279 | + Reply = | |
4280 | + case sql_query_internal(DBRef, Query) of | |
4281 | + {updated, 0} -> | |
4282 | + IQuery = ["INSERT INTO ",settings_table(VHost)," ", | |
4283 | + "(owner_id, dolog_default, dolog_list, donotlog_list) ", | |
4284 | + "VALUES ", | |
4285 | + "(",User_id,",",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"], | |
4286 | + case sql_query_internal_silent(DBRef, IQuery) of | |
4287 | + {updated, _} -> | |
4288 | + ?MYDEBUG("New settings for ~s@~s", [User, VHost]), | |
4289 | + ok; | |
4290 | + {error, Reason} -> | |
046546ef | 4291 | + case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>) of |
f7ce3e3a | 4292 | + % Already exists |
0d78319d | 4293 | + match -> |
f7ce3e3a | 4294 | + ok; |
4295 | + _ -> | |
4296 | + ?ERROR_MSG("Failed setup user ~p@~p: ~p", [User, VHost, Reason]), | |
4297 | + error | |
4298 | + end | |
4299 | + end; | |
4300 | + {updated, 1} -> | |
4301 | + ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]), | |
4302 | + ok; | |
4303 | + {error, _} -> | |
4304 | + error | |
4305 | + end, | |
4306 | + {reply, Reply, State}; | |
4307 | +handle_call({stop}, _From, #state{vhost=VHost}=State) -> | |
4308 | + ?MYDEBUG("Stoping mysql5 backend for ~p", [VHost]), | |
4309 | + {stop, normal, ok, State}; | |
4310 | +handle_call(Msg, _From, State) -> | |
4311 | + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]), | |
4312 | + {noreply, State}. | |
4313 | + | |
234c6b10 | 4314 | +handle_cast({log_message, Msg}, #state{dbref=DBRef, vhost=VHost}=State) -> |
4315 | + Fun = fun() -> | |
4316 | + Date = convert_timestamp_brief(Msg#msg.timestamp), | |
4317 | + TableName = messages_table(VHost, Date), | |
4318 | + | |
4319 | + Query = [ "CALL ",logmessage_name(VHost)," " | |
4320 | + "('", TableName, "',", | |
4321 | + "'", Date, "',", | |
046546ef AM |
4322 | + "'", binary_to_list(Msg#msg.owner_name), "',", |
4323 | + "'", binary_to_list(Msg#msg.peer_name), "',", | |
4324 | + "'", binary_to_list(Msg#msg.peer_server), "',", | |
bb18ce72 | 4325 | + "'", binary_to_list( ejabberd_sql:escape(Msg#msg.peer_resource) ), "',", |
234c6b10 | 4326 | + "'", atom_to_list(Msg#msg.direction), "',", |
046546ef | 4327 | + "'", binary_to_list(Msg#msg.type), "',", |
bb18ce72 AM |
4328 | + "'", binary_to_list( ejabberd_sql:escape(Msg#msg.subject) ), "',", |
4329 | + "'", binary_to_list( ejabberd_sql:escape(Msg#msg.body) ), "',", | |
234c6b10 | 4330 | + "'", Msg#msg.timestamp, "');"], |
4331 | + | |
4332 | + case sql_query_internal(DBRef, Query) of | |
4333 | + {updated, _} -> | |
046546ef AM |
4334 | + ?MYDEBUG("Logged ok for ~s, peer: ~s", [ [Msg#msg.owner_name, <<"@">>, VHost], |
4335 | + [Msg#msg.peer_name, <<"@">>, Msg#msg.peer_server] ]), | |
234c6b10 | 4336 | + ok; |
4337 | + {error, _Reason} -> | |
4338 | + error | |
4339 | + end | |
4340 | + end, | |
4341 | + spawn(Fun), | |
4342 | + {noreply, State}; | |
4343 | +handle_cast({rebuild_stats}, State) -> | |
4344 | + rebuild_all_stats_int(State), | |
4345 | + {noreply, State}; | |
4346 | +handle_cast({drop_user, User}, #state{vhost=VHost} = State) -> | |
4347 | + Fun = fun() -> | |
4348 | + {ok, DBRef} = open_mysql_connection(State), | |
4349 | + {ok, Dates} = get_user_stats_int(DBRef, User, VHost), | |
4350 | + MDResult = lists:map(fun({Date, _}) -> | |
4351 | + delete_all_messages_by_user_at_int(DBRef, User, VHost, Date) | |
4352 | + end, Dates), | |
4353 | + StDResult = delete_all_stats_by_user_int(DBRef, User, VHost), | |
4354 | + SDResult = delete_user_settings_int(DBRef, User, VHost), | |
4355 | + case lists:all(fun(Result) when Result == ok -> | |
4356 | + true; | |
4357 | + (Result) when Result == error -> | |
4358 | + false | |
4359 | + end, lists:append([MDResult, [StDResult], [SDResult]])) of | |
4360 | + true -> | |
4361 | + ?INFO_MSG("Removed ~s@~s", [User, VHost]); | |
4362 | + false -> | |
4363 | + ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost]) | |
4364 | + end, | |
4365 | + close_mysql_connection(DBRef) | |
4366 | + end, | |
4367 | + spawn(Fun), | |
4368 | + {noreply, State}; | |
f7ce3e3a | 4369 | +handle_cast(Msg, State) -> |
4370 | + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]), | |
4371 | + {noreply, State}. | |
4372 | + | |
4373 | +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) -> | |
4374 | + {stop, connection_dropped, State}; | |
4375 | +handle_info(Info, State) -> | |
4376 | + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]), | |
4377 | + {noreply, State}. | |
4378 | + | |
234c6b10 | 4379 | +terminate(_Reason, #state{dbref=DBRef}=_State) -> |
4380 | + close_mysql_connection(DBRef), | |
f7ce3e3a | 4381 | + ok. |
4382 | + | |
4383 | +code_change(_OldVsn, State, _Extra) -> | |
4384 | + {ok, State}. | |
4385 | + | |
4386 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
4387 | +% | |
4388 | +% gen_logdb callbacks | |
4389 | +% | |
4390 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
4391 | +log_message(VHost, Msg) -> | |
4392 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
234c6b10 | 4393 | + gen_server:cast(Proc, {log_message, Msg}). |
f7ce3e3a | 4394 | +rebuild_stats(VHost) -> |
4395 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
234c6b10 | 4396 | + gen_server:cast(Proc, {rebuild_stats}). |
f7ce3e3a | 4397 | +rebuild_stats_at(VHost, Date) -> |
4398 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
4399 | + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT). | |
4400 | +delete_messages_by_user_at(VHost, Msgs, Date) -> | |
4401 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
4402 | + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT). | |
4403 | +delete_all_messages_by_user_at(User, VHost, Date) -> | |
4404 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
4405 | + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT). | |
4406 | +delete_messages_at(VHost, Date) -> | |
4407 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
4408 | + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT). | |
4409 | +get_vhost_stats(VHost) -> | |
4410 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
4411 | + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT). | |
4412 | +get_vhost_stats_at(VHost, Date) -> | |
4413 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
4414 | + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT). | |
4415 | +get_user_stats(User, VHost) -> | |
4416 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
4417 | + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT). | |
4418 | +get_user_messages_at(User, VHost, Date) -> | |
4419 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
4420 | + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT). | |
4421 | +get_dates(VHost) -> | |
4422 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
4423 | + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT). | |
4424 | +get_users_settings(VHost) -> | |
4425 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
4426 | + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT). | |
4427 | +get_user_settings(User, VHost) -> | |
4428 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
4429 | + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT). | |
4430 | +set_user_settings(User, VHost, Set) -> | |
4431 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
4432 | + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT). | |
234c6b10 | 4433 | +drop_user(User, VHost) -> |
4434 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
4435 | + gen_server:cast(Proc, {drop_user, User}). | |
f7ce3e3a | 4436 | + |
4437 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
4438 | +% | |
4439 | +% internals | |
4440 | +% | |
4441 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
4442 | +get_dates_int(DBRef, VHost) -> | |
4443 | + case sql_query_internal(DBRef, ["SHOW TABLES"]) of | |
4444 | + {data, Tables} -> | |
3f23be8e | 4445 | + Reg = "^" ++ lists:sublist(prefix(),2,length(prefix())) ++ ".*" ++ escape_vhost(VHost), |
f7ce3e3a | 4446 | + lists:foldl(fun([Table], Dates) -> |
0d78319d | 4447 | + case re:run(Table, Reg) of |
3f23be8e AM |
4448 | + {match, _} -> |
4449 | + case re:run(Table, "[0-9]+-[0-9]+-[0-9]+") of | |
0d78319d | 4450 | + {match, [{S, E}]} -> |
3f23be8e | 4451 | + lists:append(Dates, [lists:sublist(Table, S+1, E)]); |
f7ce3e3a | 4452 | + nomatch -> |
4453 | + Dates | |
4454 | + end; | |
234c6b10 | 4455 | + _ -> |
f7ce3e3a | 4456 | + Dates |
4457 | + end | |
4458 | + end, [], Tables); | |
4459 | + {error, _} -> | |
4460 | + [] | |
4461 | + end. | |
4462 | + | |
234c6b10 | 4463 | +rebuild_all_stats_int(#state{vhost=VHost}=State) -> |
4464 | + Fun = fun() -> | |
4465 | + {ok, DBRef} = open_mysql_connection(State), | |
4466 | + ok = delete_nonexistent_stats(DBRef, VHost), | |
4467 | + case lists:filter(fun(Date) -> | |
4468 | + case catch rebuild_stats_at_int(DBRef, VHost, Date) of | |
4469 | + ok -> false; | |
4470 | + error -> true; | |
4471 | + {'EXIT', _} -> true | |
4472 | + end | |
4473 | + end, get_dates_int(DBRef, VHost)) of | |
4474 | + [] -> ok; | |
4475 | + FTables -> | |
4476 | + ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]), | |
4477 | + error | |
4478 | + end, | |
4479 | + close_mysql_connection(DBRef) | |
4480 | + end, | |
4481 | + spawn(Fun). | |
f7ce3e3a | 4482 | + |
234c6b10 | 4483 | +rebuild_stats_at_int(DBRef, VHost, Date) -> |
4484 | + TempTable = temp_table(VHost), | |
4485 | + Fun = fun() -> | |
4486 | + Table = messages_table(VHost, Date), | |
4487 | + STable = stats_table(VHost), | |
f7ce3e3a | 4488 | + |
234c6b10 | 4489 | + DQuery = [ "DELETE FROM ",STable," ", |
4490 | + "WHERE at='",Date,"';"], | |
f7ce3e3a | 4491 | + |
234c6b10 | 4492 | + ok = create_temp_table(DBRef, TempTable), |
4493 | + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," WRITE, ",TempTable," WRITE;"]), | |
4494 | + SQuery = ["INSERT INTO ",TempTable," ", | |
4495 | + "(owner_id,peer_name_id,peer_server_id,at,count) ", | |
4496 | + "SELECT owner_id,peer_name_id,peer_server_id,\"",Date,"\",count(*) ", | |
4497 | + "FROM ",Table," WHERE ext is NULL GROUP BY owner_id,peer_name_id,peer_server_id;"], | |
4498 | + case sql_query_internal(DBRef, SQuery) of | |
4499 | + {updated, 0} -> | |
4500 | + Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]), | |
4501 | + case Count of | |
4502 | + {data, [["0"]]} -> | |
234c6b10 | 4503 | + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]), |
046546ef AM |
4504 | + sql_query_internal(DBRef, ["UNLOCK TABLES;"]), |
4505 | + {updated, _} = sql_query_internal(DBRef, ["DROP VIEW IF EXISTS ",view_table(VHost,Date),";"]), | |
4506 | + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE, ",TempTable," WRITE;"]), | |
234c6b10 | 4507 | + {updated, _} = sql_query_internal(DBRef, DQuery), |
4508 | + ok; | |
4509 | + _ -> | |
4510 | + ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]), | |
4511 | + error | |
4512 | + end; | |
4513 | + {updated, _} -> | |
4514 | + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE, ",TempTable," WRITE;"]), | |
4515 | + {updated, _} = sql_query_internal(DBRef, DQuery), | |
4516 | + SQuery1 = ["INSERT INTO ",STable," ", | |
4517 | + "(owner_id,peer_name_id,peer_server_id,at,count) ", | |
4518 | + "SELECT owner_id,peer_name_id,peer_server_id,at,count ", | |
4519 | + "FROM ",TempTable,";"], | |
4520 | + case sql_query_internal(DBRef, SQuery1) of | |
4521 | + {updated, _} -> ok; | |
4522 | + {error, _} -> error | |
4523 | + end; | |
4524 | + {error, _} -> error | |
4525 | + end | |
4526 | + end, | |
f7ce3e3a | 4527 | + |
234c6b10 | 4528 | + case catch apply(Fun, []) of |
4529 | + ok -> | |
4530 | + ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]), | |
4531 | + ok; | |
4532 | + error -> | |
4533 | + error; | |
4534 | + {'EXIT', Reason} -> | |
4535 | + ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]), | |
4536 | + error | |
4537 | + end, | |
4538 | + sql_query_internal(DBRef, ["UNLOCK TABLES;"]), | |
4539 | + sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]), | |
4540 | + ok. | |
f7ce3e3a | 4541 | + |
4542 | +delete_nonexistent_stats(DBRef, VHost) -> | |
4543 | + Dates = get_dates_int(DBRef, VHost), | |
4544 | + STable = stats_table(VHost), | |
4545 | + | |
4546 | + Temp = lists:flatmap(fun(Date) -> | |
4547 | + ["\"",Date,"\"",","] | |
4548 | + end, Dates), | |
234c6b10 | 4549 | + case Temp of |
4550 | + [] -> | |
4551 | + ok; | |
4552 | + _ -> | |
4553 | + % replace last "," with ");" | |
4554 | + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]), | |
4555 | + Query = ["DELETE FROM ",STable," ", | |
4556 | + "WHERE at NOT IN (", Temp1], | |
4557 | + case sql_query_internal(DBRef, Query) of | |
4558 | + {updated, _} -> | |
4559 | + ok; | |
4560 | + {error, _} -> | |
4561 | + error | |
4562 | + end | |
4563 | + end. | |
f7ce3e3a | 4564 | + |
234c6b10 | 4565 | +get_user_stats_int(DBRef, User, VHost) -> |
4566 | + SName = stats_table(VHost), | |
4567 | + UName = users_table(VHost), | |
4568 | + Query = ["SELECT stats.at, sum(stats.count) ", | |
4569 | + "FROM ",UName," AS users ", | |
4570 | + "JOIN ",SName," AS stats ON owner_id=user_id " | |
4571 | + "WHERE users.username=\"",User,"\" ", | |
4572 | + "GROUP BY stats.at " | |
4573 | + "ORDER BY DATE(stats.at) DESC;" | |
4574 | + ], | |
4575 | + case sql_query_internal(DBRef, Query) of | |
4576 | + {data, Result} -> | |
4577 | + {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]}; | |
4578 | + {error, Result} -> | |
4579 | + {error, Result} | |
4580 | + end. | |
4581 | + | |
4582 | +delete_all_messages_by_user_at_int(DBRef, User, VHost, Date) -> | |
4583 | + DQuery = ["DELETE FROM ",messages_table(VHost, Date)," ", | |
4584 | + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"], | |
4585 | + case sql_query_internal(DBRef, DQuery) of | |
4586 | + {updated, _} -> | |
4587 | + ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]), | |
4588 | + ok; | |
4589 | + {error, _} -> | |
4590 | + error | |
4591 | + end. | |
4592 | + | |
4593 | +delete_all_stats_by_user_int(DBRef, User, VHost) -> | |
4594 | + SQuery = ["DELETE FROM ",stats_table(VHost)," ", | |
4595 | + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"], | |
4596 | + case sql_query_internal(DBRef, SQuery) of | |
4597 | + {updated, _} -> | |
4598 | + ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]), | |
4599 | + ok; | |
4600 | + {error, _} -> error | |
4601 | + end. | |
f7ce3e3a | 4602 | + |
234c6b10 | 4603 | +delete_stats_by_user_at_int(DBRef, User, VHost, Date) -> |
4604 | + SQuery = ["DELETE FROM ",stats_table(VHost)," ", | |
4605 | + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\") ", | |
4606 | + "AND at=\"",Date,"\";"], | |
4607 | + case sql_query_internal(DBRef, SQuery) of | |
4608 | + {updated, _} -> | |
4609 | + ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]), | |
4610 | + ok; | |
4611 | + {error, _} -> error | |
4612 | + end. | |
f7ce3e3a | 4613 | + |
234c6b10 | 4614 | +delete_user_settings_int(DBRef, User, VHost) -> |
4615 | + Query = ["DELETE FROM ",settings_table(VHost)," ", | |
4616 | + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"], | |
f7ce3e3a | 4617 | + case sql_query_internal(DBRef, Query) of |
4618 | + {updated, _} -> | |
234c6b10 | 4619 | + ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]), |
f7ce3e3a | 4620 | + ok; |
234c6b10 | 4621 | + {error, Reason} -> |
4622 | + ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]), | |
f7ce3e3a | 4623 | + error |
4624 | + end. | |
4625 | + | |
4626 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
4627 | +% | |
4628 | +% tables internals | |
4629 | +% | |
4630 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
234c6b10 | 4631 | +create_temp_table(DBRef, Name) -> |
4632 | + Query = ["CREATE TABLE ",Name," (", | |
4633 | + "owner_id MEDIUMINT UNSIGNED, ", | |
4634 | + "peer_name_id MEDIUMINT UNSIGNED, ", | |
4635 | + "peer_server_id MEDIUMINT UNSIGNED, ", | |
4636 | + "at VARCHAR(11), ", | |
4637 | + "count INT(11) ", | |
4638 | + ") ENGINE=MyISAM CHARACTER SET utf8;" | |
4639 | + ], | |
4640 | + case sql_query_internal(DBRef, Query) of | |
4641 | + {updated, _} -> ok; | |
4642 | + {error, _Reason} -> error | |
4643 | + end. | |
4644 | + | |
4645 | +create_stats_table(#state{dbref=DBRef, vhost=VHost}=State) -> | |
f7ce3e3a | 4646 | + SName = stats_table(VHost), |
4647 | + Query = ["CREATE TABLE ",SName," (", | |
4648 | + "owner_id MEDIUMINT UNSIGNED, ", | |
234c6b10 | 4649 | + "peer_name_id MEDIUMINT UNSIGNED, ", |
4650 | + "peer_server_id MEDIUMINT UNSIGNED, ", | |
f7ce3e3a | 4651 | + "at VARCHAR(11), ", |
4652 | + "count INT(11), ", | |
234c6b10 | 4653 | + "ext INTEGER DEFAULT NULL, " |
4654 | + "INDEX ext_i (ext), " | |
4655 | + "INDEX(owner_id,peer_name_id,peer_server_id), ", | |
4656 | + "INDEX(at) ", | |
4657 | + ") ENGINE=MyISAM CHARACTER SET utf8;" | |
f7ce3e3a | 4658 | + ], |
4659 | + case sql_query_internal_silent(DBRef, Query) of | |
4660 | + {updated, _} -> | |
4661 | + ?MYDEBUG("Created stats table for ~p", [VHost]), | |
234c6b10 | 4662 | + rebuild_all_stats_int(State), |
f7ce3e3a | 4663 | + ok; |
4664 | + {error, Reason} -> | |
046546ef | 4665 | + case ejabberd_regexp:run(iolist_to_binary(Reason), <<"#42S01">>) of |
0d78319d | 4666 | + match -> |
f7ce3e3a | 4667 | + ?MYDEBUG("Stats table for ~p already exists", [VHost]), |
234c6b10 | 4668 | + CheckQuery = ["SHOW COLUMNS FROM ",SName," LIKE 'peer_%_id';"], |
4669 | + case sql_query_internal(DBRef, CheckQuery) of | |
4670 | + {data, Elems} when length(Elems) == 2 -> | |
4671 | + ?MYDEBUG("Stats table structure is ok", []), | |
4672 | + ok; | |
4673 | + _ -> | |
4674 | + ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []), | |
4675 | + case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of | |
4676 | + {updated, _} -> | |
4677 | + ?INFO_MSG("Successfully dropped ~p", [SName]); | |
4678 | + _ -> | |
4679 | + ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName]) | |
4680 | + end, | |
4681 | + error | |
4682 | + end; | |
f7ce3e3a | 4683 | + _ -> |
4684 | + ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]), | |
4685 | + error | |
4686 | + end | |
4687 | + end. | |
4688 | + | |
234c6b10 | 4689 | +create_settings_table(#state{dbref=DBRef, vhost=VHost}) -> |
f7ce3e3a | 4690 | + SName = settings_table(VHost), |
4691 | + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (", | |
4692 | + "owner_id MEDIUMINT UNSIGNED PRIMARY KEY, ", | |
4693 | + "dolog_default TINYINT(1) NOT NULL DEFAULT 1, ", | |
4694 | + "dolog_list TEXT, ", | |
4695 | + "donotlog_list TEXT ", | |
4696 | + ") ENGINE=InnoDB CHARACTER SET utf8;" | |
4697 | + ], | |
4698 | + case sql_query_internal(DBRef, Query) of | |
4699 | + {updated, _} -> | |
4700 | + ?MYDEBUG("Created settings table for ~p", [VHost]), | |
4701 | + ok; | |
4702 | + {error, _} -> | |
4703 | + error | |
4704 | + end. | |
4705 | + | |
234c6b10 | 4706 | +create_users_table(#state{dbref=DBRef, vhost=VHost}) -> |
f7ce3e3a | 4707 | + SName = users_table(VHost), |
4708 | + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (", | |
4709 | + "username TEXT NOT NULL, ", | |
4710 | + "user_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ", | |
4711 | + "UNIQUE INDEX(username(",?INDEX_SIZE,")) ", | |
4712 | + ") ENGINE=InnoDB CHARACTER SET utf8;" | |
4713 | + ], | |
4714 | + case sql_query_internal(DBRef, Query) of | |
4715 | + {updated, _} -> | |
4716 | + ?MYDEBUG("Created users table for ~p", [VHost]), | |
4717 | + ok; | |
4718 | + {error, _} -> | |
4719 | + error | |
4720 | + end. | |
4721 | + | |
234c6b10 | 4722 | +create_servers_table(#state{dbref=DBRef, vhost=VHost}) -> |
f7ce3e3a | 4723 | + SName = servers_table(VHost), |
4724 | + Query = ["CREATE TABLE IF NOT EXISTS ",SName," (", | |
4725 | + "server TEXT NOT NULL, ", | |
4726 | + "server_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ", | |
4727 | + "UNIQUE INDEX(server(",?INDEX_SIZE,")) ", | |
4728 | + ") ENGINE=InnoDB CHARACTER SET utf8;" | |
4729 | + ], | |
4730 | + case sql_query_internal(DBRef, Query) of | |
4731 | + {updated, _} -> | |
4732 | + ?MYDEBUG("Created servers table for ~p", [VHost]), | |
4733 | + ok; | |
4734 | + {error, _} -> | |
4735 | + error | |
4736 | + end. | |
4737 | + | |
234c6b10 | 4738 | +create_resources_table(#state{dbref=DBRef, vhost=VHost}) -> |
f7ce3e3a | 4739 | + RName = resources_table(VHost), |
4740 | + Query = ["CREATE TABLE IF NOT EXISTS ",RName," (", | |
4741 | + "resource TEXT NOT NULL, ", | |
4742 | + "resource_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ", | |
4743 | + "UNIQUE INDEX(resource(",?INDEX_SIZE,")) ", | |
4744 | + ") ENGINE=InnoDB CHARACTER SET utf8;" | |
4745 | + ], | |
4746 | + case sql_query_internal(DBRef, Query) of | |
4747 | + {updated, _} -> | |
4748 | + ?MYDEBUG("Created resources table for ~p", [VHost]), | |
4749 | + ok; | |
4750 | + {error, _} -> | |
4751 | + error | |
4752 | + end. | |
4753 | + | |
234c6b10 | 4754 | +create_internals(#state{dbref=DBRef, vhost=VHost}) -> |
4755 | + sql_query_internal(DBRef, ["DROP PROCEDURE IF EXISTS ",logmessage_name(VHost),";"]), | |
f7ce3e3a | 4756 | + case sql_query_internal(DBRef, [get_logmessage(VHost)]) of |
4757 | + {updated, _} -> | |
4758 | + ?MYDEBUG("Created logmessage for ~p", [VHost]), | |
4759 | + ok; | |
4760 | + {error, _} -> | |
4761 | + error | |
4762 | + end. | |
4763 | + | |
4764 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
4765 | +% | |
0d78319d | 4766 | +% SQL internals |
f7ce3e3a | 4767 | +% |
4768 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
f7ce3e3a | 4769 | +sql_query_internal(DBRef, Query) -> |
4770 | + case sql_query_internal_silent(DBRef, Query) of | |
4771 | + {error, Reason} -> | |
4772 | + ?ERROR_MSG("~p while ~p", [Reason, lists:append(Query)]), | |
4773 | + {error, Reason}; | |
4774 | + Rez -> Rez | |
4775 | + end. | |
4776 | + | |
4777 | +sql_query_internal_silent(DBRef, Query) -> | |
4778 | + ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]), | |
046546ef | 4779 | + get_result(p1_mysql_conn:fetch(DBRef, Query, self(), ?MYSQL_TIMEOUT)). |
f7ce3e3a | 4780 | + |
4781 | +get_result({updated, MySQLRes}) -> | |
046546ef | 4782 | + {updated, p1_mysql:get_result_affected_rows(MySQLRes)}; |
f7ce3e3a | 4783 | +get_result({data, MySQLRes}) -> |
046546ef | 4784 | + {data, p1_mysql:get_result_rows(MySQLRes)}; |
f7ce3e3a | 4785 | +get_result({error, "query timed out"}) -> |
4786 | + {error, "query timed out"}; | |
4787 | +get_result({error, MySQLRes}) -> | |
046546ef | 4788 | + Reason = p1_mysql:get_result_reason(MySQLRes), |
f7ce3e3a | 4789 | + {error, Reason}. |
4790 | + | |
4791 | +get_user_id(DBRef, VHost, User) -> | |
4792 | + SQuery = ["SELECT user_id FROM ",users_table(VHost)," ", | |
4793 | + "WHERE username=\"",User,"\";"], | |
4794 | + case sql_query_internal(DBRef, SQuery) of | |
4795 | + {data, []} -> | |
4796 | + IQuery = ["INSERT INTO ",users_table(VHost)," ", | |
4797 | + "SET username=\"",User,"\";"], | |
4798 | + case sql_query_internal_silent(DBRef, IQuery) of | |
4799 | + {updated, _} -> | |
4800 | + {data, [[DBIdNew]]} = sql_query_internal(DBRef, SQuery), | |
4801 | + DBIdNew; | |
4802 | + {error, Reason} -> | |
4803 | + % this can be in clustered environment | |
046546ef | 4804 | + match = ejabberd_regexp:run(iolist_to_binary(Reason), <<"#23000">>), |
f7ce3e3a | 4805 | + ?ERROR_MSG("Duplicate key name for ~p", [User]), |
4806 | + {data, [[ClID]]} = sql_query_internal(DBRef, SQuery), | |
4807 | + ClID | |
4808 | + end; | |
4809 | + {data, [[DBId]]} -> | |
4810 | + DBId | |
4811 | + end. | |
4812 | + | |
4813 | +get_logmessage(VHost) -> | |
4814 | + UName = users_table(VHost), | |
4815 | + SName = servers_table(VHost), | |
4816 | + RName = resources_table(VHost), | |
4817 | + StName = stats_table(VHost), | |
4818 | + io_lib:format(" | |
234c6b10 | 4819 | +CREATE PROCEDURE ~s(tablename TEXT, atdate TEXT, owner TEXT, peer_name TEXT, peer_server TEXT, peer_resource TEXT, mdirection VARCHAR(4), mtype VARCHAR(10), msubject TEXT, mbody TEXT, mtimestamp DOUBLE) |
f7ce3e3a | 4820 | +BEGIN |
0d78319d | 4821 | + DECLARE ownerID MEDIUMINT UNSIGNED; |
f7ce3e3a | 4822 | + DECLARE peer_nameID MEDIUMINT UNSIGNED; |
4823 | + DECLARE peer_serverID MEDIUMINT UNSIGNED; | |
4824 | + DECLARE peer_resourceID MEDIUMINT UNSIGNED; | |
4825 | + DECLARE Vmtype VARCHAR(10); | |
4826 | + DECLARE Vmtimestamp DOUBLE; | |
4827 | + DECLARE Vmdirection VARCHAR(4); | |
4828 | + DECLARE Vmbody TEXT; | |
4829 | + DECLARE Vmsubject TEXT; | |
4830 | + DECLARE iq TEXT; | |
4831 | + DECLARE cq TEXT; | |
4832 | + DECLARE viewname TEXT; | |
4833 | + DECLARE notable INT; | |
4834 | + DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' SET @notable = 1; | |
4835 | + | |
4836 | + SET @notable = 0; | |
4837 | + SET @ownerID = NULL; | |
4838 | + SET @peer_nameID = NULL; | |
4839 | + SET @peer_serverID = NULL; | |
4840 | + SET @peer_resourceID = NULL; | |
4841 | + | |
4842 | + SET @Vmtype = mtype; | |
4843 | + SET @Vmtimestamp = mtimestamp; | |
4844 | + SET @Vmdirection = mdirection; | |
4845 | + SET @Vmbody = mbody; | |
4846 | + SET @Vmsubject = msubject; | |
4847 | + | |
4848 | + SELECT user_id INTO @ownerID FROM ~s WHERE username=owner; | |
4849 | + IF @ownerID IS NULL THEN | |
4850 | + INSERT INTO ~s SET username=owner; | |
0d78319d | 4851 | + SET @ownerID = LAST_INSERT_ID(); |
f7ce3e3a | 4852 | + END IF; |
4853 | + | |
4854 | + SELECT user_id INTO @peer_nameID FROM ~s WHERE username=peer_name; | |
4855 | + IF @peer_nameID IS NULL THEN | |
4856 | + INSERT INTO ~s SET username=peer_name; | |
4857 | + SET @peer_nameID = LAST_INSERT_ID(); | |
4858 | + END IF; | |
4859 | + | |
4860 | + SELECT server_id INTO @peer_serverID FROM ~s WHERE server=peer_server; | |
4861 | + IF @peer_serverID IS NULL THEN | |
4862 | + INSERT INTO ~s SET server=peer_server; | |
4863 | + SET @peer_serverID = LAST_INSERT_ID(); | |
4864 | + END IF; | |
4865 | + | |
4866 | + SELECT resource_id INTO @peer_resourceID FROM ~s WHERE resource=peer_resource; | |
4867 | + IF @peer_resourceID IS NULL THEN | |
4868 | + INSERT INTO ~s SET resource=peer_resource; | |
4869 | + SET @peer_resourceID = LAST_INSERT_ID(); | |
4870 | + END IF; | |
4871 | + | |
4872 | + SET @iq = CONCAT(\"INSERT INTO \",tablename,\" (owner_id, peer_name_id, peer_server_id, peer_resource_id, direction, type, subject, body, timestamp) VALUES (@ownerID,@peer_nameID,@peer_serverID,@peer_resourceID,@Vmdirection,@Vmtype,@Vmsubject,@Vmbody,@Vmtimestamp);\"); | |
4873 | + PREPARE insertmsg FROM @iq; | |
4874 | + | |
4875 | + IF @notable = 1 THEN | |
4876 | + SET @cq = CONCAT(\"CREATE TABLE \",tablename,\" ( | |
234c6b10 | 4877 | + owner_id MEDIUMINT UNSIGNED NOT NULL, |
4878 | + peer_name_id MEDIUMINT UNSIGNED NOT NULL, | |
4879 | + peer_server_id MEDIUMINT UNSIGNED NOT NULL, | |
4880 | + peer_resource_id MEDIUMINT(8) UNSIGNED NOT NULL, | |
4881 | + direction ENUM('to', 'from') NOT NULL, | |
f7ce3e3a | 4882 | + type ENUM('chat','error','groupchat','headline','normal') NOT NULL, |
4883 | + subject TEXT, | |
4884 | + body TEXT, | |
234c6b10 | 4885 | + timestamp DOUBLE NOT NULL, |
f7ce3e3a | 4886 | + ext INTEGER DEFAULT NULL, |
234c6b10 | 4887 | + INDEX search_i (owner_id, peer_name_id, peer_server_id, peer_resource_id), |
f7ce3e3a | 4888 | + INDEX ext_i (ext), |
4889 | + FULLTEXT (body) | |
234c6b10 | 4890 | + ) ENGINE=MyISAM |
4891 | + PACK_KEYS=1 | |
4892 | + CHARACTER SET utf8;\"); | |
f7ce3e3a | 4893 | + PREPARE createtable FROM @cq; |
4894 | + EXECUTE createtable; | |
4895 | + DEALLOCATE PREPARE createtable; | |
4896 | + | |
4897 | + SET @viewname = CONCAT(\"`v_\", TRIM(BOTH '`' FROM tablename), \"`\"); | |
4898 | + SET @cq = CONCAT(\"CREATE OR REPLACE VIEW \",@viewname,\" AS | |
4899 | + SELECT owner.username AS owner_name, | |
4900 | + peer.username AS peer_name, | |
4901 | + servers.server AS peer_server, | |
4902 | + resources.resource AS peer_resource, | |
4903 | + messages.direction, | |
4904 | + messages.type, | |
4905 | + messages.subject, | |
4906 | + messages.body, | |
4907 | + messages.timestamp | |
4908 | + FROM | |
4909 | + ~s owner, | |
4910 | + ~s peer, | |
4911 | + ~s servers, | |
4912 | + ~s resources, | |
4913 | + \", tablename,\" messages | |
4914 | + WHERE | |
4915 | + owner.user_id=messages.owner_id and | |
4916 | + peer.user_id=messages.peer_name_id and | |
4917 | + servers.server_id=messages.peer_server_id and | |
4918 | + resources.resource_id=messages.peer_resource_id | |
4919 | + ORDER BY messages.timestamp;\"); | |
4920 | + PREPARE createview FROM @cq; | |
4921 | + EXECUTE createview; | |
4922 | + DEALLOCATE PREPARE createview; | |
4923 | + | |
4924 | + SET @notable = 0; | |
4925 | + PREPARE insertmsg FROM @iq; | |
4926 | + EXECUTE insertmsg; | |
4927 | + ELSEIF @notable = 0 THEN | |
4928 | + EXECUTE insertmsg; | |
4929 | + END IF; | |
4930 | + | |
4931 | + DEALLOCATE PREPARE insertmsg; | |
4932 | + | |
4933 | + IF @notable = 0 THEN | |
234c6b10 | 4934 | + UPDATE ~s SET count=count+1 WHERE owner_id=@ownerID AND peer_name_id=@peer_nameID AND peer_server_id=@peer_serverID AND at=atdate; |
f7ce3e3a | 4935 | + IF ROW_COUNT() = 0 THEN |
234c6b10 | 4936 | + INSERT INTO ~s (owner_id, peer_name_id, peer_server_id, at, count) VALUES (@ownerID, @peer_nameID, @peer_serverID, atdate, 1); |
f7ce3e3a | 4937 | + END IF; |
4938 | + END IF; | |
234c6b10 | 4939 | +END;", [logmessage_name(VHost),UName,UName,UName,UName,SName,SName,RName,RName,UName,UName,SName,RName,StName,StName]). |
046546ef | 4940 | diff --git a/src/mod_logdb_pgsql.erl b/src/mod_logdb_pgsql.erl |
0d78319d | 4941 | new file mode 100644 |
dd02533f | 4942 | index 0000000000..202c6ed4a8 |
0d78319d | 4943 | --- /dev/null |
046546ef | 4944 | +++ b/src/mod_logdb_pgsql.erl |
a815cc6c | 4945 | @@ -0,0 +1,1104 @@ |
046546ef AM |
4946 | +% {ok, DBRef} = pgsql:connect([{host, "127.0.0.1"}, {database, "logdb"}, {user, "logdb"}, {password, "logdb"}, {port, 5432}, {as_binary, true}]). |
4947 | +% Schema = "test". | |
4948 | +% pgsql:squery(DBRef, "CREATE TABLE test.\"logdb_stats_test\" (owner_id INTEGER, peer_name_id INTEGER, peer_server_id INTEGER, at VARCHAR(20), count integer);" ). | |
f7ce3e3a | 4949 | +%%%---------------------------------------------------------------------- |
4950 | +%%% File : mod_logdb_pgsql.erl | |
3f23be8e | 4951 | +%%% Author : Oleg Palij (mailto:o.palij@gmail.com) |
f7ce3e3a | 4952 | +%%% Purpose : Posgresql backend for mod_logdb |
3f23be8e | 4953 | +%%% Url : https://paleg.github.io/mod_logdb/ |
f7ce3e3a | 4954 | +%%%---------------------------------------------------------------------- |
4955 | + | |
4956 | +-module(mod_logdb_pgsql). | |
4957 | +-author('o.palij@gmail.com'). | |
f7ce3e3a | 4958 | + |
4959 | +-include("mod_logdb.hrl"). | |
046546ef | 4960 | +-include("logger.hrl"). |
f7ce3e3a | 4961 | + |
4962 | +-behaviour(gen_logdb). | |
4963 | +-behaviour(gen_server). | |
4964 | + | |
4965 | +% gen_server | |
4966 | +-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]). | |
4967 | +% gen_mod | |
bb18ce72 | 4968 | +-export([start/2, stop/1]). |
f7ce3e3a | 4969 | +% gen_logdb |
4970 | +-export([log_message/2, | |
4971 | + rebuild_stats/1, | |
4972 | + rebuild_stats_at/2, | |
4973 | + delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2, | |
4974 | + get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3, | |
4975 | + get_dates/1, | |
234c6b10 | 4976 | + get_users_settings/1, get_user_settings/2, set_user_settings/3, |
4977 | + drop_user/2]). | |
4978 | + | |
4979 | +-export([view_table/3]). | |
f7ce3e3a | 4980 | + |
4981 | +% gen_server call timeout | |
234c6b10 | 4982 | +-define(CALL_TIMEOUT, 30000). |
4983 | +-define(PGSQL_TIMEOUT, 60000). | |
f7ce3e3a | 4984 | +-define(PROCNAME, mod_logdb_pgsql). |
4985 | + | |
4986 | +-import(mod_logdb, [list_to_bool/1, bool_to_list/1, | |
4987 | + list_to_string/1, string_to_list/1, | |
4988 | + convert_timestamp_brief/1]). | |
4989 | + | |
234c6b10 | 4990 | +-record(state, {dbref, vhost, server, port, db, user, password, schema}). |
f7ce3e3a | 4991 | + |
4992 | +% replace "." with "_" | |
4993 | +escape_vhost(VHost) -> lists:map(fun(46) -> 95; | |
4994 | + (A) -> A | |
046546ef | 4995 | + end, binary_to_list(VHost)). |
f7ce3e3a | 4996 | + |
4997 | +prefix(Schema) -> | |
4998 | + Schema ++ ".\"" ++ "logdb_". | |
4999 | + | |
5000 | +suffix(VHost) -> | |
5001 | + "_" ++ escape_vhost(VHost) ++ "\"". | |
5002 | + | |
5003 | +messages_table(VHost, Schema, Date) -> | |
5004 | + prefix(Schema) ++ "messages_" ++ Date ++ suffix(VHost). | |
5005 | + | |
f7ce3e3a | 5006 | +view_table(VHost, Schema, Date) -> |
5007 | + Table = messages_table(VHost, Schema, Date), | |
5008 | + TablewoS = lists:sublist(Table, length(Schema) + 3, length(Table) - length(Schema) - 3), | |
5009 | + lists:append([Schema, ".\"v_", TablewoS, "\""]). | |
5010 | + | |
5011 | +stats_table(VHost, Schema) -> | |
5012 | + prefix(Schema) ++ "stats" ++ suffix(VHost). | |
5013 | + | |
234c6b10 | 5014 | +temp_table(VHost, Schema) -> |
5015 | + prefix(Schema) ++ "temp" ++ suffix(VHost). | |
5016 | + | |
f7ce3e3a | 5017 | +settings_table(VHost, Schema) -> |
5018 | + prefix(Schema) ++ "settings" ++ suffix(VHost). | |
5019 | + | |
5020 | +users_table(VHost, Schema) -> | |
5021 | + prefix(Schema) ++ "users" ++ suffix(VHost). | |
5022 | +servers_table(VHost, Schema) -> | |
5023 | + prefix(Schema) ++ "servers" ++ suffix(VHost). | |
5024 | +resources_table(VHost, Schema) -> | |
5025 | + prefix(Schema) ++ "resources" ++ suffix(VHost). | |
5026 | + | |
234c6b10 | 5027 | +logmessage_name(VHost, Schema) -> |
5028 | + prefix(Schema) ++ "logmessage" ++ suffix(VHost). | |
5029 | + | |
f7ce3e3a | 5030 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
5031 | +% | |
5032 | +% gen_mod callbacks | |
5033 | +% | |
5034 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
5035 | +start(VHost, Opts) -> | |
5036 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
5037 | + gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []). | |
5038 | + | |
5039 | +stop(VHost) -> | |
5040 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
5041 | + gen_server:call(Proc, {stop}, ?CALL_TIMEOUT). | |
5042 | + | |
5043 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
5044 | +% | |
5045 | +% gen_server callbacks | |
5046 | +% | |
5047 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
5048 | +init([VHost, Opts]) -> | |
046546ef AM |
5049 | + Server = gen_mod:get_opt(server, Opts, fun(A) -> A end, <<"localhost">>), |
5050 | + DB = gen_mod:get_opt(db, Opts, fun(A) -> A end, <<"ejabberd_logdb">>), | |
5051 | + User = gen_mod:get_opt(user, Opts, fun(A) -> A end, <<"root">>), | |
5052 | + Port = gen_mod:get_opt(port, Opts, fun(A) -> A end, 5432), | |
5053 | + Password = gen_mod:get_opt(password, Opts, fun(A) -> A end, <<"">>), | |
5054 | + Schema = binary_to_list(gen_mod:get_opt(schema, Opts, fun(A) -> A end, <<"public">>)), | |
f7ce3e3a | 5055 | + |
046546ef | 5056 | + ?MYDEBUG("Starting pgsql backend for ~s", [VHost]), |
234c6b10 | 5057 | + |
5058 | + St = #state{vhost=VHost, | |
5059 | + server=Server, port=Port, db=DB, | |
5060 | + user=User, password=Password, | |
5061 | + schema=Schema}, | |
5062 | + | |
5063 | + case open_pgsql_connection(St) of | |
f7ce3e3a | 5064 | + {ok, DBRef} -> |
234c6b10 | 5065 | + State = St#state{dbref=DBRef}, |
5066 | + ok = create_internals(State), | |
5067 | + ok = create_stats_table(State), | |
5068 | + ok = create_settings_table(State), | |
5069 | + ok = create_users_table(State), | |
5070 | + ok = create_servers_table(State), | |
5071 | + ok = create_resources_table(State), | |
f7ce3e3a | 5072 | + erlang:monitor(process, DBRef), |
234c6b10 | 5073 | + {ok, State}; |
f7ce3e3a | 5074 | + % this does not work |
5075 | + {error, Reason} -> | |
5076 | + ?ERROR_MSG("PgSQL connection failed: ~p~n", [Reason]), | |
5077 | + {stop, db_connection_failed}; | |
5078 | + % and this too, becouse pgsql_conn do exit() which can not be catched | |
5079 | + {'EXIT', Rez} -> | |
5080 | + ?ERROR_MSG("Rez: ~p~n", [Rez]), | |
5081 | + {stop, db_connection_failed} | |
5082 | + end. | |
5083 | + | |
234c6b10 | 5084 | +open_pgsql_connection(#state{server=Server, port=Port, db=DB, schema=Schema, |
5085 | + user=User, password=Password} = _State) -> | |
26b6b0c9 | 5086 | + ?INFO_MSG("Opening pgsql connection ~s@~s:~p/~s", [User, Server, Port, DB]), |
234c6b10 | 5087 | + {ok, DBRef} = pgsql:connect(Server, DB, User, Password, Port), |
5088 | + {updated, _} = sql_query_internal(DBRef, ["SET SEARCH_PATH TO ",Schema,";"]), | |
5089 | + {ok, DBRef}. | |
5090 | + | |
5091 | +close_pgsql_connection(DBRef) -> | |
5092 | + ?MYDEBUG("Closing ~p pgsql connection", [DBRef]), | |
5093 | + pgsql:terminate(DBRef). | |
5094 | + | |
f7ce3e3a | 5095 | +handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) -> |
5096 | + Date = convert_timestamp_brief(Msg#msg.timestamp), | |
5097 | + TableName = messages_table(VHost, Schema, Date), | |
234c6b10 | 5098 | + ViewName = view_table(VHost, Schema, Date), |
f7ce3e3a | 5099 | + |
234c6b10 | 5100 | + Query = [ "SELECT ", logmessage_name(VHost, Schema)," " |
f7ce3e3a | 5101 | + "('", TableName, "',", |
234c6b10 | 5102 | + "'", ViewName, "',", |
f7ce3e3a | 5103 | + "'", Date, "',", |
046546ef AM |
5104 | + "'", binary_to_list(Msg#msg.owner_name), "',", |
5105 | + "'", binary_to_list(Msg#msg.peer_name), "',", | |
5106 | + "'", binary_to_list(Msg#msg.peer_server), "',", | |
bb18ce72 | 5107 | + "'", binary_to_list( ejabberd_sql:escape(Msg#msg.peer_resource) ), "',", |
234c6b10 | 5108 | + "'", atom_to_list(Msg#msg.direction), "',", |
046546ef | 5109 | + "'", binary_to_list(Msg#msg.type), "',", |
bb18ce72 AM |
5110 | + "'", binary_to_list( ejabberd_sql:escape(Msg#msg.subject) ), "',", |
5111 | + "'", binary_to_list( ejabberd_sql:escape(Msg#msg.body) ), "',", | |
234c6b10 | 5112 | + "'", Msg#msg.timestamp, "');"], |
5113 | + | |
5114 | + case sql_query_internal_silent(DBRef, Query) of | |
5115 | + % TODO: change this | |
5116 | + {data, [{"0"}]} -> | |
046546ef AM |
5117 | + ?MYDEBUG("Logged ok for ~s, peer: ~s", [ [Msg#msg.owner_name, <<"@">>, VHost], |
5118 | + [Msg#msg.peer_name, <<"@">>, Msg#msg.peer_server] ]), | |
234c6b10 | 5119 | + ok; |
5120 | + {error, _Reason} -> | |
5121 | + error | |
5122 | + end, | |
5123 | + {reply, ok, State}; | |
f7ce3e3a | 5124 | +handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) -> |
5125 | + Reply = rebuild_stats_at_int(DBRef, VHost, Schema, Date), | |
5126 | + {reply, Reply, State}; | |
5127 | +handle_call({delete_messages_by_user_at, [], _Date}, _From, State) -> | |
5128 | + {reply, error, State}; | |
5129 | +handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) -> | |
5130 | + Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) -> | |
5131 | + ["'",Timestamp,"'",","] | |
5132 | + end, Msgs), | |
5133 | + | |
5134 | + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]), | |
5135 | + | |
5136 | + Query = ["DELETE FROM ",messages_table(VHost, Schema, Date)," ", | |
5137 | + "WHERE timestamp IN (", Temp1], | |
5138 | + | |
5139 | + Reply = | |
5140 | + case sql_query_internal(DBRef, Query) of | |
5141 | + {updated, _} -> | |
5142 | + rebuild_stats_at_int(DBRef, VHost, Schema, Date); | |
5143 | + {error, _} -> | |
5144 | + error | |
5145 | + end, | |
5146 | + {reply, Reply, State}; | |
5147 | +handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) -> | |
234c6b10 | 5148 | + ok = delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date), |
5149 | + ok = delete_stats_by_user_at_int(DBRef, Schema, User, VHost, Date), | |
5150 | + {reply, ok, State}; | |
f7ce3e3a | 5151 | +handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) -> |
f7ce3e3a | 5152 | + {updated, _} = sql_query_internal(DBRef, ["DROP VIEW ",view_table(VHost, Schema, Date),";"]), |
5153 | + Reply = | |
234c6b10 | 5154 | + case sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Schema, Date)," CASCADE;"]) of |
f7ce3e3a | 5155 | + {updated, _} -> |
5156 | + Query = ["DELETE FROM ",stats_table(VHost, Schema)," " | |
5157 | + "WHERE at='",Date,"';"], | |
5158 | + case sql_query_internal(DBRef, Query) of | |
5159 | + {updated, _} -> | |
5160 | + ok; | |
5161 | + {error, _} -> | |
5162 | + error | |
5163 | + end; | |
5164 | + {error, _} -> | |
5165 | + error | |
5166 | + end, | |
5167 | + {reply, Reply, State}; | |
5168 | +handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) -> | |
5169 | + SName = stats_table(VHost, Schema), | |
5170 | + Query = ["SELECT at, sum(count) ", | |
5171 | + "FROM ",SName," ", | |
5172 | + "GROUP BY at ", | |
5173 | + "ORDER BY DATE(at) DESC;" | |
5174 | + ], | |
5175 | + Reply = | |
5176 | + case sql_query_internal(DBRef, Query) of | |
5177 | + {data, Recs} -> | |
5178 | + {ok, [ {Date, list_to_integer(Count)} || {Date, Count} <- Recs]}; | |
5179 | + {error, Reason} -> | |
5180 | + % TODO: Duplicate error message ? | |
5181 | + {error, Reason} | |
5182 | + end, | |
5183 | + {reply, Reply, State}; | |
5184 | +handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) -> | |
5185 | + SName = stats_table(VHost, Schema), | |
234c6b10 | 5186 | + Query = ["SELECT username, sum(count) AS allcount ", |
f7ce3e3a | 5187 | + "FROM ",SName," ", |
234c6b10 | 5188 | + "JOIN ",users_table(VHost, Schema)," ON owner_id=user_id ", |
5189 | + "WHERE at='",Date,"' ", | |
5190 | + "GROUP BY username ", | |
5191 | + "ORDER BY allcount DESC;" | |
f7ce3e3a | 5192 | + ], |
5193 | + Reply = | |
5194 | + case sql_query_internal(DBRef, Query) of | |
5195 | + {data, Recs} -> | |
5196 | + RFun = fun({User, Count}) -> | |
5197 | + {User, list_to_integer(Count)} | |
5198 | + end, | |
5199 | + {ok, lists:reverse(lists:keysort(2, lists:map(RFun, Recs)))}; | |
5200 | + {error, Reason} -> | |
5201 | + % TODO: | |
5202 | + {error, Reason} | |
5203 | + end, | |
5204 | + {reply, Reply, State}; | |
5205 | +handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) -> | |
234c6b10 | 5206 | + {reply, get_user_stats_int(DBRef, Schema, User, VHost), State}; |
f7ce3e3a | 5207 | +handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) -> |
5208 | + Query = ["SELECT peer_name,", | |
5209 | + "peer_server,", | |
5210 | + "peer_resource,", | |
5211 | + "direction," | |
5212 | + "type," | |
5213 | + "subject," | |
5214 | + "body," | |
5215 | + "timestamp " | |
5216 | + "FROM ",view_table(VHost, Schema, Date)," " | |
5217 | + "WHERE owner_name='",User,"';"], | |
5218 | + Reply = | |
5219 | + case sql_query_internal(DBRef, Query) of | |
5220 | + {data, Recs} -> | |
5221 | + Fun = fun({Peer_name, Peer_server, Peer_resource, | |
5222 | + Direction, | |
5223 | + Type, | |
5224 | + Subject, Body, | |
5225 | + Timestamp}) -> | |
5226 | + #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource, | |
5227 | + direction=list_to_atom(Direction), | |
5228 | + type=Type, | |
5229 | + subject=Subject, body=Body, | |
5230 | + timestamp=Timestamp} | |
5231 | + end, | |
5232 | + {ok, lists:map(Fun, Recs)}; | |
5233 | + {error, Reason} -> | |
5234 | + {error, Reason} | |
5235 | + end, | |
5236 | + {reply, Reply, State}; | |
5237 | +handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) -> | |
5238 | + SName = stats_table(VHost, Schema), | |
5239 | + Query = ["SELECT at ", | |
5240 | + "FROM ",SName," ", | |
5241 | + "GROUP BY at ", | |
5242 | + "ORDER BY at DESC;" | |
5243 | + ], | |
5244 | + Reply = | |
5245 | + case sql_query_internal(DBRef, Query) of | |
5246 | + {data, Result} -> | |
5247 | + [ Date || {Date} <- Result ]; | |
5248 | + {error, Reason} -> | |
5249 | + {error, Reason} | |
5250 | + end, | |
5251 | + {reply, Reply, State}; | |
5252 | +handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) -> | |
5253 | + Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ", | |
5254 | + "FROM ",settings_table(VHost, Schema)," ", | |
5255 | + "JOIN ",users_table(VHost, Schema)," ON user_id=owner_id;"], | |
5256 | + Reply = | |
5257 | + case sql_query_internal(DBRef, Query) of | |
5258 | + {data, Recs} -> | |
5259 | + {ok, [#user_settings{owner_name=Owner, | |
5260 | + dolog_default=list_to_bool(DoLogDef), | |
5261 | + dolog_list=string_to_list(DoLogL), | |
5262 | + donotlog_list=string_to_list(DoNotLogL) | |
5263 | + } || {Owner, DoLogDef, DoLogL, DoNotLogL} <- Recs]}; | |
5264 | + {error, Reason} -> | |
5265 | + {error, Reason} | |
5266 | + end, | |
5267 | + {reply, Reply, State}; | |
5268 | +handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) -> | |
5269 | + Query = ["SELECT dolog_default,dolog_list,donotlog_list ", | |
5270 | + "FROM ",settings_table(VHost, Schema)," ", | |
5271 | + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"], | |
5272 | + Reply = | |
5273 | + case sql_query_internal_silent(DBRef, Query) of | |
5274 | + {data, []} -> | |
5275 | + {ok, []}; | |
5276 | + {data, [{DoLogDef, DoLogL, DoNotLogL}]} -> | |
5277 | + {ok, #user_settings{owner_name=User, | |
5278 | + dolog_default=list_to_bool(DoLogDef), | |
5279 | + dolog_list=string_to_list(DoLogL), | |
5280 | + donotlog_list=string_to_list(DoNotLogL)}}; | |
5281 | + {error, Reason} -> | |
046546ef | 5282 | + ?ERROR_MSG("Failed to get_user_settings for ~s@~s: ~p", [User, VHost, Reason]), |
f7ce3e3a | 5283 | + error |
5284 | + end, | |
5285 | + {reply, Reply, State}; | |
5286 | +handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef, | |
5287 | + dolog_list=DoLogL, | |
5288 | + donotlog_list=DoNotLogL}}, | |
5289 | + _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) -> | |
5290 | + User_id = get_user_id(DBRef, VHost, Schema, User), | |
5291 | + Query = ["UPDATE ",settings_table(VHost, Schema)," ", | |
5292 | + "SET dolog_default=",bool_to_list(DoLogDef),", ", | |
5293 | + "dolog_list='",list_to_string(DoLogL),"', ", | |
5294 | + "donotlog_list='",list_to_string(DoNotLogL),"' ", | |
5295 | + "WHERE owner_id=",User_id,";"], | |
5296 | + | |
5297 | + Reply = | |
5298 | + case sql_query_internal(DBRef, Query) of | |
5299 | + {updated, 0} -> | |
5300 | + IQuery = ["INSERT INTO ",settings_table(VHost, Schema)," ", | |
5301 | + "(owner_id, dolog_default, dolog_list, donotlog_list) ", | |
5302 | + "VALUES ", | |
5303 | + "(",User_id,", ",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"], | |
5304 | + case sql_query_internal(DBRef, IQuery) of | |
5305 | + {updated, 1} -> | |
5306 | + ?MYDEBUG("New settings for ~s@~s", [User, VHost]), | |
5307 | + ok; | |
5308 | + {error, _} -> | |
5309 | + error | |
5310 | + end; | |
5311 | + {updated, 1} -> | |
5312 | + ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]), | |
5313 | + ok; | |
5314 | + {error, _} -> | |
5315 | + error | |
5316 | + end, | |
5317 | + {reply, Reply, State}; | |
5318 | +handle_call({stop}, _From, State) -> | |
5319 | + ?MYDEBUG("Stoping pgsql backend for ~p", [State#state.vhost]), | |
5320 | + {stop, normal, ok, State}; | |
5321 | +handle_call(Msg, _From, State) -> | |
5322 | + ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]), | |
5323 | + {noreply, State}. | |
5324 | + | |
234c6b10 | 5325 | + |
5326 | +handle_cast({rebuild_stats}, State) -> | |
5327 | + rebuild_all_stats_int(State), | |
5328 | + {noreply, State}; | |
5329 | +handle_cast({drop_user, User}, #state{vhost=VHost, schema=Schema}=State) -> | |
5330 | + Fun = fun() -> | |
5331 | + {ok, DBRef} = open_pgsql_connection(State), | |
5332 | + {ok, Dates} = get_user_stats_int(DBRef, Schema, User, VHost), | |
5333 | + MDResult = lists:map(fun({Date, _}) -> | |
5334 | + delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date) | |
5335 | + end, Dates), | |
5336 | + StDResult = delete_all_stats_by_user_int(DBRef, Schema, User, VHost), | |
5337 | + SDResult = delete_user_settings_int(DBRef, Schema, User, VHost), | |
5338 | + case lists:all(fun(Result) when Result == ok -> | |
5339 | + true; | |
5340 | + (Result) when Result == error -> | |
5341 | + false | |
5342 | + end, lists:append([MDResult, [StDResult], [SDResult]])) of | |
5343 | + true -> | |
5344 | + ?INFO_MSG("Removed ~s@~s", [User, VHost]); | |
5345 | + false -> | |
5346 | + ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost]) | |
5347 | + end, | |
5348 | + close_pgsql_connection(DBRef) | |
5349 | + end, | |
5350 | + spawn(Fun), | |
5351 | + {noreply, State}; | |
f7ce3e3a | 5352 | +handle_cast(Msg, State) -> |
5353 | + ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]), | |
5354 | + {noreply, State}. | |
5355 | + | |
5356 | +handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) -> | |
5357 | + {stop, connection_dropped, State}; | |
5358 | +handle_info(Info, State) -> | |
5359 | + ?INFO_MSG("Got Info:~p, State:~p", [Info, State]), | |
5360 | + {noreply, State}. | |
5361 | + | |
234c6b10 | 5362 | +terminate(_Reason, #state{dbref=DBRef}=_State) -> |
5363 | + close_pgsql_connection(DBRef), | |
f7ce3e3a | 5364 | + ok. |
5365 | + | |
5366 | +code_change(_OldVsn, State, _Extra) -> | |
5367 | + {ok, State}. | |
5368 | + | |
5369 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
5370 | +% | |
5371 | +% gen_logdb callbacks | |
5372 | +% | |
5373 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
5374 | +log_message(VHost, Msg) -> | |
5375 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
5376 | + gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT). | |
5377 | +rebuild_stats(VHost) -> | |
5378 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
234c6b10 | 5379 | + gen_server:cast(Proc, {rebuild_stats}). |
f7ce3e3a | 5380 | +rebuild_stats_at(VHost, Date) -> |
5381 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
5382 | + gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT). | |
5383 | +delete_messages_by_user_at(VHost, Msgs, Date) -> | |
5384 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
5385 | + gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT). | |
5386 | +delete_all_messages_by_user_at(User, VHost, Date) -> | |
5387 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
5388 | + gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT). | |
5389 | +delete_messages_at(VHost, Date) -> | |
5390 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
5391 | + gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT). | |
5392 | +get_vhost_stats(VHost) -> | |
5393 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
5394 | + gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT). | |
5395 | +get_vhost_stats_at(VHost, Date) -> | |
5396 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
5397 | + gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT). | |
5398 | +get_user_stats(User, VHost) -> | |
5399 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
5400 | + gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT). | |
5401 | +get_user_messages_at(User, VHost, Date) -> | |
5402 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
5403 | + gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT). | |
5404 | +get_dates(VHost) -> | |
5405 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
5406 | + gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT). | |
5407 | +get_users_settings(VHost) -> | |
5408 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
5409 | + gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT). | |
5410 | +get_user_settings(User, VHost) -> | |
5411 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
5412 | + gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT). | |
5413 | +set_user_settings(User, VHost, Set) -> | |
5414 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
5415 | + gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT). | |
234c6b10 | 5416 | +drop_user(User, VHost) -> |
5417 | + Proc = gen_mod:get_module_proc(VHost, ?PROCNAME), | |
5418 | + gen_server:cast(Proc, {drop_user, User}). | |
f7ce3e3a | 5419 | + |
5420 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
5421 | +% | |
5422 | +% internals | |
5423 | +% | |
5424 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
5425 | +get_dates_int(DBRef, VHost) -> | |
5426 | + Query = ["SELECT n.nspname as \"Schema\", | |
5427 | + c.relname as \"Name\", | |
5428 | + CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' END as \"Type\", | |
5429 | + r.rolname as \"Owner\" | |
5430 | + FROM pg_catalog.pg_class c | |
5431 | + JOIN pg_catalog.pg_roles r ON r.oid = c.relowner | |
5432 | + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace | |
5433 | + WHERE c.relkind IN ('r','') | |
5434 | + AND n.nspname NOT IN ('pg_catalog', 'pg_toast') | |
5435 | + AND c.relname ~ '^(.*",escape_vhost(VHost),".*)$' | |
5436 | + AND pg_catalog.pg_table_is_visible(c.oid) | |
5437 | + ORDER BY 1,2;"], | |
5438 | + case sql_query_internal(DBRef, Query) of | |
5439 | + {data, Recs} -> | |
5440 | + lists:foldl(fun({_Schema, Table, _Type, _Owner}, Dates) -> | |
0d78319d AM |
5441 | + case re:run(Table,"[0-9]+-[0-9]+-[0-9]+") of |
5442 | + {match, [{S, E}]} -> | |
3f23be8e | 5443 | + lists:append(Dates, [lists:sublist(Table, S+1, E)]); |
f7ce3e3a | 5444 | + nomatch -> |
5445 | + Dates | |
5446 | + end | |
5447 | + end, [], Recs); | |
5448 | + {error, _} -> | |
5449 | + [] | |
5450 | + end. | |
5451 | + | |
234c6b10 | 5452 | +rebuild_all_stats_int(#state{vhost=VHost, schema=Schema}=State) -> |
5453 | + Fun = fun() -> | |
5454 | + {ok, DBRef} = open_pgsql_connection(State), | |
5455 | + ok = delete_nonexistent_stats(DBRef, Schema, VHost), | |
5456 | + case lists:filter(fun(Date) -> | |
5457 | + case catch rebuild_stats_at_int(DBRef, VHost, Schema, Date) of | |
5458 | + ok -> false; | |
5459 | + error -> true; | |
5460 | + {'EXIT', _} -> true | |
5461 | + end | |
5462 | + end, get_dates_int(DBRef, VHost)) of | |
5463 | + [] -> ok; | |
5464 | + FTables -> | |
5465 | + ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]), | |
5466 | + error | |
5467 | + end, | |
5468 | + close_pgsql_connection(DBRef) | |
5469 | + end, | |
5470 | + spawn(Fun). | |
f7ce3e3a | 5471 | + |
234c6b10 | 5472 | +rebuild_stats_at_int(DBRef, VHost, Schema, Date) -> |
5473 | + TempTable = temp_table(VHost, Schema), | |
f7ce3e3a | 5474 | + Fun = |
5475 | + fun() -> | |
234c6b10 | 5476 | + Table = messages_table(VHost, Schema, Date), |
5477 | + STable = stats_table(VHost, Schema), | |
f7ce3e3a | 5478 | + |
5479 | + DQuery = [ "DELETE FROM ",STable," ", | |
5480 | + "WHERE at='",Date,"';"], | |
5481 | + | |
234c6b10 | 5482 | + ok = create_temp_table(DBRef, VHost, Schema), |
5483 | + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," IN ACCESS EXCLUSIVE MODE;"]), | |
5484 | + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",TempTable," IN ACCESS EXCLUSIVE MODE;"]), | |
5485 | + SQuery = ["INSERT INTO ",TempTable," ", | |
5486 | + "(owner_id,peer_name_id,peer_server_id,at,count) ", | |
5487 | + "SELECT owner_id,peer_name_id,peer_server_id,'",Date,"'",",count(*) ", | |
5488 | + "FROM ",Table," GROUP BY owner_id,peer_name_id,peer_server_id;"], | |
f7ce3e3a | 5489 | + case sql_query_internal(DBRef, SQuery) of |
5490 | + {updated, 0} -> | |
234c6b10 | 5491 | + Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]), |
5492 | + case Count of | |
5493 | + {data, [{"0"}]} -> | |
5494 | + {updated, _} = sql_query_internal(DBRef, ["DROP VIEW ",view_table(VHost, Schema, Date),";"]), | |
5495 | + {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table," CASCADE;"]), | |
5496 | + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," IN ACCESS EXCLUSIVE MODE;"]), | |
5497 | + {updated, _} = sql_query_internal(DBRef, DQuery), | |
5498 | + ok; | |
5499 | + _ -> | |
5500 | + ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]), | |
5501 | + error | |
5502 | + end; | |
5503 | + {updated, _} -> | |
5504 | + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," IN ACCESS EXCLUSIVE MODE;"]), | |
5505 | + {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",TempTable," IN ACCESS EXCLUSIVE MODE;"]), | |
5506 | + {updated, _} = sql_query_internal(DBRef, DQuery), | |
5507 | + SQuery1 = ["INSERT INTO ",STable," ", | |
5508 | + "(owner_id,peer_name_id,peer_server_id,at,count) ", | |
5509 | + "SELECT owner_id,peer_name_id,peer_server_id,at,count ", | |
5510 | + "FROM ",TempTable,";"], | |
5511 | + case sql_query_internal(DBRef, SQuery1) of | |
5512 | + {updated, _} -> ok; | |
5513 | + {error, _} -> error | |
5514 | + end; | |
f7ce3e3a | 5515 | + {error, _} -> error |
5516 | + end | |
234c6b10 | 5517 | + end, % fun |
f7ce3e3a | 5518 | + |
5519 | + case sql_transaction_internal(DBRef, Fun) of | |
5520 | + {atomic, _} -> | |
046546ef | 5521 | + ?INFO_MSG("Rebuilded stats for ~s at ~s", [VHost, Date]), |
234c6b10 | 5522 | + ok; |
5523 | + {aborted, Reason} -> | |
5524 | + ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]), | |
5525 | + error | |
5526 | + end, | |
5527 | + sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]), | |
5528 | + ok. | |
f7ce3e3a | 5529 | + |
234c6b10 | 5530 | +delete_nonexistent_stats(DBRef, Schema, VHost) -> |
f7ce3e3a | 5531 | + Dates = get_dates_int(DBRef, VHost), |
5532 | + STable = stats_table(VHost, Schema), | |
5533 | + | |
5534 | + Temp = lists:flatmap(fun(Date) -> | |
5535 | + ["'",Date,"'",","] | |
5536 | + end, Dates), | |
5537 | + | |
234c6b10 | 5538 | + case Temp of |
5539 | + [] -> | |
5540 | + ok; | |
5541 | + _ -> | |
5542 | + % replace last "," with ");" | |
5543 | + Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]), | |
5544 | + Query = ["DELETE FROM ",STable," ", | |
5545 | + "WHERE at NOT IN (", Temp1], | |
5546 | + case sql_query_internal(DBRef, Query) of | |
5547 | + {updated, _} -> | |
5548 | + ok; | |
5549 | + {error, _} -> | |
5550 | + error | |
5551 | + end | |
5552 | + end. | |
f7ce3e3a | 5553 | + |
234c6b10 | 5554 | +get_user_stats_int(DBRef, Schema, User, VHost) -> |
5555 | + SName = stats_table(VHost, Schema), | |
5556 | + UName = users_table(VHost, Schema), | |
5557 | + Query = ["SELECT stats.at, sum(stats.count) ", | |
5558 | + "FROM ",UName," AS users ", | |
5559 | + "JOIN ",SName," AS stats ON owner_id=user_id " | |
5560 | + "WHERE users.username='",User,"' ", | |
5561 | + "GROUP BY stats.at " | |
5562 | + "ORDER BY DATE(at) DESC;" | |
5563 | + ], | |
f7ce3e3a | 5564 | + case sql_query_internal(DBRef, Query) of |
234c6b10 | 5565 | + {data, Recs} -> |
5566 | + {ok, [ {Date, list_to_integer(Count)} || {Date, Count} <- Recs ]}; | |
5567 | + {error, Result} -> | |
5568 | + {error, Result} | |
5569 | + end. | |
5570 | + | |
5571 | +delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date) -> | |
5572 | + DQuery = ["DELETE FROM ",messages_table(VHost, Schema, Date)," ", | |
5573 | + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"], | |
5574 | + case sql_query_internal(DBRef, DQuery) of | |
f7ce3e3a | 5575 | + {updated, _} -> |
234c6b10 | 5576 | + ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]), |
f7ce3e3a | 5577 | + ok; |
5578 | + {error, _} -> | |
5579 | + error | |
5580 | + end. | |
5581 | + | |
234c6b10 | 5582 | +delete_all_stats_by_user_int(DBRef, Schema, User, VHost) -> |
5583 | + SQuery = ["DELETE FROM ",stats_table(VHost, Schema)," ", | |
5584 | + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"], | |
5585 | + case sql_query_internal(DBRef, SQuery) of | |
5586 | + {updated, _} -> | |
5587 | + ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]), | |
5588 | + ok; | |
5589 | + {error, _} -> error | |
5590 | + end. | |
5591 | + | |
5592 | +delete_stats_by_user_at_int(DBRef, Schema, User, VHost, Date) -> | |
5593 | + SQuery = ["DELETE FROM ",stats_table(VHost, Schema)," ", | |
5594 | + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"') ", | |
5595 | + "AND at='",Date,"';"], | |
5596 | + case sql_query_internal(DBRef, SQuery) of | |
5597 | + {updated, _} -> | |
5598 | + ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]), | |
5599 | + ok; | |
5600 | + {error, _} -> error | |
5601 | + end. | |
5602 | + | |
5603 | +delete_user_settings_int(DBRef, Schema, User, VHost) -> | |
5604 | + Query = ["DELETE FROM ",settings_table(VHost, Schema)," ", | |
5605 | + "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"], | |
5606 | + case sql_query_internal(DBRef, Query) of | |
5607 | + {updated, _} -> | |
5608 | + ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]), | |
5609 | + ok; | |
5610 | + {error, Reason} -> | |
5611 | + ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]), | |
5612 | + error | |
5613 | + end. | |
5614 | + | |
f7ce3e3a | 5615 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
5616 | +% | |
5617 | +% tables internals | |
5618 | +% | |
5619 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
234c6b10 | 5620 | +create_temp_table(DBRef, VHost, Schema) -> |
5621 | + TName = temp_table(VHost, Schema), | |
5622 | + Query = ["CREATE TABLE ",TName," (", | |
5623 | + "owner_id INTEGER, ", | |
5624 | + "peer_name_id INTEGER, ", | |
5625 | + "peer_server_id INTEGER, ", | |
5626 | + "at VARCHAR(20), ", | |
5627 | + "count INTEGER ", | |
5628 | + ");" | |
5629 | + ], | |
5630 | + case sql_query_internal(DBRef, Query) of | |
5631 | + {updated, _} -> ok; | |
5632 | + {error, _Reason} -> error | |
5633 | + end. | |
5634 | + | |
5635 | +create_stats_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}=State) -> | |
f7ce3e3a | 5636 | + SName = stats_table(VHost, Schema), |
5637 | + | |
5638 | + Fun = | |
5639 | + fun() -> | |
5640 | + Query = ["CREATE TABLE ",SName," (", | |
5641 | + "owner_id INTEGER, ", | |
234c6b10 | 5642 | + "peer_name_id INTEGER, ", |
5643 | + "peer_server_id INTEGER, ", | |
f7ce3e3a | 5644 | + "at VARCHAR(20), ", |
5645 | + "count integer", | |
5646 | + ");" | |
5647 | + ], | |
5648 | + case sql_query_internal_silent(DBRef, Query) of | |
5649 | + {updated, _} -> | |
234c6b10 | 5650 | + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"s_search_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (owner_id, peer_name_id, peer_server_id);"]), |
f7ce3e3a | 5651 | + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"s_at_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (at);"]), |
5652 | + created; | |
5653 | + {error, Reason} -> | |
5654 | + case lists:keysearch(code, 1, Reason) of | |
5655 | + {value, {code, "42P07"}} -> | |
5656 | + exists; | |
5657 | + _ -> | |
046546ef | 5658 | + ?ERROR_MSG("Failed to create stats table for ~s: ~p", [VHost, Reason]), |
f7ce3e3a | 5659 | + error |
5660 | + end | |
5661 | + end | |
5662 | + end, | |
5663 | + case sql_transaction_internal(DBRef, Fun) of | |
5664 | + {atomic, created} -> | |
046546ef | 5665 | + ?MYDEBUG("Created stats table for ~s", [VHost]), |
234c6b10 | 5666 | + rebuild_all_stats_int(State), |
5667 | + ok; | |
f7ce3e3a | 5668 | + {atomic, exists} -> |
046546ef | 5669 | + ?MYDEBUG("Stats table for ~s already exists", [VHost]), |
0d78319d | 5670 | + {match, [{F, L}]} = re:run(SName, "\".*\""), |
046546ef | 5671 | + QTable = lists:sublist(SName, F+2, L-2), |
234c6b10 | 5672 | + 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);"], |
5673 | + {data,[{OID}]} = sql_query_internal(DBRef, OIDQuery), | |
5674 | + 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$';"], | |
5675 | + case sql_query_internal(DBRef, CheckQuery) of | |
5676 | + {data, Elems} when length(Elems) == 2 -> | |
5677 | + ?MYDEBUG("Stats table structure is ok", []), | |
5678 | + ok; | |
5679 | + _ -> | |
5680 | + ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []), | |
5681 | + case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of | |
5682 | + {updated, _} -> | |
5683 | + ?INFO_MSG("Successfully dropped ~p", [SName]); | |
5684 | + _ -> | |
5685 | + ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName]) | |
5686 | + end, | |
5687 | + error | |
5688 | + end; | |
f7ce3e3a | 5689 | + {error, _} -> error |
5690 | + end. | |
5691 | + | |
234c6b10 | 5692 | +create_settings_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) -> |
f7ce3e3a | 5693 | + SName = settings_table(VHost, Schema), |
5694 | + Query = ["CREATE TABLE ",SName," (", | |
5695 | + "owner_id INTEGER PRIMARY KEY, ", | |
5696 | + "dolog_default BOOLEAN, ", | |
5697 | + "dolog_list TEXT DEFAULT '', ", | |
5698 | + "donotlog_list TEXT DEFAULT ''", | |
5699 | + ");" | |
5700 | + ], | |
5701 | + case sql_query_internal_silent(DBRef, Query) of | |
5702 | + {updated, _} -> | |
046546ef | 5703 | + ?MYDEBUG("Created settings table for ~s", [VHost]), |
f7ce3e3a | 5704 | + ok; |
5705 | + {error, Reason} -> | |
5706 | + case lists:keysearch(code, 1, Reason) of | |
5707 | + {value, {code, "42P07"}} -> | |
046546ef | 5708 | + ?MYDEBUG("Settings table for ~s already exists", [VHost]), |
f7ce3e3a | 5709 | + ok; |
5710 | + _ -> | |
046546ef | 5711 | + ?ERROR_MSG("Failed to create settings table for ~s: ~p", [VHost, Reason]), |
f7ce3e3a | 5712 | + error |
5713 | + end | |
5714 | + end. | |
5715 | + | |
234c6b10 | 5716 | +create_users_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) -> |
f7ce3e3a | 5717 | + SName = users_table(VHost, Schema), |
5718 | + | |
5719 | + Fun = | |
5720 | + fun() -> | |
5721 | + Query = ["CREATE TABLE ",SName," (", | |
5722 | + "username TEXT UNIQUE, ", | |
5723 | + "user_id SERIAL PRIMARY KEY", | |
5724 | + ");" | |
5725 | + ], | |
5726 | + case sql_query_internal_silent(DBRef, Query) of | |
5727 | + {updated, _} -> | |
5728 | + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"username_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (username);"]), | |
5729 | + created; | |
5730 | + {error, Reason} -> | |
5731 | + case lists:keysearch(code, 1, Reason) of | |
5732 | + {value, {code, "42P07"}} -> | |
5733 | + exists; | |
5734 | + _ -> | |
046546ef | 5735 | + ?ERROR_MSG("Failed to create users table for ~s: ~p", [VHost, Reason]), |
f7ce3e3a | 5736 | + error |
5737 | + end | |
5738 | + end | |
5739 | + end, | |
5740 | + case sql_transaction_internal(DBRef, Fun) of | |
5741 | + {atomic, created} -> | |
046546ef | 5742 | + ?MYDEBUG("Created users table for ~s", [VHost]), |
f7ce3e3a | 5743 | + ok; |
0d78319d | 5744 | + {atomic, exists} -> |
046546ef | 5745 | + ?MYDEBUG("Users table for ~s already exists", [VHost]), |
f7ce3e3a | 5746 | + ok; |
5747 | + {aborted, _} -> error | |
5748 | + end. | |
5749 | + | |
234c6b10 | 5750 | +create_servers_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) -> |
f7ce3e3a | 5751 | + SName = servers_table(VHost, Schema), |
f7ce3e3a | 5752 | + Fun = |
5753 | + fun() -> | |
5754 | + Query = ["CREATE TABLE ",SName," (", | |
5755 | + "server TEXT UNIQUE, ", | |
5756 | + "server_id SERIAL PRIMARY KEY", | |
5757 | + ");" | |
5758 | + ], | |
5759 | + case sql_query_internal_silent(DBRef, Query) of | |
5760 | + {updated, _} -> | |
5761 | + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"server_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (server);"]), | |
5762 | + created; | |
5763 | + {error, Reason} -> | |
5764 | + case lists:keysearch(code, 1, Reason) of | |
5765 | + {value, {code, "42P07"}} -> | |
5766 | + exists; | |
5767 | + _ -> | |
046546ef | 5768 | + ?ERROR_MSG("Failed to create servers table for ~s: ~p", [VHost, Reason]), |
f7ce3e3a | 5769 | + error |
5770 | + end | |
5771 | + end | |
5772 | + end, | |
5773 | + case sql_transaction_internal(DBRef, Fun) of | |
5774 | + {atomic, created} -> | |
046546ef | 5775 | + ?MYDEBUG("Created servers table for ~s", [VHost]), |
f7ce3e3a | 5776 | + ok; |
5777 | + {atomic, exists} -> | |
046546ef | 5778 | + ?MYDEBUG("Servers table for ~s already exists", [VHost]), |
f7ce3e3a | 5779 | + ok; |
5780 | + {aborted, _} -> error | |
5781 | + end. | |
5782 | + | |
234c6b10 | 5783 | +create_resources_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) -> |
f7ce3e3a | 5784 | + RName = resources_table(VHost, Schema), |
5785 | + Fun = fun() -> | |
5786 | + Query = ["CREATE TABLE ",RName," (", | |
5787 | + "resource TEXT UNIQUE, ", | |
5788 | + "resource_id SERIAL PRIMARY KEY", | |
5789 | + ");" | |
5790 | + ], | |
5791 | + case sql_query_internal_silent(DBRef, Query) of | |
5792 | + {updated, _} -> | |
5793 | + {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"resource_i_",Schema,"_",escape_vhost(VHost),"\" ON ",RName," (resource);"]), | |
5794 | + created; | |
5795 | + {error, Reason} -> | |
5796 | + case lists:keysearch(code, 1, Reason) of | |
5797 | + {value, {code, "42P07"}} -> | |
5798 | + exists; | |
5799 | + _ -> | |
046546ef | 5800 | + ?ERROR_MSG("Failed to create users table for ~s: ~p", [VHost, Reason]), |
f7ce3e3a | 5801 | + error |
5802 | + end | |
5803 | + end | |
5804 | + end, | |
5805 | + case sql_transaction_internal(DBRef, Fun) of | |
5806 | + {atomic, created} -> | |
046546ef | 5807 | + ?MYDEBUG("Created resources table for ~s", [VHost]), |
f7ce3e3a | 5808 | + ok; |
5809 | + {atomic, exists} -> | |
046546ef | 5810 | + ?MYDEBUG("Resources table for ~s already exists", [VHost]), |
f7ce3e3a | 5811 | + ok; |
5812 | + {aborted, _} -> error | |
5813 | + end. | |
5814 | + | |
26b6b0c9 | 5815 | +create_internals(#state{dbref=DBRef, vhost=VHost, schema=Schema}=State) -> |
234c6b10 | 5816 | + sql_query_internal(DBRef, ["DROP FUNCTION IF EXISTS ",logmessage_name(VHost,Schema)," (tbname TEXT, atdt TEXT, owner TEXT, peer_name TEXT, peer_server TEXT, peer_resource TEXT, mdirection VARCHAR(4), mtype VARCHAR(9), msubj TEXT, mbody TEXT, mtimestamp DOUBLE PRECISION);"]), |
f7ce3e3a | 5817 | + case sql_query_internal(DBRef, [get_logmessage(VHost, Schema)]) of |
5818 | + {updated, _} -> | |
5819 | + ?MYDEBUG("Created logmessage for ~p", [VHost]), | |
5820 | + ok; | |
26b6b0c9 AM |
5821 | + {error, Reason} -> |
5822 | + case lists:keysearch(code, 1, Reason) of | |
5823 | + {value, {code, "42704"}} -> | |
5824 | + ?ERROR_MSG("plpgsql language must be installed into database '~s'. Use CREATE LANGUAGE...", [State#state.db]), | |
5825 | + error; | |
5826 | + _ -> | |
5827 | + error | |
5828 | + end | |
f7ce3e3a | 5829 | + end. |
5830 | + | |
5831 | +get_user_id(DBRef, VHost, Schema, User) -> | |
5832 | + SQuery = ["SELECT user_id FROM ",users_table(VHost, Schema)," ", | |
5833 | + "WHERE username='",User,"';"], | |
5834 | + case sql_query_internal(DBRef, SQuery) of | |
5835 | + {data, []} -> | |
5836 | + IQuery = ["INSERT INTO ",users_table(VHost, Schema)," ", | |
5837 | + "VALUES ('",User,"');"], | |
5838 | + case sql_query_internal_silent(DBRef, IQuery) of | |
5839 | + {updated, _} -> | |
5840 | + {data, [{DBIdNew}]} = sql_query_internal(DBRef, SQuery), | |
5841 | + DBIdNew; | |
5842 | + {error, Reason} -> | |
5843 | + % this can be in clustered environment | |
5844 | + {value, {code, "23505"}} = lists:keysearch(code, 1, Reason), | |
5845 | + ?ERROR_MSG("Duplicate key name for ~p", [User]), | |
5846 | + {data, [{ClID}]} = sql_query_internal(DBRef, SQuery), | |
5847 | + ClID | |
5848 | + end; | |
5849 | + {data, [{DBId}]} -> | |
5850 | + DBId | |
5851 | + end. | |
5852 | + | |
5853 | +get_logmessage(VHost,Schema) -> | |
5854 | + UName = users_table(VHost,Schema), | |
5855 | + SName = servers_table(VHost,Schema), | |
5856 | + RName = resources_table(VHost,Schema), | |
5857 | + StName = stats_table(VHost,Schema), | |
234c6b10 | 5858 | + io_lib:format("CREATE OR REPLACE FUNCTION ~s (tbname TEXT, vname TEXT, atdt TEXT, owner TEXT, peer_name TEXT, peer_server TEXT, peer_resource TEXT, mdirection VARCHAR(4), mtype VARCHAR(9), msubj TEXT, mbody TEXT, mtimestamp DOUBLE PRECISION) RETURNS INTEGER AS $$ |
f7ce3e3a | 5859 | +DECLARE |
5860 | + ownerID INTEGER; | |
5861 | + peer_nameID INTEGER; | |
5862 | + peer_serverID INTEGER; | |
5863 | + peer_resourceID INTEGER; | |
5864 | + tablename ALIAS for $1; | |
234c6b10 | 5865 | + viewname ALIAS for $2; |
5866 | + atdate ALIAS for $3; | |
f7ce3e3a | 5867 | +BEGIN |
5868 | + SELECT INTO ownerID user_id FROM ~s WHERE username = owner; | |
5869 | + IF NOT FOUND THEN | |
5870 | + INSERT INTO ~s (username) VALUES (owner); | |
5871 | + ownerID := lastval(); | |
5872 | + END IF; | |
5873 | + | |
5874 | + SELECT INTO peer_nameID user_id FROM ~s WHERE username = peer_name; | |
5875 | + IF NOT FOUND THEN | |
5876 | + INSERT INTO ~s (username) VALUES (peer_name); | |
5877 | + peer_nameID := lastval(); | |
5878 | + END IF; | |
5879 | + | |
5880 | + SELECT INTO peer_serverID server_id FROM ~s WHERE server = peer_server; | |
5881 | + IF NOT FOUND THEN | |
5882 | + INSERT INTO ~s (server) VALUES (peer_server); | |
5883 | + peer_serverID := lastval(); | |
5884 | + END IF; | |
5885 | + | |
5886 | + SELECT INTO peer_resourceID resource_id FROM ~s WHERE resource = peer_resource; | |
5887 | + IF NOT FOUND THEN | |
5888 | + INSERT INTO ~s (resource) VALUES (peer_resource); | |
5889 | + peer_resourceID := lastval(); | |
5890 | + END IF; | |
5891 | + | |
5892 | + BEGIN | |
234c6b10 | 5893 | + EXECUTE 'INSERT INTO ' || tablename || ' (owner_id, peer_name_id, peer_server_id, peer_resource_id, direction, type, subject, body, timestamp) VALUES (' || ownerID || ',' || peer_nameID || ',' || peer_serverID || ',' || peer_resourceID || ',''' || mdirection || ''',''' || mtype || ''',' || quote_literal(msubj) || ',' || quote_literal(mbody) || ',' || mtimestamp || ')'; |
f7ce3e3a | 5894 | + EXCEPTION WHEN undefined_table THEN |
5895 | + EXECUTE 'CREATE TABLE ' || tablename || ' (' || | |
5896 | + 'owner_id INTEGER, ' || | |
5897 | + 'peer_name_id INTEGER, ' || | |
5898 | + 'peer_server_id INTEGER, ' || | |
5899 | + 'peer_resource_id INTEGER, ' || | |
5900 | + 'direction VARCHAR(4) CHECK (direction IN (''to'',''from'')), ' || | |
5901 | + 'type VARCHAR(9) CHECK (type IN (''chat'',''error'',''groupchat'',''headline'',''normal'')), ' || | |
5902 | + 'subject TEXT, ' || | |
5903 | + 'body TEXT, ' || | |
5904 | + 'timestamp DOUBLE PRECISION)'; | |
234c6b10 | 5905 | + EXECUTE 'CREATE INDEX \"search_i_' || '~s' || '_' || atdate || '_' || '~s' || '\"' || ' ON ' || tablename || ' (owner_id, peer_name_id, peer_server_id, peer_resource_id)'; |
f7ce3e3a | 5906 | + |
5907 | + EXECUTE 'CREATE OR REPLACE VIEW ' || viewname || ' AS ' || | |
5908 | + 'SELECT owner.username AS owner_name, ' || | |
5909 | + 'peer.username AS peer_name, ' || | |
5910 | + 'servers.server AS peer_server, ' || | |
5911 | + 'resources.resource AS peer_resource, ' || | |
5912 | + 'messages.direction, ' || | |
5913 | + 'messages.type, ' || | |
5914 | + 'messages.subject, ' || | |
5915 | + 'messages.body, ' || | |
5916 | + 'messages.timestamp ' || | |
5917 | + 'FROM ' || | |
5918 | + '~s owner, ' || | |
5919 | + '~s peer, ' || | |
5920 | + '~s servers, ' || | |
5921 | + '~s resources, ' || | |
5922 | + tablename || ' messages ' || | |
5923 | + 'WHERE ' || | |
5924 | + 'owner.user_id=messages.owner_id and ' || | |
5925 | + 'peer.user_id=messages.peer_name_id and ' || | |
5926 | + 'servers.server_id=messages.peer_server_id and ' || | |
5927 | + 'resources.resource_id=messages.peer_resource_id ' || | |
5928 | + 'ORDER BY messages.timestamp'; | |
5929 | + | |
234c6b10 | 5930 | + EXECUTE 'INSERT INTO ' || tablename || ' (owner_id, peer_name_id, peer_server_id, peer_resource_id, direction, type, subject, body, timestamp) VALUES (' || ownerID || ',' || peer_nameID || ',' || peer_serverID || ',' || peer_resourceID || ',''' || mdirection || ''',''' || mtype || ''',' || quote_literal(msubj) || ',' || quote_literal(mbody) || ',' || mtimestamp || ')'; |
f7ce3e3a | 5931 | + END; |
5932 | + | |
234c6b10 | 5933 | + UPDATE ~s SET count=count+1 where at=atdate and owner_id=ownerID and peer_name_id=peer_nameID and peer_server_id=peer_serverID; |
f7ce3e3a | 5934 | + IF NOT FOUND THEN |
234c6b10 | 5935 | + INSERT INTO ~s (owner_id, peer_name_id, peer_server_id, at, count) VALUES (ownerID, peer_nameID, peer_serverID, atdate, 1); |
f7ce3e3a | 5936 | + END IF; |
5937 | + RETURN 0; | |
5938 | +END; | |
5939 | +$$ LANGUAGE plpgsql; | |
234c6b10 | 5940 | +", [logmessage_name(VHost,Schema),UName,UName,UName,UName,SName,SName,RName,RName,Schema,escape_vhost(VHost),UName,UName,SName,RName,StName,StName]). |
f7ce3e3a | 5941 | + |
5942 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
5943 | +% | |
0d78319d | 5944 | +% SQL internals |
f7ce3e3a | 5945 | +% |
5946 | +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
5947 | +% like do_transaction/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>) | |
5948 | +sql_transaction_internal(DBRef, Fun) -> | |
5949 | + case sql_query_internal(DBRef, ["BEGIN;"]) of | |
5950 | + {updated, _} -> | |
5951 | + case catch Fun() of | |
5952 | + error = Err -> | |
5953 | + rollback_internal(DBRef, Err); | |
5954 | + {error, _} = Err -> | |
5955 | + rollback_internal(DBRef, Err); | |
5956 | + {'EXIT', _} = Err -> | |
5957 | + rollback_internal(DBRef, Err); | |
5958 | + Res -> | |
5959 | + case sql_query_internal(DBRef, ["COMMIT;"]) of | |
5960 | + {error, _} -> rollback_internal(DBRef, {commit_error}); | |
5961 | + {updated, _} -> | |
5962 | + case Res of | |
5963 | + {atomic, _} -> Res; | |
5964 | + _ -> {atomic, Res} | |
5965 | + end | |
5966 | + end | |
5967 | + end; | |
5968 | + {error, _} -> | |
5969 | + {aborted, {begin_error}} | |
5970 | + end. | |
5971 | + | |
5972 | +% like rollback/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>) | |
5973 | +rollback_internal(DBRef, Reason) -> | |
5974 | + Res = sql_query_internal(DBRef, ["ROLLBACK;"]), | |
5975 | + {aborted, {Reason, {rollback_result, Res}}}. | |
5976 | + | |
5977 | +sql_query_internal(DBRef, Query) -> | |
5978 | + case sql_query_internal_silent(DBRef, Query) of | |
5979 | + {error, undefined, Rez} -> | |
5980 | + ?ERROR_MSG("Got undefined result: ~p while ~p", [Rez, lists:append(Query)]), | |
5981 | + {error, undefined}; | |
5982 | + {error, Error} -> | |
5983 | + ?ERROR_MSG("Failed: ~p while ~p", [Error, lists:append(Query)]), | |
5984 | + {error, Error}; | |
0d78319d AM |
5985 | + Rez -> Rez |
5986 | + end. | |
234c6b10 | 5987 | + |
0d78319d AM |
5988 | +sql_query_internal_silent(DBRef, Query) -> |
5989 | + ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]), | |
5990 | + % TODO: use pquery? | |
5991 | + get_result(pgsql:squery(DBRef, Query)). | |
234c6b10 | 5992 | + |
0d78319d AM |
5993 | +get_result({ok, ["CREATE TABLE"]}) -> |
5994 | + {updated, 1}; | |
5995 | +get_result({ok, ["DROP TABLE"]}) -> | |
5996 | + {updated, 1}; | |
5997 | +get_result({ok, ["ALTER TABLE"]}) -> | |
5998 | + {updated, 1}; | |
5999 | +get_result({ok,["DROP VIEW"]}) -> | |
6000 | + {updated, 1}; | |
6001 | +get_result({ok,["DROP FUNCTION"]}) -> | |
6002 | + {updated, 1}; | |
6003 | +get_result({ok, ["CREATE INDEX"]}) -> | |
6004 | + {updated, 1}; | |
6005 | +get_result({ok, ["CREATE FUNCTION"]}) -> | |
6006 | + {updated, 1}; | |
046546ef | 6007 | +get_result({ok, [{[$S, $E, $L, $E, $C, $T, $ | _Rest], _Rows, Recs}]}) -> |
0d78319d AM |
6008 | + Fun = fun(Rec) -> |
6009 | + list_to_tuple( | |
6010 | + lists:map(fun(Elem) when is_binary(Elem) -> | |
6011 | + binary_to_list(Elem); | |
6012 | + (Elem) when is_list(Elem) -> | |
6013 | + Elem; | |
6014 | + (Elem) when is_integer(Elem) -> | |
6015 | + integer_to_list(Elem); | |
6016 | + (Elem) when is_float(Elem) -> | |
6017 | + float_to_list(Elem); | |
6018 | + (Elem) when is_boolean(Elem) -> | |
6019 | + atom_to_list(Elem); | |
6020 | + (Elem) -> | |
6021 | + ?ERROR_MSG("Unknown element type ~p", [Elem]), | |
6022 | + Elem | |
6023 | + end, Rec)) | |
6024 | + end, | |
6025 | + Res = lists:map(Fun, Recs), | |
6026 | + %{data, [list_to_tuple(Rec) || Rec <- Recs]}; | |
6027 | + {data, Res}; | |
6028 | +get_result({ok, ["INSERT " ++ OIDN]}) -> | |
6029 | + [_OID, N] = string:tokens(OIDN, " "), | |
6030 | + {updated, list_to_integer(N)}; | |
6031 | +get_result({ok, ["DELETE " ++ N]}) -> | |
6032 | + {updated, list_to_integer(N)}; | |
6033 | +get_result({ok, ["UPDATE " ++ N]}) -> | |
6034 | + {updated, list_to_integer(N)}; | |
6035 | +get_result({ok, ["BEGIN"]}) -> | |
6036 | + {updated, 1}; | |
6037 | +get_result({ok, ["LOCK TABLE"]}) -> | |
6038 | + {updated, 1}; | |
6039 | +get_result({ok, ["ROLLBACK"]}) -> | |
6040 | + {updated, 1}; | |
6041 | +get_result({ok, ["COMMIT"]}) -> | |
6042 | + {updated, 1}; | |
6043 | +get_result({ok, ["SET"]}) -> | |
6044 | + {updated, 1}; | |
6045 | +get_result({ok, [{error, Error}]}) -> | |
6046 | + {error, Error}; | |
6047 | +get_result(Rez) -> | |
6048 | + {error, undefined, Rez}. | |
bb18ce72 | 6049 | + |
046546ef | 6050 | diff --git a/src/mod_roster.erl b/src/mod_roster.erl |
dd02533f | 6051 | index 426589319c..6b51d3c381 100644 |
046546ef AM |
6052 | --- a/src/mod_roster.erl |
6053 | +++ b/src/mod_roster.erl | |
08278fc0 | 6054 | @@ -65,6 +65,8 @@ |
a815cc6c AM |
6055 | -define(ROSTER_ITEM_CACHE, roster_item_cache). |
6056 | -define(ROSTER_VERSION_CACHE, roster_version_cache). | |
0d78319d | 6057 | |
234c6b10 | 6058 | +-include("mod_logdb.hrl"). |
0d78319d | 6059 | + |
08278fc0 | 6060 | -type c2s_state() :: ejabberd_c2s:state(). |
046546ef | 6061 | -export_type([subscription/0]). |
234c6b10 | 6062 | |
dd02533f | 6063 | @@ -943,6 +945,14 @@ user_roster(User, Server, Query, Lang) -> |
046546ef | 6064 | Query), |
234c6b10 | 6065 | Items = get_roster(LUser, LServer), |
6066 | SItems = lists:sort(Items), | |
6067 | + | |
6068 | + Settings = case gen_mod:is_loaded(Server, mod_logdb) of | |
6069 | + true -> | |
6070 | + mod_logdb:get_user_settings(User, Server); | |
6071 | + false -> | |
6072 | + [] | |
6073 | + end, | |
6074 | + | |
046546ef | 6075 | FItems = case SItems of |
dd02533f | 6076 | [] -> [?CT(?T("None"))]; |
046546ef | 6077 | _ -> |
dd02533f | 6078 | @@ -1000,7 +1010,33 @@ user_roster(User, Server, Query, Lang) -> |
046546ef AM |
6079 | [?INPUTT(<<"submit">>, |
6080 | <<"remove", | |
6081 | (ejabberd_web_admin:term_to_id(R#roster.jid))/binary>>, | |
08278fc0 | 6082 | - ?T("Remove"))])]) |
dd02533f AM |
6083 | + ?T("Remove"))]), |
6084 | + case gen_mod:is_loaded(Server, mod_logdb) of | |
046546ef | 6085 | + true -> |
3f23be8e | 6086 | + Peer = jid:encode(R#roster.jid), |
046546ef AM |
6087 | + A = lists:member(Peer, Settings#user_settings.dolog_list), |
6088 | + B = lists:member(Peer, Settings#user_settings.donotlog_list), | |
6089 | + {Name, Value} = | |
6090 | + if | |
6091 | + A -> | |
6092 | + {<<"donotlog">>, <<"Do Not Log Messages">>}; | |
6093 | + B -> | |
6094 | + {<<"dolog">>, <<"Log Messages">>}; | |
6095 | + Settings#user_settings.dolog_default == true -> | |
6096 | + {<<"donotlog">>, <<"Do Not Log Messages">>}; | |
6097 | + Settings#user_settings.dolog_default == false -> | |
6098 | + {<<"dolog">>, <<"Log Messages">>} | |
6099 | + end, | |
6100 | + | |
6101 | + ?XAE(<<"td">>, [{<<"class">>, <<"valign">>}], | |
6102 | + [?INPUTT(<<"submit">>, | |
bb18ce72 | 6103 | + <<Name/binary, |
046546ef AM |
6104 | + (ejabberd_web_admin:term_to_id(R#roster.jid))/binary>>, |
6105 | + Value)]); | |
6106 | + false -> | |
6107 | + ?X([]) | |
dd02533f | 6108 | + end |
046546ef AM |
6109 | + ]) |
6110 | end, | |
6111 | SItems)))])] | |
6112 | end, | |
dd02533f | 6113 | @@ -1107,9 +1143,42 @@ user_roster_item_parse_query(User, Server, Items, |
3f23be8e AM |
6114 | sub_els = [#roster_query{ |
6115 | items = [RosterItem]}]}), | |
046546ef AM |
6116 | throw(submitted); |
6117 | - false -> ok | |
6118 | - end | |
6119 | - end | |
6120 | + false -> | |
6121 | + case lists:keysearch( | |
bb18ce72 | 6122 | + <<"donotlog", (ejabberd_web_admin:term_to_id(JID))/binary>>, 1, Query) of |
046546ef | 6123 | + {value, _} -> |
3f23be8e | 6124 | + Peer = jid:encode(JID), |
046546ef AM |
6125 | + Settings = mod_logdb:get_user_settings(User, Server), |
6126 | + DNLL = case lists:member(Peer, Settings#user_settings.donotlog_list) of | |
6127 | + false -> lists:append(Settings#user_settings.donotlog_list, [Peer]); | |
6128 | + true -> Settings#user_settings.donotlog_list | |
6129 | + end, | |
3f23be8e | 6130 | + DLL = lists:delete(jid:encode(JID), Settings#user_settings.dolog_list), |
046546ef AM |
6131 | + Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL}, |
6132 | + % TODO: check returned value | |
6133 | + ok = mod_logdb:set_user_settings(User, Server, Sett), | |
6134 | + throw(nothing); | |
6135 | + false -> | |
6136 | + case lists:keysearch( | |
bb18ce72 | 6137 | + <<"dolog", (ejabberd_web_admin:term_to_id(JID))/binary>>, 1, Query) of |
046546ef | 6138 | + {value, _} -> |
3f23be8e | 6139 | + Peer = jid:encode(JID), |
046546ef AM |
6140 | + Settings = mod_logdb:get_user_settings(User, Server), |
6141 | + DLL = case lists:member(Peer, Settings#user_settings.dolog_list) of | |
6142 | + false -> lists:append(Settings#user_settings.dolog_list, [Peer]); | |
6143 | + true -> Settings#user_settings.dolog_list | |
6144 | + end, | |
3f23be8e | 6145 | + DNLL = lists:delete(jid:encode(JID), Settings#user_settings.donotlog_list), |
046546ef AM |
6146 | + Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL}, |
6147 | + % TODO: check returned value | |
6148 | + ok = mod_logdb:set_user_settings(User, Server, Sett), | |
6149 | + throw(nothing); | |
6150 | + false -> | |
6151 | + ok | |
6152 | + end % dolog | |
6153 | + end % donotlog | |
6154 | + end % remove | |
234c6b10 | 6155 | + end % validate |
046546ef AM |
6156 | end, |
6157 | Items), | |
234c6b10 | 6158 | nothing. |
a815cc6c | 6159 | |
dd02533f AM |
6160 | From 5043114bc1a74caa522e8a1569b485ccc1808a79 Mon Sep 17 00:00:00 2001 |
6161 | From: Oleh Palii <o.palij@gmail.com> | |
6162 | Date: Sat, 31 Aug 2019 15:23:19 +0300 | |
6163 | Subject: [PATCH 2/3] mod_logdb 19.08 adaptation | |
6164 | ||
6165 | --- | |
6166 | src/mod_logdb.erl | 187 +++++++++++++++++++++++---------------- | |
6167 | src/mod_logdb_mysql.erl | 10 +-- | |
6168 | src/mod_logdb_mysql5.erl | 10 +-- | |
6169 | src/mod_logdb_pgsql.erl | 12 +-- | |
6170 | 4 files changed, 125 insertions(+), 94 deletions(-) | |
6171 | ||
6172 | diff --git a/src/mod_logdb.erl b/src/mod_logdb.erl | |
6173 | index bf0240d139..0b5c2ec687 100644 | |
6174 | --- a/src/mod_logdb.erl | |
6175 | +++ b/src/mod_logdb.erl | |
6176 | @@ -26,6 +26,7 @@ | |
6177 | -export([get_local_identity/5, | |
6178 | get_local_features/5, | |
6179 | get_local_items/5, | |
6180 | + mod_options/1, | |
6181 | adhoc_local_items/4, | |
6182 | adhoc_local_commands/4 | |
6183 | ]). | |
6184 | @@ -56,6 +57,8 @@ | |
6185 | user_messages_stats/4, | |
6186 | user_messages_stats_at/5]). | |
6187 | ||
6188 | +-export([get_opt/3]). | |
6189 | + | |
6190 | -include("mod_logdb.hrl"). | |
6191 | -include("xmpp.hrl"). | |
6192 | -include("mod_roster.hrl"). | |
6193 | @@ -64,6 +67,7 @@ | |
6194 | -include("ejabberd_web_admin.hrl"). | |
6195 | -include("ejabberd_http.hrl"). | |
6196 | -include("logger.hrl"). | |
6197 | +-include("translate.hrl"). | |
6198 | ||
6199 | -define(PROCNAME, ejabberd_mod_logdb). | |
6200 | % gen_server call timeout | |
6201 | @@ -73,6 +77,28 @@ | |
6202 | ||
6203 | ets_settings_table(VHost) -> list_to_atom("ets_logdb_settings_" ++ binary_to_list(VHost)). | |
6204 | ||
6205 | +-spec tr(binary(), binary()) -> binary(). | |
6206 | +tr(Lang, Text) -> | |
6207 | + translate:translate(Lang, Text). | |
6208 | + | |
6209 | +mod_options(VHost) -> | |
6210 | + [ | |
6211 | + {dbs, [{mnesia, []}]}, | |
6212 | + {vhosts, [{VHost, mnesia}]}, | |
6213 | + {ignore_jids, []}, | |
6214 | + {groupchat, none}, | |
6215 | + {drop_messages_on_user_removal, true}, | |
6216 | + {purge_older_days, never}, | |
6217 | + {dolog_default, true}, | |
6218 | + {poll_users_settings, 5} | |
6219 | + ]. | |
6220 | + | |
6221 | +get_opt(Opt, Opts, Default) -> | |
6222 | + case lists:keyfind(Opt, 1, Opts) of | |
6223 | + false -> Default; | |
6224 | + {_, Result} -> Result | |
6225 | + end. | |
6226 | + | |
6227 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
6228 | % | |
6229 | % gen_mod/gen_server callbacks | |
6230 | @@ -88,7 +114,8 @@ start(VHost, Opts) -> | |
6231 | worker, | |
6232 | [?MODULE]}, | |
6233 | % add child to ejabberd_sup | |
6234 | - supervisor:start_child(ejabberd_gen_mod_sup, ChildSpec). | |
6235 | + supervisor:start_child(ejabberd_gen_mod_sup, ChildSpec), | |
6236 | + ok. | |
6237 | ||
6238 | depends(_Host, _Opts) -> | |
6239 | []. | |
6240 | @@ -106,14 +133,14 @@ start_link(VHost, Opts) -> | |
6241 | ||
6242 | init([VHost, Opts]) -> | |
6243 | process_flag(trap_exit, true), | |
6244 | - DBsRaw = gen_mod:get_opt(dbs, Opts, fun(A) -> A end, [{mnesia, []}]), | |
6245 | + DBsRaw = gen_mod:get_opt(dbs, Opts), | |
6246 | DBs = case lists:keysearch(mnesia, 1, DBsRaw) of | |
6247 | false -> lists:append(DBsRaw, [{mnesia,[]}]); | |
6248 | {value, _} -> DBsRaw | |
6249 | end, | |
6250 | - VHostDB = gen_mod:get_opt(vhosts, Opts, fun(A) -> A end, [{VHost, mnesia}]), | |
6251 | + VHostDB = gen_mod:get_opt(vhosts, Opts), | |
6252 | % 10 is default because of using in clustered environment | |
6253 | - PollUsersSettings = gen_mod:get_opt(poll_users_settings, Opts, fun(A) -> A end, 10), | |
6254 | + PollUsersSettings = gen_mod:get_opt(poll_users_settings, Opts), | |
6255 | ||
6256 | {DBName, DBOpts} = | |
6257 | case lists:keysearch(VHost, 1, VHostDB) of | |
6258 | @@ -139,11 +166,11 @@ init([VHost, Opts]) -> | |
6259 | dbopts=DBOpts, | |
6260 | % dbs used for convert messages from one backend to other | |
6261 | dbs=DBs, | |
6262 | - dolog_default=gen_mod:get_opt(dolog_default, Opts, fun(A) -> A end, true), | |
6263 | - drop_messages_on_user_removal=gen_mod:get_opt(drop_messages_on_user_removal, Opts, fun(A) -> A end, true), | |
6264 | - ignore_jids=gen_mod:get_opt(ignore_jids, Opts, fun(A) -> A end, []), | |
6265 | - groupchat=gen_mod:get_opt(groupchat, Opts, fun(A) -> A end, none), | |
6266 | - purge_older_days=gen_mod:get_opt(purge_older_days, Opts, fun(A) -> A end, never), | |
6267 | + dolog_default=gen_mod:get_opt(dolog_default, Opts), | |
6268 | + drop_messages_on_user_removal=gen_mod:get_opt(drop_messages_on_user_removal, Opts), | |
6269 | + ignore_jids=gen_mod:get_opt(ignore_jids, Opts), | |
6270 | + groupchat=gen_mod:get_opt(groupchat, Opts), | |
6271 | + purge_older_days=gen_mod:get_opt(purge_older_days, Opts), | |
6272 | poll_users_settings=PollUsersSettings}}. | |
6273 | ||
6274 | cleanup(#state{vhost=VHost} = _State) -> | |
6275 | @@ -444,7 +471,7 @@ handle_info(scheduled_purging, #state{vhost=VHost, purge_older_days=Days} = Stat | |
6276 | % from timer:send_interval/2 (in start handle_info) | |
6277 | handle_info(poll_users_settings, #state{dbmod=DBMod, vhost=VHost}=State) -> | |
6278 | {ok, DoLog} = DBMod:get_users_settings(VHost), | |
6279 | - ?MYDEBUG("DoLog=~p", [DoLog]), | |
6280 | +% ?MYDEBUG("DoLog=~p", [DoLog]), | |
6281 | true = ets:delete_all_objects(ets_settings_table(VHost)), | |
6282 | ets:insert(ets_settings_table(VHost), DoLog), | |
6283 | {noreply, State}; | |
6284 | @@ -654,8 +681,7 @@ sort_stats(Stats) -> | |
6285 | % return float seconds elapsed from "zero hour" as list | |
6286 | get_timestamp() -> | |
6287 | {MegaSec, Sec, MicroSec} = now(), | |
6288 | - [List] = io_lib:format("~.5f", [MegaSec*1000000 + Sec + MicroSec/1000000]), | |
6289 | - List. | |
6290 | + io_lib:format("~.5f", [MegaSec*1000000 + Sec + MicroSec/1000000]). | |
6291 | ||
6292 | % convert float seconds elapsed from "zero hour" to local time "%Y-%m-%d %H:%M:%S" string | |
6293 | convert_timestamp(Seconds) when is_list(Seconds) -> | |
6294 | @@ -907,7 +933,7 @@ copy_messages_int_tc([FromDBMod, ToDBMod, VHost, Date]) -> | |
6295 | % mysql, pgsql removes final zeros after decimal point | |
6296 | (#msg{timestamp=Tst}) when length(Tst) < 16 -> | |
6297 | {F, _} = string:to_float(Tst++".0"), | |
6298 | - [T] = io_lib:format("~.5f", [F]), | |
6299 | + T = io_lib:format("~.5f", [F]), | |
6300 | ets:insert(mod_logdb_temp, {T}) | |
6301 | end, ToMsgs), | |
6302 | {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date), | |
6303 | @@ -992,16 +1018,25 @@ string_to_list(String) -> | |
6304 | % ad-hoc (copy/pasted from mod_configure.erl) | |
6305 | % | |
6306 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
6307 | +-spec get_permission_level(jid()) -> global | vhost. | |
6308 | +get_permission_level(JID) -> | |
6309 | + case acl:match_rule(global, configure, JID) of | |
6310 | + allow -> global; | |
6311 | + deny -> vhost | |
6312 | + end. | |
6313 | + | |
6314 | -define(ITEMS_RESULT(Allow, LNode, Fallback), | |
6315 | - case Allow of | |
6316 | - deny -> Fallback; | |
6317 | - allow -> | |
6318 | - case get_local_items(LServer, LNode, | |
6319 | - jid:encode(To), Lang) of | |
6320 | - {result, Res} -> {result, Res}; | |
6321 | - {error, Error} -> {error, Error} | |
6322 | - end | |
6323 | - end). | |
6324 | + case Allow of | |
6325 | + deny -> Fallback; | |
6326 | + allow -> | |
6327 | + PermLev = get_permission_level(From), | |
6328 | + case get_local_items({PermLev, LServer}, LNode, | |
6329 | + jid:encode(To), Lang) | |
6330 | + of | |
6331 | + {result, Res} -> {result, Res}; | |
6332 | + {error, Error} -> {error, Error} | |
6333 | + end | |
6334 | + end). | |
6335 | ||
6336 | get_local_items(Acc, From, #jid{lserver = LServer} = To, | |
6337 | <<"">>, Lang) -> | |
6338 | @@ -1051,15 +1086,13 @@ get_local_items(Acc, From, #jid{lserver = LServer} = To, | |
6339 | end | |
6340 | end. | |
6341 | ||
6342 | --define(T(Lang, Text), translate:translate(Lang, Text)). | |
6343 | - | |
6344 | -define(NODE(Name, Node), | |
6345 | - #disco_item{jid = jid:make(Server), | |
6346 | - node = Node, | |
6347 | - name = ?T(Lang, Name)}). | |
6348 | + #disco_item{jid = jid:make(Server), | |
6349 | + node = Node, | |
6350 | + name = tr(Lang, Name)}). | |
6351 | ||
6352 | -define(NS_ADMINX(Sub), | |
6353 | - <<(?NS_ADMIN)/binary, "#", Sub/binary>>). | |
6354 | + <<(?NS_ADMIN)/binary, "#", Sub/binary>>). | |
6355 | ||
6356 | tokenize(Node) -> str:tokens(Node, <<"/#">>). | |
6357 | ||
6358 | @@ -1098,10 +1131,10 @@ get_local_items(_Host, Item, _Server, _Lang) -> | |
6359 | {error, xmpp:err_item_not_found()}. | |
6360 | ||
6361 | -define(INFO_RESULT(Allow, Feats, Lang), | |
6362 | - case Allow of | |
6363 | - deny -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)}; | |
6364 | - allow -> {result, Feats} | |
6365 | - end). | |
6366 | + case Allow of | |
6367 | + deny -> {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)}; | |
6368 | + allow -> {result, Feats} | |
6369 | + end). | |
6370 | ||
6371 | get_local_features(Acc, From, | |
6372 | #jid{lserver = LServer} = _To, Node, Lang) -> | |
6373 | @@ -1133,11 +1166,11 @@ get_local_features(Acc, From, | |
6374 | end. | |
6375 | ||
6376 | -define(INFO_IDENTITY(Category, Type, Name, Lang), | |
6377 | - [#identity{category = Category, type = Type, name = ?T(Lang, Name)}]). | |
6378 | + [#identity{category = Category, type = Type, name = tr(Lang, Name)}]). | |
6379 | ||
6380 | -define(INFO_COMMAND(Name, Lang), | |
6381 | - ?INFO_IDENTITY(<<"automation">>, <<"command-node">>, | |
6382 | - Name, Lang)). | |
6383 | + ?INFO_IDENTITY(<<"automation">>, <<"command-node">>, | |
6384 | + Name, Lang)). | |
6385 | ||
6386 | get_local_identity(Acc, _From, _To, Node, Lang) -> | |
6387 | LNode = tokenize(Node), | |
6388 | @@ -1198,10 +1231,8 @@ recursively_get_local_items(LServer, | |
6389 | ||
6390 | -define(COMMANDS_RESULT(Allow, From, To, Request), | |
6391 | case Allow of | |
6392 | - deny -> | |
6393 | - {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)}; | |
6394 | - allow -> | |
6395 | - adhoc_local_commands(From, To, Request) | |
6396 | + deny -> {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)}; | |
6397 | + allow -> adhoc_local_commands(From, To, Request) | |
6398 | end). | |
6399 | ||
6400 | adhoc_local_commands(Acc, From, #jid{lserver = LServer} = To, | |
6401 | @@ -1278,28 +1309,28 @@ get_user_form(LUser, LServer, Lang) -> | |
6402 | Fs = [ | |
6403 | #xdata_field{ | |
6404 | type = 'list-single', | |
6405 | - label = ?T(Lang, <<"Default">>), | |
6406 | + label = tr(Lang, ?T("Default")), | |
6407 | var = <<"dolog_default">>, | |
6408 | values = [misc:atom_to_binary(DLD)], | |
6409 | - options = [#xdata_option{label = ?T(Lang, <<"Log Messages">>), | |
6410 | + options = [#xdata_option{label = tr(Lang, ?T("Log Messages")), | |
6411 | value = <<"true">>}, | |
6412 | - #xdata_option{label = ?T(Lang, <<"Do Not Log Messages">>), | |
6413 | + #xdata_option{label = tr(Lang, ?T("Do Not Log Messages")), | |
6414 | value = <<"false">>}]}, | |
6415 | #xdata_field{ | |
6416 | type = 'text-multi', | |
6417 | - label = ?T(Lang, <<"Log Messages">>), | |
6418 | + label = tr(Lang, ?T("Log Messages")), | |
6419 | var = <<"dolog_list">>, | |
6420 | values = DLL}, | |
6421 | #xdata_field{ | |
6422 | type = 'text-multi', | |
6423 | - label = ?T(Lang, <<"Do Not Log Messages">>), | |
6424 | + label = tr(Lang, ?T("Do Not Log Messages")), | |
6425 | var = <<"donotlog_list">>, | |
6426 | values = DNLL} | |
6427 | ], | |
6428 | {result, #xdata{ | |
6429 | - title = ?T(Lang, <<"Messages logging engine settings">>), | |
6430 | + title = tr(Lang, ?T("Messages logging engine settings")), | |
6431 | type = form, | |
6432 | - instructions = [<< (?T(Lang, <<"Set logging preferences">>))/binary, | |
6433 | + instructions = [<< (tr(Lang, ?T("Set logging preferences")))/binary, | |
6434 | (iolist_to_binary(": "))/binary, | |
6435 | LUser/binary, "@", LServer/binary >>], | |
6436 | fields = [?HFIELD()| | |
6437 | @@ -1325,52 +1356,52 @@ get_settings_form(Host, Lang) -> | |
6438 | Fs = [ | |
6439 | #xdata_field{ | |
6440 | type = 'list-single', | |
6441 | - label = ?T(Lang, <<"Default">>), | |
6442 | + label = tr(Lang, ?T("Default")), | |
6443 | var = <<"dolog_default">>, | |
6444 | values = [misc:atom_to_binary(DLD)], | |
6445 | - options = [#xdata_option{label = ?T(Lang, <<"Log Messages">>), | |
6446 | + options = [#xdata_option{label = tr(Lang, ?T("Log Messages")), | |
6447 | value = <<"true">>}, | |
6448 | - #xdata_option{label = ?T(Lang, <<"Do Not Log Messages">>), | |
6449 | + #xdata_option{label = tr(Lang, ?T("Do Not Log Messages")), | |
6450 | value = <<"false">>}]}, | |
6451 | #xdata_field{ | |
6452 | type = 'list-single', | |
6453 | - label = ?T(Lang, <<"Drop messages on user removal">>), | |
6454 | + label = tr(Lang, ?T("Drop messages on user removal")), | |
6455 | var = <<"drop_messages_on_user_removal">>, | |
6456 | values = [misc:atom_to_binary(MRemoval)], | |
6457 | - options = [#xdata_option{label = ?T(Lang, <<"Drop">>), | |
6458 | + options = [#xdata_option{label = tr(Lang, ?T("Drop")), | |
6459 | value = <<"true">>}, | |
6460 | - #xdata_option{label = ?T(Lang, <<"Do not drop">>), | |
6461 | + #xdata_option{label = tr(Lang, ?T("Do not drop")), | |
6462 | value = <<"false">>}]}, | |
6463 | #xdata_field{ | |
6464 | type = 'list-single', | |
6465 | - label = ?T(Lang, <<"Groupchat messages logging">>), | |
6466 | + label = tr(Lang, ?T("Groupchat messages logging")), | |
6467 | var = <<"groupchat">>, | |
6468 | values = [misc:atom_to_binary(GroupChat)], | |
6469 | - options = [#xdata_option{label = ?T(Lang, <<"all">>), | |
6470 | + options = [#xdata_option{label = tr(Lang, ?T("all")), | |
6471 | value = <<"all">>}, | |
6472 | - #xdata_option{label = ?T(Lang, <<"none">>), | |
6473 | + #xdata_option{label = tr(Lang, ?T("none")), | |
6474 | value = <<"none">>}, | |
6475 | - #xdata_option{label = ?T(Lang, <<"send">>), | |
6476 | + #xdata_option{label = tr(Lang, ?T("send")), | |
6477 | value = <<"send">>}]}, | |
6478 | #xdata_field{ | |
6479 | type = 'text-multi', | |
6480 | - label = ?T(Lang, <<"Jids/Domains to ignore">>), | |
6481 | + label = tr(Lang, ?T("Jids/Domains to ignore")), | |
6482 | var = <<"ignore_list">>, | |
6483 | values = IgnoreJids}, | |
6484 | #xdata_field{ | |
6485 | type = 'text-single', | |
6486 | - label = ?T(Lang, <<"Purge messages older than (days)">>), | |
6487 | + label = tr(Lang, ?T("Purge messages older than (days)")), | |
6488 | var = <<"purge_older_days">>, | |
6489 | values = [iolist_to_binary(PurgeDays)]}, | |
6490 | #xdata_field{ | |
6491 | type = 'text-single', | |
6492 | - label = ?T(Lang, <<"Poll users settings (seconds)">>), | |
6493 | + label = tr(Lang, ?T("Poll users settings (seconds)")), | |
6494 | var = <<"poll_users_settings">>, | |
6495 | values = [integer_to_binary(PollTime)]} | |
6496 | ], | |
6497 | {result, #xdata{ | |
6498 | - title = ?T(Lang, <<"Messages logging engine settings (run-time)">>), | |
6499 | - instructions = [?T(Lang, <<"Set run-time settings">>)], | |
6500 | + title = tr(Lang, ?T("Messages logging engine settings (run-time)")), | |
6501 | + instructions = [tr(Lang, ?T("Set run-time settings"))], | |
6502 | type = form, | |
6503 | fields = [?HFIELD()| | |
6504 | Fs]}}. | |
6505 | @@ -1578,7 +1609,7 @@ get_all_vh_users(Host, Server) -> | |
6506 | % | |
6507 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
6508 | webadmin_menu(Acc, _Host, Lang) -> | |
6509 | - [{<<"messages">>, ?T(<<"Users Messages">>)} | Acc]. | |
6510 | + [{<<"messages">>, tr(Lang, ?T("Users Messages"))} | Acc]. | |
6511 | ||
6512 | webadmin_user(Acc, User, Server, Lang) -> | |
6513 | Sett = get_user_settings(User, Server), | |
6514 | @@ -1649,12 +1680,12 @@ vhost_messages_stats(Server, Query, Lang) -> | |
6515 | case Value of | |
6516 | {'EXIT', CReason} -> | |
6517 | ?ERROR_MSG("Failed to get_vhost_stats: ~p", [CReason]), | |
6518 | - [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))]; | |
6519 | + [?XC(<<"h1">>, tr(Lang, ?T("Error occupied while fetching list")))]; | |
6520 | {error, GReason} -> | |
6521 | ?ERROR_MSG("Failed to get_vhost_stats: ~p", [GReason]), | |
6522 | - [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))]; | |
6523 | + [?XC(<<"h1">>, tr(Lang, ?T("Error occupied while fetching list")))]; | |
6524 | {ok, []} -> | |
6525 | - [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s">>), [Server])))]; | |
6526 | + [?XC(<<"h1">>, list_to_binary(io_lib:format(tr(Lang, ?T("No logged messages for ~s")), [Server])))]; | |
6527 | {ok, Dates} -> | |
6528 | Fun = fun({Date, Count}) -> | |
6529 | DateBin = iolist_to_binary(Date), | |
6530 | @@ -1667,7 +1698,7 @@ vhost_messages_stats(Server, Query, Lang) -> | |
6531 | ]) | |
6532 | end, | |
6533 | ||
6534 | - [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Logged messages for ~s">>), [Server])))] ++ | |
6535 | + [?XC(<<"h1">>, list_to_binary(io_lib:format(tr(Lang, ?T("Logged messages for ~s")), [Server])))] ++ | |
6536 | case Res of | |
6537 | ok -> [?CT(<<"Submitted">>), ?P]; | |
6538 | error -> [?CT(<<"Bad format">>), ?P]; | |
6539 | @@ -1696,12 +1727,12 @@ vhost_messages_stats_at(Server, Query, Lang, Date) -> | |
6540 | case Value of | |
6541 | {'EXIT', CReason} -> | |
6542 | ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [CReason]), | |
6543 | - [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))]; | |
6544 | + [?XC(<<"h1">>, tr(Lang, ?T("Error occupied while fetching list")))]; | |
6545 | {error, GReason} -> | |
6546 | ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [GReason]), | |
6547 | - [?XC(<<"h1">>, ?T(<<"Error occupied while fetching list">>))]; | |
6548 | + [?XC(<<"h1">>, tr(Lang, ?T("Error occupied while fetching list")))]; | |
6549 | {ok, []} -> | |
6550 | - [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s at ~s">>), [Server, Date])))]; | |
6551 | + [?XC(<<"h1">>, list_to_binary(io_lib:format(tr(Lang, ?T("No logged messages for ~s at ~s")), [Server, Date])))]; | |
6552 | {ok, Stats} -> | |
6553 | Res = case catch vhost_messages_at_parse_query(Server, Date, Stats, Query) of | |
6554 | {'EXIT', Reason} -> | |
6555 | @@ -1719,7 +1750,7 @@ vhost_messages_stats_at(Server, Query, Lang, Date) -> | |
6556 | ?XC(<<"td">>, integer_to_binary(Count)) | |
6557 | ]) | |
6558 | end, | |
6559 | - [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Logged messages for ~s at ~s">>), [Server, Date])))] ++ | |
6560 | + [?XC(<<"h1">>, list_to_binary(io_lib:format(tr(Lang, ?T("Logged messages for ~s at ~s")), [Server, Date])))] ++ | |
6561 | case Res of | |
6562 | ok -> [?CT(<<"Submitted">>), ?P]; | |
6563 | error -> [?CT(<<"Bad format">>), ?P]; | |
6564 | @@ -1757,12 +1788,12 @@ user_messages_stats(User, Server, Query, Lang) -> | |
6565 | case Value of | |
6566 | {'EXIT', CReason} -> | |
6567 | ?ERROR_MSG("Failed to get_user_stats: ~p", [CReason]), | |
6568 | - [?XC(<<"h1">>, ?T(<<"Error occupied while fetching days">>))]; | |
6569 | + [?XC(<<"h1">>, tr(Lang, ?T("Error occupied while fetching days")))]; | |
6570 | {error, GReason} -> | |
6571 | ?ERROR_MSG("Failed to get_user_stats: ~p", [GReason]), | |
6572 | - [?XC(<<"h1">>, ?T(<<"Error occupied while fetching days">>))]; | |
6573 | + [?XC(<<"h1">>, tr(Lang,?T("Error occupied while fetching days")))]; | |
6574 | {ok, []} -> | |
6575 | - [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s">>), [Jid])))]; | |
6576 | + [?XC(<<"h1">>, list_to_binary(io_lib:format(tr(Lang, ?T("No logged messages for ~s")), [Jid])))]; | |
6577 | {ok, Dates} -> | |
6578 | Fun = fun({Date, Count}) -> | |
6579 | DateBin = iolist_to_binary(Date), | |
6580 | @@ -1814,12 +1845,12 @@ user_messages_stats_at(User, Server, Query, Lang, Date) -> | |
6581 | case Value of | |
6582 | {'EXIT', CReason} -> | |
6583 | ?ERROR_MSG("Failed to get_user_messages_at: ~p", [CReason]), | |
6584 | - [?XC(<<"h1">>, ?T(<<"Error occupied while fetching messages">>))]; | |
6585 | + [?XC(<<"h1">>, tr(Lang, ?T("Error occupied while fetching messages")))]; | |
6586 | {error, GReason} -> | |
6587 | ?ERROR_MSG("Failed to get_user_messages_at: ~p", [GReason]), | |
6588 | - [?XC(<<"h1">>, ?T(<<"Error occupied while fetching messages">>))]; | |
6589 | + [?XC(<<"h1">>, tr(Lang, ?T("Error occupied while fetching messages")))]; | |
6590 | {ok, []} -> | |
6591 | - [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"No logged messages for ~s at ~s">>), [Jid, Date])))]; | |
6592 | + [?XC(<<"h1">>, list_to_binary(io_lib:format(tr(Lang, ?T("No logged messages for ~s at ~s")), [Jid, Date])))]; | |
6593 | {ok, User_messages} -> | |
6594 | Res = case catch user_messages_at_parse_query(Server, | |
6595 | Date, | |
6596 | @@ -1888,7 +1919,7 @@ user_messages_stats_at(User, Server, Query, Lang, Date) -> | |
6597 | body=Body}) -> | |
6598 | Text = case Subject of | |
6599 | "" -> iolist_to_binary(Body); | |
6600 | - _ -> iolist_to_binary([binary_to_list(?T(<<"Subject">>)) ++ ": " ++ Subject ++ "\n" ++ Body]) | |
6601 | + _ -> iolist_to_binary([binary_to_list(tr(Lang, ?T("Subject"))) ++ ": " ++ Subject ++ "\n" ++ Body]) | |
6602 | end, | |
6603 | Resource = case PRes of | |
6604 | [] -> []; | |
6605 | @@ -1915,7 +1946,7 @@ user_messages_stats_at(User, Server, Query, Lang, Date) -> | |
6606 | % Filtered user messages in html | |
6607 | Msgs = lists:map(Msgs_Fun, lists:sort(User_messages_filtered)), | |
6608 | ||
6609 | - [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Logged messages for ~s at ~s">>), [Jid, Date])))] ++ | |
6610 | + [?XC(<<"h1">>, list_to_binary(io_lib:format(tr(Lang, ?T("Logged messages for ~s at ~s")), [Jid, Date])))] ++ | |
6611 | case Res of | |
6612 | ok -> [?CT(<<"Submitted">>), ?P]; | |
6613 | error -> [?CT(<<"Bad format">>), ?P]; | |
6614 | diff --git a/src/mod_logdb_mysql.erl b/src/mod_logdb_mysql.erl | |
6615 | index 21d65e6578..66b50acc86 100644 | |
6616 | --- a/src/mod_logdb_mysql.erl | |
6617 | +++ b/src/mod_logdb_mysql.erl | |
6618 | @@ -94,11 +94,11 @@ stop(VHost) -> | |
6619 | init([VHost, Opts]) -> | |
6620 | crypto:start(), | |
6621 | ||
6622 | - Server = gen_mod:get_opt(server, Opts, fun(A) -> A end, <<"localhost">>), | |
6623 | - Port = gen_mod:get_opt(port, Opts, fun(A) -> A end, 3306), | |
6624 | - DB = gen_mod:get_opt(db, Opts, fun(A) -> A end, <<"logdb">>), | |
6625 | - User = gen_mod:get_opt(user, Opts, fun(A) -> A end, <<"root">>), | |
6626 | - Password = gen_mod:get_opt(password, Opts, fun(A) -> A end, <<"">>), | |
6627 | + Server = mod_logdb:get_opt(server, Opts, <<"localhost">>), | |
6628 | + Port = mod_logdb:get_opt(port, Opts, 3306), | |
6629 | + DB = mod_logdb:get_opt(db, Opts, <<"logdb">>), | |
6630 | + User = mod_logdb:get_opt(user, Opts, <<"root">>), | |
6631 | + Password = mod_logdb:get_opt(password, Opts, <<"">>), | |
6632 | ||
6633 | St = #state{vhost=VHost, | |
6634 | server=Server, port=Port, db=DB, | |
6635 | diff --git a/src/mod_logdb_mysql5.erl b/src/mod_logdb_mysql5.erl | |
6636 | index c05ab958e2..72fa72e72e 100644 | |
6637 | --- a/src/mod_logdb_mysql5.erl | |
6638 | +++ b/src/mod_logdb_mysql5.erl | |
6639 | @@ -99,11 +99,11 @@ stop(VHost) -> | |
6640 | init([VHost, Opts]) -> | |
6641 | crypto:start(), | |
6642 | ||
6643 | - Server = gen_mod:get_opt(server, Opts, fun(A) -> A end, <<"localhost">>), | |
6644 | - Port = gen_mod:get_opt(port, Opts, fun(A) -> A end, 3306), | |
6645 | - DB = gen_mod:get_opt(db, Opts, fun(A) -> A end, <<"logdb">>), | |
6646 | - User = gen_mod:get_opt(user, Opts, fun(A) -> A end, <<"root">>), | |
6647 | - Password = gen_mod:get_opt(password, Opts, fun(A) -> A end, <<"">>), | |
6648 | + Server = mod_logdb:get_opt(server, Opts, <<"localhost">>), | |
6649 | + Port = mod_logdb:get_opt(port, Opts, 3306), | |
6650 | + DB = mod_logdb:get_opt(db, Opts, <<"logdb">>), | |
6651 | + User = mod_logdb:get_opt(user, Opts, <<"root">>), | |
6652 | + Password = mod_logdb:get_opt(password, Opts, <<"">>), | |
6653 | ||
6654 | St = #state{vhost=VHost, | |
6655 | server=Server, port=Port, db=DB, | |
6656 | diff --git a/src/mod_logdb_pgsql.erl b/src/mod_logdb_pgsql.erl | |
6657 | index 202c6ed4a8..7f74887b9d 100644 | |
6658 | --- a/src/mod_logdb_pgsql.erl | |
6659 | +++ b/src/mod_logdb_pgsql.erl | |
6660 | @@ -101,12 +101,12 @@ stop(VHost) -> | |
6661 | % | |
6662 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
6663 | init([VHost, Opts]) -> | |
6664 | - Server = gen_mod:get_opt(server, Opts, fun(A) -> A end, <<"localhost">>), | |
6665 | - DB = gen_mod:get_opt(db, Opts, fun(A) -> A end, <<"ejabberd_logdb">>), | |
6666 | - User = gen_mod:get_opt(user, Opts, fun(A) -> A end, <<"root">>), | |
6667 | - Port = gen_mod:get_opt(port, Opts, fun(A) -> A end, 5432), | |
6668 | - Password = gen_mod:get_opt(password, Opts, fun(A) -> A end, <<"">>), | |
6669 | - Schema = binary_to_list(gen_mod:get_opt(schema, Opts, fun(A) -> A end, <<"public">>)), | |
6670 | + Server = mod_logdb:get_opt(server, Opts, <<"localhost">>), | |
6671 | + Port = mod_logdb:get_opt(port, Opts, 5432), | |
6672 | + DB = mod_logdb:get_opt(db, Opts, <<"logdb">>), | |
6673 | + User = mod_logdb:get_opt(user, Opts, <<"root">>), | |
6674 | + Password = mod_logdb:get_opt(password, Opts, <<"">>), | |
6675 | + Schema = mod_logdb:get_opt(schema, Opts, <<"public">>), | |
6676 | ||
6677 | ?MYDEBUG("Starting pgsql backend for ~s", [VHost]), | |
6678 | ||
6679 | ||
6680 | From 55274ef5a3deb5979e0d97cdb48768eb472c36ec Mon Sep 17 00:00:00 2001 | |
6681 | From: Oleh Palii <o.palij@gmail.com> | |
6682 | Date: Sat, 31 Aug 2019 22:43:11 +0300 | |
6683 | Subject: [PATCH 3/3] mod_logdb mod_opt_type fixes | |
6684 | ||
6685 | --- | |
6686 | src/mod_logdb.erl | 33 ++++++++++++++++++++++----------- | |
6687 | 1 file changed, 22 insertions(+), 11 deletions(-) | |
6688 | ||
6689 | diff --git a/src/mod_logdb.erl b/src/mod_logdb.erl | |
6690 | index 0b5c2ec687..0766241fec 100644 | |
6691 | --- a/src/mod_logdb.erl | |
6692 | +++ b/src/mod_logdb.erl | |
6693 | @@ -220,24 +220,35 @@ get_commands_spec() -> | |
6694 | result = {res, rescode}}]. | |
6695 | ||
6696 | mod_opt_type(dbs) -> | |
6697 | - fun (A) when is_list(A) -> A end; | |
6698 | + econf:map( | |
6699 | + econf:enum([mnesia, mysql, mysql5, pgsql]), | |
6700 | + econf:map( | |
6701 | + econf:enum([user, password, server, port, db, schema]), | |
6702 | + econf:string() | |
6703 | + ) | |
6704 | + ); | |
6705 | mod_opt_type(vhosts) -> | |
6706 | - fun (A) when is_list(A) -> A end; | |
6707 | + econf:map( | |
6708 | + econf:string(), | |
6709 | + econf:enum([mnesia, mysql, mysql5, pgsql]) | |
6710 | + ); | |
6711 | mod_opt_type(poll_users_settings) -> | |
6712 | - fun (I) when is_integer(I) -> I end; | |
6713 | + econf:non_neg_int(); | |
6714 | mod_opt_type(groupchat) -> | |
6715 | - fun (all) -> all; | |
6716 | - (send) -> send; | |
6717 | - (none) -> none | |
6718 | - end; | |
6719 | + econf:enum([all, send, none]); | |
6720 | mod_opt_type(dolog_default) -> | |
6721 | - fun (B) when is_boolean(B) -> B end; | |
6722 | + econf:bool(); | |
6723 | +mod_opt_type(drop_messages_on_user_removal) -> | |
6724 | + econf:bool(); | |
6725 | mod_opt_type(ignore_jids) -> | |
6726 | - fun (A) when is_list(A) -> A end; | |
6727 | + econf:list(econf:string()); | |
6728 | mod_opt_type(purge_older_days) -> | |
6729 | - fun (I) when is_integer(I) -> I end; | |
6730 | + econf:either( | |
6731 | + never, | |
6732 | + econf:non_neg_int() | |
6733 | + ); | |
6734 | mod_opt_type(_) -> | |
6735 | - [dbs, vhosts, poll_users_settings, groupchat, dolog_default, ignore_jids, purge_older_days]. | |
6736 | + [dbs, vhosts, poll_users_settings, groupchat, dolog_default, drop_messages_on_user_removal, ignore_jids, purge_older_days]. | |
6737 | ||
6738 | handle_call({cleanup}, _From, State) -> | |
6739 | cleanup(State), |