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