6648 lines
282 KiB
Diff
6648 lines
282 KiB
Diff
|
--- mod_logdb.erl.orig 2009-02-05 19:21:29.000000000 +0200
|
|||
|
+++ mod_logdb.erl 2009-02-05 19:19:51.000000000 +0200
|
|||
|
@@ -0,0 +1,2094 @@
|
|||
|
+%%%----------------------------------------------------------------------
|
|||
|
+%%% File : mod_logdb.erl
|
|||
|
+%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
|
|||
|
+%%% Purpose : Frontend for log user messages to db
|
|||
|
+%%% Version : trunk
|
|||
|
+%%% Id : $Id: mod_logdb.erl 1272 2009-02-05 17:16:20Z malik $
|
|||
|
+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
|
|||
|
+%%%----------------------------------------------------------------------
|
|||
|
+
|
|||
|
+-module(mod_logdb).
|
|||
|
+-author('o.palij@gmail.com').
|
|||
|
+
|
|||
|
+-behaviour(gen_server).
|
|||
|
+-behaviour(gen_mod).
|
|||
|
+
|
|||
|
+% supervisor
|
|||
|
+-export([start_link/2]).
|
|||
|
+% gen_mod
|
|||
|
+-export([start/2,stop/1]).
|
|||
|
+% gen_server
|
|||
|
+-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
|
|||
|
+% hooks
|
|||
|
+-export([send_packet/3, receive_packet/4, offline_packet/3, remove_user/2]).
|
|||
|
+-export([get_local_identity/5,
|
|||
|
+ get_local_features/5,
|
|||
|
+ get_local_items/5,
|
|||
|
+ adhoc_local_items/4,
|
|||
|
+ adhoc_local_commands/4
|
|||
|
+% get_sm_identity/5,
|
|||
|
+% get_sm_features/5,
|
|||
|
+% get_sm_items/5,
|
|||
|
+% adhoc_sm_items/4,
|
|||
|
+% adhoc_sm_commands/4]).
|
|||
|
+ ]).
|
|||
|
+% ejabberdctl
|
|||
|
+-export([rebuild_stats/3,
|
|||
|
+ copy_messages/1, copy_messages_ctl/3, copy_messages_int_tc/1]).
|
|||
|
+%
|
|||
|
+-export([get_vhost_stats/1, get_vhost_stats_at/2,
|
|||
|
+ get_user_stats/2, get_user_messages_at/3,
|
|||
|
+ get_dates/1,
|
|||
|
+ sort_stats/1,
|
|||
|
+ convert_timestamp/1, convert_timestamp_brief/1,
|
|||
|
+ get_user_settings/2, set_user_settings/3,
|
|||
|
+ user_messages_at_parse_query/4, user_messages_parse_query/3,
|
|||
|
+ vhost_messages_parse_query/2, vhost_messages_at_parse_query/4,
|
|||
|
+ list_to_bool/1, bool_to_list/1,
|
|||
|
+ list_to_string/1, string_to_list/1,
|
|||
|
+ get_module_settings/1, set_module_settings/2,
|
|||
|
+ purge_old_records/2]).
|
|||
|
+% webadmin hooks
|
|||
|
+-export([webadmin_menu/3,
|
|||
|
+ webadmin_user/4,
|
|||
|
+ webadmin_page/3,
|
|||
|
+ user_parse_query/5]).
|
|||
|
+% webadmin queries
|
|||
|
+-export([vhost_messages_stats/3,
|
|||
|
+ vhost_messages_stats_at/4,
|
|||
|
+ user_messages_stats/4,
|
|||
|
+ user_messages_stats_at/5]).
|
|||
|
+
|
|||
|
+-include("mod_logdb.hrl").
|
|||
|
+-include("ejabberd.hrl").
|
|||
|
+-include("mod_roster.hrl").
|
|||
|
+-include("jlib.hrl").
|
|||
|
+-include("ejabberd_ctl.hrl").
|
|||
|
+-include("adhoc.hrl").
|
|||
|
+-include("web/ejabberd_web_admin.hrl").
|
|||
|
+-include("web/ejabberd_http.hrl").
|
|||
|
+
|
|||
|
+-define(PROCNAME, ejabberd_mod_logdb).
|
|||
|
+% gen_server call timeout
|
|||
|
+-define(CALL_TIMEOUT, 10000).
|
|||
|
+
|
|||
|
+-record(state, {vhost, dbmod, backendPid, monref, purgeRef, pollRef, dbopts, dbs, dolog_default, ignore_jids, groupchat, purge_older_days, poll_users_settings, drop_messages_on_user_removal}).
|
|||
|
+
|
|||
|
+ets_settings_table(VHost) -> list_to_atom("ets_logdb_settings_" ++ VHost).
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% gen_mod/gen_server callbacks
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+% ejabberd starts module
|
|||
|
+start(VHost, Opts) ->
|
|||
|
+ ChildSpec =
|
|||
|
+ {gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ {?MODULE, start_link, [VHost, Opts]},
|
|||
|
+ permanent,
|
|||
|
+ 1000,
|
|||
|
+ worker,
|
|||
|
+ [?MODULE]},
|
|||
|
+ % add child to ejabberd_sup
|
|||
|
+ supervisor:start_child(ejabberd_sup, ChildSpec).
|
|||
|
+
|
|||
|
+% supervisor starts gen_server
|
|||
|
+start_link(VHost, Opts) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ {ok, Pid} = gen_server:start_link({local, Proc}, ?MODULE, [VHost, Opts], []),
|
|||
|
+ Pid ! start,
|
|||
|
+ {ok, Pid}.
|
|||
|
+
|
|||
|
+init([VHost, Opts]) ->
|
|||
|
+ process_flag(trap_exit, true),
|
|||
|
+ DBs = gen_mod:get_opt(dbs, Opts, [{mnesia, []}]),
|
|||
|
+ VHostDB = gen_mod:get_opt(vhosts, Opts, [{VHost, mnesia}]),
|
|||
|
+ % 10 is default becouse of using in clustered environment
|
|||
|
+ PollUsersSettings = gen_mod:get_opt(poll_users_settings, Opts, 10),
|
|||
|
+
|
|||
|
+ {value,{_, DBName}} = lists:keysearch(VHost, 1, VHostDB),
|
|||
|
+ {value, {DBName, DBOpts}} = lists:keysearch(DBName, 1, DBs),
|
|||
|
+
|
|||
|
+ ?MYDEBUG("Starting mod_logdb for ~p with ~p backend", [VHost, DBName]),
|
|||
|
+
|
|||
|
+ DBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(DBName)),
|
|||
|
+
|
|||
|
+ {ok, #state{vhost=VHost,
|
|||
|
+ dbmod=DBMod,
|
|||
|
+ dbopts=DBOpts,
|
|||
|
+ % dbs used for convert messages from one backend to other
|
|||
|
+ dbs=DBs,
|
|||
|
+ dolog_default=gen_mod:get_opt(dolog_default, Opts, true),
|
|||
|
+ drop_messages_on_user_removal=gen_mod:get_opt(drop_messages_on_user_removal, Opts, true),
|
|||
|
+ ignore_jids=gen_mod:get_opt(ignore_jids, Opts, []),
|
|||
|
+ groupchat=gen_mod:get_opt(groupchat, Opts, none),
|
|||
|
+ purge_older_days=gen_mod:get_opt(purge_older_days, Opts, never),
|
|||
|
+ poll_users_settings=PollUsersSettings}}.
|
|||
|
+
|
|||
|
+cleanup(#state{vhost=VHost} = State) ->
|
|||
|
+ ?MYDEBUG("Stopping ~s for ~p", [?MODULE, VHost]),
|
|||
|
+
|
|||
|
+ %ets:delete(ets_settings_table(VHost)),
|
|||
|
+
|
|||
|
+ ejabberd_hooks:delete(remove_user, VHost, ?MODULE, remove_user, 90),
|
|||
|
+ ejabberd_hooks:delete(user_send_packet, VHost, ?MODULE, send_packet, 90),
|
|||
|
+ ejabberd_hooks:delete(user_receive_packet, VHost, ?MODULE, receive_packet, 90),
|
|||
|
+ ejabberd_hooks:delete(offline_message_hook, VHost, ?MODULE, offline_packet, 10),
|
|||
|
+ %ejabberd_hooks:delete(adhoc_sm_commands, VHost, ?MODULE, adhoc_sm_commands, 110),
|
|||
|
+ %ejabberd_hooks:delete(adhoc_sm_items, VHost, ?MODULE, adhoc_sm_items, 110),
|
|||
|
+ ejabberd_hooks:delete(adhoc_local_commands, VHost, ?MODULE, adhoc_local_commands, 110),
|
|||
|
+ ejabberd_hooks:delete(adhoc_local_items, VHost, ?MODULE, adhoc_local_items, 110),
|
|||
|
+ %ejabberd_hooks:delete(disco_sm_identity, VHost, ?MODULE, get_sm_identity, 110),
|
|||
|
+ %ejabberd_hooks:delete(disco_sm_features, VHost, ?MODULE, get_sm_features, 110),
|
|||
|
+ %ejabberd_hooks:delete(disco_sm_items, VHost, ?MODULE, get_sm_items, 110),
|
|||
|
+ ejabberd_hooks:delete(disco_local_identity, VHost, ?MODULE, get_local_identity, 110),
|
|||
|
+ ejabberd_hooks:delete(disco_local_features, VHost, ?MODULE, get_local_features, 110),
|
|||
|
+ ejabberd_hooks:delete(disco_local_items, VHost, ?MODULE, get_local_items, 110),
|
|||
|
+
|
|||
|
+ ejabberd_hooks:delete(webadmin_menu_host, VHost, ?MODULE, webadmin_menu, 70),
|
|||
|
+ ejabberd_hooks:delete(webadmin_user, VHost, ?MODULE, webadmin_user, 50),
|
|||
|
+ ejabberd_hooks:delete(webadmin_page_host, VHost, ?MODULE, webadmin_page, 50),
|
|||
|
+ ejabberd_hooks:delete(webadmin_user_parse_query, VHost, ?MODULE, user_parse_query, 50),
|
|||
|
+
|
|||
|
+ ?MYDEBUG("Removed hooks for ~p", [VHost]),
|
|||
|
+
|
|||
|
+ ejabberd_ctl:unregister_commands(VHost, [{"rebuild_stats", "rebuild mod_logdb module stats for vhost"}], ?MODULE, rebuild_stats),
|
|||
|
+ Supported_backends = lists:flatmap(fun({Backend, _Opts}) ->
|
|||
|
+ [atom_to_list(Backend), " "]
|
|||
|
+ end, State#state.dbs),
|
|||
|
+ ejabberd_ctl:unregister_commands(
|
|||
|
+ VHost,
|
|||
|
+ [{"copy_messages backend", "copy messages from backend to current backend. backends could be: " ++ Supported_backends }],
|
|||
|
+ ?MODULE, copy_messages_ctl),
|
|||
|
+ ?MYDEBUG("Unregistered commands for ~p", [VHost]).
|
|||
|
+
|
|||
|
+stop(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ %gen_server:call(Proc, {cleanup}),
|
|||
|
+ %?MYDEBUG("Cleanup in stop finished!!!!", []),
|
|||
|
+ %timer:sleep(10000),
|
|||
|
+ ok = supervisor:terminate_child(ejabberd_sup, Proc),
|
|||
|
+ ok = supervisor:delete_child(ejabberd_sup, Proc).
|
|||
|
+
|
|||
|
+handle_call({cleanup}, _From, State) ->
|
|||
|
+ cleanup(State),
|
|||
|
+ ?MYDEBUG("Cleanup finished!!!!!", []),
|
|||
|
+ {reply, ok, State};
|
|||
|
+handle_call({get_dates}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
|
|||
|
+ Reply = DBMod:get_dates(VHost),
|
|||
|
+ {reply, Reply, State};
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+% ejabberd_web_admin callbacks
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+handle_call({delete_messages_by_user_at, PMsgs, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
|
|||
|
+ Reply = DBMod:delete_messages_by_user_at(VHost, PMsgs, Date),
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
|
|||
|
+ Reply = DBMod:delete_all_messages_by_user_at(User, VHost, Date),
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({delete_messages_at, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
|
|||
|
+ Reply = DBMod:delete_messages_at(VHost, Date),
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_vhost_stats}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
|
|||
|
+ Reply = DBMod:get_vhost_stats(VHost),
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_vhost_stats_at, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
|
|||
|
+ Reply = DBMod:get_vhost_stats_at(VHost, Date),
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_user_stats, User}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
|
|||
|
+ Reply = DBMod:get_user_stats(User, VHost),
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_user_messages_at, User, Date}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
|
|||
|
+ Reply = DBMod:get_user_messages_at(User, VHost, Date),
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_user_settings, User}, _From, #state{dbmod=_DBMod, vhost=VHost}=State) ->
|
|||
|
+ Reply = case ets:match_object(ets_settings_table(VHost),
|
|||
|
+ #user_settings{owner_name=User, _='_'}) of
|
|||
|
+ [Set] -> Set;
|
|||
|
+ _ -> #user_settings{owner_name=User,
|
|||
|
+ dolog_default=State#state.dolog_default,
|
|||
|
+ dolog_list=[],
|
|||
|
+ donotlog_list=[]}
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+% TODO: remove User ??
|
|||
|
+handle_call({set_user_settings, User, GSet}, _From, #state{dbmod=DBMod, vhost=VHost}=State) ->
|
|||
|
+ Set = GSet#user_settings{owner_name=User},
|
|||
|
+ Reply =
|
|||
|
+ case ets:match_object(ets_settings_table(VHost),
|
|||
|
+ #user_settings{owner_name=User, _='_'}) of
|
|||
|
+ [Set] ->
|
|||
|
+ ?MYDEBUG("Settings is equal", []),
|
|||
|
+ ok;
|
|||
|
+ _ ->
|
|||
|
+ case DBMod:set_user_settings(User, VHost, Set) of
|
|||
|
+ error ->
|
|||
|
+ error;
|
|||
|
+ ok ->
|
|||
|
+ true = ets:insert(ets_settings_table(VHost), Set),
|
|||
|
+ ok
|
|||
|
+ end
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_module_settings}, _From, State) ->
|
|||
|
+ {reply, State, State};
|
|||
|
+handle_call({set_module_settings, #state{purge_older_days=PurgeDays,
|
|||
|
+ poll_users_settings=PollSec} = Settings},
|
|||
|
+ _From,
|
|||
|
+ #state{purgeRef=PurgeRefOld,
|
|||
|
+ pollRef=PollRefOld,
|
|||
|
+ purge_older_days=PurgeDaysOld,
|
|||
|
+ poll_users_settings=PollSecOld} = State) ->
|
|||
|
+ PurgeRef = if
|
|||
|
+ PurgeDays == never, PurgeDaysOld /= never ->
|
|||
|
+ {ok, cancel} = timer:cancel(PurgeRefOld),
|
|||
|
+ disabled;
|
|||
|
+ is_integer(PurgeDays), PurgeDaysOld == never ->
|
|||
|
+ set_purge_timer(PurgeDays);
|
|||
|
+ true ->
|
|||
|
+ PurgeRefOld
|
|||
|
+ end,
|
|||
|
+
|
|||
|
+ PollRef = if
|
|||
|
+ PollSec == PollSecOld ->
|
|||
|
+ PollRefOld;
|
|||
|
+ PollSec == 0, PollSecOld /= 0 ->
|
|||
|
+ {ok, cancel} = timer:cancel(PollRefOld),
|
|||
|
+ disabled;
|
|||
|
+ is_integer(PollSec), PollSecOld == 0 ->
|
|||
|
+ set_poll_timer(PollSec);
|
|||
|
+ is_integer(PollSec), PollSecOld /= 0 ->
|
|||
|
+ {ok, cancel} = timer:cancel(PollRefOld),
|
|||
|
+ set_poll_timer(PollSec)
|
|||
|
+ end,
|
|||
|
+
|
|||
|
+ NewState = State#state{dolog_default=Settings#state.dolog_default,
|
|||
|
+ ignore_jids=Settings#state.ignore_jids,
|
|||
|
+ groupchat=Settings#state.groupchat,
|
|||
|
+ drop_messages_on_user_removal=Settings#state.drop_messages_on_user_removal,
|
|||
|
+ purge_older_days=PurgeDays,
|
|||
|
+ poll_users_settings=PollSec,
|
|||
|
+ purgeRef=PurgeRef,
|
|||
|
+ pollRef=PollRef},
|
|||
|
+ {reply, ok, NewState};
|
|||
|
+handle_call(Msg, _From, State) ->
|
|||
|
+ ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
|
|||
|
+ {noreply, State}.
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+% end ejabberd_web_admin callbacks
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+
|
|||
|
+% ejabberd_hooks call
|
|||
|
+handle_cast({addlog, Direction, Owner, Peer, Packet}, #state{dbmod=DBMod, vhost=VHost}=State) ->
|
|||
|
+ case filter(Owner, Peer, State) of
|
|||
|
+ true ->
|
|||
|
+ case catch packet_parse(Owner, Peer, Packet, Direction, State) of
|
|||
|
+ ignore ->
|
|||
|
+ ok;
|
|||
|
+ {'EXIT', Reason} ->
|
|||
|
+ ?ERROR_MSG("Failed to parse: ~p", [Reason]);
|
|||
|
+ Msg ->
|
|||
|
+ DBMod:log_message(VHost, Msg)
|
|||
|
+ end;
|
|||
|
+ false ->
|
|||
|
+ ok
|
|||
|
+ end,
|
|||
|
+ {noreply, State};
|
|||
|
+handle_cast({remove_user, User}, #state{dbmod=DBMod, vhost=VHost}=State) ->
|
|||
|
+ case State#state.drop_messages_on_user_removal of
|
|||
|
+ true ->
|
|||
|
+ DBMod:drop_user(User, VHost),
|
|||
|
+ ?INFO_MSG("Launched ~s@~s removal", [User, VHost]);
|
|||
|
+ false ->
|
|||
|
+ ?INFO_MSG("Message removing is disabled. Keeping messages for ~s@~s", [User, VHost])
|
|||
|
+ end,
|
|||
|
+ {noreply, State};
|
|||
|
+% ejabberdctl rebuild_stats/3
|
|||
|
+handle_cast({rebuild_stats}, #state{dbmod=DBMod, vhost=VHost}=State) ->
|
|||
|
+ DBMod:rebuild_stats(VHost),
|
|||
|
+ {noreply, State};
|
|||
|
+handle_cast({copy_messages, Backend}, State) ->
|
|||
|
+ spawn(?MODULE, copy_messages, [[State, Backend]]),
|
|||
|
+ {noreply, State};
|
|||
|
+handle_cast({copy_messages, Backend, Date}, State) ->
|
|||
|
+ spawn(?MODULE, copy_messages, [[State, Backend, Date]]),
|
|||
|
+ {noreply, State};
|
|||
|
+handle_cast(Msg, State) ->
|
|||
|
+ ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
|
|||
|
+ {noreply, State}.
|
|||
|
+
|
|||
|
+% return: disabled | timer reference
|
|||
|
+set_purge_timer(PurgeDays) ->
|
|||
|
+ case PurgeDays of
|
|||
|
+ never -> disabled;
|
|||
|
+ Days when is_integer(Days) ->
|
|||
|
+ {ok, Ref1} = timer:send_interval(timer:hours(24), scheduled_purging),
|
|||
|
+ Ref1
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+% return: disabled | timer reference
|
|||
|
+set_poll_timer(PollSec) ->
|
|||
|
+ if
|
|||
|
+ PollSec > 0 ->
|
|||
|
+ {ok, Ref2} = timer:send_interval(timer:seconds(PollSec), poll_users_settings),
|
|||
|
+ Ref2;
|
|||
|
+ % db polling disabled
|
|||
|
+ PollSec == 0 ->
|
|||
|
+ disabled;
|
|||
|
+ true ->
|
|||
|
+ {ok, Ref3} = timer:send_interval(timer:seconds(10), poll_users_settings),
|
|||
|
+ Ref3
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+% actual starting of logging
|
|||
|
+% from timer:send_after (in init)
|
|||
|
+handle_info(start, #state{dbmod=DBMod, vhost=VHost}=State) ->
|
|||
|
+ case DBMod:start(VHost, State#state.dbopts) of
|
|||
|
+ {error,{already_started,_}} ->
|
|||
|
+ ?MYDEBUG("backend module already started - trying to stop it", []),
|
|||
|
+ DBMod:stop(VHost),
|
|||
|
+ {stop, already_started, State};
|
|||
|
+ {error, Reason} ->
|
|||
|
+ timer:sleep(30000),
|
|||
|
+ ?ERROR_MSG("Failed to start: ~p", [Reason]),
|
|||
|
+ {stop, db_connection_failed, State};
|
|||
|
+ {ok, SPid} ->
|
|||
|
+ ?INFO_MSG("~p connection established", [DBMod]),
|
|||
|
+
|
|||
|
+ MonRef = erlang:monitor(process, SPid),
|
|||
|
+
|
|||
|
+ ets:new(ets_settings_table(VHost), [named_table,public,set,{keypos, #user_settings.owner_name}]),
|
|||
|
+ {ok, DoLog} = DBMod:get_users_settings(VHost),
|
|||
|
+ ets:insert(ets_settings_table(VHost), DoLog),
|
|||
|
+
|
|||
|
+ TrefPurge = set_purge_timer(State#state.purge_older_days),
|
|||
|
+ TrefPoll = set_poll_timer(State#state.poll_users_settings),
|
|||
|
+
|
|||
|
+ ejabberd_hooks:add(remove_user, VHost, ?MODULE, remove_user, 90),
|
|||
|
+ ejabberd_hooks:add(user_send_packet, VHost, ?MODULE, send_packet, 90),
|
|||
|
+ ejabberd_hooks:add(user_receive_packet, VHost, ?MODULE, receive_packet, 90),
|
|||
|
+ ejabberd_hooks:add(offline_message_hook, VHost, ?MODULE, offline_packet, 10),
|
|||
|
+
|
|||
|
+ ejabberd_hooks:add(disco_local_items, VHost, ?MODULE, get_local_items, 110),
|
|||
|
+ ejabberd_hooks:add(disco_local_features, VHost, ?MODULE, get_local_features, 110),
|
|||
|
+ ejabberd_hooks:add(disco_local_identity, VHost, ?MODULE, get_local_identity, 110),
|
|||
|
+ %ejabberd_hooks:add(disco_sm_items, VHost, ?MODULE, get_sm_items, 110),
|
|||
|
+ %ejabberd_hooks:add(disco_sm_features, VHost, ?MODULE, get_sm_features, 110),
|
|||
|
+ %ejabberd_hooks:add(disco_sm_identity, VHost, ?MODULE, get_sm_identity, 110),
|
|||
|
+ ejabberd_hooks:add(adhoc_local_items, VHost, ?MODULE, adhoc_local_items, 110),
|
|||
|
+ ejabberd_hooks:add(adhoc_local_commands, VHost, ?MODULE, adhoc_local_commands, 110),
|
|||
|
+ %ejabberd_hooks:add(adhoc_sm_items, VHost, ?MODULE, adhoc_sm_items, 110),
|
|||
|
+ %ejabberd_hooks:add(adhoc_sm_commands, VHost, ?MODULE, adhoc_sm_commands, 110),
|
|||
|
+
|
|||
|
+ ejabberd_hooks:add(webadmin_menu_host, VHost, ?MODULE, webadmin_menu, 70),
|
|||
|
+ ejabberd_hooks:add(webadmin_user, VHost, ?MODULE, webadmin_user, 50),
|
|||
|
+ ejabberd_hooks:add(webadmin_page_host, VHost, ?MODULE, webadmin_page, 50),
|
|||
|
+ ejabberd_hooks:add(webadmin_user_parse_query, VHost, ?MODULE, user_parse_query, 50),
|
|||
|
+
|
|||
|
+ ?MYDEBUG("Added hooks for ~p", [VHost]),
|
|||
|
+
|
|||
|
+ ejabberd_ctl:register_commands(
|
|||
|
+ VHost,
|
|||
|
+ [{"rebuild_stats", "rebuild mod_logdb module stats for vhost"}],
|
|||
|
+ ?MODULE, rebuild_stats),
|
|||
|
+ Supported_backends = lists:flatmap(fun({Backend, _Opts}) ->
|
|||
|
+ [atom_to_list(Backend), " "]
|
|||
|
+ end, State#state.dbs),
|
|||
|
+ ejabberd_ctl:register_commands(
|
|||
|
+ VHost,
|
|||
|
+ [{"copy_messages backend", "copy messages from backend to current backend. backends could be: " ++ Supported_backends }],
|
|||
|
+ ?MODULE, copy_messages_ctl),
|
|||
|
+ ?MYDEBUG("Registered commands for ~p", [VHost]),
|
|||
|
+
|
|||
|
+ NewState=State#state{monref = MonRef, backendPid=SPid, purgeRef=TrefPurge, pollRef=TrefPoll},
|
|||
|
+ {noreply, NewState};
|
|||
|
+ Rez ->
|
|||
|
+ ?ERROR_MSG("Rez=~p", [Rez]),
|
|||
|
+ timer:sleep(30000),
|
|||
|
+ {stop, db_connection_failed, State}
|
|||
|
+ end;
|
|||
|
+% from timer:send_interval/2 (in start handle_info)
|
|||
|
+handle_info(scheduled_purging, #state{vhost=VHost, purge_older_days=Days} = State) ->
|
|||
|
+ ?MYDEBUG("Starting scheduled purging of old records for ~p", [VHost]),
|
|||
|
+ spawn(?MODULE, purge_old_records, [VHost, integer_to_list(Days)]),
|
|||
|
+ {noreply, State};
|
|||
|
+% from timer:send_interval/2 (in start handle_info)
|
|||
|
+handle_info(poll_users_settings, #state{dbmod=DBMod, vhost=VHost}=State) ->
|
|||
|
+ {ok, DoLog} = DBMod:get_users_settings(VHost),
|
|||
|
+ ?MYDEBUG("DoLog=~p", [DoLog]),
|
|||
|
+ true = ets:delete_all_objects(ets_settings_table(VHost)),
|
|||
|
+ ets:insert(ets_settings_table(VHost), DoLog),
|
|||
|
+ {noreply, State};
|
|||
|
+handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
|
|||
|
+ {stop, db_connection_dropped, State};
|
|||
|
+handle_info({fetch_result, _, _}, State) ->
|
|||
|
+ ?MYDEBUG("Got timed out mysql fetch result", []),
|
|||
|
+ {noreply, State};
|
|||
|
+handle_info(Info, State) ->
|
|||
|
+ ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
|
|||
|
+ {noreply, State}.
|
|||
|
+
|
|||
|
+terminate(db_connection_failed, _State) ->
|
|||
|
+ ok;
|
|||
|
+terminate(db_connection_dropped, State) ->
|
|||
|
+ ?MYDEBUG("Got terminate with db_connection_dropped", []),
|
|||
|
+ cleanup(State),
|
|||
|
+ ok;
|
|||
|
+terminate(Reason, #state{monref=undefined} = State) ->
|
|||
|
+ ?MYDEBUG("Got terminate with undefined monref.~nReason: ~p", [Reason]),
|
|||
|
+ cleanup(State),
|
|||
|
+ ok;
|
|||
|
+terminate(Reason, #state{dbmod=DBMod, vhost=VHost, monref=MonRef, backendPid=Pid} = State) ->
|
|||
|
+ ?INFO_MSG("Reason: ~p", [Reason]),
|
|||
|
+ case erlang:is_process_alive(Pid) of
|
|||
|
+ true ->
|
|||
|
+ erlang:demonitor(MonRef, [flush]),
|
|||
|
+ DBMod:stop(VHost);
|
|||
|
+ false ->
|
|||
|
+ ok
|
|||
|
+ end,
|
|||
|
+ cleanup(State),
|
|||
|
+ ok.
|
|||
|
+
|
|||
|
+code_change(_OldVsn, State, _Extra) ->
|
|||
|
+ {ok, State}.
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% ejabberd_hooks callbacks
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+% TODO: change to/from to list as sql stores it as list
|
|||
|
+send_packet(Owner, Peer, P) ->
|
|||
|
+ VHost = Owner#jid.lserver,
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:cast(Proc, {addlog, to, Owner, Peer, P}).
|
|||
|
+
|
|||
|
+offline_packet(Peer, Owner, P) ->
|
|||
|
+ VHost = Owner#jid.lserver,
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:cast(Proc, {addlog, from, Owner, Peer, P}).
|
|||
|
+
|
|||
|
+receive_packet(_JID, Peer, Owner, P) ->
|
|||
|
+ VHost = Owner#jid.lserver,
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:cast(Proc, {addlog, from, Owner, Peer, P}).
|
|||
|
+
|
|||
|
+remove_user(User, Server) ->
|
|||
|
+ LUser = jlib:nodeprep(User),
|
|||
|
+ LServer = jlib:nameprep(Server),
|
|||
|
+ Proc = gen_mod:get_module_proc(LServer, ?PROCNAME),
|
|||
|
+ gen_server:cast(Proc, {remove_user, LUser}).
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% ejabberdctl
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+rebuild_stats(_Val, VHost, ["rebuild_stats"]) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:cast(Proc, {rebuild_stats}),
|
|||
|
+ {stop, ?STATUS_SUCCESS};
|
|||
|
+rebuild_stats(Val, _VHost, _Args) ->
|
|||
|
+ Val.
|
|||
|
+
|
|||
|
+copy_messages_ctl(_Val, VHost, ["copy_messages", Backend]) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:cast(Proc, {copy_messages, Backend}),
|
|||
|
+ {stop, ?STATUS_SUCCESS};
|
|||
|
+copy_messages_ctl(_Val, VHost, ["copy_messages", Backend, Date]) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:cast(Proc, {copy_messages, Backend, Date}),
|
|||
|
+ {stop, ?STATUS_SUCCESS};
|
|||
|
+copy_messages_ctl(Val, _VHost, _Args) ->
|
|||
|
+ Val.
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% misc operations
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+
|
|||
|
+% handle_cast({addlog, E}, _)
|
|||
|
+% raw packet -> #msg
|
|||
|
+packet_parse(Owner, Peer, Packet, Direction, State) ->
|
|||
|
+ case xml:get_subtag(Packet, "body") of
|
|||
|
+ false ->
|
|||
|
+ ignore;
|
|||
|
+ Body_xml ->
|
|||
|
+ Message_type =
|
|||
|
+ case xml:get_tag_attr_s("type", Packet) of
|
|||
|
+ [] -> "normal";
|
|||
|
+ MType -> MType
|
|||
|
+ end,
|
|||
|
+
|
|||
|
+ case Message_type of
|
|||
|
+ "groupchat" when State#state.groupchat == send, Direction == to ->
|
|||
|
+ ok;
|
|||
|
+ "groupchat" when State#state.groupchat == send, Direction == from ->
|
|||
|
+ throw(ignore);
|
|||
|
+ "groupchat" when State#state.groupchat == half ->
|
|||
|
+ Rooms = ets:match(muc_online_room, '$1'),
|
|||
|
+ Ni=lists:foldl(fun([{muc_online_room, {GName, GHost}, Pid}], Names) ->
|
|||
|
+ case gen_fsm:sync_send_all_state_event(Pid, {get_jid_nick,Owner}) of
|
|||
|
+ [] -> Names;
|
|||
|
+ Nick ->
|
|||
|
+ lists:append(Names, [jlib:jid_to_string({GName, GHost, Nick})])
|
|||
|
+ end
|
|||
|
+ end, [], Rooms),
|
|||
|
+ case lists:member(jlib:jid_to_string(Peer), Ni) of
|
|||
|
+ true when Direction == from ->
|
|||
|
+ throw(ignore);
|
|||
|
+ _ ->
|
|||
|
+ ok
|
|||
|
+ end;
|
|||
|
+ "groupchat" when State#state.groupchat == none ->
|
|||
|
+ throw(ignore);
|
|||
|
+ _ ->
|
|||
|
+ ok
|
|||
|
+ end,
|
|||
|
+
|
|||
|
+ Message_body = xml:get_tag_cdata(Body_xml),
|
|||
|
+ Message_subject =
|
|||
|
+ case xml:get_subtag(Packet, "subject") of
|
|||
|
+ false ->
|
|||
|
+ "";
|
|||
|
+ Subject_xml ->
|
|||
|
+ xml:get_tag_cdata(Subject_xml)
|
|||
|
+ end,
|
|||
|
+
|
|||
|
+ OwnerName = stringprep:tolower(Owner#jid.user),
|
|||
|
+ PName = stringprep:tolower(Peer#jid.user),
|
|||
|
+ PServer = stringprep:tolower(Peer#jid.server),
|
|||
|
+ PResource = Peer#jid.resource,
|
|||
|
+
|
|||
|
+ #msg{timestamp=get_timestamp(),
|
|||
|
+ owner_name=OwnerName,
|
|||
|
+ peer_name=PName,
|
|||
|
+ peer_server=PServer,
|
|||
|
+ peer_resource=PResource,
|
|||
|
+ direction=Direction,
|
|||
|
+ type=Message_type,
|
|||
|
+ subject=Message_subject,
|
|||
|
+ body=Message_body}
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+% called from handle_cast({addlog, _}, _) -> true (log messages) | false (do not log messages)
|
|||
|
+filter(Owner, Peer, State) ->
|
|||
|
+ OwnerStr = Owner#jid.luser++"@"++Owner#jid.lserver,
|
|||
|
+ OwnerServ = "@"++Owner#jid.lserver,
|
|||
|
+ PeerStr = Peer#jid.luser++"@"++Peer#jid.lserver,
|
|||
|
+ PeerServ = "@"++Peer#jid.lserver,
|
|||
|
+
|
|||
|
+ LogTo = case ets:match_object(ets_settings_table(State#state.vhost),
|
|||
|
+ #user_settings{owner_name=Owner#jid.luser, _='_'}) of
|
|||
|
+ [#user_settings{dolog_default=Default,
|
|||
|
+ dolog_list=DLL,
|
|||
|
+ donotlog_list=DNLL}] ->
|
|||
|
+ A = lists:member(PeerStr, DLL),
|
|||
|
+ B = lists:member(PeerStr, DNLL),
|
|||
|
+ if
|
|||
|
+ A -> true;
|
|||
|
+ B -> false;
|
|||
|
+ Default == true -> true;
|
|||
|
+ Default == false -> false;
|
|||
|
+ true -> State#state.dolog_default
|
|||
|
+ end;
|
|||
|
+ _ -> State#state.dolog_default
|
|||
|
+ end,
|
|||
|
+
|
|||
|
+ lists:all(fun(O) -> O end,
|
|||
|
+ [not lists:member(OwnerStr, State#state.ignore_jids),
|
|||
|
+ not lists:member(PeerStr, State#state.ignore_jids),
|
|||
|
+ not lists:member(OwnerServ, State#state.ignore_jids),
|
|||
|
+ not lists:member(PeerServ, State#state.ignore_jids),
|
|||
|
+ LogTo]).
|
|||
|
+
|
|||
|
+purge_old_records(VHost, Days) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+
|
|||
|
+ Dates = ?MODULE:get_dates(VHost),
|
|||
|
+ DateNow = calendar:datetime_to_gregorian_seconds({date(), {0,0,1}}),
|
|||
|
+ DateDiff = list_to_integer(Days)*24*60*60,
|
|||
|
+ ?MYDEBUG("Purging tables older than ~s days", [Days]),
|
|||
|
+ lists:foreach(fun(Date) ->
|
|||
|
+ {ok, [Year, Month, Day]} = regexp:split(Date, "[^0-9]+"),
|
|||
|
+ DateInSec = calendar:datetime_to_gregorian_seconds({{list_to_integer(Year), list_to_integer(Month), list_to_integer(Day)}, {0,0,1}}),
|
|||
|
+ if
|
|||
|
+ (DateNow - DateInSec) > DateDiff ->
|
|||
|
+ gen_server:call(Proc, {delete_messages_at, Date});
|
|||
|
+ true ->
|
|||
|
+ ?MYDEBUG("Skipping messages at ~p", [Date])
|
|||
|
+ end
|
|||
|
+ end, Dates).
|
|||
|
+
|
|||
|
+% called from get_vhost_stats/2, get_user_stats/3
|
|||
|
+sort_stats(Stats) ->
|
|||
|
+ % Stats = [{"2003-4-15",1}, {"2006-8-18",1}, ... ]
|
|||
|
+ CFun = fun({TableName, Count}) ->
|
|||
|
+ {ok, [Year, Month, Day]} = regexp:split(TableName, "[^0-9]+"),
|
|||
|
+ { calendar:datetime_to_gregorian_seconds({{list_to_integer(Year), list_to_integer(Month), list_to_integer(Day)}, {0,0,1}}), Count }
|
|||
|
+ end,
|
|||
|
+ % convert to [{63364377601,1}, {63360662401,1}, ... ]
|
|||
|
+ CStats = lists:map(CFun, Stats),
|
|||
|
+ % sort by date
|
|||
|
+ SortedStats = lists:reverse(lists:keysort(1, CStats)),
|
|||
|
+ % convert to [{"2007-12-9",1}, {"2007-10-27",1}, ... ] sorted list
|
|||
|
+ [{mod_logdb:convert_timestamp_brief(TableSec), Count} || {TableSec, Count} <- SortedStats].
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% Date/Time operations
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+% return float seconds elapsed from "zero hour" as list
|
|||
|
+get_timestamp() ->
|
|||
|
+ {MegaSec, Sec, MicroSec} = now(),
|
|||
|
+ [List] = io_lib:format("~.5f", [MegaSec*1000000 + Sec + MicroSec/1000000]),
|
|||
|
+ List.
|
|||
|
+
|
|||
|
+% convert float seconds elapsed from "zero hour" to local time "%Y-%m-%d %H:%M:%S" string
|
|||
|
+convert_timestamp(Seconds) when is_list(Seconds) ->
|
|||
|
+ case string:to_float(Seconds++".0") of
|
|||
|
+ {F,_} when is_float(F) -> convert_timestamp(F);
|
|||
|
+ _ -> erlang:error(badarg, [Seconds])
|
|||
|
+ end;
|
|||
|
+convert_timestamp(Seconds) when is_float(Seconds) ->
|
|||
|
+ GregSec = trunc(Seconds + 719528*86400),
|
|||
|
+ UnivDT = calendar:gregorian_seconds_to_datetime(GregSec),
|
|||
|
+ {{Year, Month, Day},{Hour, Minute, Sec}} = calendar:universal_time_to_local_time(UnivDT),
|
|||
|
+ integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day) ++ " " ++ integer_to_list(Hour) ++ ":" ++ integer_to_list(Minute) ++ ":" ++ integer_to_list(Sec).
|
|||
|
+
|
|||
|
+% convert float seconds elapsed from "zero hour" to local time "%Y-%m-%d" string
|
|||
|
+convert_timestamp_brief(Seconds) when is_list(Seconds) ->
|
|||
|
+ convert_timestamp_brief(list_to_float(Seconds));
|
|||
|
+convert_timestamp_brief(Seconds) when is_float(Seconds) ->
|
|||
|
+ GregSec = trunc(Seconds + 719528*86400),
|
|||
|
+ UnivDT = calendar:gregorian_seconds_to_datetime(GregSec),
|
|||
|
+ {{Year, Month, Day},{_Hour, _Minute, _Sec}} = calendar:universal_time_to_local_time(UnivDT),
|
|||
|
+ integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day);
|
|||
|
+convert_timestamp_brief(Seconds) when is_integer(Seconds) ->
|
|||
|
+ {{Year, Month, Day},{_Hour, _Minute, _Sec}} = calendar:gregorian_seconds_to_datetime(Seconds),
|
|||
|
+ integer_to_list(Year) ++ "-" ++ integer_to_list(Month) ++ "-" ++ integer_to_list(Day).
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% DB operations (get)
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+get_vhost_stats(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
|
|||
|
+
|
|||
|
+get_vhost_stats_at(VHost, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
|
|||
|
+
|
|||
|
+get_user_stats(User, VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
|
|||
|
+
|
|||
|
+get_user_messages_at(User, VHost, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
|
|||
|
+
|
|||
|
+get_dates(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
|
|||
|
+
|
|||
|
+get_user_settings(User, VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
|
|||
|
+
|
|||
|
+set_user_settings(User, VHost, Set) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {set_user_settings, User, Set}).
|
|||
|
+
|
|||
|
+get_module_settings(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_module_settings}).
|
|||
|
+
|
|||
|
+set_module_settings(VHost, Settings) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {set_module_settings, Settings}).
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% Web admin callbacks (delete)
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+user_messages_at_parse_query(VHost, Date, Msgs, Query) ->
|
|||
|
+ case lists:keysearch("delete", 1, Query) of
|
|||
|
+ {value, _} ->
|
|||
|
+ PMsgs = lists:filter(
|
|||
|
+ fun(Msg) ->
|
|||
|
+ ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg#msg.timestamp))),
|
|||
|
+ lists:member({"selected", ID}, Query)
|
|||
|
+ end, Msgs),
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {delete_messages_by_user_at, PMsgs, Date}, ?CALL_TIMEOUT);
|
|||
|
+ false ->
|
|||
|
+ nothing
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+user_messages_parse_query(User, VHost, Query) ->
|
|||
|
+ case lists:keysearch("delete", 1, Query) of
|
|||
|
+ {value, _} ->
|
|||
|
+ Dates = get_dates(VHost),
|
|||
|
+ PDates = lists:filter(
|
|||
|
+ fun(Date) ->
|
|||
|
+ ID = jlib:encode_base64(binary_to_list(term_to_binary(User++Date))),
|
|||
|
+ lists:member({"selected", ID}, Query)
|
|||
|
+ end, Dates),
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ Rez = lists:foldl(
|
|||
|
+ fun(Date, Acc) ->
|
|||
|
+ lists:append(Acc,
|
|||
|
+ [gen_server:call(Proc,
|
|||
|
+ {delete_all_messages_by_user_at, User, Date},
|
|||
|
+ ?CALL_TIMEOUT)])
|
|||
|
+ end, [], PDates),
|
|||
|
+ case lists:member(error, Rez) of
|
|||
|
+ true ->
|
|||
|
+ error;
|
|||
|
+ false ->
|
|||
|
+ nothing
|
|||
|
+ end;
|
|||
|
+ false ->
|
|||
|
+ nothing
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+vhost_messages_parse_query(VHost, Query) ->
|
|||
|
+ case lists:keysearch("delete", 1, Query) of
|
|||
|
+ {value, _} ->
|
|||
|
+ Dates = get_dates(VHost),
|
|||
|
+ PDates = lists:filter(
|
|||
|
+ fun(Date) ->
|
|||
|
+ ID = jlib:encode_base64(binary_to_list(term_to_binary(VHost++Date))),
|
|||
|
+ lists:member({"selected", ID}, Query)
|
|||
|
+ end, Dates),
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ Rez = lists:foldl(fun(Date, Acc) ->
|
|||
|
+ lists:append(Acc, [gen_server:call(Proc,
|
|||
|
+ {delete_messages_at, Date},
|
|||
|
+ ?CALL_TIMEOUT)])
|
|||
|
+ end, [], PDates),
|
|||
|
+ case lists:member(error, Rez) of
|
|||
|
+ true ->
|
|||
|
+ error;
|
|||
|
+ false ->
|
|||
|
+ nothing
|
|||
|
+ end;
|
|||
|
+ false ->
|
|||
|
+ nothing
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+vhost_messages_at_parse_query(VHost, Date, Stats, Query) ->
|
|||
|
+ case lists:keysearch("delete", 1, Query) of
|
|||
|
+ {value, _} ->
|
|||
|
+ PStats = lists:filter(
|
|||
|
+ fun({User, _Count}) ->
|
|||
|
+ ID = jlib:encode_base64(binary_to_list(term_to_binary(User++VHost))),
|
|||
|
+ lists:member({"selected", ID}, Query)
|
|||
|
+ end, Stats),
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ Rez = lists:foldl(fun({User, _Count}, Acc) ->
|
|||
|
+ lists:append(Acc, [gen_server:call(Proc,
|
|||
|
+ {delete_all_messages_by_user_at,
|
|||
|
+ User, Date},
|
|||
|
+ ?CALL_TIMEOUT)])
|
|||
|
+ end, [], PStats),
|
|||
|
+ case lists:member(error, Rez) of
|
|||
|
+ true ->
|
|||
|
+ error;
|
|||
|
+ false ->
|
|||
|
+ ok
|
|||
|
+ end;
|
|||
|
+ false ->
|
|||
|
+ nothing
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+copy_messages([#state{vhost=VHost}=State, From]) ->
|
|||
|
+ ?INFO_MSG("Going to copy messages from ~p for ~p", [From, VHost]),
|
|||
|
+
|
|||
|
+ {FromDBName, FromDBOpts} =
|
|||
|
+ case lists:keysearch(list_to_atom(From), 1, State#state.dbs) of
|
|||
|
+ {value, {FN, FO}} ->
|
|||
|
+ {FN, FO};
|
|||
|
+ false ->
|
|||
|
+ ?ERROR_MSG("Failed to find record for ~p in dbs", [From]),
|
|||
|
+ throw(error)
|
|||
|
+ end,
|
|||
|
+
|
|||
|
+ FromDBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(FromDBName)),
|
|||
|
+
|
|||
|
+ {ok, _FromPid} = FromDBMod:start(VHost, FromDBOpts),
|
|||
|
+
|
|||
|
+ Dates = FromDBMod:get_dates(VHost),
|
|||
|
+ DatesLength = length(Dates),
|
|||
|
+
|
|||
|
+ lists:foldl(fun(Date, Acc) ->
|
|||
|
+ case copy_messages_int([FromDBMod, State#state.dbmod, VHost, Date]) of
|
|||
|
+ ok ->
|
|||
|
+ ?INFO_MSG("Copied messages at ~p (~p/~p)", [Date, Acc, DatesLength]);
|
|||
|
+ Value ->
|
|||
|
+ ?ERROR_MSG("Failed to copy messages at ~p (~p/~p): ~p", [Date, Acc, DatesLength, Value]),
|
|||
|
+ FromDBMod:stop(VHost),
|
|||
|
+ throw(error)
|
|||
|
+ end,
|
|||
|
+ Acc + 1
|
|||
|
+ end, 1, Dates),
|
|||
|
+ ?INFO_MSG("Copied messages from ~p", [From]),
|
|||
|
+ FromDBMod:stop(VHost);
|
|||
|
+copy_messages([#state{vhost=VHost}=State, From, Date]) ->
|
|||
|
+ {value, {FromDBName, FromDBOpts}} = lists:keysearch(list_to_atom(From), 1, State#state.dbs),
|
|||
|
+ FromDBMod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(FromDBName)),
|
|||
|
+ {ok, _FromPid} = FromDBMod:start(VHost, FromDBOpts),
|
|||
|
+ case catch copy_messages_int([FromDBMod, State#state.dbmod, VHost, Date]) of
|
|||
|
+ {'exit', Reason} ->
|
|||
|
+ ?ERROR_MSG("Failed to copy messages at ~p: ~p", [Date, Reason]);
|
|||
|
+ ok ->
|
|||
|
+ ?INFO_MSG("Copied messages at ~p", [Date]);
|
|||
|
+ Value ->
|
|||
|
+ ?ERROR_MSG("Failed to copy messages at ~p: ~p", [Date, Value])
|
|||
|
+ end,
|
|||
|
+ FromDBMod:stop(VHost).
|
|||
|
+
|
|||
|
+copy_messages_int([FromDBMod, ToDBMod, VHost, Date]) ->
|
|||
|
+ ets:new(mod_logdb_temp, [named_table, set, public]),
|
|||
|
+ {Time, Value} = timer:tc(?MODULE, copy_messages_int_tc, [[FromDBMod, ToDBMod, VHost, Date]]),
|
|||
|
+ ets:delete_all_objects(mod_logdb_temp),
|
|||
|
+ ets:delete(mod_logdb_temp),
|
|||
|
+ ?INFO_MSG("copy_messages at ~p elapsed ~p sec", [Date, Time/1000000]),
|
|||
|
+ Value.
|
|||
|
+
|
|||
|
+copy_messages_int_tc([FromDBMod, ToDBMod, VHost, Date]) ->
|
|||
|
+ ?INFO_MSG("Going to copy messages from ~p for ~p at ~p", [FromDBMod, VHost, Date]),
|
|||
|
+
|
|||
|
+ ok = FromDBMod:rebuild_stats_at(VHost, Date),
|
|||
|
+ catch mod_logdb:rebuild_stats_at(VHost, Date),
|
|||
|
+ {ok, FromStats} = FromDBMod:get_vhost_stats_at(VHost, Date),
|
|||
|
+ ToStats = case mod_logdb:get_vhost_stats_at(VHost, Date) of
|
|||
|
+ {ok, Stats} -> Stats;
|
|||
|
+ {error, _} -> []
|
|||
|
+ end,
|
|||
|
+
|
|||
|
+ FromStatsS = lists:keysort(1, FromStats),
|
|||
|
+ ToStatsS = lists:keysort(1, ToStats),
|
|||
|
+
|
|||
|
+ StatsLength = length(FromStats),
|
|||
|
+
|
|||
|
+ CopyFun = if
|
|||
|
+ % destination table is empty
|
|||
|
+ FromDBMod /= mod_logdb_mnesia_old, ToStats == [] ->
|
|||
|
+ fun({User, _Count}, Acc) ->
|
|||
|
+ {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date),
|
|||
|
+ MAcc =
|
|||
|
+ lists:foldl(fun(Msg, MFAcc) ->
|
|||
|
+ ok = ToDBMod:log_message(VHost, Msg),
|
|||
|
+ MFAcc + 1
|
|||
|
+ end, 0, Msgs),
|
|||
|
+ NewAcc = Acc + 1,
|
|||
|
+ ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
|
|||
|
+ %timer:sleep(100),
|
|||
|
+ NewAcc
|
|||
|
+ end;
|
|||
|
+ % destination table is not empty
|
|||
|
+ FromDBMod /= mod_logdb_mnesia_old, ToStats /= [] ->
|
|||
|
+ fun({User, _Count}, Acc) ->
|
|||
|
+ {ok, ToMsgs} = ToDBMod:get_user_messages_at(User, VHost, Date),
|
|||
|
+ lists:foreach(fun(#msg{timestamp=Tst}) when length(Tst) == 16 ->
|
|||
|
+ ets:insert(mod_logdb_temp, {Tst});
|
|||
|
+ % mysql, pgsql removes final zeros after decimal point
|
|||
|
+ (#msg{timestamp=Tst}) when length(Tst) < 16 ->
|
|||
|
+ {F, _} = string:to_float(Tst++".0"),
|
|||
|
+ [T] = io_lib:format("~.5f", [F]),
|
|||
|
+ ets:insert(mod_logdb_temp, {T})
|
|||
|
+ end, ToMsgs),
|
|||
|
+ {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date),
|
|||
|
+ MAcc =
|
|||
|
+ lists:foldl(fun(#msg{timestamp=ToTimestamp} = Msg, MFAcc) ->
|
|||
|
+ case ets:member(mod_logdb_temp, ToTimestamp) of
|
|||
|
+ false ->
|
|||
|
+ ok = ToDBMod:log_message(VHost, Msg),
|
|||
|
+ ets:insert(mod_logdb_temp, {ToTimestamp}),
|
|||
|
+ MFAcc + 1;
|
|||
|
+ true ->
|
|||
|
+ MFAcc
|
|||
|
+ end
|
|||
|
+ end, 0, Msgs),
|
|||
|
+ NewAcc = Acc + 1,
|
|||
|
+ ets:delete_all_objects(mod_logdb_temp),
|
|||
|
+ ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
|
|||
|
+ %timer:sleep(100),
|
|||
|
+ NewAcc
|
|||
|
+ end;
|
|||
|
+ % copying from mod_logmnesia
|
|||
|
+ true ->
|
|||
|
+ fun({User, _Count}, Acc) ->
|
|||
|
+ ToStats =
|
|||
|
+ case ToDBMod:get_user_messages_at(User, VHost, Date) of
|
|||
|
+ {ok, []} ->
|
|||
|
+ ok;
|
|||
|
+ {ok, ToMsgs} ->
|
|||
|
+ lists:foreach(fun(#msg{timestamp=Tst}) when length(Tst) == 16 ->
|
|||
|
+ ets:insert(mod_logdb_temp, {Tst});
|
|||
|
+ % mysql, pgsql removes final zeros after decimal point
|
|||
|
+ (#msg{timestamp=Tst}) when length(Tst) < 15 ->
|
|||
|
+ {F, _} = string:to_float(Tst++".0"),
|
|||
|
+ [T] = io_lib:format("~.5f", [F]),
|
|||
|
+ ets:insert(mod_logdb_temp, {T})
|
|||
|
+ end, ToMsgs);
|
|||
|
+ {error, _} ->
|
|||
|
+ ok
|
|||
|
+ end,
|
|||
|
+ {ok, Msgs} = FromDBMod:get_user_messages_at(User, VHost, Date),
|
|||
|
+
|
|||
|
+ MAcc =
|
|||
|
+ lists:foldl(
|
|||
|
+ fun({msg, TU, TS, TR, FU, FS, FR, Type, Subj, Body, Timest},
|
|||
|
+ MFAcc) ->
|
|||
|
+ [Timestamp] = if is_float(Timest) == true ->
|
|||
|
+ io_lib:format("~.5f", [Timest]);
|
|||
|
+ % early versions of mod_logmnesia
|
|||
|
+ is_integer(Timest) == true ->
|
|||
|
+ io_lib:format("~.5f", [Timest-719528*86400.0]);
|
|||
|
+ true ->
|
|||
|
+ ?ERROR_MSG("Incorrect timestamp ~p", [Timest]),
|
|||
|
+ throw(error)
|
|||
|
+ end,
|
|||
|
+ case ets:member(mod_logdb_temp, Timestamp) of
|
|||
|
+ false ->
|
|||
|
+ if
|
|||
|
+ % from
|
|||
|
+ TS == VHost ->
|
|||
|
+ TMsg = #msg{timestamp=Timestamp,
|
|||
|
+ owner_name=TU,
|
|||
|
+ peer_name=FU, peer_server=FS, peer_resource=FR,
|
|||
|
+ direction=from,
|
|||
|
+ type=Type,
|
|||
|
+ subject=Subj, body=Body},
|
|||
|
+ ok = ToDBMod:log_message(VHost, TMsg);
|
|||
|
+ true -> ok
|
|||
|
+ end,
|
|||
|
+ if
|
|||
|
+ % to
|
|||
|
+ FS == VHost ->
|
|||
|
+ FMsg = #msg{timestamp=Timestamp,
|
|||
|
+ owner_name=FU,
|
|||
|
+ peer_name=TU, peer_server=TS, peer_resource=TR,
|
|||
|
+ direction=to,
|
|||
|
+ type=Type,
|
|||
|
+ subject=Subj, body=Body},
|
|||
|
+ ok = ToDBMod:log_message(VHost, FMsg);
|
|||
|
+ true -> ok
|
|||
|
+ end,
|
|||
|
+ ets:insert(mod_logdb_temp, {Timestamp}),
|
|||
|
+ MFAcc + 1;
|
|||
|
+ true -> % not ets:member
|
|||
|
+ MFAcc
|
|||
|
+ end % case
|
|||
|
+ end, 0, Msgs), % foldl
|
|||
|
+ NewAcc = Acc + 1,
|
|||
|
+ ?INFO_MSG("Copied ~p messages for ~p (~p/~p) at ~p", [MAcc, User, NewAcc, StatsLength, Date]),
|
|||
|
+ %timer:sleep(100),
|
|||
|
+ NewAcc
|
|||
|
+ end % fun
|
|||
|
+ end, % if FromDBMod /= mod_logdb_mnesia_old
|
|||
|
+
|
|||
|
+ if
|
|||
|
+ FromStats == [] ->
|
|||
|
+ ?INFO_MSG("No messages were found at ~p", [Date]);
|
|||
|
+ FromStatsS == ToStatsS ->
|
|||
|
+ ?INFO_MSG("Stats are equal at ~p", [Date]);
|
|||
|
+ FromStatsS /= ToStatsS ->
|
|||
|
+ lists:foldl(CopyFun, 0, FromStats),
|
|||
|
+ ok = ToDBMod:rebuild_stats_at(VHost, Date)
|
|||
|
+ %timer:sleep(1000)
|
|||
|
+ end,
|
|||
|
+
|
|||
|
+ ok.
|
|||
|
+
|
|||
|
+list_to_bool(Num) ->
|
|||
|
+ case lists:member(Num, ["t", "true", "y", "yes", "1"]) of
|
|||
|
+ true ->
|
|||
|
+ true;
|
|||
|
+ false ->
|
|||
|
+ case lists:member(Num, ["f", "false", "n", "no", "0"]) of
|
|||
|
+ true ->
|
|||
|
+ false;
|
|||
|
+ false ->
|
|||
|
+ error
|
|||
|
+ end
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+bool_to_list(true) ->
|
|||
|
+ "TRUE";
|
|||
|
+bool_to_list(false) ->
|
|||
|
+ "FALSE".
|
|||
|
+
|
|||
|
+list_to_string([]) ->
|
|||
|
+ "";
|
|||
|
+list_to_string(List) when is_list(List) ->
|
|||
|
+ Str = lists:flatmap(fun(Elm) -> Elm ++ "\n" end, List),
|
|||
|
+ lists:sublist(Str, length(Str)-1).
|
|||
|
+
|
|||
|
+string_to_list(null) ->
|
|||
|
+ [];
|
|||
|
+string_to_list([]) ->
|
|||
|
+ [];
|
|||
|
+string_to_list(String) ->
|
|||
|
+ {ok, List} = regexp:split(String, "\n"),
|
|||
|
+ List.
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% ad-hoc (copy/pasted from mod_configure.erl)
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+-define(ITEMS_RESULT(Allow, LNode, Fallback),
|
|||
|
+ case Allow of
|
|||
|
+ deny ->
|
|||
|
+ Fallback;
|
|||
|
+ allow ->
|
|||
|
+ case get_local_items(LServer, LNode,
|
|||
|
+ jlib:jid_to_string(To), Lang) of
|
|||
|
+ {result, Res} ->
|
|||
|
+ {result, Res};
|
|||
|
+ {error, Error} ->
|
|||
|
+ {error, Error}
|
|||
|
+ end
|
|||
|
+ end).
|
|||
|
+
|
|||
|
+get_local_items(Acc, From, #jid{lserver = LServer} = To, "", Lang) ->
|
|||
|
+ case gen_mod:is_loaded(LServer, mod_adhoc) of
|
|||
|
+ false ->
|
|||
|
+ Acc;
|
|||
|
+ _ ->
|
|||
|
+ Items = case Acc of
|
|||
|
+ {result, Its} -> Its;
|
|||
|
+ empty -> []
|
|||
|
+ end,
|
|||
|
+ AllowUser = acl:match_rule(LServer, mod_logdb, From),
|
|||
|
+ AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
|
|||
|
+ if
|
|||
|
+ AllowUser == allow; AllowAdmin == allow ->
|
|||
|
+ case get_local_items(LServer, [],
|
|||
|
+ jlib:jid_to_string(To), Lang) of
|
|||
|
+ {result, Res} ->
|
|||
|
+ {result, Items ++ Res};
|
|||
|
+ {error, _Error} ->
|
|||
|
+ {result, Items}
|
|||
|
+ end;
|
|||
|
+ true ->
|
|||
|
+ {result, Items}
|
|||
|
+ end
|
|||
|
+ end;
|
|||
|
+get_local_items(Acc, From, #jid{lserver = LServer} = To, Node, Lang) ->
|
|||
|
+ case gen_mod:is_loaded(LServer, mod_adhoc) of
|
|||
|
+ false ->
|
|||
|
+ Acc;
|
|||
|
+ _ ->
|
|||
|
+ LNode = string:tokens(Node, "/"),
|
|||
|
+ AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
|
|||
|
+ case LNode of
|
|||
|
+ ["mod_logdb"] ->
|
|||
|
+ ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
|
|||
|
+ ["mod_logdb_users"] ->
|
|||
|
+ ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
|
|||
|
+ ["mod_logdb_users", [$@ | _]] ->
|
|||
|
+ ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
|
|||
|
+ ["mod_logdb_users", _User] ->
|
|||
|
+ ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
|
|||
|
+ ["mod_logdb_settings"] ->
|
|||
|
+ ?ITEMS_RESULT(AllowAdmin, LNode, {error, ?ERR_FORBIDDEN});
|
|||
|
+ _ ->
|
|||
|
+ Acc
|
|||
|
+ end
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+-define(NODE(Name, Node),
|
|||
|
+ {xmlelement, "item",
|
|||
|
+ [{"jid", Server},
|
|||
|
+ {"name", translate:translate(Lang, Name)},
|
|||
|
+ {"node", Node}], []}).
|
|||
|
+
|
|||
|
+get_local_items(_Host, [], Server, Lang) ->
|
|||
|
+ {result,
|
|||
|
+ [?NODE("Messages logging engine", "mod_logdb")]
|
|||
|
+ };
|
|||
|
+get_local_items(_Host, ["mod_logdb"], Server, Lang) ->
|
|||
|
+ {result,
|
|||
|
+ [?NODE("Messages logging engine users", "mod_logdb_users"),
|
|||
|
+ ?NODE("Messages logging engine settings", "mod_logdb_settings")]
|
|||
|
+ };
|
|||
|
+get_local_items(Host, ["mod_logdb_users"], Server, Lang) ->
|
|||
|
+ {result, get_all_vh_users(Host, Server, Lang)};
|
|||
|
+get_local_items(_Host, ["mod_logdb_users", [$@ | Diap]], Server, Lang) ->
|
|||
|
+ case catch ejabberd_auth:dirty_get_registered_users() of
|
|||
|
+ {'EXIT', _Reason} ->
|
|||
|
+ ?ERR_INTERNAL_SERVER_ERROR;
|
|||
|
+ Users ->
|
|||
|
+ SUsers = lists:sort([{S, U} || {U, S} <- Users]),
|
|||
|
+ case catch begin
|
|||
|
+ {ok, [S1, S2]} = regexp:split(Diap, "-"),
|
|||
|
+ N1 = list_to_integer(S1),
|
|||
|
+ N2 = list_to_integer(S2),
|
|||
|
+ Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
|
|||
|
+ lists:map(fun({S, U}) ->
|
|||
|
+ ?NODE(U ++ "@" ++ S, "mod_logdb_users/" ++ U ++ "@" ++ S)
|
|||
|
+ end, Sub)
|
|||
|
+ end of
|
|||
|
+ {'EXIT', _Reason} ->
|
|||
|
+ ?ERR_NOT_ACCEPTABLE;
|
|||
|
+ Res ->
|
|||
|
+ {result, Res}
|
|||
|
+ end
|
|||
|
+ end;
|
|||
|
+get_local_items(_Host, ["mod_logdb_users", _User], _Server, _Lang) ->
|
|||
|
+ {result, []};
|
|||
|
+get_local_items(_Host, ["mod_logdb_settings"], _Server, _Lang) ->
|
|||
|
+ {result, []};
|
|||
|
+get_local_items(_Host, Item, _Server, _Lang) ->
|
|||
|
+ ?MYDEBUG("asked for items in ~p", [Item]),
|
|||
|
+ {error, ?ERR_ITEM_NOT_FOUND}.
|
|||
|
+
|
|||
|
+-define(INFO_RESULT(Allow, Feats),
|
|||
|
+ case Allow of
|
|||
|
+ deny ->
|
|||
|
+ {error, ?ERR_FORBIDDEN};
|
|||
|
+ allow ->
|
|||
|
+ {result, Feats}
|
|||
|
+ end).
|
|||
|
+
|
|||
|
+get_local_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
|
|||
|
+ case gen_mod:is_loaded(LServer, mod_adhoc) of
|
|||
|
+ false ->
|
|||
|
+ Acc;
|
|||
|
+ _ ->
|
|||
|
+ LNode = string:tokens(Node, "/"),
|
|||
|
+ AllowUser = acl:match_rule(LServer, mod_logdb, From),
|
|||
|
+ AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
|
|||
|
+ case LNode of
|
|||
|
+ ["mod_logdb"] when AllowUser == allow; AllowAdmin == allow ->
|
|||
|
+ ?INFO_RESULT(allow, [?NS_COMMANDS]);
|
|||
|
+ ["mod_logdb"] ->
|
|||
|
+ ?INFO_RESULT(deny, [?NS_COMMANDS]);
|
|||
|
+ ["mod_logdb_users"] ->
|
|||
|
+ ?INFO_RESULT(AllowAdmin, []);
|
|||
|
+ ["mod_logdb_users", [$@ | _]] ->
|
|||
|
+ ?INFO_RESULT(AllowAdmin, []);
|
|||
|
+ ["mod_logdb_users", _User] ->
|
|||
|
+ ?INFO_RESULT(AllowAdmin, [?NS_COMMANDS]);
|
|||
|
+ ["mod_logdb_settings"] ->
|
|||
|
+ ?INFO_RESULT(AllowAdmin, [?NS_COMMANDS]);
|
|||
|
+ [] ->
|
|||
|
+ Acc;
|
|||
|
+ _ ->
|
|||
|
+ %?MYDEBUG("asked for ~p features: ~p", [LNode, Allow]),
|
|||
|
+ Acc
|
|||
|
+ end
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+-define(INFO_IDENTITY(Category, Type, Name, Lang),
|
|||
|
+ [{xmlelement, "identity",
|
|||
|
+ [{"category", Category},
|
|||
|
+ {"type", Type},
|
|||
|
+ {"name", translate:translate(Lang, Name)}], []}]).
|
|||
|
+
|
|||
|
+-define(INFO_COMMAND(Name, Lang),
|
|||
|
+ ?INFO_IDENTITY("automation", "command-node", Name, Lang)).
|
|||
|
+
|
|||
|
+get_local_identity(Acc, _From, _To, Node, Lang) ->
|
|||
|
+ LNode = string:tokens(Node, "/"),
|
|||
|
+ case LNode of
|
|||
|
+ ["mod_logdb"] ->
|
|||
|
+ ?INFO_COMMAND("Messages logging engine", Lang);
|
|||
|
+ ["mod_logdb_users"] ->
|
|||
|
+ ?INFO_COMMAND("Messages logging engine users", Lang);
|
|||
|
+ ["mod_logdb_users", [$@ | _]] ->
|
|||
|
+ Acc;
|
|||
|
+ ["mod_logdb_users", User] ->
|
|||
|
+ ?INFO_COMMAND(User, Lang);
|
|||
|
+ ["mod_logdb_settings"] ->
|
|||
|
+ ?INFO_COMMAND("Messages logging engine settings", Lang);
|
|||
|
+ [] ->
|
|||
|
+ Acc;
|
|||
|
+ _ ->
|
|||
|
+ Acc
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+%get_sm_items(Acc, From, To, Node, Lang) ->
|
|||
|
+% ?MYDEBUG("get_sm_items Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
|
|||
|
+% Acc.
|
|||
|
+
|
|||
|
+%get_sm_features(Acc, From, To, Node, Lang) ->
|
|||
|
+% ?MYDEBUG("get_sm_features Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
|
|||
|
+% Acc.
|
|||
|
+
|
|||
|
+%get_sm_identity(Acc, From, To, Node, Lang) ->
|
|||
|
+% ?MYDEBUG("get_sm_identity Acc=~p From=~p To=~p Node=~p Lang=~p", [Acc, From, To, Node, Lang]),
|
|||
|
+% Acc.
|
|||
|
+
|
|||
|
+adhoc_local_items(Acc, From, #jid{lserver = LServer, server = Server} = To,
|
|||
|
+ Lang) ->
|
|||
|
+ Items = case Acc of
|
|||
|
+ {result, Its} -> Its;
|
|||
|
+ empty -> []
|
|||
|
+ end,
|
|||
|
+ Nodes = recursively_get_local_items(LServer, "", Server, Lang),
|
|||
|
+ Nodes1 = lists:filter(
|
|||
|
+ fun(N) ->
|
|||
|
+ Nd = xml:get_tag_attr_s("node", N),
|
|||
|
+ F = get_local_features([], From, To, Nd, Lang),
|
|||
|
+ case F of
|
|||
|
+ {result, [?NS_COMMANDS]} ->
|
|||
|
+ true;
|
|||
|
+ _ ->
|
|||
|
+ false
|
|||
|
+ end
|
|||
|
+ end, Nodes),
|
|||
|
+ {result, Items ++ Nodes1}.
|
|||
|
+
|
|||
|
+recursively_get_local_items(_LServer, "mod_logdb_users", _Server, _Lang) ->
|
|||
|
+ [];
|
|||
|
+recursively_get_local_items(LServer, Node, Server, Lang) ->
|
|||
|
+ LNode = string:tokens(Node, "/"),
|
|||
|
+ Items = case get_local_items(LServer, LNode, Server, Lang) of
|
|||
|
+ {result, Res} ->
|
|||
|
+ Res;
|
|||
|
+ {error, _Error} ->
|
|||
|
+ []
|
|||
|
+ end,
|
|||
|
+ Nodes = lists:flatten(
|
|||
|
+ lists:map(
|
|||
|
+ fun(N) ->
|
|||
|
+ S = xml:get_tag_attr_s("jid", N),
|
|||
|
+ Nd = xml:get_tag_attr_s("node", N),
|
|||
|
+ if (S /= Server) or (Nd == "") ->
|
|||
|
+ [];
|
|||
|
+ true ->
|
|||
|
+ [N, recursively_get_local_items(
|
|||
|
+ LServer, Nd, Server, Lang)]
|
|||
|
+ end
|
|||
|
+ end, Items)),
|
|||
|
+ Nodes.
|
|||
|
+
|
|||
|
+-define(COMMANDS_RESULT(Allow, From, To, Request),
|
|||
|
+ case Allow of
|
|||
|
+ deny ->
|
|||
|
+ {error, ?ERR_FORBIDDEN};
|
|||
|
+ allow ->
|
|||
|
+ adhoc_local_commands(From, To, Request)
|
|||
|
+ end).
|
|||
|
+
|
|||
|
+adhoc_local_commands(Acc, From, #jid{lserver = LServer} = To,
|
|||
|
+ #adhoc_request{node = Node} = Request) ->
|
|||
|
+ LNode = string:tokens(Node, "/"),
|
|||
|
+ AllowUser = acl:match_rule(LServer, mod_logdb, From),
|
|||
|
+ AllowAdmin = acl:match_rule(LServer, mod_logdb_admin, From),
|
|||
|
+ case LNode of
|
|||
|
+ ["mod_logdb"] when AllowUser == allow; AllowAdmin == allow ->
|
|||
|
+ ?COMMANDS_RESULT(allow, From, To, Request);
|
|||
|
+ ["mod_logdb_users", _User] when AllowAdmin == allow ->
|
|||
|
+ ?COMMANDS_RESULT(allow, From, To, Request);
|
|||
|
+ ["mod_logdb_settings"] when AllowAdmin == allow ->
|
|||
|
+ ?COMMANDS_RESULT(allow, From, To, Request);
|
|||
|
+ _ ->
|
|||
|
+ Acc
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+adhoc_local_commands(From, #jid{lserver = LServer} = _To,
|
|||
|
+ #adhoc_request{lang = Lang,
|
|||
|
+ node = Node,
|
|||
|
+ sessionid = SessionID,
|
|||
|
+ action = Action,
|
|||
|
+ xdata = XData} = Request) ->
|
|||
|
+ LNode = string:tokens(Node, "/"),
|
|||
|
+ %% If the "action" attribute is not present, it is
|
|||
|
+ %% understood as "execute". If there was no <actions/>
|
|||
|
+ %% element in the first response (which there isn't in our
|
|||
|
+ %% case), "execute" and "complete" are equivalent.
|
|||
|
+ ActionIsExecute = lists:member(Action,
|
|||
|
+ ["", "execute", "complete"]),
|
|||
|
+ if Action == "cancel" ->
|
|||
|
+ %% User cancels request
|
|||
|
+ adhoc:produce_response(
|
|||
|
+ Request,
|
|||
|
+ #adhoc_response{status = canceled});
|
|||
|
+ XData == false, ActionIsExecute ->
|
|||
|
+ %% User requests form
|
|||
|
+ case get_form(LServer, LNode, From, Lang) of
|
|||
|
+ {result, Form} ->
|
|||
|
+ adhoc:produce_response(
|
|||
|
+ Request,
|
|||
|
+ #adhoc_response{status = executing,
|
|||
|
+ elements = Form});
|
|||
|
+ {error, Error} ->
|
|||
|
+ {error, Error}
|
|||
|
+ end;
|
|||
|
+ XData /= false, ActionIsExecute ->
|
|||
|
+ %% User returns form.
|
|||
|
+ case jlib:parse_xdata_submit(XData) of
|
|||
|
+ invalid ->
|
|||
|
+ {error, ?ERR_BAD_REQUEST};
|
|||
|
+ Fields ->
|
|||
|
+ case set_form(From, LServer, LNode, Lang, Fields) of
|
|||
|
+ {result, _Res} ->
|
|||
|
+ adhoc:produce_response(
|
|||
|
+ #adhoc_response{lang = Lang,
|
|||
|
+ node = Node,
|
|||
|
+ sessionid = SessionID,
|
|||
|
+ status = completed});
|
|||
|
+ {error, Error} ->
|
|||
|
+ {error, Error}
|
|||
|
+ end
|
|||
|
+ end;
|
|||
|
+ true ->
|
|||
|
+ {error, ?ERR_BAD_REQUEST}
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+-define(LISTLINE(Label, Value),
|
|||
|
+ {xmlelement, "option", [{"label", Label}],
|
|||
|
+ [{xmlelement, "value", [], [{xmlcdata, Value}]}]}).
|
|||
|
+-define(DEFVAL(Value), {xmlelement, "value", [], [{xmlcdata, Value}]}).
|
|||
|
+
|
|||
|
+get_user_form(LUser, LServer, Lang) ->
|
|||
|
+ %From = jlib:jid_to_string(jlib:jid_remove_resource(Jid)),
|
|||
|
+ #user_settings{dolog_default=DLD,
|
|||
|
+ dolog_list=DLL,
|
|||
|
+ donotlog_list=DNLL} = get_user_settings(LUser, LServer),
|
|||
|
+ {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
|
|||
|
+ [{xmlelement, "title", [],
|
|||
|
+ [{xmlcdata,
|
|||
|
+ translate:translate(
|
|||
|
+ Lang, "Messages logging engine settings")}]},
|
|||
|
+ {xmlelement, "instructions", [],
|
|||
|
+ [{xmlcdata,
|
|||
|
+ translate:translate(
|
|||
|
+ Lang, "Set logging preferences")++ ": " ++ LUser ++ "@" ++ LServer}]},
|
|||
|
+ % default to log
|
|||
|
+ {xmlelement, "field", [{"type", "list-single"},
|
|||
|
+ {"label",
|
|||
|
+ translate:translate(Lang, "Default")},
|
|||
|
+ {"var", "dolog_default"}],
|
|||
|
+ [?DEFVAL(atom_to_list(DLD)),
|
|||
|
+ ?LISTLINE(translate:translate(Lang, "Log Messages"), "true"),
|
|||
|
+ ?LISTLINE(translate:translate(Lang, "Do Not Log Messages"), "false")
|
|||
|
+ ]},
|
|||
|
+ % do log list
|
|||
|
+ {xmlelement, "field", [{"type", "text-multi"},
|
|||
|
+ {"label",
|
|||
|
+ translate:translate(
|
|||
|
+ Lang, "Log Messages")},
|
|||
|
+ {"var", "dolog_list"}],
|
|||
|
+ [{xmlelement, "value", [], [{xmlcdata, list_to_string(DLL)}]}]},
|
|||
|
+ % do not log list
|
|||
|
+ {xmlelement, "field", [{"type", "text-multi"},
|
|||
|
+ {"label",
|
|||
|
+ translate:translate(
|
|||
|
+ Lang, "Do Not Log Messages")},
|
|||
|
+ {"var", "donotlog_list"}],
|
|||
|
+ [{xmlelement, "value", [], [{xmlcdata, list_to_string(DNLL)}]}]}
|
|||
|
+ ]}]}.
|
|||
|
+
|
|||
|
+get_settings_form(Host, Lang) ->
|
|||
|
+ #state{dbmod=DBMod,
|
|||
|
+ dbs=DBs,
|
|||
|
+ dolog_default=DLD,
|
|||
|
+ ignore_jids=IgnoreJids,
|
|||
|
+ groupchat=GroupChat,
|
|||
|
+ purge_older_days=PurgeDaysT,
|
|||
|
+ drop_messages_on_user_removal=MRemoval,
|
|||
|
+ poll_users_settings=PollTime} = mod_logdb:get_module_settings(Host),
|
|||
|
+
|
|||
|
+ Backends = lists:map(fun({Backend, _Opts}) ->
|
|||
|
+ ?LISTLINE(atom_to_list(Backend), atom_to_list(Backend))
|
|||
|
+ end, DBs),
|
|||
|
+ DB = lists:sublist(atom_to_list(DBMod), length(atom_to_list(?MODULE)) + 2, length(atom_to_list(DBMod))),
|
|||
|
+ DBsL = lists:append([?DEFVAL(DB)], Backends),
|
|||
|
+
|
|||
|
+ PurgeDays =
|
|||
|
+ case PurgeDaysT of
|
|||
|
+ never -> "never";
|
|||
|
+ Num when is_integer(Num) -> integer_to_list(Num);
|
|||
|
+ _ -> "unknown"
|
|||
|
+ end,
|
|||
|
+ {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}],
|
|||
|
+ [{xmlelement, "title", [],
|
|||
|
+ [{xmlcdata,
|
|||
|
+ translate:translate(
|
|||
|
+ Lang, "Messages logging engine settings") ++ " (run-time)"}]},
|
|||
|
+ {xmlelement, "instructions", [],
|
|||
|
+ [{xmlcdata,
|
|||
|
+ translate:translate(
|
|||
|
+ Lang, "Set run-time settings")}]},
|
|||
|
+ % backends
|
|||
|
+ {xmlelement, "field", [{"type", "list-single"},
|
|||
|
+ {"label",
|
|||
|
+ translate:translate(Lang, "Backend")},
|
|||
|
+ {"var", "backend"}],
|
|||
|
+ DBsL},
|
|||
|
+ % dbs
|
|||
|
+ {xmlelement, "field", [{"type", "text-multi"},
|
|||
|
+ {"label",
|
|||
|
+ translate:translate(
|
|||
|
+ Lang, "dbs")},
|
|||
|
+ {"var", "dbs"}],
|
|||
|
+ [{xmlelement, "value", [], [{xmlcdata, lists:flatten(io_lib:format("~p.",[DBs]))}]}]},
|
|||
|
+ % default to log
|
|||
|
+ {xmlelement, "field", [{"type", "list-single"},
|
|||
|
+ {"label",
|
|||
|
+ translate:translate(Lang, "Default")},
|
|||
|
+ {"var", "dolog_default"}],
|
|||
|
+ [?DEFVAL(atom_to_list(DLD)),
|
|||
|
+ ?LISTLINE(translate:translate(Lang, "Log Messages"), "true"),
|
|||
|
+ ?LISTLINE(translate:translate(Lang, "Do Not Log Messages"), "false")
|
|||
|
+ ]},
|
|||
|
+ % drop_messages_on_user_removal
|
|||
|
+ {xmlelement, "field", [{"type", "list-single"},
|
|||
|
+ {"label",
|
|||
|
+ translate:translate(Lang, "Drop messages on user removal")},
|
|||
|
+ {"var", "drop_messages_on_user_removal"}],
|
|||
|
+ [?DEFVAL(atom_to_list(MRemoval)),
|
|||
|
+ ?LISTLINE(translate:translate(Lang, "Drop"), "true"),
|
|||
|
+ ?LISTLINE(translate:translate(Lang, "Do not drop"), "false")
|
|||
|
+ ]},
|
|||
|
+ % groupchat
|
|||
|
+ {xmlelement, "field", [{"type", "list-single"},
|
|||
|
+ {"label",
|
|||
|
+ translate:translate(Lang, "Groupchat messages logging")},
|
|||
|
+ {"var", "groupchat"}],
|
|||
|
+ [?DEFVAL(atom_to_list(GroupChat)),
|
|||
|
+ ?LISTLINE("all", "all"),
|
|||
|
+ ?LISTLINE("none", "none"),
|
|||
|
+ ?LISTLINE("send", "send"),
|
|||
|
+ ?LISTLINE("half", "half")
|
|||
|
+ ]},
|
|||
|
+ % ignore_jids
|
|||
|
+ {xmlelement, "field", [{"type", "text-multi"},
|
|||
|
+ {"label",
|
|||
|
+ translate:translate(
|
|||
|
+ Lang, "Jids/Domains to ignore")},
|
|||
|
+ {"var", "ignore_list"}],
|
|||
|
+ [{xmlelement, "value", [], [{xmlcdata, list_to_string(IgnoreJids)}]}]},
|
|||
|
+ % purge older days
|
|||
|
+ {xmlelement, "field", [{"type", "text-single"},
|
|||
|
+ {"label",
|
|||
|
+ translate:translate(
|
|||
|
+ Lang, "Purge messages older than (days)")},
|
|||
|
+ {"var", "purge_older_days"}],
|
|||
|
+ [{xmlelement, "value", [], [{xmlcdata, PurgeDays}]}]},
|
|||
|
+ % poll users settings
|
|||
|
+ {xmlelement, "field", [{"type", "text-single"},
|
|||
|
+ {"label",
|
|||
|
+ translate:translate(
|
|||
|
+ Lang, "Poll users settings (seconds)")},
|
|||
|
+ {"var", "poll_users_settings"}],
|
|||
|
+ [{xmlelement, "value", [], [{xmlcdata, integer_to_list(PollTime)}]}]}
|
|||
|
+ ]}]}.
|
|||
|
+
|
|||
|
+get_form(_Host, ["mod_logdb"], #jid{luser = LUser, lserver = LServer} = _Jid, Lang) ->
|
|||
|
+ get_user_form(LUser, LServer, Lang);
|
|||
|
+get_form(_Host, ["mod_logdb_users", User], _JidFrom, Lang) ->
|
|||
|
+ #jid{luser=LUser, lserver=LServer} = jlib:string_to_jid(User),
|
|||
|
+ get_user_form(LUser, LServer, Lang);
|
|||
|
+get_form(Host, ["mod_logdb_settings"], _JidFrom, Lang) ->
|
|||
|
+ get_settings_form(Host, Lang);
|
|||
|
+get_form(_Host, Command, _, _Lang) ->
|
|||
|
+ ?MYDEBUG("asked for form ~p", [Command]),
|
|||
|
+ {error, ?ERR_SERVICE_UNAVAILABLE}.
|
|||
|
+
|
|||
|
+check_log_list([Head | Tail]) ->
|
|||
|
+ case lists:member($@, Head) of
|
|||
|
+ true -> ok;
|
|||
|
+ false -> throw(error)
|
|||
|
+ end,
|
|||
|
+ % this check for Head to be valid jid
|
|||
|
+ case jlib:string_to_jid(Head) of
|
|||
|
+ error ->
|
|||
|
+ throw(error);
|
|||
|
+ _ ->
|
|||
|
+ check_log_list(Tail)
|
|||
|
+ end;
|
|||
|
+check_log_list([]) ->
|
|||
|
+ ok.
|
|||
|
+
|
|||
|
+check_ignore_list([Head | Tail]) ->
|
|||
|
+ case lists:member($@, Head) of
|
|||
|
+ true -> ok;
|
|||
|
+ false -> throw(error)
|
|||
|
+ end,
|
|||
|
+ % this check for Head to be valid jid
|
|||
|
+ case jlib:string_to_jid(Head) of
|
|||
|
+ error ->
|
|||
|
+ % this check for Head to be valid domain "@domain.org"
|
|||
|
+ case lists:nth(1, Head) of
|
|||
|
+ $@ ->
|
|||
|
+ % TODO: this allows spaces and special characters in Head. May be change to nodeprep?
|
|||
|
+ case jlib:nameprep(lists:delete($@, Head)) of
|
|||
|
+ error -> throw(error);
|
|||
|
+ _ -> check_log_list(Tail)
|
|||
|
+ end;
|
|||
|
+ _ -> throw(error)
|
|||
|
+ end;
|
|||
|
+ _ ->
|
|||
|
+ check_ignore_list(Tail)
|
|||
|
+ end;
|
|||
|
+check_ignore_list([]) ->
|
|||
|
+ ok.
|
|||
|
+
|
|||
|
+parse_users_settings(XData) ->
|
|||
|
+ DLD = case lists:keysearch("dolog_default", 1, XData) of
|
|||
|
+ {value, {_, [String]}} when String == "true"; String == "false" ->
|
|||
|
+ list_to_bool(String);
|
|||
|
+ _ ->
|
|||
|
+ throw(bad_request)
|
|||
|
+ end,
|
|||
|
+ DLL = case lists:keysearch("dolog_list", 1, XData) of
|
|||
|
+ false ->
|
|||
|
+ throw(bad_request);
|
|||
|
+ {value, {_, [[]]}} ->
|
|||
|
+ [];
|
|||
|
+ {value, {_, List1}} ->
|
|||
|
+ case catch check_log_list(List1) of
|
|||
|
+ error ->
|
|||
|
+ throw(bad_request);
|
|||
|
+ ok ->
|
|||
|
+ List1
|
|||
|
+ end
|
|||
|
+ end,
|
|||
|
+ DNLL = case lists:keysearch("donotlog_list", 1, XData) of
|
|||
|
+ false ->
|
|||
|
+ throw(bad_request);
|
|||
|
+ {value, {_, [[]]}} ->
|
|||
|
+ [];
|
|||
|
+ {value, {_, List2}} ->
|
|||
|
+ case catch check_log_list(List2) of
|
|||
|
+ error ->
|
|||
|
+ throw(bad_request);
|
|||
|
+ ok ->
|
|||
|
+ List2
|
|||
|
+ end
|
|||
|
+ end,
|
|||
|
+ #user_settings{dolog_default=DLD,
|
|||
|
+ dolog_list=DLL,
|
|||
|
+ donotlog_list=DNLL}.
|
|||
|
+
|
|||
|
+parse_module_settings(XData) ->
|
|||
|
+ DLD = case lists:keysearch("dolog_default", 1, XData) of
|
|||
|
+ {value, {_, [Str1]}} when Str1 == "true"; Str1 == "false" ->
|
|||
|
+ list_to_bool(Str1);
|
|||
|
+ _ ->
|
|||
|
+ throw(bad_request)
|
|||
|
+ end,
|
|||
|
+ MRemoval = case lists:keysearch("drop_messages_on_user_removal", 1, XData) of
|
|||
|
+ {value, {_, [Str5]}} when Str5 == "true"; Str5 == "false" ->
|
|||
|
+ list_to_bool(Str5);
|
|||
|
+ _ ->
|
|||
|
+ throw(bad_request)
|
|||
|
+ end,
|
|||
|
+ GroupChat = case lists:keysearch("groupchat", 1, XData) of
|
|||
|
+ {value, {_, [Str2]}} when Str2 == "none";
|
|||
|
+ Str2 == "all";
|
|||
|
+ Str2 == "send";
|
|||
|
+ Str2 == "half" ->
|
|||
|
+ list_to_atom(Str2);
|
|||
|
+ _ ->
|
|||
|
+ throw(bad_request)
|
|||
|
+ end,
|
|||
|
+ Ignore = case lists:keysearch("ignore_list", 1, XData) of
|
|||
|
+ {value, {_, List}} ->
|
|||
|
+ case catch check_ignore_list(List) of
|
|||
|
+ ok ->
|
|||
|
+ List;
|
|||
|
+ error ->
|
|||
|
+ throw(bad_request)
|
|||
|
+ end;
|
|||
|
+ _ ->
|
|||
|
+ throw(bad_request)
|
|||
|
+ end,
|
|||
|
+ Purge = case lists:keysearch("purge_older_days", 1, XData) of
|
|||
|
+ {value, {_, ["never"]}} ->
|
|||
|
+ never;
|
|||
|
+ {value, {_, [Str3]}} ->
|
|||
|
+ case catch list_to_integer(Str3) of
|
|||
|
+ {'EXIT', {badarg, _}} -> throw(bad_request);
|
|||
|
+ Int1 -> Int1
|
|||
|
+ end;
|
|||
|
+ _ ->
|
|||
|
+ throw(bad_request)
|
|||
|
+ end,
|
|||
|
+ Poll = case lists:keysearch("poll_users_settings", 1, XData) of
|
|||
|
+ {value, {_, [Str4]}} ->
|
|||
|
+ case catch list_to_integer(Str4) of
|
|||
|
+ {'EXIT', {badarg, _}} -> throw(bad_request);
|
|||
|
+ Int2 -> Int2
|
|||
|
+ end;
|
|||
|
+ _ ->
|
|||
|
+ throw(bad_request)
|
|||
|
+ end,
|
|||
|
+ #state{dolog_default=DLD,
|
|||
|
+ groupchat=GroupChat,
|
|||
|
+ ignore_jids=Ignore,
|
|||
|
+ purge_older_days=Purge,
|
|||
|
+ drop_messages_on_user_removal=MRemoval,
|
|||
|
+ poll_users_settings=Poll}.
|
|||
|
+
|
|||
|
+set_form(From, _Host, ["mod_logdb"], _Lang, XData) ->
|
|||
|
+ #jid{luser=LUser, lserver=LServer} = From,
|
|||
|
+ case catch parse_users_settings(XData) of
|
|||
|
+ bad_request ->
|
|||
|
+ {error, ?ERR_BAD_REQUEST};
|
|||
|
+ UserSettings ->
|
|||
|
+ case mod_logdb:set_user_settings(LUser, LServer, UserSettings) of
|
|||
|
+ ok ->
|
|||
|
+ {result, []};
|
|||
|
+ error ->
|
|||
|
+ {error, ?ERR_INTERNAL_SERVER_ERROR}
|
|||
|
+ end
|
|||
|
+ end;
|
|||
|
+set_form(_From, _Host, ["mod_logdb_users", User], _Lang, XData) ->
|
|||
|
+ #jid{luser=LUser, lserver=LServer} = jlib:string_to_jid(User),
|
|||
|
+ case catch parse_users_settings(XData) of
|
|||
|
+ bad_request -> {error, ?ERR_BAD_REQUEST};
|
|||
|
+ UserSettings ->
|
|||
|
+ case mod_logdb:set_user_settings(LUser, LServer, UserSettings) of
|
|||
|
+ ok ->
|
|||
|
+ {result, []};
|
|||
|
+ error ->
|
|||
|
+ {error, ?ERR_INTERNAL_SERVER_ERROR}
|
|||
|
+ end
|
|||
|
+ end;
|
|||
|
+set_form(_From, Host, ["mod_logdb_settings"], _Lang, XData) ->
|
|||
|
+ case catch parse_module_settings(XData) of
|
|||
|
+ bad_request -> {error, ?ERR_BAD_REQUEST};
|
|||
|
+ Settings ->
|
|||
|
+ case mod_logdb:set_module_settings(Host, Settings) of
|
|||
|
+ ok ->
|
|||
|
+ {result, []};
|
|||
|
+ error ->
|
|||
|
+ {error, ?ERR_INTERNAL_SERVER_ERROR}
|
|||
|
+ end
|
|||
|
+ end;
|
|||
|
+set_form(From, _Host, Node, _Lang, XData) ->
|
|||
|
+ User = jlib:jid_to_string(jlib:jid_remove_resource(From)),
|
|||
|
+ ?MYDEBUG("set form for ~p at ~p XData=~p", [User, Node, XData]),
|
|||
|
+ {error, ?ERR_SERVICE_UNAVAILABLE}.
|
|||
|
+
|
|||
|
+%adhoc_sm_items(Acc, From, To, Request) ->
|
|||
|
+% ?MYDEBUG("adhoc_sm_items Acc=~p From=~p To=~p Request=~p", [Acc, From, To, Request]),
|
|||
|
+% Acc.
|
|||
|
+
|
|||
|
+%adhoc_sm_commands(Acc, From, To, Request) ->
|
|||
|
+% ?MYDEBUG("adhoc_sm_commands Acc=~p From=~p To=~p Request=~p", [Acc, From, To, Request]),
|
|||
|
+% Acc.
|
|||
|
+
|
|||
|
+get_all_vh_users(Host, Server, Lang) ->
|
|||
|
+ case catch ejabberd_auth:get_vh_registered_users(Host) of
|
|||
|
+ {'EXIT', _Reason} ->
|
|||
|
+ [];
|
|||
|
+ Users ->
|
|||
|
+ SUsers = lists:sort([{S, U} || {U, S} <- Users]),
|
|||
|
+ case length(SUsers) of
|
|||
|
+ N when N =< 100 ->
|
|||
|
+ lists:map(fun({S, U}) ->
|
|||
|
+ ?NODE(U ++ "@" ++ S, "mod_logdb_users/" ++ U ++ "@" ++ S)
|
|||
|
+ end, SUsers);
|
|||
|
+ N ->
|
|||
|
+ NParts = trunc(math:sqrt(N * 0.618)) + 1,
|
|||
|
+ M = trunc(N / NParts) + 1,
|
|||
|
+ lists:map(fun(K) ->
|
|||
|
+ L = K + M - 1,
|
|||
|
+ Node =
|
|||
|
+ "@" ++ integer_to_list(K) ++
|
|||
|
+ "-" ++ integer_to_list(L),
|
|||
|
+ {FS, FU} = lists:nth(K, SUsers),
|
|||
|
+ {LS, LU} =
|
|||
|
+ if L < N -> lists:nth(L, SUsers);
|
|||
|
+ true -> lists:last(SUsers)
|
|||
|
+ end,
|
|||
|
+ Name =
|
|||
|
+ FU ++ "@" ++ FS ++
|
|||
|
+ " -- " ++
|
|||
|
+ LU ++ "@" ++ LS,
|
|||
|
+ ?NODE(Name, "mod_logdb_users/" ++ Node)
|
|||
|
+ end, lists:seq(1, N, M))
|
|||
|
+ end
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% webadmin hooks
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+webadmin_menu(Acc, _Host, Lang) ->
|
|||
|
+ [{"messages", ?T("Users Messages")} | Acc].
|
|||
|
+
|
|||
|
+webadmin_user(Acc, User, Server, Lang) ->
|
|||
|
+ Sett = get_user_settings(User, Server),
|
|||
|
+ Log =
|
|||
|
+ case Sett#user_settings.dolog_default of
|
|||
|
+ false ->
|
|||
|
+ ?INPUTT("submit", "dolog", "Log Messages");
|
|||
|
+ true ->
|
|||
|
+ ?INPUTT("submit", "donotlog", "Do Not Log Messages");
|
|||
|
+ _ -> []
|
|||
|
+ end,
|
|||
|
+ Acc ++ [?XE("h3", [?ACT("messages/", "Messages"), ?C(" "), Log])].
|
|||
|
+
|
|||
|
+webadmin_page(_, Host,
|
|||
|
+ #request{path = ["messages"],
|
|||
|
+ q = Query,
|
|||
|
+ lang = Lang}) when is_list(Host) ->
|
|||
|
+ Res = vhost_messages_stats(Host, Query, Lang),
|
|||
|
+ {stop, Res};
|
|||
|
+webadmin_page(_, Host,
|
|||
|
+ #request{path = ["messages", Date],
|
|||
|
+ q = Query,
|
|||
|
+ lang = Lang}) when is_list(Host) ->
|
|||
|
+ Res = vhost_messages_stats_at(Host, Query, Lang, Date),
|
|||
|
+ {stop, Res};
|
|||
|
+webadmin_page(_, Host,
|
|||
|
+ #request{path = ["user", U, "messages"],
|
|||
|
+ q = Query,
|
|||
|
+ lang = Lang}) ->
|
|||
|
+ Res = user_messages_stats(U, Host, Query, Lang),
|
|||
|
+ {stop, Res};
|
|||
|
+webadmin_page(_, Host,
|
|||
|
+ #request{path = ["user", U, "messages", Date],
|
|||
|
+ q = Query,
|
|||
|
+ lang = Lang}) ->
|
|||
|
+ Res = mod_logdb:user_messages_stats_at(U, Host, Query, Lang, Date),
|
|||
|
+ {stop, Res};
|
|||
|
+webadmin_page(Acc, _, _) -> Acc.
|
|||
|
+
|
|||
|
+user_parse_query(_, "dolog", User, Server, _Query) ->
|
|||
|
+ Sett = get_user_settings(User, Server),
|
|||
|
+ % TODO: check returned value
|
|||
|
+ set_user_settings(User, Server, Sett#user_settings{dolog_default=true}),
|
|||
|
+ {stop, ok};
|
|||
|
+user_parse_query(_, "donotlog", User, Server, _Query) ->
|
|||
|
+ Sett = get_user_settings(User, Server),
|
|||
|
+ % TODO: check returned value
|
|||
|
+ set_user_settings(User, Server, Sett#user_settings{dolog_default=false}),
|
|||
|
+ {stop, ok};
|
|||
|
+user_parse_query(Acc, _Action, _User, _Server, _Query) ->
|
|||
|
+ Acc.
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% webadmin funcs
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+vhost_messages_stats(Server, Query, Lang) ->
|
|||
|
+ Res = case catch vhost_messages_parse_query(Server, Query) of
|
|||
|
+ {'EXIT', Reason} ->
|
|||
|
+ ?ERROR_MSG("~p", [Reason]),
|
|||
|
+ error;
|
|||
|
+ VResult -> VResult
|
|||
|
+ end,
|
|||
|
+ {Time, Value} = timer:tc(mod_logdb, get_vhost_stats, [Server]),
|
|||
|
+ ?INFO_MSG("get_vhost_stats(~p) elapsed ~p sec", [Server, Time/1000000]),
|
|||
|
+ %case get_vhost_stats(Server) of
|
|||
|
+ case Value of
|
|||
|
+ {'EXIT', CReason} ->
|
|||
|
+ ?ERROR_MSG("Failed to get_vhost_stats: ~p", [CReason]),
|
|||
|
+ [?XC("h1", ?T("Error occupied while fetching list"))];
|
|||
|
+ {error, GReason} ->
|
|||
|
+ ?ERROR_MSG("Failed to get_vhost_stats: ~p", [GReason]),
|
|||
|
+ [?XC("h1", ?T("Error occupied while fetching list"))];
|
|||
|
+ {ok, []} ->
|
|||
|
+ [?XC("h1", ?T("No logged messages for ") ++ Server)];
|
|||
|
+ {ok, Dates} ->
|
|||
|
+ Fun = fun({Date, Count}) ->
|
|||
|
+ ID = jlib:encode_base64(binary_to_list(term_to_binary(Server++Date))),
|
|||
|
+ ?XE("tr",
|
|||
|
+ [?XE("td", [?INPUT("checkbox", "selected", ID)]),
|
|||
|
+ ?XE("td", [?AC(Date, Date)]),
|
|||
|
+ ?XC("td", integer_to_list(Count))
|
|||
|
+ ])
|
|||
|
+ end,
|
|||
|
+ [?XC("h1", ?T("Logged messages for ") ++ Server)] ++
|
|||
|
+ case Res of
|
|||
|
+ ok -> [?CT("Submitted"), ?P];
|
|||
|
+ error -> [?CT("Bad format"), ?P];
|
|||
|
+ nothing -> []
|
|||
|
+ end ++
|
|||
|
+ [?XAE("form", [{"action", ""}, {"method", "post"}],
|
|||
|
+ [?XE("table",
|
|||
|
+ [?XE("thead",
|
|||
|
+ [?XE("tr",
|
|||
|
+ [?X("td"),
|
|||
|
+ ?XCT("td", "Date"),
|
|||
|
+ ?XCT("td", "Count")
|
|||
|
+ ])]),
|
|||
|
+ ?XE("tbody",
|
|||
|
+ lists:map(Fun, Dates)
|
|||
|
+ )]),
|
|||
|
+ ?BR,
|
|||
|
+ ?INPUTT("submit", "delete", "Delete Selected")
|
|||
|
+ ])]
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+vhost_messages_stats_at(Server, Query, Lang, Date) ->
|
|||
|
+ {Time, Value} = timer:tc(mod_logdb, get_vhost_stats_at, [Server, Date]),
|
|||
|
+ ?INFO_MSG("get_vhost_stats_at(~p,~p) elapsed ~p sec", [Server, Date, Time/1000000]),
|
|||
|
+ %case get_vhost_stats_at(Server, Date) of
|
|||
|
+ case Value of
|
|||
|
+ {'EXIT', CReason} ->
|
|||
|
+ ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [CReason]),
|
|||
|
+ [?XC("h1", ?T("Error occupied while fetching list"))];
|
|||
|
+ {error, GReason} ->
|
|||
|
+ ?ERROR_MSG("Failed to get_vhost_stats_at: ~p", [GReason]),
|
|||
|
+ [?XC("h1", ?T("Error occupied while fetching list"))];
|
|||
|
+ {ok, []} ->
|
|||
|
+ [?XC("h1", ?T("No logged messages for ") ++ Server ++ ?T(" at ") ++ Date)];
|
|||
|
+ {ok, Users} ->
|
|||
|
+ Res = case catch vhost_messages_at_parse_query(Server, Date, Users, Query) of
|
|||
|
+ {'EXIT', Reason} ->
|
|||
|
+ ?ERROR_MSG("~p", [Reason]),
|
|||
|
+ error;
|
|||
|
+ VResult -> VResult
|
|||
|
+ end,
|
|||
|
+ Fun = fun({User, Count}) ->
|
|||
|
+ ID = jlib:encode_base64(binary_to_list(term_to_binary(User++Server))),
|
|||
|
+ ?XE("tr",
|
|||
|
+ [?XE("td", [?INPUT("checkbox", "selected", ID)]),
|
|||
|
+ ?XE("td", [?AC("../user/"++User++"/messages/"++Date, User)]),
|
|||
|
+ ?XC("td", integer_to_list(Count))
|
|||
|
+ ])
|
|||
|
+ end,
|
|||
|
+ [?XC("h1", ?T("Logged messages for ") ++ Server ++ ?T(" at ") ++ Date)] ++
|
|||
|
+ case Res of
|
|||
|
+ ok -> [?CT("Submitted"), ?P];
|
|||
|
+ error -> [?CT("Bad format"), ?P];
|
|||
|
+ nothing -> []
|
|||
|
+ end ++
|
|||
|
+ [?XAE("form", [{"action", ""}, {"method", "post"}],
|
|||
|
+ [?XE("table",
|
|||
|
+ [?XE("thead",
|
|||
|
+ [?XE("tr",
|
|||
|
+ [?X("td"),
|
|||
|
+ ?XCT("td", "User"),
|
|||
|
+ ?XCT("td", "Count")
|
|||
|
+ ])]),
|
|||
|
+ ?XE("tbody",
|
|||
|
+ lists:map(Fun, Users)
|
|||
|
+ )]),
|
|||
|
+ ?BR,
|
|||
|
+ ?INPUTT("submit", "delete", "Delete Selected")
|
|||
|
+ ])]
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+user_messages_stats(User, Server, Query, Lang) ->
|
|||
|
+ Jid = jlib:jid_to_string({User, Server, ""}),
|
|||
|
+
|
|||
|
+ Res = case catch user_messages_parse_query(User, Server, Query) of
|
|||
|
+ {'EXIT', Reason} ->
|
|||
|
+ ?ERROR_MSG("~p", [Reason]),
|
|||
|
+ error;
|
|||
|
+ VResult -> VResult
|
|||
|
+ end,
|
|||
|
+
|
|||
|
+ {Time, Value} = timer:tc(mod_logdb, get_user_stats, [User, Server]),
|
|||
|
+ ?INFO_MSG("get_user_stats(~p,~p) elapsed ~p sec", [User, Server, Time/1000000]),
|
|||
|
+
|
|||
|
+ case Value of
|
|||
|
+ {'EXIT', CReason} ->
|
|||
|
+ ?ERROR_MSG("Failed to get_user_stats: ~p", [CReason]),
|
|||
|
+ [?XC("h1", ?T("Error occupied while fetching days"))];
|
|||
|
+ {error, GReason} ->
|
|||
|
+ ?ERROR_MSG("Failed to get_user_stats: ~p", [GReason]),
|
|||
|
+ [?XC("h1", ?T("Error occupied while fetching days"))];
|
|||
|
+ {ok, []} ->
|
|||
|
+ [?XC("h1", ?T("No logged messages for ") ++ Jid)];
|
|||
|
+ {ok, Dates} ->
|
|||
|
+ Fun = fun({Date, Count}) ->
|
|||
|
+ ID = jlib:encode_base64(binary_to_list(term_to_binary(User++Date))),
|
|||
|
+ ?XE("tr",
|
|||
|
+ [?XE("td", [?INPUT("checkbox", "selected", ID)]),
|
|||
|
+ ?XE("td", [?AC(Date, Date)]),
|
|||
|
+ ?XC("td", integer_to_list(Count))
|
|||
|
+ ])
|
|||
|
+ %[?AC(Date, Date ++ " (" ++ integer_to_list(Count) ++ ")"), ?BR]
|
|||
|
+ end,
|
|||
|
+ [?XC("h1", ?T("Logged messages for ") ++ Jid)] ++
|
|||
|
+ case Res of
|
|||
|
+ ok -> [?CT("Submitted"), ?P];
|
|||
|
+ error -> [?CT("Bad format"), ?P];
|
|||
|
+ nothing -> []
|
|||
|
+ end ++
|
|||
|
+ [?XAE("form", [{"action", ""}, {"method", "post"}],
|
|||
|
+ [?XE("table",
|
|||
|
+ [?XE("thead",
|
|||
|
+ [?XE("tr",
|
|||
|
+ [?X("td"),
|
|||
|
+ ?XCT("td", "Date"),
|
|||
|
+ ?XCT("td", "Count")
|
|||
|
+ ])]),
|
|||
|
+ ?XE("tbody",
|
|||
|
+ lists:map(Fun, Dates)
|
|||
|
+ )]),
|
|||
|
+ ?BR,
|
|||
|
+ ?INPUTT("submit", "delete", "Delete Selected")
|
|||
|
+ ])]
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+search_user_nick(User, List) ->
|
|||
|
+ case lists:keysearch(User, 1, List) of
|
|||
|
+ {value,{User, []}} ->
|
|||
|
+ nothing;
|
|||
|
+ {value,{User, Nick}} ->
|
|||
|
+ Nick;
|
|||
|
+ false ->
|
|||
|
+ nothing
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+user_messages_stats_at(User, Server, Query, Lang, Date) ->
|
|||
|
+ Jid = jlib:jid_to_string({User, Server, ""}),
|
|||
|
+
|
|||
|
+ {Time, Value} = timer:tc(mod_logdb, get_user_messages_at, [User, Server, Date]),
|
|||
|
+ ?INFO_MSG("get_user_messages_at(~p,~p,~p) elapsed ~p sec", [User, Server, Date, Time/1000000]),
|
|||
|
+ case Value of
|
|||
|
+ {'EXIT', CReason} ->
|
|||
|
+ ?ERROR_MSG("Failed to get_user_messages_at: ~p", [CReason]),
|
|||
|
+ [?XC("h1", ?T("Error occupied while fetching messages"))];
|
|||
|
+ {error, GReason} ->
|
|||
|
+ ?ERROR_MSG("Failed to get_user_messages_at: ~p", [GReason]),
|
|||
|
+ [?XC("h1", ?T("Error occupied while fetching messages"))];
|
|||
|
+ {ok, []} ->
|
|||
|
+ [?XC("h1", ?T("No logged messages for ") ++ Jid ++ ?T(" at ") ++ Date)];
|
|||
|
+ {ok, User_messages} ->
|
|||
|
+ Res = case catch user_messages_at_parse_query(Server,
|
|||
|
+ Date,
|
|||
|
+ User_messages,
|
|||
|
+ Query) of
|
|||
|
+ {'EXIT', Reason} ->
|
|||
|
+ ?ERROR_MSG("~p", [Reason]),
|
|||
|
+ error;
|
|||
|
+ VResult -> VResult
|
|||
|
+ end,
|
|||
|
+
|
|||
|
+ UR = ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]),
|
|||
|
+ UserRoster =
|
|||
|
+ lists:map(fun(Item) ->
|
|||
|
+ {jlib:jid_to_string(Item#roster.jid), Item#roster.name}
|
|||
|
+ end, UR),
|
|||
|
+
|
|||
|
+ UniqUsers = lists:foldl(fun(#msg{peer_name=PName, peer_server=PServer}, List) ->
|
|||
|
+ ToAdd = PName++"@"++PServer,
|
|||
|
+ case lists:member(ToAdd, List) of
|
|||
|
+ true -> List;
|
|||
|
+ false -> lists:append([ToAdd], List)
|
|||
|
+ end
|
|||
|
+ end, [], User_messages),
|
|||
|
+
|
|||
|
+ % Users to filter (sublist of UniqUsers)
|
|||
|
+ CheckedUsers = case lists:keysearch("filter", 1, Query) of
|
|||
|
+ {value, _} ->
|
|||
|
+ lists:filter(fun(UFUser) ->
|
|||
|
+ ID = jlib:encode_base64(binary_to_list(term_to_binary(UFUser))),
|
|||
|
+ lists:member({"selected", ID}, Query)
|
|||
|
+ end, UniqUsers);
|
|||
|
+ false -> []
|
|||
|
+ end,
|
|||
|
+
|
|||
|
+ % UniqUsers in html (noone selected -> everyone selected)
|
|||
|
+ Users = lists:map(fun(UHUser) ->
|
|||
|
+ ID = jlib:encode_base64(binary_to_list(term_to_binary(UHUser))),
|
|||
|
+ Input = case lists:member(UHUser, CheckedUsers) of
|
|||
|
+ true -> [?INPUTC("checkbox", "selected", ID)];
|
|||
|
+ false when CheckedUsers == [] -> [?INPUTC("checkbox", "selected", ID)];
|
|||
|
+ false -> [?INPUT("checkbox", "selected", ID)]
|
|||
|
+ end,
|
|||
|
+ Nick =
|
|||
|
+ case search_user_nick(UHUser, UserRoster) of
|
|||
|
+ nothing -> "";
|
|||
|
+ N -> " ("++ N ++")"
|
|||
|
+ end,
|
|||
|
+ ?XE("tr",
|
|||
|
+ [?XE("td", Input),
|
|||
|
+ ?XC("td", UHUser++Nick)])
|
|||
|
+ end, lists:sort(UniqUsers)),
|
|||
|
+ % Messages to show (based on Users)
|
|||
|
+ User_messages_filtered = case CheckedUsers of
|
|||
|
+ [] -> User_messages;
|
|||
|
+ _ -> lists:filter(fun(#msg{peer_name=PName, peer_server=PServer}) ->
|
|||
|
+ lists:member(PName++"@"++PServer, CheckedUsers)
|
|||
|
+ end, User_messages)
|
|||
|
+ end,
|
|||
|
+
|
|||
|
+ Msgs_Fun = fun(#msg{timestamp=Timestamp,
|
|||
|
+ subject=Subject,
|
|||
|
+ direction=Direction,
|
|||
|
+ peer_name=PName, peer_server=PServer, peer_resource=PRes,
|
|||
|
+ type=Type,
|
|||
|
+ body=Body}) ->
|
|||
|
+ TextRaw = case Subject of
|
|||
|
+ "" -> Body;
|
|||
|
+ _ -> [?T("Subject"),": ",Subject,"<br>", Body]
|
|||
|
+ end,
|
|||
|
+ ID = jlib:encode_base64(binary_to_list(term_to_binary(Timestamp))),
|
|||
|
+ % replace \n with <br>
|
|||
|
+ Text = lists:map(fun(10) -> "<br>";
|
|||
|
+ (A) -> A
|
|||
|
+ end, TextRaw),
|
|||
|
+ Resource = case PRes of
|
|||
|
+ [] -> [];
|
|||
|
+ undefined -> [];
|
|||
|
+ R -> "/" ++ R
|
|||
|
+ end,
|
|||
|
+ UserNick =
|
|||
|
+ case search_user_nick(PName++"@"++PServer, UserRoster) of
|
|||
|
+ nothing when PServer == Server ->
|
|||
|
+ PName;
|
|||
|
+ nothing when Type == "groupchat", Direction == from ->
|
|||
|
+ PName++"@"++PServer++Resource;
|
|||
|
+ nothing ->
|
|||
|
+ PName++"@"++PServer;
|
|||
|
+ N -> N
|
|||
|
+ end,
|
|||
|
+ ?XE("tr",
|
|||
|
+ [?XE("td", [?INPUT("checkbox", "selected", ID)]),
|
|||
|
+ ?XC("td", convert_timestamp(Timestamp)),
|
|||
|
+ ?XC("td", atom_to_list(Direction)++": "++UserNick),
|
|||
|
+ ?XC("td", Text)])
|
|||
|
+ end,
|
|||
|
+ % Filtered user messages in html
|
|||
|
+ Msgs = lists:map(Msgs_Fun, lists:sort(User_messages_filtered)),
|
|||
|
+
|
|||
|
+ [?XC("h1", ?T("Logged messages for ") ++ Jid ++ ?T(" at ") ++ Date)] ++
|
|||
|
+ case Res of
|
|||
|
+ ok -> [?CT("Submitted"), ?P];
|
|||
|
+ error -> [?CT("Bad format"), ?P];
|
|||
|
+ nothing -> []
|
|||
|
+ end ++
|
|||
|
+ [?XAE("form", [{"action", ""}, {"method", "post"}],
|
|||
|
+ [?XE("table",
|
|||
|
+ [?XE("thead",
|
|||
|
+ [?X("td"),
|
|||
|
+ ?XCT("td", "User")
|
|||
|
+ ]
|
|||
|
+ ),
|
|||
|
+ ?XE("tbody",
|
|||
|
+ Users
|
|||
|
+ )]),
|
|||
|
+ ?INPUTT("submit", "filter", "Filter Selected")
|
|||
|
+ ] ++
|
|||
|
+ [?XE("table",
|
|||
|
+ [?XE("thead",
|
|||
|
+ [?XE("tr",
|
|||
|
+ [?X("td"),
|
|||
|
+ ?XCT("td", "Date, Time"),
|
|||
|
+ ?XCT("td", "Direction: Jid"),
|
|||
|
+ ?XCT("td", "Body")
|
|||
|
+ ])]),
|
|||
|
+ ?XE("tbody",
|
|||
|
+ Msgs
|
|||
|
+ )]),
|
|||
|
+ ?INPUTT("submit", "delete", "Delete Selected"),
|
|||
|
+ ?BR
|
|||
|
+ ]
|
|||
|
+ )]
|
|||
|
+ end.
|
|||
|
--- mod_logdb.hrl.orig 2009-02-05 19:21:29.000000000 +0200
|
|||
|
+++ mod_logdb.hrl 2009-02-05 19:21:02.000000000 +0200
|
|||
|
@@ -0,0 +1,35 @@
|
|||
|
+%%%----------------------------------------------------------------------
|
|||
|
+%%% File : mod_logdb.hrl
|
|||
|
+%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
|
|||
|
+%%% Purpose :
|
|||
|
+%%% Version : trunk
|
|||
|
+%%% Id : $Id: mod_logdb.hrl 1163 2008-09-15 11:12:08Z malik $
|
|||
|
+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
|
|||
|
+%%%----------------------------------------------------------------------
|
|||
|
+
|
|||
|
+-define(logdb_debug, true).
|
|||
|
+
|
|||
|
+-ifdef(logdb_debug).
|
|||
|
+-define(MYDEBUG(Format, Args), io:format("D(~p:~p:~p) : "++Format++"~n",
|
|||
|
+ [calendar:local_time(),?MODULE,?LINE]++Args)).
|
|||
|
+-else.
|
|||
|
+-define(MYDEBUG(_F,_A),[]).
|
|||
|
+-endif.
|
|||
|
+
|
|||
|
+-record(msg, {timestamp,
|
|||
|
+ owner_name,
|
|||
|
+ peer_name, peer_server, peer_resource,
|
|||
|
+ direction,
|
|||
|
+ type, subject,
|
|||
|
+ body}).
|
|||
|
+
|
|||
|
+-record(user_settings, {owner_name,
|
|||
|
+ dolog_default,
|
|||
|
+ dolog_list=[],
|
|||
|
+ donotlog_list=[]}).
|
|||
|
+
|
|||
|
+-define(INPUTC(Type, Name, Value),
|
|||
|
+ ?XA("input", [{"type", Type},
|
|||
|
+ {"name", Name},
|
|||
|
+ {"value", Value},
|
|||
|
+ {"checked", "true"}])).
|
|||
|
--- mod_logdb_mnesia.erl.orig 2009-02-05 19:21:29.000000000 +0200
|
|||
|
+++ mod_logdb_mnesia.erl 2009-02-05 19:19:59.000000000 +0200
|
|||
|
@@ -0,0 +1,546 @@
|
|||
|
+%%%----------------------------------------------------------------------
|
|||
|
+%%% File : mod_logdb_mnesia.erl
|
|||
|
+%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
|
|||
|
+%%% Purpose : mnesia backend for mod_logdb
|
|||
|
+%%% Version : trunk
|
|||
|
+%%% Id : $Id: mod_logdb_mnesia.erl 1169 2008-09-16 12:14:36Z malik $
|
|||
|
+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
|
|||
|
+%%%----------------------------------------------------------------------
|
|||
|
+
|
|||
|
+-module(mod_logdb_mnesia).
|
|||
|
+-author('o.palij@gmail.com').
|
|||
|
+
|
|||
|
+-include("mod_logdb.hrl").
|
|||
|
+-include("ejabberd.hrl").
|
|||
|
+-include("jlib.hrl").
|
|||
|
+
|
|||
|
+-behaviour(gen_logdb).
|
|||
|
+-behaviour(gen_server).
|
|||
|
+
|
|||
|
+% gen_server
|
|||
|
+-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
|
|||
|
+% gen_mod
|
|||
|
+-export([start/2, stop/1]).
|
|||
|
+% gen_logdb
|
|||
|
+-export([log_message/2,
|
|||
|
+ rebuild_stats/1,
|
|||
|
+ rebuild_stats_at/2,
|
|||
|
+ delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
|
|||
|
+ get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
|
|||
|
+ get_dates/1,
|
|||
|
+ get_users_settings/1, get_user_settings/2, set_user_settings/3,
|
|||
|
+ drop_user/2]).
|
|||
|
+
|
|||
|
+-define(PROCNAME, mod_logdb_mnesia).
|
|||
|
+-define(CALL_TIMEOUT, 10000).
|
|||
|
+
|
|||
|
+-record(state, {vhost}).
|
|||
|
+
|
|||
|
+-record(stats, {user, at, count}).
|
|||
|
+
|
|||
|
+prefix() ->
|
|||
|
+ "logdb_".
|
|||
|
+
|
|||
|
+suffix(VHost) ->
|
|||
|
+ "_" ++ VHost.
|
|||
|
+
|
|||
|
+stats_table(VHost) ->
|
|||
|
+ list_to_atom(prefix() ++ "stats" ++ suffix(VHost)).
|
|||
|
+
|
|||
|
+table_name(VHost, Date) ->
|
|||
|
+ list_to_atom(prefix() ++ "messages_" ++ Date ++ suffix(VHost)).
|
|||
|
+
|
|||
|
+settings_table(VHost) ->
|
|||
|
+ list_to_atom(prefix() ++ "settings" ++ suffix(VHost)).
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% gen_mod callbacks
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+start(VHost, Opts) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
|
|||
|
+
|
|||
|
+stop(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% gen_server callbacks
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+init([VHost, _Opts]) ->
|
|||
|
+ case mnesia:system_info(is_running) of
|
|||
|
+ yes ->
|
|||
|
+ ok = create_stats_table(VHost),
|
|||
|
+ ok = create_settings_table(VHost),
|
|||
|
+ {ok, #state{vhost=VHost}};
|
|||
|
+ no ->
|
|||
|
+ ?ERROR_MSG("Mnesia not running", []),
|
|||
|
+ {stop, db_connection_failed};
|
|||
|
+ Status ->
|
|||
|
+ ?ERROR_MSG("Mnesia status: ~p", [Status]),
|
|||
|
+ {stop, db_connection_failed}
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+handle_call({log_message, Msg}, _From, #state{vhost=VHost}=State) ->
|
|||
|
+ {reply, log_message_int(VHost, Msg), State};
|
|||
|
+handle_call({rebuild_stats}, _From, #state{vhost=VHost}=State) ->
|
|||
|
+ {atomic, ok} = delete_nonexistent_stats(VHost),
|
|||
|
+ Reply =
|
|||
|
+ lists:foreach(fun(Date) ->
|
|||
|
+ rebuild_stats_at_int(VHost, Date)
|
|||
|
+ end, get_dates_int(VHost)),
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({rebuild_stats_at, Date}, _From, #state{vhost=VHost}=State) ->
|
|||
|
+ Reply = rebuild_stats_at_int(VHost, Date),
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{vhost=VHost}=State) ->
|
|||
|
+ Table = table_name(VHost, Date),
|
|||
|
+ Fun = fun() ->
|
|||
|
+ lists:foreach(
|
|||
|
+ fun(Msg) ->
|
|||
|
+ mnesia:write_lock_table(stats_table(VHost)),
|
|||
|
+ mnesia:write_lock_table(Table),
|
|||
|
+ mnesia:delete_object(Table, Msg, write)
|
|||
|
+ end, Msgs)
|
|||
|
+ end,
|
|||
|
+ DRez = case mnesia:transaction(Fun) of
|
|||
|
+ {aborted, Reason} ->
|
|||
|
+ ?ERROR_MSG("Failed to delete_messages_by_user_at at ~p for ~p: ~p", [Date, VHost, Reason]),
|
|||
|
+ error;
|
|||
|
+ _ ->
|
|||
|
+ ok
|
|||
|
+ end,
|
|||
|
+ Reply =
|
|||
|
+ case rebuild_stats_at_int(VHost, Date) of
|
|||
|
+ error ->
|
|||
|
+ error;
|
|||
|
+ ok ->
|
|||
|
+ DRez
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{vhost=VHost}=State) ->
|
|||
|
+ {reply, delete_all_messages_by_user_at_int(User, VHost, Date), State};
|
|||
|
+handle_call({delete_messages_at, Date}, _From, #state{vhost=VHost}=State) ->
|
|||
|
+ Reply =
|
|||
|
+ case mnesia:delete_table(table_name(VHost, Date)) of
|
|||
|
+ {atomic, ok} ->
|
|||
|
+ delete_stats_by_vhost_at_int(VHost, Date);
|
|||
|
+ {aborted, Reason} ->
|
|||
|
+ ?ERROR_MSG("Failed to delete_messages_at for ~p at ~p", [VHost, Date, Reason]),
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_vhost_stats}, _From, #state{vhost=VHost}=State) ->
|
|||
|
+ Fun = fun(#stats{at=Date, count=Count}, Stats) ->
|
|||
|
+ case lists:keysearch(Date, 1, Stats) of
|
|||
|
+ false ->
|
|||
|
+ lists:append(Stats, [{Date, Count}]);
|
|||
|
+ {value, {_, TempCount}} ->
|
|||
|
+ lists:keyreplace(Date, 1, Stats, {Date, TempCount+Count})
|
|||
|
+ end
|
|||
|
+ end,
|
|||
|
+ Reply =
|
|||
|
+ case mnesia:transaction(fun() ->
|
|||
|
+ mnesia:foldl(Fun, [], stats_table(VHost))
|
|||
|
+ end) of
|
|||
|
+ {atomic, Result} -> {ok, mod_logdb:sort_stats(Result)};
|
|||
|
+ {aborted, Reason} -> {error, Reason}
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_vhost_stats_at, Date}, _From, #state{vhost=VHost}=State) ->
|
|||
|
+ Fun = fun() ->
|
|||
|
+ Pat = #stats{user='$1', at=Date, count='$2'},
|
|||
|
+ mnesia:select(stats_table(VHost), [{Pat, [], [['$1', '$2']]}])
|
|||
|
+ end,
|
|||
|
+ Reply =
|
|||
|
+ case mnesia:transaction(Fun) of
|
|||
|
+ {atomic, Result} ->
|
|||
|
+ {ok, lists:reverse(lists:keysort(2, [{User, Count} || [User, Count] <- Result]))};
|
|||
|
+ {aborted, Reason} ->
|
|||
|
+ {error, Reason}
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_user_stats, User}, _From, #state{vhost=VHost}=State) ->
|
|||
|
+ {reply, get_user_stats_int(User, VHost), State};
|
|||
|
+handle_call({get_user_messages_at, User, Date}, _From, #state{vhost=VHost}=State) ->
|
|||
|
+ Reply =
|
|||
|
+ case mnesia:transaction(fun() ->
|
|||
|
+ Pat = #msg{owner_name=User, _='_'},
|
|||
|
+ mnesia:select(table_name(VHost, Date),
|
|||
|
+ [{Pat, [], ['$_']}])
|
|||
|
+ end) of
|
|||
|
+ {atomic, Result} -> {ok, Result};
|
|||
|
+ {aborted, Reason} ->
|
|||
|
+ {error, Reason}
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_dates}, _From, #state{vhost=VHost}=State) ->
|
|||
|
+ {reply, get_dates_int(VHost), State};
|
|||
|
+handle_call({get_users_settings}, _From, #state{vhost=VHost}=State) ->
|
|||
|
+ Reply = mnesia:dirty_match_object(settings_table(VHost), #user_settings{_='_'}),
|
|||
|
+ {reply, {ok, Reply}, State};
|
|||
|
+handle_call({get_user_settings, User}, _From, #state{vhost=VHost}=State) ->
|
|||
|
+ Reply =
|
|||
|
+ case mnesia:dirty_match_object(settings_table(VHost), #user_settings{owner_name=User, _='_'}) of
|
|||
|
+ [] -> [];
|
|||
|
+ [Setting] ->
|
|||
|
+ Setting
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({set_user_settings, _User, Set}, _From, #state{vhost=VHost}=State) ->
|
|||
|
+ ?MYDEBUG("~p~n~p", [settings_table(VHost), Set]),
|
|||
|
+ Reply = mnesia:dirty_write(settings_table(VHost), Set),
|
|||
|
+ ?MYDEBUG("~p", [Reply]),
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({drop_user, User}, _From, #state{vhost=VHost}=State) ->
|
|||
|
+ {ok, Dates} = get_user_stats_int(User, VHost),
|
|||
|
+ MDResult = lists:map(fun({Date, _}) ->
|
|||
|
+ delete_all_messages_by_user_at_int(User, VHost, Date)
|
|||
|
+ end, Dates),
|
|||
|
+ SDResult = delete_user_settings_int(User, VHost),
|
|||
|
+ Reply =
|
|||
|
+ case lists:all(fun(Result) when Result == ok ->
|
|||
|
+ true;
|
|||
|
+ (Result) when Result == error ->
|
|||
|
+ false
|
|||
|
+ end, lists:append(MDResult, [SDResult])) of
|
|||
|
+ true ->
|
|||
|
+ ok;
|
|||
|
+ false ->
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({stop}, _From, State) ->
|
|||
|
+ {stop, normal, ok, State};
|
|||
|
+handle_call(Msg, _From, State) ->
|
|||
|
+ ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
|
|||
|
+ {noreply, State}.
|
|||
|
+
|
|||
|
+handle_cast(Msg, State) ->
|
|||
|
+ ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
|
|||
|
+ {noreply, State}.
|
|||
|
+
|
|||
|
+handle_info(Info, State) ->
|
|||
|
+ ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
|
|||
|
+ {noreply, State}.
|
|||
|
+
|
|||
|
+terminate(_Reason, _State) ->
|
|||
|
+ ok.
|
|||
|
+
|
|||
|
+code_change(_OldVsn, State, _Extra) ->
|
|||
|
+ {ok, State}.
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% gen_logdb callbacks
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+log_message(VHost, Msg) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
|
|||
|
+rebuild_stats(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {rebuild_stats}, ?CALL_TIMEOUT).
|
|||
|
+rebuild_stats_at(VHost, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
|
|||
|
+delete_messages_by_user_at(VHost, Msgs, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
|
|||
|
+delete_all_messages_by_user_at(User, VHost, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
|
|||
|
+delete_messages_at(VHost, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
|
|||
|
+get_vhost_stats(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
|
|||
|
+get_vhost_stats_at(VHost, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
|
|||
|
+get_user_stats(User, VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
|
|||
|
+get_user_messages_at(User, VHost, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
|
|||
|
+get_dates(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
|
|||
|
+get_user_settings(User, VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
|
|||
|
+get_users_settings(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
|
|||
|
+set_user_settings(User, VHost, Set) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
|
|||
|
+drop_user(User, VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {drop_user, User}, ?CALL_TIMEOUT).
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% internals
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+log_message_int(VHost, #msg{timestamp=Timestamp}=Msg) ->
|
|||
|
+ Date = mod_logdb:convert_timestamp_brief(Timestamp),
|
|||
|
+
|
|||
|
+ ATable = table_name(VHost, Date),
|
|||
|
+ Fun = fun() ->
|
|||
|
+ mnesia:write_lock_table(ATable),
|
|||
|
+ mnesia:write(ATable, Msg, write)
|
|||
|
+ end,
|
|||
|
+ % log message, increment stats for both users
|
|||
|
+ case mnesia:transaction(Fun) of
|
|||
|
+ % if table does not exists - create it and try to log message again
|
|||
|
+ {aborted,{no_exists, _Table}} ->
|
|||
|
+ case create_msg_table(VHost, Date) of
|
|||
|
+ {aborted, CReason} ->
|
|||
|
+ ?ERROR_MSG("Failed to log message: ~p", [CReason]),
|
|||
|
+ error;
|
|||
|
+ {atomic, ok} ->
|
|||
|
+ ?MYDEBUG("Created msg table for ~p at ~p", [VHost, Date]),
|
|||
|
+ log_message_int(VHost, Msg)
|
|||
|
+ end;
|
|||
|
+ {aborted, TReason} ->
|
|||
|
+ ?ERROR_MSG("Failed to log message: ~p", [TReason]),
|
|||
|
+ error;
|
|||
|
+ {atomic, _} ->
|
|||
|
+ ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
|
|||
|
+ Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
|
|||
|
+ increment_user_stats(Msg#msg.owner_name, VHost, Date)
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+increment_user_stats(Owner, VHost, Date) ->
|
|||
|
+ Fun = fun() ->
|
|||
|
+ Pat = #stats{user=Owner, at=Date, count='$1'},
|
|||
|
+ mnesia:write_lock_table(stats_table(VHost)),
|
|||
|
+ case mnesia:select(stats_table(VHost), [{Pat, [], ['$_']}]) of
|
|||
|
+ [] ->
|
|||
|
+ mnesia:write(stats_table(VHost),
|
|||
|
+ #stats{user=Owner,
|
|||
|
+ at=Date,
|
|||
|
+ count=1},
|
|||
|
+ write);
|
|||
|
+ [Stats] ->
|
|||
|
+ mnesia:delete_object(stats_table(VHost),
|
|||
|
+ #stats{user=Owner,
|
|||
|
+ at=Date,
|
|||
|
+ count=Stats#stats.count},
|
|||
|
+ write),
|
|||
|
+ New = Stats#stats{count = Stats#stats.count+1},
|
|||
|
+ if
|
|||
|
+ New#stats.count > 0 -> mnesia:write(stats_table(VHost),
|
|||
|
+ New,
|
|||
|
+ write);
|
|||
|
+ true -> ok
|
|||
|
+ end
|
|||
|
+ end
|
|||
|
+ end,
|
|||
|
+ case mnesia:transaction(Fun) of
|
|||
|
+ {aborted, Reason} ->
|
|||
|
+ ?ERROR_MSG("Failed to update stats for ~s@~s: ~p", [Owner, VHost, Reason]),
|
|||
|
+ error;
|
|||
|
+ {atomic, _} ->
|
|||
|
+ ?MYDEBUG("Updated stats for ~s@~s", [Owner, VHost]),
|
|||
|
+ ok
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+get_dates_int(VHost) ->
|
|||
|
+ Tables = mnesia:system_info(tables),
|
|||
|
+ lists:foldl(fun(ATable, Dates) ->
|
|||
|
+ Table = atom_to_list(ATable),
|
|||
|
+ case regexp:match(Table, VHost++"$") of
|
|||
|
+ {match, _, _} ->
|
|||
|
+ case regexp:match(Table,"_[0-9]+-[0-9]+-[0-9]+_") of
|
|||
|
+ {match, S, E} ->
|
|||
|
+ lists:append(Dates, [lists:sublist(Table,S+1,E-2)]);
|
|||
|
+ nomatch ->
|
|||
|
+ Dates
|
|||
|
+ end;
|
|||
|
+ nomatch ->
|
|||
|
+ Dates
|
|||
|
+ end
|
|||
|
+ end, [], Tables).
|
|||
|
+
|
|||
|
+rebuild_stats_at_int(VHost, Date) ->
|
|||
|
+ Table = table_name(VHost, Date),
|
|||
|
+ STable = stats_table(VHost),
|
|||
|
+ CFun = fun(Msg, Stats) ->
|
|||
|
+ Owner = Msg#msg.owner_name,
|
|||
|
+ case lists:keysearch(Owner, 1, Stats) of
|
|||
|
+ {value, {_, Count}} ->
|
|||
|
+ lists:keyreplace(Owner, 1, Stats, {Owner, Count + 1});
|
|||
|
+ false ->
|
|||
|
+ lists:append(Stats, [{Owner, 1}])
|
|||
|
+ end
|
|||
|
+ end,
|
|||
|
+ DFun = fun(#stats{at=SDate} = Stat, _Acc)
|
|||
|
+ when SDate == Date ->
|
|||
|
+ mnesia:delete_object(stats_table(VHost), Stat, write);
|
|||
|
+ (_Stat, _Acc) -> ok
|
|||
|
+ end,
|
|||
|
+ % TODO: Maybe unregister hooks ?
|
|||
|
+ case mnesia:transaction(fun() ->
|
|||
|
+ mnesia:write_lock_table(Table),
|
|||
|
+ mnesia:write_lock_table(STable),
|
|||
|
+ % Calc stats for VHost at Date
|
|||
|
+ case mnesia:foldl(CFun, [], Table) of
|
|||
|
+ [] -> empty;
|
|||
|
+ AStats ->
|
|||
|
+ % Delete all stats for VHost at Date
|
|||
|
+ mnesia:foldl(DFun, [], STable),
|
|||
|
+ % Write new calc'ed stats
|
|||
|
+ lists:foreach(fun({Owner, Count}) ->
|
|||
|
+ WStat = #stats{user=Owner, at=Date, count=Count},
|
|||
|
+ mnesia:write(stats_table(VHost), WStat, write)
|
|||
|
+ end, AStats),
|
|||
|
+ ok
|
|||
|
+ end
|
|||
|
+ end) of
|
|||
|
+ {aborted, Reason} ->
|
|||
|
+ ?ERROR_MSG("Failed to rebuild_stats_at for ~p at ~p: ~p", [VHost, Date, Reason]),
|
|||
|
+ error;
|
|||
|
+ {atomic, ok} ->
|
|||
|
+ ok;
|
|||
|
+ {atomic, empty} ->
|
|||
|
+ {atomic,ok} = mnesia:delete_table(Table),
|
|||
|
+ ?MYDEBUG("Dropped table at ~p", [Date]),
|
|||
|
+ ok
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+delete_nonexistent_stats(VHost) ->
|
|||
|
+ Dates = get_dates_int(VHost),
|
|||
|
+ mnesia:transaction(fun() ->
|
|||
|
+ mnesia:foldl(fun(#stats{at=Date} = Stat, _Acc) ->
|
|||
|
+ case lists:member(Date, Dates) of
|
|||
|
+ false -> mnesia:delete_object(Stat);
|
|||
|
+ true -> ok
|
|||
|
+ end
|
|||
|
+ end, ok, stats_table(VHost))
|
|||
|
+ end).
|
|||
|
+
|
|||
|
+delete_stats_by_vhost_at_int(VHost, Date) ->
|
|||
|
+ StatsDelete = fun(#stats{at=SDate} = Stat, _Acc)
|
|||
|
+ when SDate == Date ->
|
|||
|
+ mnesia:delete_object(stats_table(VHost), Stat, write),
|
|||
|
+ ok;
|
|||
|
+ (_Msg, _Acc) -> ok
|
|||
|
+ end,
|
|||
|
+ case mnesia:transaction(fun() ->
|
|||
|
+ mnesia:write_lock_table(stats_table(VHost)),
|
|||
|
+ mnesia:foldl(StatsDelete, ok, stats_table(VHost))
|
|||
|
+ end) of
|
|||
|
+ {aborted, Reason} ->
|
|||
|
+ ?ERROR_MSG("Failed to update stats at ~p for ~p: ~p", [Date, VHost, Reason]),
|
|||
|
+ rebuild_stats_at_int(VHost, Date);
|
|||
|
+ _ ->
|
|||
|
+ ?INFO_MSG("Updated stats at ~p for ~p", [Date, VHost]),
|
|||
|
+ ok
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+get_user_stats_int(User, VHost) ->
|
|||
|
+ case mnesia:transaction(fun() ->
|
|||
|
+ Pat = #stats{user=User, at='$1', count='$2'},
|
|||
|
+ mnesia:select(stats_table(VHost), [{Pat, [], [['$1', '$2']]}])
|
|||
|
+ end) of
|
|||
|
+ {atomic, Result} ->
|
|||
|
+ {ok, mod_logdb:sort_stats([{Date, Count} || [Date, Count] <- Result])};
|
|||
|
+ {aborted, Reason} ->
|
|||
|
+ {error, Reason}
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+delete_all_messages_by_user_at_int(User, VHost, Date) ->
|
|||
|
+ Table = table_name(VHost, Date),
|
|||
|
+ MsgDelete = fun(#msg{owner_name=Owner} = Msg, _Acc)
|
|||
|
+ when Owner == User ->
|
|||
|
+ mnesia:delete_object(Table, Msg, write),
|
|||
|
+ ok;
|
|||
|
+ (_Msg, _Acc) -> ok
|
|||
|
+ end,
|
|||
|
+ DRez = case mnesia:transaction(fun() ->
|
|||
|
+ mnesia:foldl(MsgDelete, ok, Table)
|
|||
|
+ end) of
|
|||
|
+ {aborted, Reason} ->
|
|||
|
+ ?ERROR_MSG("Failed to delete_all_messages_by_user_at for ~p@~p at ~p: ~p", [User, VHost, Date, Reason]),
|
|||
|
+ error;
|
|||
|
+ _ ->
|
|||
|
+ ok
|
|||
|
+ end,
|
|||
|
+ case rebuild_stats_at_int(VHost, Date) of
|
|||
|
+ error ->
|
|||
|
+ error;
|
|||
|
+ ok ->
|
|||
|
+ DRez
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+delete_user_settings_int(User, VHost) ->
|
|||
|
+ STable = settings_table(VHost),
|
|||
|
+ case mnesia:dirty_match_object(STable, #user_settings{owner_name=User, _='_'}) of
|
|||
|
+ [] ->
|
|||
|
+ ok;
|
|||
|
+ [UserSettings] ->
|
|||
|
+ mnesia:dirty_delete_object(STable, UserSettings)
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% tables internals
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+create_stats_table(VHost) ->
|
|||
|
+ SName = stats_table(VHost),
|
|||
|
+ case mnesia:create_table(SName,
|
|||
|
+ [{disc_only_copies, [node()]},
|
|||
|
+ {type, bag},
|
|||
|
+ {attributes, record_info(fields, stats)},
|
|||
|
+ {record_name, stats}
|
|||
|
+ ]) of
|
|||
|
+ {atomic, ok} ->
|
|||
|
+ ?MYDEBUG("Created stats table for ~p", [VHost]),
|
|||
|
+ lists:foreach(fun(Date) ->
|
|||
|
+ rebuild_stats_at_int(VHost, Date)
|
|||
|
+ end, get_dates_int(VHost)),
|
|||
|
+ ok;
|
|||
|
+ {aborted, {already_exists, _}} ->
|
|||
|
+ ?MYDEBUG("Stats table for ~p already exists", [VHost]),
|
|||
|
+ ok;
|
|||
|
+ {aborted, Reason} ->
|
|||
|
+ ?ERROR_MSG("Failed to create stats table: ~p", [Reason]),
|
|||
|
+ error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+create_settings_table(VHost) ->
|
|||
|
+ SName = settings_table(VHost),
|
|||
|
+ case mnesia:create_table(SName,
|
|||
|
+ [{disc_copies, [node()]},
|
|||
|
+ {type, set},
|
|||
|
+ {attributes, record_info(fields, user_settings)},
|
|||
|
+ {record_name, user_settings}
|
|||
|
+ ]) of
|
|||
|
+ {atomic, ok} ->
|
|||
|
+ ?MYDEBUG("Created settings table for ~p", [VHost]),
|
|||
|
+ ok;
|
|||
|
+ {aborted, {already_exists, _}} ->
|
|||
|
+ ?MYDEBUG("Settings table for ~p already exists", [VHost]),
|
|||
|
+ ok;
|
|||
|
+ {aborted, Reason} ->
|
|||
|
+ ?ERROR_MSG("Failed to create settings table: ~p", [Reason]),
|
|||
|
+ error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+create_msg_table(VHost, Date) ->
|
|||
|
+ mnesia:create_table(
|
|||
|
+ table_name(VHost, Date),
|
|||
|
+ [{disc_only_copies, [node()]},
|
|||
|
+ {type, bag},
|
|||
|
+ {attributes, record_info(fields, msg)},
|
|||
|
+ {record_name, msg}]).
|
|||
|
--- mod_logdb_mysql.erl.orig 2009-02-05 19:21:29.000000000 +0200
|
|||
|
+++ mod_logdb_mysql.erl 2009-02-05 19:20:23.000000000 +0200
|
|||
|
@@ -0,0 +1,1052 @@
|
|||
|
+%%%----------------------------------------------------------------------
|
|||
|
+%%% File : mod_logdb_mysql.erl
|
|||
|
+%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
|
|||
|
+%%% Purpose : MySQL backend for mod_logdb
|
|||
|
+%%% Version : trunk
|
|||
|
+%%% Id : $Id: mod_logdb_mysql.erl 1253 2009-02-02 11:10:03Z malik $
|
|||
|
+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
|
|||
|
+%%%----------------------------------------------------------------------
|
|||
|
+
|
|||
|
+-module(mod_logdb_mysql).
|
|||
|
+-author('o.palij@gmail.com').
|
|||
|
+
|
|||
|
+-include("mod_logdb.hrl").
|
|||
|
+-include("ejabberd.hrl").
|
|||
|
+-include("jlib.hrl").
|
|||
|
+
|
|||
|
+-behaviour(gen_logdb).
|
|||
|
+-behaviour(gen_server).
|
|||
|
+
|
|||
|
+% gen_server
|
|||
|
+-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
|
|||
|
+% gen_mod
|
|||
|
+-export([start/2, stop/1]).
|
|||
|
+% gen_logdb
|
|||
|
+-export([log_message/2,
|
|||
|
+ rebuild_stats/1,
|
|||
|
+ rebuild_stats_at/2,
|
|||
|
+ delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
|
|||
|
+ get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
|
|||
|
+ get_dates/1,
|
|||
|
+ get_users_settings/1, get_user_settings/2, set_user_settings/3,
|
|||
|
+ drop_user/2]).
|
|||
|
+
|
|||
|
+% gen_server call timeout
|
|||
|
+-define(CALL_TIMEOUT, 30000).
|
|||
|
+-define(MYSQL_TIMEOUT, 60000).
|
|||
|
+-define(INDEX_SIZE, integer_to_list(170)).
|
|||
|
+-define(PROCNAME, mod_logdb_mysql).
|
|||
|
+
|
|||
|
+-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
|
|||
|
+ list_to_string/1, string_to_list/1,
|
|||
|
+ convert_timestamp_brief/1]).
|
|||
|
+
|
|||
|
+-record(state, {dbref, vhost, server, port, db, user, password}).
|
|||
|
+
|
|||
|
+% replace "." with "_"
|
|||
|
+escape_vhost(VHost) -> lists:map(fun(46) -> 95;
|
|||
|
+ (A) -> A
|
|||
|
+ end, VHost).
|
|||
|
+prefix() ->
|
|||
|
+ "`logdb_".
|
|||
|
+
|
|||
|
+suffix(VHost) ->
|
|||
|
+ "_" ++ escape_vhost(VHost) ++ "`".
|
|||
|
+
|
|||
|
+messages_table(VHost, Date) ->
|
|||
|
+ prefix() ++ "messages_" ++ Date ++ suffix(VHost).
|
|||
|
+
|
|||
|
+stats_table(VHost) ->
|
|||
|
+ prefix() ++ "stats" ++ suffix(VHost).
|
|||
|
+
|
|||
|
+temp_table(VHost) ->
|
|||
|
+ prefix() ++ "temp" ++ suffix(VHost).
|
|||
|
+
|
|||
|
+settings_table(VHost) ->
|
|||
|
+ prefix() ++ "settings" ++ suffix(VHost).
|
|||
|
+
|
|||
|
+users_table(VHost) ->
|
|||
|
+ prefix() ++ "users" ++ suffix(VHost).
|
|||
|
+servers_table(VHost) ->
|
|||
|
+ prefix() ++ "servers" ++ suffix(VHost).
|
|||
|
+resources_table(VHost) ->
|
|||
|
+ prefix() ++ "resources" ++ suffix(VHost).
|
|||
|
+
|
|||
|
+ets_users_table(VHost) -> list_to_atom("logdb_users_" ++ VHost).
|
|||
|
+ets_servers_table(VHost) -> list_to_atom("logdb_servers_" ++ VHost).
|
|||
|
+ets_resources_table(VHost) -> list_to_atom("logdb_resources_" ++ VHost).
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% gen_mod callbacks
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+start(VHost, Opts) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
|
|||
|
+
|
|||
|
+stop(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% gen_server callbacks
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+init([VHost, Opts]) ->
|
|||
|
+ crypto:start(),
|
|||
|
+
|
|||
|
+ Server = gen_mod:get_opt(server, Opts, "localhost"),
|
|||
|
+ Port = gen_mod:get_opt(port, Opts, 3306),
|
|||
|
+ DB = gen_mod:get_opt(db, Opts, "logdb"),
|
|||
|
+ User = gen_mod:get_opt(user, Opts, "root"),
|
|||
|
+ Password = gen_mod:get_opt(password, Opts, ""),
|
|||
|
+
|
|||
|
+ St = #state{vhost=VHost,
|
|||
|
+ server=Server, port=Port, db=DB,
|
|||
|
+ user=User, password=Password},
|
|||
|
+
|
|||
|
+ case open_mysql_connection(St) of
|
|||
|
+ {ok, DBRef} ->
|
|||
|
+ State = St#state{dbref=DBRef},
|
|||
|
+ ok = create_stats_table(State),
|
|||
|
+ ok = create_settings_table(State),
|
|||
|
+ ok = create_users_table(State),
|
|||
|
+ % clear ets cache every ...
|
|||
|
+ timer:send_interval(timer:hours(12), clear_ets_tables),
|
|||
|
+ ok = create_servers_table(State),
|
|||
|
+ ok = create_resources_table(State),
|
|||
|
+ erlang:monitor(process, DBRef),
|
|||
|
+ {ok, State};
|
|||
|
+ {error, Reason} ->
|
|||
|
+ ?ERROR_MSG("MySQL connection failed: ~p~n", [Reason]),
|
|||
|
+ {stop, db_connection_failed}
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+open_mysql_connection(#state{server=Server, port=Port, db=DB,
|
|||
|
+ user=DBUser, password=Password} = _State) ->
|
|||
|
+ LogFun = fun(debug, _Format, _Argument) ->
|
|||
|
+ %?MYDEBUG(Format, Argument);
|
|||
|
+ ok;
|
|||
|
+ (error, Format, Argument) ->
|
|||
|
+ ?ERROR_MSG(Format, Argument);
|
|||
|
+ (Level, Format, Argument) ->
|
|||
|
+ ?MYDEBUG("MySQL (~p)~n", [Level]),
|
|||
|
+ ?MYDEBUG(Format, Argument)
|
|||
|
+ end,
|
|||
|
+ mysql_conn:start(Server, Port, DBUser, Password, DB, LogFun).
|
|||
|
+
|
|||
|
+close_mysql_connection(DBRef) ->
|
|||
|
+ ?MYDEBUG("Closing ~p mysql connection", [DBRef]),
|
|||
|
+ mysql_conn:stop(DBRef).
|
|||
|
+
|
|||
|
+handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ Date = convert_timestamp_brief(Msg#msg.timestamp),
|
|||
|
+
|
|||
|
+ Table = messages_table(VHost, Date),
|
|||
|
+ Owner_id = get_user_id(DBRef, VHost, Msg#msg.owner_name),
|
|||
|
+ Peer_name_id = get_user_id(DBRef, VHost, Msg#msg.peer_name),
|
|||
|
+ Peer_server_id = get_server_id(DBRef, VHost, Msg#msg.peer_server),
|
|||
|
+ Peer_resource_id = get_resource_id(DBRef, VHost, Msg#msg.peer_resource),
|
|||
|
+
|
|||
|
+ Query = ["INSERT INTO ",Table," ",
|
|||
|
+ "(owner_id,",
|
|||
|
+ "peer_name_id,",
|
|||
|
+ "peer_server_id,",
|
|||
|
+ "peer_resource_id,",
|
|||
|
+ "direction,",
|
|||
|
+ "type,",
|
|||
|
+ "subject,",
|
|||
|
+ "body,",
|
|||
|
+ "timestamp) ",
|
|||
|
+ "VALUES ",
|
|||
|
+ "('", Owner_id, "',",
|
|||
|
+ "'", Peer_name_id, "',",
|
|||
|
+ "'", Peer_server_id, "',",
|
|||
|
+ "'", Peer_resource_id, "',",
|
|||
|
+ "'", atom_to_list(Msg#msg.direction), "',",
|
|||
|
+ "'", Msg#msg.type, "',",
|
|||
|
+ "'", ejabberd_odbc:escape(Msg#msg.subject), "',",
|
|||
|
+ "'", ejabberd_odbc:escape(Msg#msg.body), "',",
|
|||
|
+ "'", Msg#msg.timestamp, "');"],
|
|||
|
+
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal_silent(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
|
|||
|
+ Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
|
|||
|
+ increment_user_stats(DBRef, Msg#msg.owner_name, Owner_id, VHost, Peer_name_id, Peer_server_id, Date);
|
|||
|
+ {error, Reason} ->
|
|||
|
+ case regexp:match(Reason, "#42S02") of
|
|||
|
+ % Table doesn't exist
|
|||
|
+ {match, _, _} ->
|
|||
|
+ case create_msg_table(DBRef, VHost, Date) of
|
|||
|
+ error ->
|
|||
|
+ error;
|
|||
|
+ ok ->
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, Query),
|
|||
|
+ increment_user_stats(DBRef, Msg#msg.owner_name, Owner_id, VHost, Peer_name_id, Peer_server_id, Date)
|
|||
|
+ end;
|
|||
|
+ _ ->
|
|||
|
+ ?ERROR_MSG("Failed to log message: ~p", [Reason]),
|
|||
|
+ error
|
|||
|
+ end
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ Reply = rebuild_stats_at_int(DBRef, VHost, Date),
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
|
|||
|
+ {reply, error, State};
|
|||
|
+handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
|
|||
|
+ ["\"",Timestamp,"\"",","]
|
|||
|
+ end, Msgs),
|
|||
|
+
|
|||
|
+ Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
|
|||
|
+
|
|||
|
+ Query = ["DELETE FROM ",messages_table(VHost, Date)," ",
|
|||
|
+ "WHERE timestamp IN (", Temp1],
|
|||
|
+
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, Aff} ->
|
|||
|
+ ?MYDEBUG("Aff=~p", [Aff]),
|
|||
|
+ rebuild_stats_at_int(DBRef, VHost, Date);
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ ok = delete_all_messages_by_user_at_int(DBRef, User, VHost, Date),
|
|||
|
+ ok = delete_stats_by_user_at_int(DBRef, User, VHost, Date),
|
|||
|
+ {reply, ok, State};
|
|||
|
+handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Date),";"]) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ Query = ["DELETE FROM ",stats_table(VHost)," "
|
|||
|
+ "WHERE at=\"",Date,"\";"],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ SName = stats_table(VHost),
|
|||
|
+ Query = ["SELECT at, sum(count) ",
|
|||
|
+ "FROM ",SName," ",
|
|||
|
+ "GROUP BY at ",
|
|||
|
+ "ORDER BY DATE(at) DESC;"
|
|||
|
+ ],
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {data, Result} ->
|
|||
|
+ {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
|
|||
|
+ {error, Reason} ->
|
|||
|
+ % TODO: Duplicate error message ?
|
|||
|
+ {error, Reason}
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ SName = stats_table(VHost),
|
|||
|
+ Query = ["SELECT username, sum(count) AS allcount ",
|
|||
|
+ "FROM ",SName," ",
|
|||
|
+ "JOIN ",users_table(VHost)," ON owner_id=user_id "
|
|||
|
+ "WHERE at=\"",Date,"\" "
|
|||
|
+ "GROUP BY username ",
|
|||
|
+ "ORDER BY allcount DESC;"
|
|||
|
+ ],
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {data, Result} ->
|
|||
|
+ {ok, lists:reverse(
|
|||
|
+ lists:keysort(2,
|
|||
|
+ [ {User, list_to_integer(Count)} || [User, Count] <- Result]))};
|
|||
|
+ {error, Reason} ->
|
|||
|
+ % TODO:
|
|||
|
+ {error, Reason}
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ {reply, get_user_stats_int(DBRef, User, VHost), State};
|
|||
|
+handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ TName = messages_table(VHost, Date),
|
|||
|
+ UName = users_table(VHost),
|
|||
|
+ SName = servers_table(VHost),
|
|||
|
+ RName = resources_table(VHost),
|
|||
|
+ Query = ["SELECT users.username,",
|
|||
|
+ "servers.server,",
|
|||
|
+ "resources.resource,",
|
|||
|
+ "messages.direction,"
|
|||
|
+ "messages.type,"
|
|||
|
+ "messages.subject,"
|
|||
|
+ "messages.body,"
|
|||
|
+ "messages.timestamp "
|
|||
|
+ "FROM ",TName," AS messages "
|
|||
|
+ "JOIN ",UName," AS users ON peer_name_id=user_id ",
|
|||
|
+ "JOIN ",SName," AS servers ON peer_server_id=server_id ",
|
|||
|
+ "JOIN ",RName," AS resources ON peer_resource_id=resource_id ",
|
|||
|
+ "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\" ",
|
|||
|
+ "ORDER BY timestamp ASC;"],
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {data, Result} ->
|
|||
|
+ Fun = fun([Peer_name, Peer_server, Peer_resource,
|
|||
|
+ Direction,
|
|||
|
+ Type,
|
|||
|
+ Subject, Body,
|
|||
|
+ Timestamp]) ->
|
|||
|
+ #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
|
|||
|
+ direction=list_to_atom(Direction),
|
|||
|
+ type=Type,
|
|||
|
+ subject=Subject, body=Body,
|
|||
|
+ timestamp=Timestamp}
|
|||
|
+ end,
|
|||
|
+ {ok, lists:map(Fun, Result)};
|
|||
|
+ {error, Reason} ->
|
|||
|
+ {error, Reason}
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ SName = stats_table(VHost),
|
|||
|
+ Query = ["SELECT at ",
|
|||
|
+ "FROM ",SName," ",
|
|||
|
+ "GROUP BY at ",
|
|||
|
+ "ORDER BY DATE(at) DESC;"
|
|||
|
+ ],
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {data, Result} ->
|
|||
|
+ [ Date || [Date] <- Result ];
|
|||
|
+ {error, Reason} ->
|
|||
|
+ {error, Reason}
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
|
|||
|
+ "FROM ",settings_table(VHost)," ",
|
|||
|
+ "JOIN ",users_table(VHost)," ON user_id=owner_id;"],
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {data, Result} ->
|
|||
|
+ {ok, lists:map(fun([Owner, DoLogDef, DoLogL, DoNotLogL]) ->
|
|||
|
+ #user_settings{owner_name=Owner,
|
|||
|
+ dolog_default=list_to_bool(DoLogDef),
|
|||
|
+ dolog_list=string_to_list(DoLogL),
|
|||
|
+ donotlog_list=string_to_list(DoNotLogL)
|
|||
|
+ }
|
|||
|
+ end, Result)};
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ Query = ["SELECT dolog_default,dolog_list,donotlog_list FROM ",settings_table(VHost)," ",
|
|||
|
+ "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\";"],
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {data, []} ->
|
|||
|
+ {ok, []};
|
|||
|
+ {data, [[Owner, DoLogDef, DoLogL, DoNotLogL]]} ->
|
|||
|
+ {ok, #user_settings{owner_name=Owner,
|
|||
|
+ dolog_default=list_to_bool(DoLogDef),
|
|||
|
+ dolog_list=string_to_list(DoLogL),
|
|||
|
+ donotlog_list=string_to_list(DoNotLogL)}};
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
|
|||
|
+ dolog_list=DoLogL,
|
|||
|
+ donotlog_list=DoNotLogL}},
|
|||
|
+ _From, #state{dbref=DBRef, vhost=VHost} = State) ->
|
|||
|
+ User_id = get_user_id(DBRef, VHost, User),
|
|||
|
+
|
|||
|
+ Query = ["UPDATE ",settings_table(VHost)," ",
|
|||
|
+ "SET dolog_default=",bool_to_list(DoLogDef),", ",
|
|||
|
+ "dolog_list='",list_to_string(DoLogL),"', ",
|
|||
|
+ "donotlog_list='",list_to_string(DoNotLogL),"' ",
|
|||
|
+ "WHERE owner_id=\"",User_id,"\";"],
|
|||
|
+
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, 0} ->
|
|||
|
+ IQuery = ["INSERT INTO ",settings_table(VHost)," ",
|
|||
|
+ "(owner_id, dolog_default, dolog_list, donotlog_list) ",
|
|||
|
+ "VALUES ",
|
|||
|
+ "('",User_id,"', ",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
|
|||
|
+ case sql_query_internal_silent(DBRef, IQuery) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
|
|||
|
+ ok;
|
|||
|
+ {error, Reason} ->
|
|||
|
+ case regexp:match(Reason, "#23000") of
|
|||
|
+ % Already exists
|
|||
|
+ {match, _, _} ->
|
|||
|
+ ok;
|
|||
|
+ _ ->
|
|||
|
+ ?ERROR_MSG("Failed setup user ~p@~p: ~p", [User, VHost, Reason]),
|
|||
|
+ error
|
|||
|
+ end
|
|||
|
+ end;
|
|||
|
+ {updated, 1} ->
|
|||
|
+ ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({stop}, _From, #state{vhost=VHost}=State) ->
|
|||
|
+ ets:delete(ets_users_table(VHost)),
|
|||
|
+ ets:delete(ets_servers_table(VHost)),
|
|||
|
+ ?MYDEBUG("Stoping mysql backend for ~p", [VHost]),
|
|||
|
+ {stop, normal, ok, State};
|
|||
|
+handle_call(Msg, _From, State) ->
|
|||
|
+ ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
|
|||
|
+ {noreply, State}.
|
|||
|
+
|
|||
|
+handle_cast({rebuild_stats}, State) ->
|
|||
|
+ rebuild_all_stats_int(State),
|
|||
|
+ {noreply, State};
|
|||
|
+handle_cast({drop_user, User}, #state{vhost=VHost} = State) ->
|
|||
|
+ Fun = fun() ->
|
|||
|
+ {ok, DBRef} = open_mysql_connection(State),
|
|||
|
+ {ok, Dates} = get_user_stats_int(DBRef, User, VHost),
|
|||
|
+ MDResult = lists:map(fun({Date, _}) ->
|
|||
|
+ delete_all_messages_by_user_at_int(DBRef, User, VHost, Date)
|
|||
|
+ end, Dates),
|
|||
|
+ StDResult = delete_all_stats_by_user_int(DBRef, User, VHost),
|
|||
|
+ SDResult = delete_user_settings_int(DBRef, User, VHost),
|
|||
|
+ case lists:all(fun(Result) when Result == ok ->
|
|||
|
+ true;
|
|||
|
+ (Result) when Result == error ->
|
|||
|
+ false
|
|||
|
+ end, lists:append([MDResult, [StDResult], [SDResult]])) of
|
|||
|
+ true ->
|
|||
|
+ ?INFO_MSG("Removed ~s@~s", [User, VHost]);
|
|||
|
+ false ->
|
|||
|
+ ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost])
|
|||
|
+ end,
|
|||
|
+ close_mysql_connection(DBRef)
|
|||
|
+ end,
|
|||
|
+ spawn(Fun),
|
|||
|
+ {noreply, State};
|
|||
|
+handle_cast(Msg, State) ->
|
|||
|
+ ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
|
|||
|
+ {noreply, State}.
|
|||
|
+
|
|||
|
+handle_info(clear_ets_tables, State) ->
|
|||
|
+ ets:delete_all_objects(ets_users_table(State#state.vhost)),
|
|||
|
+ ets:delete_all_objects(ets_resources_table(State#state.vhost)),
|
|||
|
+ {noreply, State};
|
|||
|
+handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
|
|||
|
+ {stop, connection_dropped, State};
|
|||
|
+handle_info(Info, State) ->
|
|||
|
+ ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
|
|||
|
+ {noreply, State}.
|
|||
|
+
|
|||
|
+terminate(_Reason, #state{dbref=DBRef}=_State) ->
|
|||
|
+ close_mysql_connection(DBRef),
|
|||
|
+ ok.
|
|||
|
+
|
|||
|
+code_change(_OldVsn, State, _Extra) ->
|
|||
|
+ {ok, State}.
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% gen_logdb callbacks
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+log_message(VHost, Msg) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
|
|||
|
+rebuild_stats(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:cast(Proc, {rebuild_stats}).
|
|||
|
+rebuild_stats_at(VHost, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
|
|||
|
+delete_messages_by_user_at(VHost, Msgs, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
|
|||
|
+delete_all_messages_by_user_at(User, VHost, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
|
|||
|
+delete_messages_at(VHost, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
|
|||
|
+get_vhost_stats(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
|
|||
|
+get_vhost_stats_at(VHost, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
|
|||
|
+get_user_stats(User, VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
|
|||
|
+get_user_messages_at(User, VHost, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
|
|||
|
+get_dates(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
|
|||
|
+get_users_settings(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
|
|||
|
+get_user_settings(User, VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
|
|||
|
+set_user_settings(User, VHost, Set) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
|
|||
|
+drop_user(User, VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:cast(Proc, {drop_user, User}).
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% internals
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+increment_user_stats(DBRef, User_name, User_id, VHost, PNameID, PServerID, Date) ->
|
|||
|
+ SName = stats_table(VHost),
|
|||
|
+ UQuery = ["UPDATE ",SName," ",
|
|||
|
+ "SET count=count+1 ",
|
|||
|
+ "WHERE owner_id=\"",User_id,"\" AND peer_name_id=\"",PNameID,"\" AND peer_server_id=\"",PServerID,"\" AND at=\"",Date,"\";"],
|
|||
|
+
|
|||
|
+ case sql_query_internal(DBRef, UQuery) of
|
|||
|
+ {updated, 0} ->
|
|||
|
+ IQuery = ["INSERT INTO ",SName," ",
|
|||
|
+ "(owner_id, peer_name_id, peer_server_id, at, count) ",
|
|||
|
+ "VALUES ",
|
|||
|
+ "('",User_id,"', '",PNameID,"', '",PServerID,"', '",Date,"', '1');"],
|
|||
|
+ case sql_query_internal(DBRef, IQuery) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?MYDEBUG("New stats for ~s@~s at ~s", [User_name, VHost, Date]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end;
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?MYDEBUG("Updated stats for ~s@~s at ~s", [User_name, VHost, Date]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+get_dates_int(DBRef, VHost) ->
|
|||
|
+ case sql_query_internal(DBRef, ["SHOW TABLES"]) of
|
|||
|
+ {data, Tables} ->
|
|||
|
+ lists:foldl(fun([Table], Dates) ->
|
|||
|
+ Reg = lists:sublist(prefix(),2,length(prefix())) ++ ".*" ++ escape_vhost(VHost),
|
|||
|
+ case regexp:match(Table, Reg) of
|
|||
|
+ {match, 1, _} ->
|
|||
|
+ ?MYDEBUG("matched ~p against ~p", [Table, Reg]),
|
|||
|
+ case regexp:match(Table,"[0-9]+-[0-9]+-[0-9]+") of
|
|||
|
+ {match, S, E} ->
|
|||
|
+ lists:append(Dates, [lists:sublist(Table,S,E)]);
|
|||
|
+ nomatch ->
|
|||
|
+ Dates
|
|||
|
+ end;
|
|||
|
+
|
|||
|
+ _ ->
|
|||
|
+ Dates
|
|||
|
+ end
|
|||
|
+ end, [], Tables);
|
|||
|
+ {error, _} ->
|
|||
|
+ []
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+rebuild_all_stats_int(#state{vhost=VHost}=State) ->
|
|||
|
+ Fun = fun() ->
|
|||
|
+ {ok, DBRef} = open_mysql_connection(State),
|
|||
|
+ ok = delete_nonexistent_stats(DBRef, VHost),
|
|||
|
+ case lists:filter(fun(Date) ->
|
|||
|
+ case catch rebuild_stats_at_int(DBRef, VHost, Date) of
|
|||
|
+ ok -> false;
|
|||
|
+ error -> true;
|
|||
|
+ {'EXIT', _} -> true
|
|||
|
+ end
|
|||
|
+ end, get_dates_int(DBRef, VHost)) of
|
|||
|
+ [] -> ok;
|
|||
|
+ FTables ->
|
|||
|
+ ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]),
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ close_mysql_connection(DBRef)
|
|||
|
+ end,
|
|||
|
+ spawn(Fun).
|
|||
|
+
|
|||
|
+rebuild_stats_at_int(DBRef, VHost, Date) ->
|
|||
|
+ TempTable = temp_table(VHost),
|
|||
|
+ Fun = fun() ->
|
|||
|
+ Table = messages_table(VHost, Date),
|
|||
|
+ STable = stats_table(VHost),
|
|||
|
+
|
|||
|
+ DQuery = [ "DELETE FROM ",STable," ",
|
|||
|
+ "WHERE at='",Date,"';"],
|
|||
|
+
|
|||
|
+ ok = create_temp_table(DBRef, TempTable),
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," WRITE, ",TempTable," WRITE;"]),
|
|||
|
+ SQuery = ["INSERT INTO ",TempTable," ",
|
|||
|
+ "(owner_id,peer_name_id,peer_server_id,at,count) ",
|
|||
|
+ "SELECT owner_id,peer_name_id,peer_server_id,\"",Date,"\",count(*) ",
|
|||
|
+ "FROM ",Table," GROUP BY owner_id,peer_name_id,peer_server_id;"],
|
|||
|
+ case sql_query_internal(DBRef, SQuery) of
|
|||
|
+ {updated, 0} ->
|
|||
|
+ Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]),
|
|||
|
+ case Count of
|
|||
|
+ {data, [["0"]]} ->
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]),
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE;"]),
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, DQuery),
|
|||
|
+ ok;
|
|||
|
+ _ ->
|
|||
|
+ ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]),
|
|||
|
+ error
|
|||
|
+ end;
|
|||
|
+ {updated, _} ->
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE, ",TempTable," WRITE;"]),
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, DQuery),
|
|||
|
+ SQuery1 = ["INSERT INTO ",STable," ",
|
|||
|
+ "(owner_id,peer_name_id,peer_server_id,at,count) ",
|
|||
|
+ "SELECT owner_id,peer_name_id,peer_server_id,at,count ",
|
|||
|
+ "FROM ",TempTable,";"],
|
|||
|
+ case sql_query_internal(DBRef, SQuery1) of
|
|||
|
+ {updated, _} -> ok;
|
|||
|
+ {error, _} -> error
|
|||
|
+ end;
|
|||
|
+ {error, _} -> error
|
|||
|
+ end
|
|||
|
+ end,
|
|||
|
+
|
|||
|
+ case catch apply(Fun, []) of
|
|||
|
+ ok ->
|
|||
|
+ ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
|
|||
|
+ ok;
|
|||
|
+ error ->
|
|||
|
+ error;
|
|||
|
+ {'EXIT', Reason} ->
|
|||
|
+ ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]),
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ sql_query_internal(DBRef, ["UNLOCK TABLES;"]),
|
|||
|
+ sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]),
|
|||
|
+ ok.
|
|||
|
+
|
|||
|
+
|
|||
|
+delete_nonexistent_stats(DBRef, VHost) ->
|
|||
|
+ Dates = get_dates_int(DBRef, VHost),
|
|||
|
+ STable = stats_table(VHost),
|
|||
|
+
|
|||
|
+ Temp = lists:flatmap(fun(Date) ->
|
|||
|
+ ["\"",Date,"\"",","]
|
|||
|
+ end, Dates),
|
|||
|
+
|
|||
|
+ case Temp of
|
|||
|
+ [] ->
|
|||
|
+ ok;
|
|||
|
+ _ ->
|
|||
|
+ % replace last "," with ");"
|
|||
|
+ Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
|
|||
|
+ Query = ["DELETE FROM ",STable," ",
|
|||
|
+ "WHERE at NOT IN (", Temp1],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+get_user_stats_int(DBRef, User, VHost) ->
|
|||
|
+ SName = stats_table(VHost),
|
|||
|
+ Query = ["SELECT at, sum(count) as allcount ",
|
|||
|
+ "FROM ",SName," ",
|
|||
|
+ "WHERE owner_id=\"",get_user_id(DBRef, VHost, User),"\" ",
|
|||
|
+ "GROUP BY at "
|
|||
|
+ "ORDER BY DATE(at) DESC;"
|
|||
|
+ ],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {data, Result} ->
|
|||
|
+ {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result]};
|
|||
|
+ {error, Result} ->
|
|||
|
+ {error, Result}
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+delete_all_messages_by_user_at_int(DBRef, User, VHost, Date) ->
|
|||
|
+ DQuery = ["DELETE FROM ",messages_table(VHost, Date)," ",
|
|||
|
+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
|
|||
|
+ case sql_query_internal(DBRef, DQuery) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+delete_all_stats_by_user_int(DBRef, User, VHost) ->
|
|||
|
+ SQuery = ["DELETE FROM ",stats_table(VHost)," ",
|
|||
|
+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
|
|||
|
+ case sql_query_internal(DBRef, SQuery) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} -> error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+delete_stats_by_user_at_int(DBRef, User, VHost, Date) ->
|
|||
|
+ SQuery = ["DELETE FROM ",stats_table(VHost)," ",
|
|||
|
+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\") ",
|
|||
|
+ "AND at=\"",Date,"\";"],
|
|||
|
+ case sql_query_internal(DBRef, SQuery) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} -> error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+delete_user_settings_int(DBRef, User, VHost) ->
|
|||
|
+ Query = ["DELETE FROM ",settings_table(VHost)," ",
|
|||
|
+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]),
|
|||
|
+ ok;
|
|||
|
+ {error, Reason} ->
|
|||
|
+ ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]),
|
|||
|
+ error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% tables internals
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+create_temp_table(DBRef, Name) ->
|
|||
|
+ Query = ["CREATE TABLE ",Name," (",
|
|||
|
+ "owner_id MEDIUMINT UNSIGNED, ",
|
|||
|
+ "peer_name_id MEDIUMINT UNSIGNED, ",
|
|||
|
+ "peer_server_id MEDIUMINT UNSIGNED, ",
|
|||
|
+ "at VARCHAR(11), ",
|
|||
|
+ "count INT(11) ",
|
|||
|
+ ") ENGINE=MyISAM CHARACTER SET utf8;"
|
|||
|
+ ],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, _} -> ok;
|
|||
|
+ {error, _Reason} -> error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+create_stats_table(#state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ SName = stats_table(VHost),
|
|||
|
+ Query = ["CREATE TABLE ",SName," (",
|
|||
|
+ "owner_id MEDIUMINT UNSIGNED, ",
|
|||
|
+ "peer_name_id MEDIUMINT UNSIGNED, ",
|
|||
|
+ "peer_server_id MEDIUMINT UNSIGNED, ",
|
|||
|
+ "at varchar(20), ",
|
|||
|
+ "count int(11), ",
|
|||
|
+ "INDEX(owner_id, peer_name_id, peer_server_id), ",
|
|||
|
+ "INDEX(at)"
|
|||
|
+ ") ENGINE=InnoDB CHARACTER SET utf8;"
|
|||
|
+ ],
|
|||
|
+ case sql_query_internal_silent(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?INFO_MSG("Created stats table for ~p", [VHost]),
|
|||
|
+ rebuild_all_stats_int(State),
|
|||
|
+ ok;
|
|||
|
+ {error, Reason} ->
|
|||
|
+ case regexp:match(Reason, "#42S01") of
|
|||
|
+ {match, _, _} ->
|
|||
|
+ ?MYDEBUG("Stats table for ~p already exists", [VHost]),
|
|||
|
+ CheckQuery = ["SHOW COLUMNS FROM ",SName," LIKE 'peer_%_id';"],
|
|||
|
+ case sql_query_internal(DBRef, CheckQuery) of
|
|||
|
+ {data, Elems} when length(Elems) == 2 ->
|
|||
|
+ ?MYDEBUG("Stats table structure is ok", []),
|
|||
|
+ ok;
|
|||
|
+ _ ->
|
|||
|
+ ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []),
|
|||
|
+ case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?INFO_MSG("Successfully dropped ~p", [SName]);
|
|||
|
+ _ ->
|
|||
|
+ ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName])
|
|||
|
+ end,
|
|||
|
+ error
|
|||
|
+ end;
|
|||
|
+ _ ->
|
|||
|
+ ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
|
|||
|
+ error
|
|||
|
+ end
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+create_settings_table(#state{dbref=DBRef, vhost=VHost}) ->
|
|||
|
+ SName = settings_table(VHost),
|
|||
|
+ Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
|
|||
|
+ "owner_id MEDIUMINT UNSIGNED PRIMARY KEY, ",
|
|||
|
+ "dolog_default TINYINT(1) NOT NULL DEFAULT 1, ",
|
|||
|
+ "dolog_list TEXT, ",
|
|||
|
+ "donotlog_list TEXT ",
|
|||
|
+ ") ENGINE=InnoDB CHARACTER SET utf8;"
|
|||
|
+ ],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?MYDEBUG("Created settings table for ~p", [VHost]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+create_users_table(#state{dbref=DBRef, vhost=VHost}) ->
|
|||
|
+ SName = users_table(VHost),
|
|||
|
+ Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
|
|||
|
+ "username TEXT NOT NULL, ",
|
|||
|
+ "user_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
|
|||
|
+ "UNIQUE INDEX(username(",?INDEX_SIZE,")) ",
|
|||
|
+ ") ENGINE=InnoDB CHARACTER SET utf8;"
|
|||
|
+ ],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?MYDEBUG("Created users table for ~p", [VHost]),
|
|||
|
+ ets:new(ets_users_table(VHost), [named_table, set, public]),
|
|||
|
+ %update_users_from_db(DBRef, VHost),
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+create_servers_table(#state{dbref=DBRef, vhost=VHost}) ->
|
|||
|
+ SName = servers_table(VHost),
|
|||
|
+ Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
|
|||
|
+ "server TEXT NOT NULL, ",
|
|||
|
+ "server_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
|
|||
|
+ "UNIQUE INDEX(server(",?INDEX_SIZE,")) ",
|
|||
|
+ ") ENGINE=InnoDB CHARACTER SET utf8;"
|
|||
|
+ ],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?MYDEBUG("Created servers table for ~p", [VHost]),
|
|||
|
+ ets:new(ets_servers_table(VHost), [named_table, set, public]),
|
|||
|
+ update_servers_from_db(DBRef, VHost),
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+create_resources_table(#state{dbref=DBRef, vhost=VHost}) ->
|
|||
|
+ RName = resources_table(VHost),
|
|||
|
+ Query = ["CREATE TABLE IF NOT EXISTS ",RName," (",
|
|||
|
+ "resource TEXT NOT NULL, ",
|
|||
|
+ "resource_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
|
|||
|
+ "UNIQUE INDEX(resource(",?INDEX_SIZE,")) ",
|
|||
|
+ ") ENGINE=InnoDB CHARACTER SET utf8;"
|
|||
|
+ ],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?MYDEBUG("Created resources table for ~p", [VHost]),
|
|||
|
+ ets:new(ets_resources_table(VHost), [named_table, set, public]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+create_msg_table(DBRef, VHost, Date) ->
|
|||
|
+ TName = messages_table(VHost, Date),
|
|||
|
+ Query = ["CREATE TABLE ",TName," (",
|
|||
|
+ "owner_id MEDIUMINT UNSIGNED, ",
|
|||
|
+ "peer_name_id MEDIUMINT UNSIGNED, ",
|
|||
|
+ "peer_server_id MEDIUMINT UNSIGNED, ",
|
|||
|
+ "peer_resource_id MEDIUMINT(8) UNSIGNED, ",
|
|||
|
+ "direction ENUM('to', 'from'), ",
|
|||
|
+ "type ENUM('chat','error','groupchat','headline','normal') NOT NULL, ",
|
|||
|
+ "subject TEXT, ",
|
|||
|
+ "body TEXT, ",
|
|||
|
+ "timestamp DOUBLE, ",
|
|||
|
+ "INDEX search_i (owner_id, peer_name_id, peer_server_id, peer_resource_id), ",
|
|||
|
+ "FULLTEXT (body) "
|
|||
|
+ ") ENGINE=MyISAM CHARACTER SET utf8;"
|
|||
|
+ ],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, _MySQLRes} ->
|
|||
|
+ ?MYDEBUG("Created msg table for ~p at ~p", [VHost, Date]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% internal ets cache (users, servers, resources)
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+update_servers_from_db(DBRef, VHost) ->
|
|||
|
+ ?INFO_MSG("Reading servers from db for ~p", [VHost]),
|
|||
|
+ SQuery = ["SELECT server, server_id FROM ",servers_table(VHost),";"],
|
|||
|
+ {data, Result} = sql_query_internal(DBRef, SQuery),
|
|||
|
+ true = ets:delete_all_objects(ets_servers_table(VHost)),
|
|||
|
+ true = ets:insert(ets_servers_table(VHost), [ {Server, Server_id} || [Server, Server_id] <- Result]).
|
|||
|
+
|
|||
|
+%update_users_from_db(DBRef, VHost) ->
|
|||
|
+% ?INFO_MSG("Reading users from db for ~p", [VHost]),
|
|||
|
+% SQuery = ["SELECT username, user_id FROM ",users_table(VHost),";"],
|
|||
|
+% {data, Result} = sql_query_internal(DBRef, SQuery),
|
|||
|
+% true = ets:delete_all_objects(ets_users_table(VHost)),
|
|||
|
+% true = ets:insert(ets_users_table(VHost), [ {Username, User_id} || [Username, User_id] <- Result]).
|
|||
|
+
|
|||
|
+%get_user_name(DBRef, VHost, User_id) ->
|
|||
|
+% case ets:match(ets_users_table(VHost), {'$1', User_id}) of
|
|||
|
+% [[User]] -> User;
|
|||
|
+% % this can be in clustered environment
|
|||
|
+% [] ->
|
|||
|
+% %update_users_from_db(DBRef, VHost),
|
|||
|
+% SQuery = ["SELECT username FROM ",users_table(VHost)," ",
|
|||
|
+% "WHERE user_id=\"",User_id,"\";"],
|
|||
|
+% {data, [[Name]]} = sql_query_internal(DBRef, SQuery),
|
|||
|
+% % cache {user, id} pair
|
|||
|
+% ets:insert(ets_users_table(VHost), {Name, User_id}),
|
|||
|
+% Name
|
|||
|
+% end.
|
|||
|
+
|
|||
|
+%get_server_name(DBRef, VHost, Server_id) ->
|
|||
|
+% case ets:match(ets_servers_table(VHost), {'$1', Server_id}) of
|
|||
|
+% [[Server]] -> Server;
|
|||
|
+ % this can be in clustered environment
|
|||
|
+% [] ->
|
|||
|
+% update_servers_from_db(DBRef, VHost),
|
|||
|
+% [[Server1]] = ets:match(ets_servers_table(VHost), {'$1', Server_id}),
|
|||
|
+% Server1
|
|||
|
+% end.
|
|||
|
+
|
|||
|
+get_user_id_from_db(DBRef, VHost, User) ->
|
|||
|
+ SQuery = ["SELECT user_id FROM ",users_table(VHost)," ",
|
|||
|
+ "WHERE username=\"",User,"\";"],
|
|||
|
+ case sql_query_internal(DBRef, SQuery) of
|
|||
|
+ % no such user in db
|
|||
|
+ {data, []} ->
|
|||
|
+ {ok, []};
|
|||
|
+ {data, [[DBId]]} ->
|
|||
|
+ % cache {user, id} pair
|
|||
|
+ ets:insert(ets_users_table(VHost), {User, DBId}),
|
|||
|
+ {ok, DBId}
|
|||
|
+ end.
|
|||
|
+get_user_id(DBRef, VHost, User) ->
|
|||
|
+ % Look at ets
|
|||
|
+ case ets:match(ets_users_table(VHost), {User, '$1'}) of
|
|||
|
+ [] ->
|
|||
|
+ % Look at db
|
|||
|
+ case get_user_id_from_db(DBRef, VHost, User) of
|
|||
|
+ % no such user in db
|
|||
|
+ {ok, []} ->
|
|||
|
+ IQuery = ["INSERT INTO ",users_table(VHost)," ",
|
|||
|
+ "SET username=\"",User,"\";"],
|
|||
|
+ case sql_query_internal_silent(DBRef, IQuery) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ {ok, NewId} = get_user_id_from_db(DBRef, VHost, User),
|
|||
|
+ NewId;
|
|||
|
+ {error, Reason} ->
|
|||
|
+ % this can be in clustered environment
|
|||
|
+ {match, _, _} = regexp:match(Reason, "#23000"),
|
|||
|
+ ?ERROR_MSG("Duplicate key name for ~p", [User]),
|
|||
|
+ {ok, ClID} = get_user_id_from_db(DBRef, VHost, User),
|
|||
|
+ ClID
|
|||
|
+ end;
|
|||
|
+ {ok, DBId} ->
|
|||
|
+ DBId
|
|||
|
+ end;
|
|||
|
+ [[EtsId]] -> EtsId
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+get_server_id(DBRef, VHost, Server) ->
|
|||
|
+ case ets:match(ets_servers_table(VHost), {Server, '$1'}) of
|
|||
|
+ [] ->
|
|||
|
+ IQuery = ["INSERT INTO ",servers_table(VHost)," ",
|
|||
|
+ "SET server=\"",Server,"\";"],
|
|||
|
+ case sql_query_internal_silent(DBRef, IQuery) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ SQuery = ["SELECT server_id FROM ",servers_table(VHost)," ",
|
|||
|
+ "WHERE server=\"",Server,"\";"],
|
|||
|
+ {data, [[Id]]} = sql_query_internal(DBRef, SQuery),
|
|||
|
+ ets:insert(ets_servers_table(VHost), {Server, Id}),
|
|||
|
+ Id;
|
|||
|
+ {error, Reason} ->
|
|||
|
+ % this can be in clustered environment
|
|||
|
+ {match, _, _} = regexp:match(Reason, "#23000"),
|
|||
|
+ ?ERROR_MSG("Duplicate key name for ~p", [Server]),
|
|||
|
+ update_servers_from_db(DBRef, VHost),
|
|||
|
+ [[Id1]] = ets:match(ets_servers_table(VHost), {Server, '$1'}),
|
|||
|
+ Id1
|
|||
|
+ end;
|
|||
|
+ [[Id]] -> Id
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+get_resource_id_from_db(DBRef, VHost, Resource) ->
|
|||
|
+ SQuery = ["SELECT resource_id FROM ",resources_table(VHost)," ",
|
|||
|
+ "WHERE resource=\"",ejabberd_odbc:escape(Resource),"\";"],
|
|||
|
+ case sql_query_internal(DBRef, SQuery) of
|
|||
|
+ % no such resource in db
|
|||
|
+ {data, []} ->
|
|||
|
+ {ok, []};
|
|||
|
+ {data, [[DBId]]} ->
|
|||
|
+ % cache {resource, id} pair
|
|||
|
+ ets:insert(ets_resources_table(VHost), {Resource, DBId}),
|
|||
|
+ {ok, DBId}
|
|||
|
+ end.
|
|||
|
+get_resource_id(DBRef, VHost, Resource) ->
|
|||
|
+ % Look at ets
|
|||
|
+ case ets:match(ets_resources_table(VHost), {Resource, '$1'}) of
|
|||
|
+ [] ->
|
|||
|
+ % Look at db
|
|||
|
+ case get_resource_id_from_db(DBRef, VHost, Resource) of
|
|||
|
+ % no such resource in db
|
|||
|
+ {ok, []} ->
|
|||
|
+ IQuery = ["INSERT INTO ",resources_table(VHost)," ",
|
|||
|
+ "SET resource=\"",ejabberd_odbc:escape(Resource),"\";"],
|
|||
|
+ case sql_query_internal_silent(DBRef, IQuery) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ {ok, NewId} = get_resource_id_from_db(DBRef, VHost, Resource),
|
|||
|
+ NewId;
|
|||
|
+ {error, Reason} ->
|
|||
|
+ % this can be in clustered environment
|
|||
|
+ {match, _, _} = regexp:match(Reason, "#23000"),
|
|||
|
+ ?ERROR_MSG("Duplicate key name for ~p", [Resource]),
|
|||
|
+ {ok, ClID} = get_resource_id_from_db(DBRef, VHost, Resource),
|
|||
|
+ ClID
|
|||
|
+ end;
|
|||
|
+ {ok, DBId} ->
|
|||
|
+ DBId
|
|||
|
+ end;
|
|||
|
+ [[EtsId]] -> EtsId
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% SQL internals
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+sql_query_internal(DBRef, Query) ->
|
|||
|
+ case sql_query_internal_silent(DBRef, Query) of
|
|||
|
+ {error, Reason} ->
|
|||
|
+ ?ERROR_MSG("~p while ~p", [Reason, lists:append(Query)]),
|
|||
|
+ {error, Reason};
|
|||
|
+ Rez -> Rez
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+sql_query_internal_silent(DBRef, Query) ->
|
|||
|
+ ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
|
|||
|
+ get_result(mysql_conn:fetch(DBRef, Query, self(), ?MYSQL_TIMEOUT)).
|
|||
|
+
|
|||
|
+get_result({updated, MySQLRes}) ->
|
|||
|
+ {updated, mysql:get_result_affected_rows(MySQLRes)};
|
|||
|
+get_result({data, MySQLRes}) ->
|
|||
|
+ {data, mysql:get_result_rows(MySQLRes)};
|
|||
|
+get_result({error, "query timed out"}) ->
|
|||
|
+ {error, "query timed out"};
|
|||
|
+get_result({error, MySQLRes}) ->
|
|||
|
+ Reason = mysql:get_result_reason(MySQLRes),
|
|||
|
+ {error, Reason}.
|
|||
|
--- mod_logdb_mysql5.erl.orig 2009-02-05 19:21:29.000000000 +0200
|
|||
|
+++ mod_logdb_mysql5.erl 2009-02-05 19:20:14.000000000 +0200
|
|||
|
@@ -0,0 +1,978 @@
|
|||
|
+%%%----------------------------------------------------------------------
|
|||
|
+%%% File : mod_logdb_mysql5.erl
|
|||
|
+%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
|
|||
|
+%%% Purpose : MySQL 5 backend for mod_logdb
|
|||
|
+%%% Version : trunk
|
|||
|
+%%% Id : $Id: mod_logdb_mysql5.erl 1253 2009-02-02 11:10:03Z malik $
|
|||
|
+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
|
|||
|
+%%%----------------------------------------------------------------------
|
|||
|
+
|
|||
|
+-module(mod_logdb_mysql5).
|
|||
|
+-author('o.palij@gmail.com').
|
|||
|
+
|
|||
|
+-include("mod_logdb.hrl").
|
|||
|
+-include("ejabberd.hrl").
|
|||
|
+-include("jlib.hrl").
|
|||
|
+
|
|||
|
+-behaviour(gen_logdb).
|
|||
|
+-behaviour(gen_server).
|
|||
|
+
|
|||
|
+% gen_server
|
|||
|
+-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
|
|||
|
+% gen_mod
|
|||
|
+-export([start/2, stop/1]).
|
|||
|
+% gen_logdb
|
|||
|
+-export([log_message/2,
|
|||
|
+ rebuild_stats/1,
|
|||
|
+ rebuild_stats_at/2,
|
|||
|
+ delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
|
|||
|
+ get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
|
|||
|
+ get_dates/1,
|
|||
|
+ get_users_settings/1, get_user_settings/2, set_user_settings/3,
|
|||
|
+ drop_user/2]).
|
|||
|
+
|
|||
|
+% gen_server call timeout
|
|||
|
+-define(CALL_TIMEOUT, 30000).
|
|||
|
+-define(MYSQL_TIMEOUT, 60000).
|
|||
|
+-define(INDEX_SIZE, integer_to_list(170)).
|
|||
|
+-define(PROCNAME, mod_logdb_mysql5).
|
|||
|
+
|
|||
|
+-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
|
|||
|
+ list_to_string/1, string_to_list/1,
|
|||
|
+ convert_timestamp_brief/1]).
|
|||
|
+
|
|||
|
+-record(state, {dbref, vhost, server, port, db, user, password}).
|
|||
|
+
|
|||
|
+% replace "." with "_"
|
|||
|
+escape_vhost(VHost) -> lists:map(fun(46) -> 95;
|
|||
|
+ (A) -> A
|
|||
|
+ end, VHost).
|
|||
|
+prefix() ->
|
|||
|
+ "`logdb_".
|
|||
|
+
|
|||
|
+suffix(VHost) ->
|
|||
|
+ "_" ++ escape_vhost(VHost) ++ "`".
|
|||
|
+
|
|||
|
+messages_table(VHost, Date) ->
|
|||
|
+ prefix() ++ "messages_" ++ Date ++ suffix(VHost).
|
|||
|
+
|
|||
|
+% TODO: this needs to be redone to unify view name in stored procedure and in delete_messages_at/2
|
|||
|
+view_table(VHost, Date) ->
|
|||
|
+ Table = messages_table(VHost, Date),
|
|||
|
+ TablewoQ = lists:sublist(Table, 2, length(Table) - 2),
|
|||
|
+ lists:append(["`v_", TablewoQ, "`"]).
|
|||
|
+
|
|||
|
+stats_table(VHost) ->
|
|||
|
+ prefix() ++ "stats" ++ suffix(VHost).
|
|||
|
+
|
|||
|
+temp_table(VHost) ->
|
|||
|
+ prefix() ++ "temp" ++ suffix(VHost).
|
|||
|
+
|
|||
|
+settings_table(VHost) ->
|
|||
|
+ prefix() ++ "settings" ++ suffix(VHost).
|
|||
|
+
|
|||
|
+users_table(VHost) ->
|
|||
|
+ prefix() ++ "users" ++ suffix(VHost).
|
|||
|
+servers_table(VHost) ->
|
|||
|
+ prefix() ++ "servers" ++ suffix(VHost).
|
|||
|
+resources_table(VHost) ->
|
|||
|
+ prefix() ++ "resources" ++ suffix(VHost).
|
|||
|
+
|
|||
|
+logmessage_name(VHost) ->
|
|||
|
+ prefix() ++ "logmessage" ++ suffix(VHost).
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% gen_mod callbacks
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+start(VHost, Opts) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
|
|||
|
+
|
|||
|
+stop(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% gen_server callbacks
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+init([VHost, Opts]) ->
|
|||
|
+ crypto:start(),
|
|||
|
+
|
|||
|
+ Server = gen_mod:get_opt(server, Opts, "localhost"),
|
|||
|
+ Port = gen_mod:get_opt(port, Opts, 3306),
|
|||
|
+ DB = gen_mod:get_opt(db, Opts, "logdb"),
|
|||
|
+ User = gen_mod:get_opt(user, Opts, "root"),
|
|||
|
+ Password = gen_mod:get_opt(password, Opts, ""),
|
|||
|
+
|
|||
|
+ St = #state{vhost=VHost,
|
|||
|
+ server=Server, port=Port, db=DB,
|
|||
|
+ user=User, password=Password},
|
|||
|
+
|
|||
|
+ case open_mysql_connection(St) of
|
|||
|
+ {ok, DBRef} ->
|
|||
|
+ State = St#state{dbref=DBRef},
|
|||
|
+ ok = create_internals(State),
|
|||
|
+ ok = create_stats_table(State),
|
|||
|
+ ok = create_settings_table(State),
|
|||
|
+ ok = create_users_table(State),
|
|||
|
+ ok = create_servers_table(State),
|
|||
|
+ ok = create_resources_table(State),
|
|||
|
+ erlang:monitor(process, DBRef),
|
|||
|
+ {ok, State};
|
|||
|
+ {error, Reason} ->
|
|||
|
+ ?ERROR_MSG("MySQL connection failed: ~p~n", [Reason]),
|
|||
|
+ {stop, db_connection_failed}
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+open_mysql_connection(#state{server=Server, port=Port, db=DB,
|
|||
|
+ user=DBUser, password=Password} = _State) ->
|
|||
|
+ LogFun = fun(debug, _Format, _Argument) ->
|
|||
|
+ %?MYDEBUG(Format, Argument);
|
|||
|
+ ok;
|
|||
|
+ (error, Format, Argument) ->
|
|||
|
+ ?ERROR_MSG(Format, Argument);
|
|||
|
+ (Level, Format, Argument) ->
|
|||
|
+ ?MYDEBUG("MySQL (~p)~n", [Level]),
|
|||
|
+ ?MYDEBUG(Format, Argument)
|
|||
|
+ end,
|
|||
|
+ mysql_conn:start(Server, Port, DBUser, Password, DB, [65536, 131072], LogFun).
|
|||
|
+
|
|||
|
+close_mysql_connection(DBRef) ->
|
|||
|
+ ?MYDEBUG("Closing ~p mysql connection", [DBRef]),
|
|||
|
+ mysql_conn:stop(DBRef).
|
|||
|
+
|
|||
|
+handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ Reply = rebuild_stats_at_int(DBRef, VHost, Date),
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
|
|||
|
+ {reply, error, State};
|
|||
|
+handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
|
|||
|
+ ["\"",Timestamp,"\"",","]
|
|||
|
+ end, Msgs),
|
|||
|
+
|
|||
|
+ Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
|
|||
|
+
|
|||
|
+ Query = ["DELETE FROM ",messages_table(VHost, Date)," ",
|
|||
|
+ "WHERE timestamp IN (", Temp1],
|
|||
|
+
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, Aff} ->
|
|||
|
+ ?MYDEBUG("Aff=~p", [Aff]),
|
|||
|
+ rebuild_stats_at_int(DBRef, VHost, Date);
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ ok = delete_all_messages_by_user_at_int(DBRef, User, VHost, Date),
|
|||
|
+ ok = delete_stats_by_user_at_int(DBRef, User, VHost, Date),
|
|||
|
+ {reply, ok, State};
|
|||
|
+handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ Fun = fun() ->
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Date),";"]),
|
|||
|
+ TQuery = ["DELETE FROM ",stats_table(VHost)," "
|
|||
|
+ "WHERE at=\"",Date,"\";"],
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, TQuery),
|
|||
|
+ VQuery = ["DROP VIEW IF EXISTS ",view_table(VHost,Date),";"],
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, VQuery),
|
|||
|
+ ok
|
|||
|
+ end,
|
|||
|
+ Reply =
|
|||
|
+ case catch apply(Fun, []) of
|
|||
|
+ ok ->
|
|||
|
+ ok;
|
|||
|
+ {'EXIT', _} ->
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ SName = stats_table(VHost),
|
|||
|
+ Query = ["SELECT at, sum(count) ",
|
|||
|
+ "FROM ",SName," ",
|
|||
|
+ "GROUP BY at ",
|
|||
|
+ "ORDER BY DATE(at) DESC;"
|
|||
|
+ ],
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {data, Result} ->
|
|||
|
+ {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
|
|||
|
+ {error, Reason} ->
|
|||
|
+ % TODO: Duplicate error message ?
|
|||
|
+ {error, Reason}
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ SName = stats_table(VHost),
|
|||
|
+ Query = ["SELECT username, sum(count) as allcount ",
|
|||
|
+ "FROM ",SName," ",
|
|||
|
+ "JOIN ",users_table(VHost)," ON owner_id=user_id "
|
|||
|
+ "WHERE at=\"",Date,"\" ",
|
|||
|
+ "GROUP BY username ",
|
|||
|
+ "ORDER BY allcount DESC;"
|
|||
|
+ ],
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {data, Result} ->
|
|||
|
+ {ok, [ {User, list_to_integer(Count)} || [User, Count] <- Result ]};
|
|||
|
+ {error, Reason} ->
|
|||
|
+ {error, Reason}
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ {reply, get_user_stats_int(DBRef, User, VHost), State};
|
|||
|
+handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ Query = ["SELECT peer_name,",
|
|||
|
+ "peer_server,",
|
|||
|
+ "peer_resource,",
|
|||
|
+ "direction,"
|
|||
|
+ "type,"
|
|||
|
+ "subject,"
|
|||
|
+ "body,"
|
|||
|
+ "timestamp "
|
|||
|
+ "FROM ",view_table(VHost, Date)," "
|
|||
|
+ "WHERE owner_name=\"",User,"\";"],
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {data, Result} ->
|
|||
|
+ Fun = fun([Peer_name, Peer_server, Peer_resource,
|
|||
|
+ Direction,
|
|||
|
+ Type,
|
|||
|
+ Subject, Body,
|
|||
|
+ Timestamp]) ->
|
|||
|
+ #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
|
|||
|
+ direction=list_to_atom(Direction),
|
|||
|
+ type=Type,
|
|||
|
+ subject=Subject, body=Body,
|
|||
|
+ timestamp=Timestamp}
|
|||
|
+ end,
|
|||
|
+ {ok, lists:map(Fun, Result)};
|
|||
|
+ {error, Reason} ->
|
|||
|
+ {error, Reason}
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ SName = stats_table(VHost),
|
|||
|
+ Query = ["SELECT at ",
|
|||
|
+ "FROM ",SName," ",
|
|||
|
+ "GROUP BY at ",
|
|||
|
+ "ORDER BY DATE(at) DESC;"
|
|||
|
+ ],
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {data, Result} ->
|
|||
|
+ [ Date || [Date] <- Result ];
|
|||
|
+ {error, Reason} ->
|
|||
|
+ {error, Reason}
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
|
|||
|
+ "FROM ",settings_table(VHost)," ",
|
|||
|
+ "JOIN ",users_table(VHost)," ON user_id=owner_id;"],
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {data, Result} ->
|
|||
|
+ {ok, lists:map(fun([Owner, DoLogDef, DoLogL, DoNotLogL]) ->
|
|||
|
+ #user_settings{owner_name=Owner,
|
|||
|
+ dolog_default=list_to_bool(DoLogDef),
|
|||
|
+ dolog_list=string_to_list(DoLogL),
|
|||
|
+ donotlog_list=string_to_list(DoNotLogL)
|
|||
|
+ }
|
|||
|
+ end, Result)};
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ Query = ["SELECT dolog_default,dolog_list,donotlog_list FROM ",settings_table(VHost)," ",
|
|||
|
+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {data, []} ->
|
|||
|
+ {ok, []};
|
|||
|
+ {data, [[Owner, DoLogDef, DoLogL, DoNotLogL]]} ->
|
|||
|
+ {ok, #user_settings{owner_name=Owner,
|
|||
|
+ dolog_default=list_to_bool(DoLogDef),
|
|||
|
+ dolog_list=string_to_list(DoLogL),
|
|||
|
+ donotlog_list=string_to_list(DoNotLogL)}};
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
|
|||
|
+ dolog_list=DoLogL,
|
|||
|
+ donotlog_list=DoNotLogL}},
|
|||
|
+ _From, #state{dbref=DBRef, vhost=VHost} = State) ->
|
|||
|
+ User_id = get_user_id(DBRef, VHost, User),
|
|||
|
+ Query = ["UPDATE ",settings_table(VHost)," ",
|
|||
|
+ "SET dolog_default=",bool_to_list(DoLogDef),", ",
|
|||
|
+ "dolog_list='",list_to_string(DoLogL),"', ",
|
|||
|
+ "donotlog_list='",list_to_string(DoNotLogL),"' ",
|
|||
|
+ "WHERE owner_id=",User_id,";"],
|
|||
|
+
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, 0} ->
|
|||
|
+ IQuery = ["INSERT INTO ",settings_table(VHost)," ",
|
|||
|
+ "(owner_id, dolog_default, dolog_list, donotlog_list) ",
|
|||
|
+ "VALUES ",
|
|||
|
+ "(",User_id,",",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
|
|||
|
+ case sql_query_internal_silent(DBRef, IQuery) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
|
|||
|
+ ok;
|
|||
|
+ {error, Reason} ->
|
|||
|
+ case regexp:match(Reason, "#23000") of
|
|||
|
+ % Already exists
|
|||
|
+ {match, _, _} ->
|
|||
|
+ ok;
|
|||
|
+ _ ->
|
|||
|
+ ?ERROR_MSG("Failed setup user ~p@~p: ~p", [User, VHost, Reason]),
|
|||
|
+ error
|
|||
|
+ end
|
|||
|
+ end;
|
|||
|
+ {updated, 1} ->
|
|||
|
+ ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({stop}, _From, #state{vhost=VHost}=State) ->
|
|||
|
+ ?MYDEBUG("Stoping mysql5 backend for ~p", [VHost]),
|
|||
|
+ {stop, normal, ok, State};
|
|||
|
+handle_call(Msg, _From, State) ->
|
|||
|
+ ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
|
|||
|
+ {noreply, State}.
|
|||
|
+
|
|||
|
+handle_cast({log_message, Msg}, #state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ Fun = fun() ->
|
|||
|
+ Date = convert_timestamp_brief(Msg#msg.timestamp),
|
|||
|
+ TableName = messages_table(VHost, Date),
|
|||
|
+
|
|||
|
+ Query = [ "CALL ",logmessage_name(VHost)," "
|
|||
|
+ "('", TableName, "',",
|
|||
|
+ "'", Date, "',",
|
|||
|
+ "'", Msg#msg.owner_name, "',",
|
|||
|
+ "'", Msg#msg.peer_name, "',",
|
|||
|
+ "'", Msg#msg.peer_server, "',",
|
|||
|
+ "'", ejabberd_odbc:escape(Msg#msg.peer_resource), "',",
|
|||
|
+ "'", atom_to_list(Msg#msg.direction), "',",
|
|||
|
+ "'", Msg#msg.type, "',",
|
|||
|
+ "'", ejabberd_odbc:escape(Msg#msg.subject), "',",
|
|||
|
+ "'", ejabberd_odbc:escape(Msg#msg.body), "',",
|
|||
|
+ "'", Msg#msg.timestamp, "');"],
|
|||
|
+
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
|
|||
|
+ Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
|
|||
|
+ ok;
|
|||
|
+ {error, _Reason} ->
|
|||
|
+ error
|
|||
|
+ end
|
|||
|
+ end,
|
|||
|
+ spawn(Fun),
|
|||
|
+ {noreply, State};
|
|||
|
+handle_cast({rebuild_stats}, State) ->
|
|||
|
+ rebuild_all_stats_int(State),
|
|||
|
+ {noreply, State};
|
|||
|
+handle_cast({drop_user, User}, #state{vhost=VHost} = State) ->
|
|||
|
+ Fun = fun() ->
|
|||
|
+ {ok, DBRef} = open_mysql_connection(State),
|
|||
|
+ {ok, Dates} = get_user_stats_int(DBRef, User, VHost),
|
|||
|
+ MDResult = lists:map(fun({Date, _}) ->
|
|||
|
+ delete_all_messages_by_user_at_int(DBRef, User, VHost, Date)
|
|||
|
+ end, Dates),
|
|||
|
+ StDResult = delete_all_stats_by_user_int(DBRef, User, VHost),
|
|||
|
+ SDResult = delete_user_settings_int(DBRef, User, VHost),
|
|||
|
+ case lists:all(fun(Result) when Result == ok ->
|
|||
|
+ true;
|
|||
|
+ (Result) when Result == error ->
|
|||
|
+ false
|
|||
|
+ end, lists:append([MDResult, [StDResult], [SDResult]])) of
|
|||
|
+ true ->
|
|||
|
+ ?INFO_MSG("Removed ~s@~s", [User, VHost]);
|
|||
|
+ false ->
|
|||
|
+ ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost])
|
|||
|
+ end,
|
|||
|
+ close_mysql_connection(DBRef)
|
|||
|
+ end,
|
|||
|
+ spawn(Fun),
|
|||
|
+ {noreply, State};
|
|||
|
+handle_cast(Msg, State) ->
|
|||
|
+ ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
|
|||
|
+ {noreply, State}.
|
|||
|
+
|
|||
|
+handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
|
|||
|
+ {stop, connection_dropped, State};
|
|||
|
+handle_info(Info, State) ->
|
|||
|
+ ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
|
|||
|
+ {noreply, State}.
|
|||
|
+
|
|||
|
+terminate(_Reason, #state{dbref=DBRef}=_State) ->
|
|||
|
+ close_mysql_connection(DBRef),
|
|||
|
+ ok.
|
|||
|
+
|
|||
|
+code_change(_OldVsn, State, _Extra) ->
|
|||
|
+ {ok, State}.
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% gen_logdb callbacks
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+log_message(VHost, Msg) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:cast(Proc, {log_message, Msg}).
|
|||
|
+rebuild_stats(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:cast(Proc, {rebuild_stats}).
|
|||
|
+rebuild_stats_at(VHost, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
|
|||
|
+delete_messages_by_user_at(VHost, Msgs, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
|
|||
|
+delete_all_messages_by_user_at(User, VHost, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
|
|||
|
+delete_messages_at(VHost, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
|
|||
|
+get_vhost_stats(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
|
|||
|
+get_vhost_stats_at(VHost, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
|
|||
|
+get_user_stats(User, VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
|
|||
|
+get_user_messages_at(User, VHost, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
|
|||
|
+get_dates(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
|
|||
|
+get_users_settings(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
|
|||
|
+get_user_settings(User, VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
|
|||
|
+set_user_settings(User, VHost, Set) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
|
|||
|
+drop_user(User, VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:cast(Proc, {drop_user, User}).
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% internals
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+get_dates_int(DBRef, VHost) ->
|
|||
|
+ case sql_query_internal(DBRef, ["SHOW TABLES"]) of
|
|||
|
+ {data, Tables} ->
|
|||
|
+ lists:foldl(fun([Table], Dates) ->
|
|||
|
+ Reg = lists:sublist(prefix(),2,length(prefix())) ++ ".*" ++ escape_vhost(VHost),
|
|||
|
+ case regexp:match(Table, Reg) of
|
|||
|
+ {match, 1, _} ->
|
|||
|
+ case regexp:match(Table,"[0-9]+-[0-9]+-[0-9]+") of
|
|||
|
+ {match, S, E} ->
|
|||
|
+ lists:append(Dates, [lists:sublist(Table,S,E)]);
|
|||
|
+ nomatch ->
|
|||
|
+ Dates
|
|||
|
+ end;
|
|||
|
+ _ ->
|
|||
|
+ Dates
|
|||
|
+ end
|
|||
|
+ end, [], Tables);
|
|||
|
+ {error, _} ->
|
|||
|
+ []
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+rebuild_all_stats_int(#state{vhost=VHost}=State) ->
|
|||
|
+ Fun = fun() ->
|
|||
|
+ {ok, DBRef} = open_mysql_connection(State),
|
|||
|
+ ok = delete_nonexistent_stats(DBRef, VHost),
|
|||
|
+ case lists:filter(fun(Date) ->
|
|||
|
+ case catch rebuild_stats_at_int(DBRef, VHost, Date) of
|
|||
|
+ ok -> false;
|
|||
|
+ error -> true;
|
|||
|
+ {'EXIT', _} -> true
|
|||
|
+ end
|
|||
|
+ end, get_dates_int(DBRef, VHost)) of
|
|||
|
+ [] -> ok;
|
|||
|
+ FTables ->
|
|||
|
+ ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]),
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ close_mysql_connection(DBRef)
|
|||
|
+ end,
|
|||
|
+ spawn(Fun).
|
|||
|
+
|
|||
|
+rebuild_stats_at_int(DBRef, VHost, Date) ->
|
|||
|
+ TempTable = temp_table(VHost),
|
|||
|
+ Fun = fun() ->
|
|||
|
+ Table = messages_table(VHost, Date),
|
|||
|
+ STable = stats_table(VHost),
|
|||
|
+
|
|||
|
+ DQuery = [ "DELETE FROM ",STable," ",
|
|||
|
+ "WHERE at='",Date,"';"],
|
|||
|
+
|
|||
|
+ ok = create_temp_table(DBRef, TempTable),
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," WRITE, ",TempTable," WRITE;"]),
|
|||
|
+ SQuery = ["INSERT INTO ",TempTable," ",
|
|||
|
+ "(owner_id,peer_name_id,peer_server_id,at,count) ",
|
|||
|
+ "SELECT owner_id,peer_name_id,peer_server_id,\"",Date,"\",count(*) ",
|
|||
|
+ "FROM ",Table," WHERE ext is NULL GROUP BY owner_id,peer_name_id,peer_server_id;"],
|
|||
|
+ case sql_query_internal(DBRef, SQuery) of
|
|||
|
+ {updated, 0} ->
|
|||
|
+ Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]),
|
|||
|
+ case Count of
|
|||
|
+ {data, [["0"]]} ->
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["DROP VIEW IF EXISTS ",view_table(VHost,Date),";"]),
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table,";"]),
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE;"]),
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, DQuery),
|
|||
|
+ ok;
|
|||
|
+ _ ->
|
|||
|
+ ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]),
|
|||
|
+ error
|
|||
|
+ end;
|
|||
|
+ {updated, _} ->
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," WRITE, ",TempTable," WRITE;"]),
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, DQuery),
|
|||
|
+ SQuery1 = ["INSERT INTO ",STable," ",
|
|||
|
+ "(owner_id,peer_name_id,peer_server_id,at,count) ",
|
|||
|
+ "SELECT owner_id,peer_name_id,peer_server_id,at,count ",
|
|||
|
+ "FROM ",TempTable,";"],
|
|||
|
+ case sql_query_internal(DBRef, SQuery1) of
|
|||
|
+ {updated, _} -> ok;
|
|||
|
+ {error, _} -> error
|
|||
|
+ end;
|
|||
|
+ {error, _} -> error
|
|||
|
+ end
|
|||
|
+ end,
|
|||
|
+
|
|||
|
+ case catch apply(Fun, []) of
|
|||
|
+ ok ->
|
|||
|
+ ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
|
|||
|
+ ok;
|
|||
|
+ error ->
|
|||
|
+ error;
|
|||
|
+ {'EXIT', Reason} ->
|
|||
|
+ ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]),
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ sql_query_internal(DBRef, ["UNLOCK TABLES;"]),
|
|||
|
+ sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]),
|
|||
|
+ ok.
|
|||
|
+
|
|||
|
+delete_nonexistent_stats(DBRef, VHost) ->
|
|||
|
+ Dates = get_dates_int(DBRef, VHost),
|
|||
|
+ STable = stats_table(VHost),
|
|||
|
+
|
|||
|
+ Temp = lists:flatmap(fun(Date) ->
|
|||
|
+ ["\"",Date,"\"",","]
|
|||
|
+ end, Dates),
|
|||
|
+ case Temp of
|
|||
|
+ [] ->
|
|||
|
+ ok;
|
|||
|
+ _ ->
|
|||
|
+ % replace last "," with ");"
|
|||
|
+ Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
|
|||
|
+ Query = ["DELETE FROM ",STable," ",
|
|||
|
+ "WHERE at NOT IN (", Temp1],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+get_user_stats_int(DBRef, User, VHost) ->
|
|||
|
+ SName = stats_table(VHost),
|
|||
|
+ UName = users_table(VHost),
|
|||
|
+ Query = ["SELECT stats.at, sum(stats.count) ",
|
|||
|
+ "FROM ",UName," AS users ",
|
|||
|
+ "JOIN ",SName," AS stats ON owner_id=user_id "
|
|||
|
+ "WHERE users.username=\"",User,"\" ",
|
|||
|
+ "GROUP BY stats.at "
|
|||
|
+ "ORDER BY DATE(stats.at) DESC;"
|
|||
|
+ ],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {data, Result} ->
|
|||
|
+ {ok, [ {Date, list_to_integer(Count)} || [Date, Count] <- Result ]};
|
|||
|
+ {error, Result} ->
|
|||
|
+ {error, Result}
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+delete_all_messages_by_user_at_int(DBRef, User, VHost, Date) ->
|
|||
|
+ DQuery = ["DELETE FROM ",messages_table(VHost, Date)," ",
|
|||
|
+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
|
|||
|
+ case sql_query_internal(DBRef, DQuery) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+delete_all_stats_by_user_int(DBRef, User, VHost) ->
|
|||
|
+ SQuery = ["DELETE FROM ",stats_table(VHost)," ",
|
|||
|
+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
|
|||
|
+ case sql_query_internal(DBRef, SQuery) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} -> error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+delete_stats_by_user_at_int(DBRef, User, VHost, Date) ->
|
|||
|
+ SQuery = ["DELETE FROM ",stats_table(VHost)," ",
|
|||
|
+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\") ",
|
|||
|
+ "AND at=\"",Date,"\";"],
|
|||
|
+ case sql_query_internal(DBRef, SQuery) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} -> error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+delete_user_settings_int(DBRef, User, VHost) ->
|
|||
|
+ Query = ["DELETE FROM ",settings_table(VHost)," ",
|
|||
|
+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost)," WHERE username=\"",User,"\");"],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]),
|
|||
|
+ ok;
|
|||
|
+ {error, Reason} ->
|
|||
|
+ ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]),
|
|||
|
+ error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% tables internals
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+create_temp_table(DBRef, Name) ->
|
|||
|
+ Query = ["CREATE TABLE ",Name," (",
|
|||
|
+ "owner_id MEDIUMINT UNSIGNED, ",
|
|||
|
+ "peer_name_id MEDIUMINT UNSIGNED, ",
|
|||
|
+ "peer_server_id MEDIUMINT UNSIGNED, ",
|
|||
|
+ "at VARCHAR(11), ",
|
|||
|
+ "count INT(11) ",
|
|||
|
+ ") ENGINE=MyISAM CHARACTER SET utf8;"
|
|||
|
+ ],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, _} -> ok;
|
|||
|
+ {error, _Reason} -> error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+create_stats_table(#state{dbref=DBRef, vhost=VHost}=State) ->
|
|||
|
+ SName = stats_table(VHost),
|
|||
|
+ Query = ["CREATE TABLE ",SName," (",
|
|||
|
+ "owner_id MEDIUMINT UNSIGNED, ",
|
|||
|
+ "peer_name_id MEDIUMINT UNSIGNED, ",
|
|||
|
+ "peer_server_id MEDIUMINT UNSIGNED, ",
|
|||
|
+ "at VARCHAR(11), ",
|
|||
|
+ "count INT(11), ",
|
|||
|
+ "ext INTEGER DEFAULT NULL, "
|
|||
|
+ "INDEX ext_i (ext), "
|
|||
|
+ "INDEX(owner_id,peer_name_id,peer_server_id), ",
|
|||
|
+ "INDEX(at) ",
|
|||
|
+ ") ENGINE=MyISAM CHARACTER SET utf8;"
|
|||
|
+ ],
|
|||
|
+ case sql_query_internal_silent(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?MYDEBUG("Created stats table for ~p", [VHost]),
|
|||
|
+ rebuild_all_stats_int(State),
|
|||
|
+ ok;
|
|||
|
+ {error, Reason} ->
|
|||
|
+ case regexp:match(Reason, "#42S01") of
|
|||
|
+ {match, _, _} ->
|
|||
|
+ ?MYDEBUG("Stats table for ~p already exists", [VHost]),
|
|||
|
+ CheckQuery = ["SHOW COLUMNS FROM ",SName," LIKE 'peer_%_id';"],
|
|||
|
+ case sql_query_internal(DBRef, CheckQuery) of
|
|||
|
+ {data, Elems} when length(Elems) == 2 ->
|
|||
|
+ ?MYDEBUG("Stats table structure is ok", []),
|
|||
|
+ ok;
|
|||
|
+ _ ->
|
|||
|
+ ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []),
|
|||
|
+ case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?INFO_MSG("Successfully dropped ~p", [SName]);
|
|||
|
+ _ ->
|
|||
|
+ ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName])
|
|||
|
+ end,
|
|||
|
+ error
|
|||
|
+ end;
|
|||
|
+ _ ->
|
|||
|
+ ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
|
|||
|
+ error
|
|||
|
+ end
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+create_settings_table(#state{dbref=DBRef, vhost=VHost}) ->
|
|||
|
+ SName = settings_table(VHost),
|
|||
|
+ Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
|
|||
|
+ "owner_id MEDIUMINT UNSIGNED PRIMARY KEY, ",
|
|||
|
+ "dolog_default TINYINT(1) NOT NULL DEFAULT 1, ",
|
|||
|
+ "dolog_list TEXT, ",
|
|||
|
+ "donotlog_list TEXT ",
|
|||
|
+ ") ENGINE=InnoDB CHARACTER SET utf8;"
|
|||
|
+ ],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?MYDEBUG("Created settings table for ~p", [VHost]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+create_users_table(#state{dbref=DBRef, vhost=VHost}) ->
|
|||
|
+ SName = users_table(VHost),
|
|||
|
+ Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
|
|||
|
+ "username TEXT NOT NULL, ",
|
|||
|
+ "user_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
|
|||
|
+ "UNIQUE INDEX(username(",?INDEX_SIZE,")) ",
|
|||
|
+ ") ENGINE=InnoDB CHARACTER SET utf8;"
|
|||
|
+ ],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?MYDEBUG("Created users table for ~p", [VHost]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+create_servers_table(#state{dbref=DBRef, vhost=VHost}) ->
|
|||
|
+ SName = servers_table(VHost),
|
|||
|
+ Query = ["CREATE TABLE IF NOT EXISTS ",SName," (",
|
|||
|
+ "server TEXT NOT NULL, ",
|
|||
|
+ "server_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
|
|||
|
+ "UNIQUE INDEX(server(",?INDEX_SIZE,")) ",
|
|||
|
+ ") ENGINE=InnoDB CHARACTER SET utf8;"
|
|||
|
+ ],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?MYDEBUG("Created servers table for ~p", [VHost]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+create_resources_table(#state{dbref=DBRef, vhost=VHost}) ->
|
|||
|
+ RName = resources_table(VHost),
|
|||
|
+ Query = ["CREATE TABLE IF NOT EXISTS ",RName," (",
|
|||
|
+ "resource TEXT NOT NULL, ",
|
|||
|
+ "resource_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, ",
|
|||
|
+ "UNIQUE INDEX(resource(",?INDEX_SIZE,")) ",
|
|||
|
+ ") ENGINE=InnoDB CHARACTER SET utf8;"
|
|||
|
+ ],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?MYDEBUG("Created resources table for ~p", [VHost]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+create_internals(#state{dbref=DBRef, vhost=VHost}) ->
|
|||
|
+ sql_query_internal(DBRef, ["DROP PROCEDURE IF EXISTS ",logmessage_name(VHost),";"]),
|
|||
|
+ case sql_query_internal(DBRef, [get_logmessage(VHost)]) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?MYDEBUG("Created logmessage for ~p", [VHost]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% SQL internals
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+sql_query_internal(DBRef, Query) ->
|
|||
|
+ case sql_query_internal_silent(DBRef, Query) of
|
|||
|
+ {error, Reason} ->
|
|||
|
+ ?ERROR_MSG("~p while ~p", [Reason, lists:append(Query)]),
|
|||
|
+ {error, Reason};
|
|||
|
+ Rez -> Rez
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+sql_query_internal_silent(DBRef, Query) ->
|
|||
|
+ ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
|
|||
|
+ get_result(mysql_conn:fetch(DBRef, Query, self(), ?MYSQL_TIMEOUT)).
|
|||
|
+
|
|||
|
+get_result({updated, MySQLRes}) ->
|
|||
|
+ {updated, mysql:get_result_affected_rows(MySQLRes)};
|
|||
|
+get_result({data, MySQLRes}) ->
|
|||
|
+ {data, mysql:get_result_rows(MySQLRes)};
|
|||
|
+get_result({error, "query timed out"}) ->
|
|||
|
+ {error, "query timed out"};
|
|||
|
+get_result({error, MySQLRes}) ->
|
|||
|
+ Reason = mysql:get_result_reason(MySQLRes),
|
|||
|
+ {error, Reason}.
|
|||
|
+
|
|||
|
+get_user_id(DBRef, VHost, User) ->
|
|||
|
+ SQuery = ["SELECT user_id FROM ",users_table(VHost)," ",
|
|||
|
+ "WHERE username=\"",User,"\";"],
|
|||
|
+ case sql_query_internal(DBRef, SQuery) of
|
|||
|
+ {data, []} ->
|
|||
|
+ IQuery = ["INSERT INTO ",users_table(VHost)," ",
|
|||
|
+ "SET username=\"",User,"\";"],
|
|||
|
+ case sql_query_internal_silent(DBRef, IQuery) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ {data, [[DBIdNew]]} = sql_query_internal(DBRef, SQuery),
|
|||
|
+ DBIdNew;
|
|||
|
+ {error, Reason} ->
|
|||
|
+ % this can be in clustered environment
|
|||
|
+ {match, _, _} = regexp:match(Reason, "#23000"),
|
|||
|
+ ?ERROR_MSG("Duplicate key name for ~p", [User]),
|
|||
|
+ {data, [[ClID]]} = sql_query_internal(DBRef, SQuery),
|
|||
|
+ ClID
|
|||
|
+ end;
|
|||
|
+ {data, [[DBId]]} ->
|
|||
|
+ DBId
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+get_logmessage(VHost) ->
|
|||
|
+ UName = users_table(VHost),
|
|||
|
+ SName = servers_table(VHost),
|
|||
|
+ RName = resources_table(VHost),
|
|||
|
+ StName = stats_table(VHost),
|
|||
|
+ io_lib:format("
|
|||
|
+CREATE PROCEDURE ~s(tablename TEXT, atdate TEXT, owner TEXT, peer_name TEXT, peer_server TEXT, peer_resource TEXT, mdirection VARCHAR(4), mtype VARCHAR(10), msubject TEXT, mbody TEXT, mtimestamp DOUBLE)
|
|||
|
+BEGIN
|
|||
|
+ DECLARE ownerID MEDIUMINT UNSIGNED;
|
|||
|
+ DECLARE peer_nameID MEDIUMINT UNSIGNED;
|
|||
|
+ DECLARE peer_serverID MEDIUMINT UNSIGNED;
|
|||
|
+ DECLARE peer_resourceID MEDIUMINT UNSIGNED;
|
|||
|
+ DECLARE Vmtype VARCHAR(10);
|
|||
|
+ DECLARE Vmtimestamp DOUBLE;
|
|||
|
+ DECLARE Vmdirection VARCHAR(4);
|
|||
|
+ DECLARE Vmbody TEXT;
|
|||
|
+ DECLARE Vmsubject TEXT;
|
|||
|
+ DECLARE iq TEXT;
|
|||
|
+ DECLARE cq TEXT;
|
|||
|
+ DECLARE viewname TEXT;
|
|||
|
+ DECLARE notable INT;
|
|||
|
+ DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' SET @notable = 1;
|
|||
|
+
|
|||
|
+ SET @notable = 0;
|
|||
|
+ SET @ownerID = NULL;
|
|||
|
+ SET @peer_nameID = NULL;
|
|||
|
+ SET @peer_serverID = NULL;
|
|||
|
+ SET @peer_resourceID = NULL;
|
|||
|
+
|
|||
|
+ SET @Vmtype = mtype;
|
|||
|
+ SET @Vmtimestamp = mtimestamp;
|
|||
|
+ SET @Vmdirection = mdirection;
|
|||
|
+ SET @Vmbody = mbody;
|
|||
|
+ SET @Vmsubject = msubject;
|
|||
|
+
|
|||
|
+ SELECT user_id INTO @ownerID FROM ~s WHERE username=owner;
|
|||
|
+ IF @ownerID IS NULL THEN
|
|||
|
+ INSERT INTO ~s SET username=owner;
|
|||
|
+ SET @ownerID = LAST_INSERT_ID();
|
|||
|
+ END IF;
|
|||
|
+
|
|||
|
+ SELECT user_id INTO @peer_nameID FROM ~s WHERE username=peer_name;
|
|||
|
+ IF @peer_nameID IS NULL THEN
|
|||
|
+ INSERT INTO ~s SET username=peer_name;
|
|||
|
+ SET @peer_nameID = LAST_INSERT_ID();
|
|||
|
+ END IF;
|
|||
|
+
|
|||
|
+ SELECT server_id INTO @peer_serverID FROM ~s WHERE server=peer_server;
|
|||
|
+ IF @peer_serverID IS NULL THEN
|
|||
|
+ INSERT INTO ~s SET server=peer_server;
|
|||
|
+ SET @peer_serverID = LAST_INSERT_ID();
|
|||
|
+ END IF;
|
|||
|
+
|
|||
|
+ SELECT resource_id INTO @peer_resourceID FROM ~s WHERE resource=peer_resource;
|
|||
|
+ IF @peer_resourceID IS NULL THEN
|
|||
|
+ INSERT INTO ~s SET resource=peer_resource;
|
|||
|
+ SET @peer_resourceID = LAST_INSERT_ID();
|
|||
|
+ END IF;
|
|||
|
+
|
|||
|
+ 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);\");
|
|||
|
+ PREPARE insertmsg FROM @iq;
|
|||
|
+
|
|||
|
+ IF @notable = 1 THEN
|
|||
|
+ SET @cq = CONCAT(\"CREATE TABLE \",tablename,\" (
|
|||
|
+ owner_id MEDIUMINT UNSIGNED NOT NULL,
|
|||
|
+ peer_name_id MEDIUMINT UNSIGNED NOT NULL,
|
|||
|
+ peer_server_id MEDIUMINT UNSIGNED NOT NULL,
|
|||
|
+ peer_resource_id MEDIUMINT(8) UNSIGNED NOT NULL,
|
|||
|
+ direction ENUM('to', 'from') NOT NULL,
|
|||
|
+ type ENUM('chat','error','groupchat','headline','normal') NOT NULL,
|
|||
|
+ subject TEXT,
|
|||
|
+ body TEXT,
|
|||
|
+ timestamp DOUBLE NOT NULL,
|
|||
|
+ ext INTEGER DEFAULT NULL,
|
|||
|
+ INDEX search_i (owner_id, peer_name_id, peer_server_id, peer_resource_id),
|
|||
|
+ INDEX ext_i (ext),
|
|||
|
+ FULLTEXT (body)
|
|||
|
+ ) ENGINE=MyISAM
|
|||
|
+ PACK_KEYS=1
|
|||
|
+ CHARACTER SET utf8;\");
|
|||
|
+ PREPARE createtable FROM @cq;
|
|||
|
+ EXECUTE createtable;
|
|||
|
+ DEALLOCATE PREPARE createtable;
|
|||
|
+
|
|||
|
+ SET @viewname = CONCAT(\"`v_\", TRIM(BOTH '`' FROM tablename), \"`\");
|
|||
|
+ SET @cq = CONCAT(\"CREATE OR REPLACE VIEW \",@viewname,\" AS
|
|||
|
+ SELECT owner.username AS owner_name,
|
|||
|
+ peer.username AS peer_name,
|
|||
|
+ servers.server AS peer_server,
|
|||
|
+ resources.resource AS peer_resource,
|
|||
|
+ messages.direction,
|
|||
|
+ messages.type,
|
|||
|
+ messages.subject,
|
|||
|
+ messages.body,
|
|||
|
+ messages.timestamp
|
|||
|
+ FROM
|
|||
|
+ ~s owner,
|
|||
|
+ ~s peer,
|
|||
|
+ ~s servers,
|
|||
|
+ ~s resources,
|
|||
|
+ \", tablename,\" messages
|
|||
|
+ WHERE
|
|||
|
+ owner.user_id=messages.owner_id and
|
|||
|
+ peer.user_id=messages.peer_name_id and
|
|||
|
+ servers.server_id=messages.peer_server_id and
|
|||
|
+ resources.resource_id=messages.peer_resource_id
|
|||
|
+ ORDER BY messages.timestamp;\");
|
|||
|
+ PREPARE createview FROM @cq;
|
|||
|
+ EXECUTE createview;
|
|||
|
+ DEALLOCATE PREPARE createview;
|
|||
|
+
|
|||
|
+ SET @notable = 0;
|
|||
|
+ PREPARE insertmsg FROM @iq;
|
|||
|
+ EXECUTE insertmsg;
|
|||
|
+ ELSEIF @notable = 0 THEN
|
|||
|
+ EXECUTE insertmsg;
|
|||
|
+ END IF;
|
|||
|
+
|
|||
|
+ DEALLOCATE PREPARE insertmsg;
|
|||
|
+
|
|||
|
+ IF @notable = 0 THEN
|
|||
|
+ 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;
|
|||
|
+ IF ROW_COUNT() = 0 THEN
|
|||
|
+ INSERT INTO ~s (owner_id, peer_name_id, peer_server_id, at, count) VALUES (@ownerID, @peer_nameID, @peer_serverID, atdate, 1);
|
|||
|
+ END IF;
|
|||
|
+ END IF;
|
|||
|
+END;", [logmessage_name(VHost),UName,UName,UName,UName,SName,SName,RName,RName,UName,UName,SName,RName,StName,StName]).
|
|||
|
--- mod_logdb_pgsql.erl.orig 2009-02-05 19:21:29.000000000 +0200
|
|||
|
+++ mod_logdb_pgsql.erl 2009-02-05 19:20:29.000000000 +0200
|
|||
|
@@ -0,0 +1,1078 @@
|
|||
|
+%%%----------------------------------------------------------------------
|
|||
|
+%%% File : mod_logdb_pgsql.erl
|
|||
|
+%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
|
|||
|
+%%% Purpose : Posgresql backend for mod_logdb
|
|||
|
+%%% Version : trunk
|
|||
|
+%%% Id : $Id: mod_logdb_pgsql.erl 1253 2009-02-02 11:10:03Z malik $
|
|||
|
+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
|
|||
|
+%%%----------------------------------------------------------------------
|
|||
|
+
|
|||
|
+-module(mod_logdb_pgsql).
|
|||
|
+-author('o.palij@gmail.com').
|
|||
|
+
|
|||
|
+-include("mod_logdb.hrl").
|
|||
|
+-include("ejabberd.hrl").
|
|||
|
+-include("jlib.hrl").
|
|||
|
+
|
|||
|
+-behaviour(gen_logdb).
|
|||
|
+-behaviour(gen_server).
|
|||
|
+
|
|||
|
+% gen_server
|
|||
|
+-export([code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
|
|||
|
+% gen_mod
|
|||
|
+-export([start/2, stop/1]).
|
|||
|
+% gen_logdb
|
|||
|
+-export([log_message/2,
|
|||
|
+ rebuild_stats/1,
|
|||
|
+ rebuild_stats_at/2,
|
|||
|
+ delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
|
|||
|
+ get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
|
|||
|
+ get_dates/1,
|
|||
|
+ get_users_settings/1, get_user_settings/2, set_user_settings/3,
|
|||
|
+ drop_user/2]).
|
|||
|
+
|
|||
|
+-export([view_table/3]).
|
|||
|
+
|
|||
|
+% gen_server call timeout
|
|||
|
+-define(CALL_TIMEOUT, 30000).
|
|||
|
+-define(PGSQL_TIMEOUT, 60000).
|
|||
|
+-define(PROCNAME, mod_logdb_pgsql).
|
|||
|
+
|
|||
|
+-import(mod_logdb, [list_to_bool/1, bool_to_list/1,
|
|||
|
+ list_to_string/1, string_to_list/1,
|
|||
|
+ convert_timestamp_brief/1]).
|
|||
|
+
|
|||
|
+-record(state, {dbref, vhost, server, port, db, user, password, schema}).
|
|||
|
+
|
|||
|
+% replace "." with "_"
|
|||
|
+escape_vhost(VHost) -> lists:map(fun(46) -> 95;
|
|||
|
+ (A) -> A
|
|||
|
+ end, VHost).
|
|||
|
+
|
|||
|
+prefix(Schema) ->
|
|||
|
+ Schema ++ ".\"" ++ "logdb_".
|
|||
|
+
|
|||
|
+suffix(VHost) ->
|
|||
|
+ "_" ++ escape_vhost(VHost) ++ "\"".
|
|||
|
+
|
|||
|
+messages_table(VHost, Schema, Date) ->
|
|||
|
+ prefix(Schema) ++ "messages_" ++ Date ++ suffix(VHost).
|
|||
|
+
|
|||
|
+view_table(VHost, Schema, Date) ->
|
|||
|
+ Table = messages_table(VHost, Schema, Date),
|
|||
|
+ TablewoS = lists:sublist(Table, length(Schema) + 3, length(Table) - length(Schema) - 3),
|
|||
|
+ lists:append([Schema, ".\"v_", TablewoS, "\""]).
|
|||
|
+
|
|||
|
+stats_table(VHost, Schema) ->
|
|||
|
+ prefix(Schema) ++ "stats" ++ suffix(VHost).
|
|||
|
+
|
|||
|
+temp_table(VHost, Schema) ->
|
|||
|
+ prefix(Schema) ++ "temp" ++ suffix(VHost).
|
|||
|
+
|
|||
|
+settings_table(VHost, Schema) ->
|
|||
|
+ prefix(Schema) ++ "settings" ++ suffix(VHost).
|
|||
|
+
|
|||
|
+users_table(VHost, Schema) ->
|
|||
|
+ prefix(Schema) ++ "users" ++ suffix(VHost).
|
|||
|
+servers_table(VHost, Schema) ->
|
|||
|
+ prefix(Schema) ++ "servers" ++ suffix(VHost).
|
|||
|
+resources_table(VHost, Schema) ->
|
|||
|
+ prefix(Schema) ++ "resources" ++ suffix(VHost).
|
|||
|
+
|
|||
|
+logmessage_name(VHost, Schema) ->
|
|||
|
+ prefix(Schema) ++ "logmessage" ++ suffix(VHost).
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% gen_mod callbacks
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+start(VHost, Opts) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:start({local, Proc}, ?MODULE, [VHost, Opts], []).
|
|||
|
+
|
|||
|
+stop(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {stop}, ?CALL_TIMEOUT).
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% gen_server callbacks
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+init([VHost, Opts]) ->
|
|||
|
+ Server = gen_mod:get_opt(server, Opts, "localhost"),
|
|||
|
+ DB = gen_mod:get_opt(db, Opts, "ejabberd_logdb"),
|
|||
|
+ User = gen_mod:get_opt(user, Opts, "root"),
|
|||
|
+ Port = gen_mod:get_opt(port, Opts, 5432),
|
|||
|
+ Password = gen_mod:get_opt(password, Opts, ""),
|
|||
|
+ Schema = gen_mod:get_opt(schema, Opts, "public"),
|
|||
|
+
|
|||
|
+ ?MYDEBUG("Starting pgsql backend for ~p", [VHost]),
|
|||
|
+
|
|||
|
+ St = #state{vhost=VHost,
|
|||
|
+ server=Server, port=Port, db=DB,
|
|||
|
+ user=User, password=Password,
|
|||
|
+ schema=Schema},
|
|||
|
+
|
|||
|
+ case open_pgsql_connection(St) of
|
|||
|
+ {ok, DBRef} ->
|
|||
|
+ State = St#state{dbref=DBRef},
|
|||
|
+ ok = create_internals(State),
|
|||
|
+ ok = create_stats_table(State),
|
|||
|
+ ok = create_settings_table(State),
|
|||
|
+ ok = create_users_table(State),
|
|||
|
+ ok = create_servers_table(State),
|
|||
|
+ ok = create_resources_table(State),
|
|||
|
+ erlang:monitor(process, DBRef),
|
|||
|
+ {ok, State};
|
|||
|
+ % this does not work
|
|||
|
+ {error, Reason} ->
|
|||
|
+ ?ERROR_MSG("PgSQL connection failed: ~p~n", [Reason]),
|
|||
|
+ {stop, db_connection_failed};
|
|||
|
+ % and this too, becouse pgsql_conn do exit() which can not be catched
|
|||
|
+ {'EXIT', Rez} ->
|
|||
|
+ ?ERROR_MSG("Rez: ~p~n", [Rez]),
|
|||
|
+ {stop, db_connection_failed}
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+open_pgsql_connection(#state{server=Server, port=Port, db=DB, schema=Schema,
|
|||
|
+ user=User, password=Password} = _State) ->
|
|||
|
+ {ok, DBRef} = pgsql:connect(Server, DB, User, Password, Port),
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["SET SEARCH_PATH TO ",Schema,";"]),
|
|||
|
+ {ok, DBRef}.
|
|||
|
+
|
|||
|
+close_pgsql_connection(DBRef) ->
|
|||
|
+ ?MYDEBUG("Closing ~p pgsql connection", [DBRef]),
|
|||
|
+ pgsql:terminate(DBRef).
|
|||
|
+
|
|||
|
+handle_call({log_message, Msg}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
|
|||
|
+ Date = convert_timestamp_brief(Msg#msg.timestamp),
|
|||
|
+ TableName = messages_table(VHost, Schema, Date),
|
|||
|
+ ViewName = view_table(VHost, Schema, Date),
|
|||
|
+
|
|||
|
+ Query = [ "SELECT ", logmessage_name(VHost, Schema)," "
|
|||
|
+ "('", TableName, "',",
|
|||
|
+ "'", ViewName, "',",
|
|||
|
+ "'", Date, "',",
|
|||
|
+ "'", Msg#msg.owner_name, "',",
|
|||
|
+ "'", Msg#msg.peer_name, "',",
|
|||
|
+ "'", Msg#msg.peer_server, "',",
|
|||
|
+ "'", ejabberd_odbc:escape(Msg#msg.peer_resource), "',",
|
|||
|
+ "'", atom_to_list(Msg#msg.direction), "',",
|
|||
|
+ "'", Msg#msg.type, "',",
|
|||
|
+ "'", ejabberd_odbc:escape(Msg#msg.subject), "',",
|
|||
|
+ "'", ejabberd_odbc:escape(Msg#msg.body), "',",
|
|||
|
+ "'", Msg#msg.timestamp, "');"],
|
|||
|
+
|
|||
|
+ case sql_query_internal_silent(DBRef, Query) of
|
|||
|
+ % TODO: change this
|
|||
|
+ {data, [{"0"}]} ->
|
|||
|
+ ?MYDEBUG("Logged ok for ~p, peer: ~p", [Msg#msg.owner_name++"@"++VHost,
|
|||
|
+ Msg#msg.peer_name++"@"++Msg#msg.peer_server]),
|
|||
|
+ ok;
|
|||
|
+ {error, _Reason} ->
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ {reply, ok, State};
|
|||
|
+handle_call({rebuild_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
|
|||
|
+ Reply = rebuild_stats_at_int(DBRef, VHost, Schema, Date),
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({delete_messages_by_user_at, [], _Date}, _From, State) ->
|
|||
|
+ {reply, error, State};
|
|||
|
+handle_call({delete_messages_by_user_at, Msgs, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
|
|||
|
+ Temp = lists:flatmap(fun(#msg{timestamp=Timestamp} = _Msg) ->
|
|||
|
+ ["'",Timestamp,"'",","]
|
|||
|
+ end, Msgs),
|
|||
|
+
|
|||
|
+ Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
|
|||
|
+
|
|||
|
+ Query = ["DELETE FROM ",messages_table(VHost, Schema, Date)," ",
|
|||
|
+ "WHERE timestamp IN (", Temp1],
|
|||
|
+
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ rebuild_stats_at_int(DBRef, VHost, Schema, Date);
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({delete_all_messages_by_user_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
|
|||
|
+ ok = delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date),
|
|||
|
+ ok = delete_stats_by_user_at_int(DBRef, Schema, User, VHost, Date),
|
|||
|
+ {reply, ok, State};
|
|||
|
+handle_call({delete_messages_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["DROP VIEW ",view_table(VHost, Schema, Date),";"]),
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, ["DROP TABLE ",messages_table(VHost, Schema, Date)," CASCADE;"]) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ Query = ["DELETE FROM ",stats_table(VHost, Schema)," "
|
|||
|
+ "WHERE at='",Date,"';"],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_vhost_stats}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
|
|||
|
+ SName = stats_table(VHost, Schema),
|
|||
|
+ Query = ["SELECT at, sum(count) ",
|
|||
|
+ "FROM ",SName," ",
|
|||
|
+ "GROUP BY at ",
|
|||
|
+ "ORDER BY DATE(at) DESC;"
|
|||
|
+ ],
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {data, Recs} ->
|
|||
|
+ {ok, [ {Date, list_to_integer(Count)} || {Date, Count} <- Recs]};
|
|||
|
+ {error, Reason} ->
|
|||
|
+ % TODO: Duplicate error message ?
|
|||
|
+ {error, Reason}
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_vhost_stats_at, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
|
|||
|
+ SName = stats_table(VHost, Schema),
|
|||
|
+ Query = ["SELECT username, sum(count) AS allcount ",
|
|||
|
+ "FROM ",SName," ",
|
|||
|
+ "JOIN ",users_table(VHost, Schema)," ON owner_id=user_id ",
|
|||
|
+ "WHERE at='",Date,"' ",
|
|||
|
+ "GROUP BY username ",
|
|||
|
+ "ORDER BY allcount DESC;"
|
|||
|
+ ],
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {data, Recs} ->
|
|||
|
+ RFun = fun({User, Count}) ->
|
|||
|
+ {User, list_to_integer(Count)}
|
|||
|
+ end,
|
|||
|
+ {ok, lists:reverse(lists:keysort(2, lists:map(RFun, Recs)))};
|
|||
|
+ {error, Reason} ->
|
|||
|
+ % TODO:
|
|||
|
+ {error, Reason}
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_user_stats, User}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
|
|||
|
+ {reply, get_user_stats_int(DBRef, Schema, User, VHost), State};
|
|||
|
+handle_call({get_user_messages_at, User, Date}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
|
|||
|
+ Query = ["SELECT peer_name,",
|
|||
|
+ "peer_server,",
|
|||
|
+ "peer_resource,",
|
|||
|
+ "direction,"
|
|||
|
+ "type,"
|
|||
|
+ "subject,"
|
|||
|
+ "body,"
|
|||
|
+ "timestamp "
|
|||
|
+ "FROM ",view_table(VHost, Schema, Date)," "
|
|||
|
+ "WHERE owner_name='",User,"';"],
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {data, Recs} ->
|
|||
|
+ Fun = fun({Peer_name, Peer_server, Peer_resource,
|
|||
|
+ Direction,
|
|||
|
+ Type,
|
|||
|
+ Subject, Body,
|
|||
|
+ Timestamp}) ->
|
|||
|
+ #msg{peer_name=Peer_name, peer_server=Peer_server, peer_resource=Peer_resource,
|
|||
|
+ direction=list_to_atom(Direction),
|
|||
|
+ type=Type,
|
|||
|
+ subject=Subject, body=Body,
|
|||
|
+ timestamp=Timestamp}
|
|||
|
+ end,
|
|||
|
+ {ok, lists:map(Fun, Recs)};
|
|||
|
+ {error, Reason} ->
|
|||
|
+ {error, Reason}
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_dates}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
|
|||
|
+ SName = stats_table(VHost, Schema),
|
|||
|
+ Query = ["SELECT at ",
|
|||
|
+ "FROM ",SName," ",
|
|||
|
+ "GROUP BY at ",
|
|||
|
+ "ORDER BY at DESC;"
|
|||
|
+ ],
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {data, Result} ->
|
|||
|
+ [ Date || {Date} <- Result ];
|
|||
|
+ {error, Reason} ->
|
|||
|
+ {error, Reason}
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_users_settings}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
|
|||
|
+ Query = ["SELECT username,dolog_default,dolog_list,donotlog_list ",
|
|||
|
+ "FROM ",settings_table(VHost, Schema)," ",
|
|||
|
+ "JOIN ",users_table(VHost, Schema)," ON user_id=owner_id;"],
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {data, Recs} ->
|
|||
|
+ {ok, [#user_settings{owner_name=Owner,
|
|||
|
+ dolog_default=list_to_bool(DoLogDef),
|
|||
|
+ dolog_list=string_to_list(DoLogL),
|
|||
|
+ donotlog_list=string_to_list(DoNotLogL)
|
|||
|
+ } || {Owner, DoLogDef, DoLogL, DoNotLogL} <- Recs]};
|
|||
|
+ {error, Reason} ->
|
|||
|
+ {error, Reason}
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({get_user_settings, User}, _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
|
|||
|
+ Query = ["SELECT dolog_default,dolog_list,donotlog_list ",
|
|||
|
+ "FROM ",settings_table(VHost, Schema)," ",
|
|||
|
+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal_silent(DBRef, Query) of
|
|||
|
+ {data, []} ->
|
|||
|
+ {ok, []};
|
|||
|
+ {data, [{DoLogDef, DoLogL, DoNotLogL}]} ->
|
|||
|
+ {ok, #user_settings{owner_name=User,
|
|||
|
+ dolog_default=list_to_bool(DoLogDef),
|
|||
|
+ dolog_list=string_to_list(DoLogL),
|
|||
|
+ donotlog_list=string_to_list(DoNotLogL)}};
|
|||
|
+ {error, Reason} ->
|
|||
|
+ ?ERROR_MSG("Failed to get_user_settings for ~p@~p: ~p", [User, VHost, Reason]),
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({set_user_settings, User, #user_settings{dolog_default=DoLogDef,
|
|||
|
+ dolog_list=DoLogL,
|
|||
|
+ donotlog_list=DoNotLogL}},
|
|||
|
+ _From, #state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
|
|||
|
+ User_id = get_user_id(DBRef, VHost, Schema, User),
|
|||
|
+ Query = ["UPDATE ",settings_table(VHost, Schema)," ",
|
|||
|
+ "SET dolog_default=",bool_to_list(DoLogDef),", ",
|
|||
|
+ "dolog_list='",list_to_string(DoLogL),"', ",
|
|||
|
+ "donotlog_list='",list_to_string(DoNotLogL),"' ",
|
|||
|
+ "WHERE owner_id=",User_id,";"],
|
|||
|
+
|
|||
|
+ Reply =
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, 0} ->
|
|||
|
+ IQuery = ["INSERT INTO ",settings_table(VHost, Schema)," ",
|
|||
|
+ "(owner_id, dolog_default, dolog_list, donotlog_list) ",
|
|||
|
+ "VALUES ",
|
|||
|
+ "(",User_id,", ",bool_to_list(DoLogDef),",'",list_to_string(DoLogL),"','",list_to_string(DoNotLogL),"');"],
|
|||
|
+ case sql_query_internal(DBRef, IQuery) of
|
|||
|
+ {updated, 1} ->
|
|||
|
+ ?MYDEBUG("New settings for ~s@~s", [User, VHost]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end;
|
|||
|
+ {updated, 1} ->
|
|||
|
+ ?MYDEBUG("Updated settings for ~s@~s", [User, VHost]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ {reply, Reply, State};
|
|||
|
+handle_call({stop}, _From, State) ->
|
|||
|
+ ?MYDEBUG("Stoping pgsql backend for ~p", [State#state.vhost]),
|
|||
|
+ {stop, normal, ok, State};
|
|||
|
+handle_call(Msg, _From, State) ->
|
|||
|
+ ?INFO_MSG("Got call Msg: ~p, State: ~p", [Msg, State]),
|
|||
|
+ {noreply, State}.
|
|||
|
+
|
|||
|
+
|
|||
|
+handle_cast({rebuild_stats}, State) ->
|
|||
|
+ rebuild_all_stats_int(State),
|
|||
|
+ {noreply, State};
|
|||
|
+handle_cast({drop_user, User}, #state{vhost=VHost, schema=Schema}=State) ->
|
|||
|
+ Fun = fun() ->
|
|||
|
+ {ok, DBRef} = open_pgsql_connection(State),
|
|||
|
+ {ok, Dates} = get_user_stats_int(DBRef, Schema, User, VHost),
|
|||
|
+ MDResult = lists:map(fun({Date, _}) ->
|
|||
|
+ delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date)
|
|||
|
+ end, Dates),
|
|||
|
+ StDResult = delete_all_stats_by_user_int(DBRef, Schema, User, VHost),
|
|||
|
+ SDResult = delete_user_settings_int(DBRef, Schema, User, VHost),
|
|||
|
+ case lists:all(fun(Result) when Result == ok ->
|
|||
|
+ true;
|
|||
|
+ (Result) when Result == error ->
|
|||
|
+ false
|
|||
|
+ end, lists:append([MDResult, [StDResult], [SDResult]])) of
|
|||
|
+ true ->
|
|||
|
+ ?INFO_MSG("Removed ~s@~s", [User, VHost]);
|
|||
|
+ false ->
|
|||
|
+ ?ERROR_MSG("Failed to remove ~s@~s", [User, VHost])
|
|||
|
+ end,
|
|||
|
+ close_pgsql_connection(DBRef)
|
|||
|
+ end,
|
|||
|
+ spawn(Fun),
|
|||
|
+ {noreply, State};
|
|||
|
+handle_cast(Msg, State) ->
|
|||
|
+ ?INFO_MSG("Got cast Msg:~p, State:~p", [Msg, State]),
|
|||
|
+ {noreply, State}.
|
|||
|
+
|
|||
|
+handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, State) ->
|
|||
|
+ {stop, connection_dropped, State};
|
|||
|
+handle_info(Info, State) ->
|
|||
|
+ ?INFO_MSG("Got Info:~p, State:~p", [Info, State]),
|
|||
|
+ {noreply, State}.
|
|||
|
+
|
|||
|
+terminate(_Reason, #state{dbref=DBRef}=_State) ->
|
|||
|
+ close_pgsql_connection(DBRef),
|
|||
|
+ ok.
|
|||
|
+
|
|||
|
+code_change(_OldVsn, State, _Extra) ->
|
|||
|
+ {ok, State}.
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% gen_logdb callbacks
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+log_message(VHost, Msg) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {log_message, Msg}, ?CALL_TIMEOUT).
|
|||
|
+rebuild_stats(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:cast(Proc, {rebuild_stats}).
|
|||
|
+rebuild_stats_at(VHost, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {rebuild_stats_at, Date}, ?CALL_TIMEOUT).
|
|||
|
+delete_messages_by_user_at(VHost, Msgs, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {delete_messages_by_user_at, Msgs, Date}, ?CALL_TIMEOUT).
|
|||
|
+delete_all_messages_by_user_at(User, VHost, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {delete_all_messages_by_user_at, User, Date}, ?CALL_TIMEOUT).
|
|||
|
+delete_messages_at(VHost, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {delete_messages_at, Date}, ?CALL_TIMEOUT).
|
|||
|
+get_vhost_stats(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_vhost_stats}, ?CALL_TIMEOUT).
|
|||
|
+get_vhost_stats_at(VHost, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_vhost_stats_at, Date}, ?CALL_TIMEOUT).
|
|||
|
+get_user_stats(User, VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_user_stats, User}, ?CALL_TIMEOUT).
|
|||
|
+get_user_messages_at(User, VHost, Date) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_user_messages_at, User, Date}, ?CALL_TIMEOUT).
|
|||
|
+get_dates(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_dates}, ?CALL_TIMEOUT).
|
|||
|
+get_users_settings(VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_users_settings}, ?CALL_TIMEOUT).
|
|||
|
+get_user_settings(User, VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {get_user_settings, User}, ?CALL_TIMEOUT).
|
|||
|
+set_user_settings(User, VHost, Set) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:call(Proc, {set_user_settings, User, Set}, ?CALL_TIMEOUT).
|
|||
|
+drop_user(User, VHost) ->
|
|||
|
+ Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
|
|||
|
+ gen_server:cast(Proc, {drop_user, User}).
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% internals
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+get_dates_int(DBRef, VHost) ->
|
|||
|
+ Query = ["SELECT n.nspname as \"Schema\",
|
|||
|
+ c.relname as \"Name\",
|
|||
|
+ 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\",
|
|||
|
+ r.rolname as \"Owner\"
|
|||
|
+ FROM pg_catalog.pg_class c
|
|||
|
+ JOIN pg_catalog.pg_roles r ON r.oid = c.relowner
|
|||
|
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|||
|
+ WHERE c.relkind IN ('r','')
|
|||
|
+ AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
|
|||
|
+ AND c.relname ~ '^(.*",escape_vhost(VHost),".*)$'
|
|||
|
+ AND pg_catalog.pg_table_is_visible(c.oid)
|
|||
|
+ ORDER BY 1,2;"],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {data, Recs} ->
|
|||
|
+ lists:foldl(fun({_Schema, Table, _Type, _Owner}, Dates) ->
|
|||
|
+ case regexp:match(Table,"[0-9]+-[0-9]+-[0-9]+") of
|
|||
|
+ {match, S, E} ->
|
|||
|
+ lists:append(Dates, [lists:sublist(Table,S,E)]);
|
|||
|
+ nomatch ->
|
|||
|
+ Dates
|
|||
|
+ end
|
|||
|
+ end, [], Recs);
|
|||
|
+ {error, _} ->
|
|||
|
+ []
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+rebuild_all_stats_int(#state{vhost=VHost, schema=Schema}=State) ->
|
|||
|
+ Fun = fun() ->
|
|||
|
+ {ok, DBRef} = open_pgsql_connection(State),
|
|||
|
+ ok = delete_nonexistent_stats(DBRef, Schema, VHost),
|
|||
|
+ case lists:filter(fun(Date) ->
|
|||
|
+ case catch rebuild_stats_at_int(DBRef, VHost, Schema, Date) of
|
|||
|
+ ok -> false;
|
|||
|
+ error -> true;
|
|||
|
+ {'EXIT', _} -> true
|
|||
|
+ end
|
|||
|
+ end, get_dates_int(DBRef, VHost)) of
|
|||
|
+ [] -> ok;
|
|||
|
+ FTables ->
|
|||
|
+ ?ERROR_MSG("Failed to rebuild stats for ~p dates", [FTables]),
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ close_pgsql_connection(DBRef)
|
|||
|
+ end,
|
|||
|
+ spawn(Fun).
|
|||
|
+
|
|||
|
+rebuild_stats_at_int(DBRef, VHost, Schema, Date) ->
|
|||
|
+ TempTable = temp_table(VHost, Schema),
|
|||
|
+ Fun =
|
|||
|
+ fun() ->
|
|||
|
+ Table = messages_table(VHost, Schema, Date),
|
|||
|
+ STable = stats_table(VHost, Schema),
|
|||
|
+
|
|||
|
+ DQuery = [ "DELETE FROM ",STable," ",
|
|||
|
+ "WHERE at='",Date,"';"],
|
|||
|
+
|
|||
|
+ ok = create_temp_table(DBRef, VHost, Schema),
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",Table," IN ACCESS EXCLUSIVE MODE;"]),
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",TempTable," IN ACCESS EXCLUSIVE MODE;"]),
|
|||
|
+ SQuery = ["INSERT INTO ",TempTable," ",
|
|||
|
+ "(owner_id,peer_name_id,peer_server_id,at,count) ",
|
|||
|
+ "SELECT owner_id,peer_name_id,peer_server_id,'",Date,"'",",count(*) ",
|
|||
|
+ "FROM ",Table," GROUP BY owner_id,peer_name_id,peer_server_id;"],
|
|||
|
+ case sql_query_internal(DBRef, SQuery) of
|
|||
|
+ {updated, 0} ->
|
|||
|
+ Count = sql_query_internal(DBRef, ["SELECT count(*) FROM ",Table,";"]),
|
|||
|
+ case Count of
|
|||
|
+ {data, [{"0"}]} ->
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["DROP VIEW ",view_table(VHost, Schema, Date),";"]),
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["DROP TABLE ",Table," CASCADE;"]),
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," IN ACCESS EXCLUSIVE MODE;"]),
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, DQuery),
|
|||
|
+ ok;
|
|||
|
+ _ ->
|
|||
|
+ ?ERROR_MSG("Failed to calculate stats for ~s table! Count was ~p.", [Date, Count]),
|
|||
|
+ error
|
|||
|
+ end;
|
|||
|
+ {updated, _} ->
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",STable," IN ACCESS EXCLUSIVE MODE;"]),
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["LOCK TABLE ",TempTable," IN ACCESS EXCLUSIVE MODE;"]),
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, DQuery),
|
|||
|
+ SQuery1 = ["INSERT INTO ",STable," ",
|
|||
|
+ "(owner_id,peer_name_id,peer_server_id,at,count) ",
|
|||
|
+ "SELECT owner_id,peer_name_id,peer_server_id,at,count ",
|
|||
|
+ "FROM ",TempTable,";"],
|
|||
|
+ case sql_query_internal(DBRef, SQuery1) of
|
|||
|
+ {updated, _} -> ok;
|
|||
|
+ {error, _} -> error
|
|||
|
+ end;
|
|||
|
+ {error, _} -> error
|
|||
|
+ end
|
|||
|
+ end, % fun
|
|||
|
+
|
|||
|
+ case sql_transaction_internal(DBRef, Fun) of
|
|||
|
+ {atomic, _} ->
|
|||
|
+ ?INFO_MSG("Rebuilded stats for ~p at ~p", [VHost, Date]),
|
|||
|
+ ok;
|
|||
|
+ {aborted, Reason} ->
|
|||
|
+ ?ERROR_MSG("Failed to rebuild stats for ~s table: ~p.", [Date, Reason]),
|
|||
|
+ error
|
|||
|
+ end,
|
|||
|
+ sql_query_internal(DBRef, ["DROP TABLE ",TempTable,";"]),
|
|||
|
+ ok.
|
|||
|
+
|
|||
|
+delete_nonexistent_stats(DBRef, Schema, VHost) ->
|
|||
|
+ Dates = get_dates_int(DBRef, VHost),
|
|||
|
+ STable = stats_table(VHost, Schema),
|
|||
|
+
|
|||
|
+ Temp = lists:flatmap(fun(Date) ->
|
|||
|
+ ["'",Date,"'",","]
|
|||
|
+ end, Dates),
|
|||
|
+
|
|||
|
+ case Temp of
|
|||
|
+ [] ->
|
|||
|
+ ok;
|
|||
|
+ _ ->
|
|||
|
+ % replace last "," with ");"
|
|||
|
+ Temp1 = lists:append([lists:sublist(Temp, length(Temp)-1), ");"]),
|
|||
|
+ Query = ["DELETE FROM ",STable," ",
|
|||
|
+ "WHERE at NOT IN (", Temp1],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+get_user_stats_int(DBRef, Schema, User, VHost) ->
|
|||
|
+ SName = stats_table(VHost, Schema),
|
|||
|
+ UName = users_table(VHost, Schema),
|
|||
|
+ Query = ["SELECT stats.at, sum(stats.count) ",
|
|||
|
+ "FROM ",UName," AS users ",
|
|||
|
+ "JOIN ",SName," AS stats ON owner_id=user_id "
|
|||
|
+ "WHERE users.username='",User,"' ",
|
|||
|
+ "GROUP BY stats.at "
|
|||
|
+ "ORDER BY DATE(at) DESC;"
|
|||
|
+ ],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {data, Recs} ->
|
|||
|
+ {ok, [ {Date, list_to_integer(Count)} || {Date, Count} <- Recs ]};
|
|||
|
+ {error, Result} ->
|
|||
|
+ {error, Result}
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+delete_all_messages_by_user_at_int(DBRef, Schema, User, VHost, Date) ->
|
|||
|
+ DQuery = ["DELETE FROM ",messages_table(VHost, Schema, Date)," ",
|
|||
|
+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
|
|||
|
+ case sql_query_internal(DBRef, DQuery) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?INFO_MSG("Dropped messages for ~s@~s at ~s", [User, VHost, Date]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+delete_all_stats_by_user_int(DBRef, Schema, User, VHost) ->
|
|||
|
+ SQuery = ["DELETE FROM ",stats_table(VHost, Schema)," ",
|
|||
|
+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
|
|||
|
+ case sql_query_internal(DBRef, SQuery) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?INFO_MSG("Dropped all stats for ~s@~s", [User, VHost]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} -> error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+delete_stats_by_user_at_int(DBRef, Schema, User, VHost, Date) ->
|
|||
|
+ SQuery = ["DELETE FROM ",stats_table(VHost, Schema)," ",
|
|||
|
+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"') ",
|
|||
|
+ "AND at='",Date,"';"],
|
|||
|
+ case sql_query_internal(DBRef, SQuery) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?INFO_MSG("Dropped stats for ~s@~s at ~s", [User, VHost, Date]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} -> error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+delete_user_settings_int(DBRef, Schema, User, VHost) ->
|
|||
|
+ Query = ["DELETE FROM ",settings_table(VHost, Schema)," ",
|
|||
|
+ "WHERE owner_id=(SELECT user_id FROM ",users_table(VHost, Schema)," WHERE username='",User,"');"],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?INFO_MSG("Dropped ~s@~s settings", [User, VHost]),
|
|||
|
+ ok;
|
|||
|
+ {error, Reason} ->
|
|||
|
+ ?ERROR_MSG("Failed to drop ~s@~s settings: ~p", [User, VHost, Reason]),
|
|||
|
+ error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% tables internals
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+create_temp_table(DBRef, VHost, Schema) ->
|
|||
|
+ TName = temp_table(VHost, Schema),
|
|||
|
+ Query = ["CREATE TABLE ",TName," (",
|
|||
|
+ "owner_id INTEGER, ",
|
|||
|
+ "peer_name_id INTEGER, ",
|
|||
|
+ "peer_server_id INTEGER, ",
|
|||
|
+ "at VARCHAR(20), ",
|
|||
|
+ "count INTEGER ",
|
|||
|
+ ");"
|
|||
|
+ ],
|
|||
|
+ case sql_query_internal(DBRef, Query) of
|
|||
|
+ {updated, _} -> ok;
|
|||
|
+ {error, _Reason} -> error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+create_stats_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}=State) ->
|
|||
|
+ SName = stats_table(VHost, Schema),
|
|||
|
+
|
|||
|
+ Fun =
|
|||
|
+ fun() ->
|
|||
|
+ Query = ["CREATE TABLE ",SName," (",
|
|||
|
+ "owner_id INTEGER, ",
|
|||
|
+ "peer_name_id INTEGER, ",
|
|||
|
+ "peer_server_id INTEGER, ",
|
|||
|
+ "at VARCHAR(20), ",
|
|||
|
+ "count integer",
|
|||
|
+ ");"
|
|||
|
+ ],
|
|||
|
+ case sql_query_internal_silent(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"s_search_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (owner_id, peer_name_id, peer_server_id);"]),
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"s_at_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (at);"]),
|
|||
|
+ created;
|
|||
|
+ {error, Reason} ->
|
|||
|
+ case lists:keysearch(code, 1, Reason) of
|
|||
|
+ {value, {code, "42P07"}} ->
|
|||
|
+ exists;
|
|||
|
+ _ ->
|
|||
|
+ ?ERROR_MSG("Failed to create stats table for ~p: ~p", [VHost, Reason]),
|
|||
|
+ error
|
|||
|
+ end
|
|||
|
+ end
|
|||
|
+ end,
|
|||
|
+ case sql_transaction_internal(DBRef, Fun) of
|
|||
|
+ {atomic, created} ->
|
|||
|
+ ?MYDEBUG("Created stats table for ~p", [VHost]),
|
|||
|
+ rebuild_all_stats_int(State),
|
|||
|
+ ok;
|
|||
|
+ {atomic, exists} ->
|
|||
|
+ ?MYDEBUG("Stats table for ~p already exists", [VHost]),
|
|||
|
+ {match, F, L} = regexp:match(SName, "\".*\""),
|
|||
|
+ QTable = lists:sublist(SName, F+1, L-2),
|
|||
|
+ OIDQuery = ["SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname='",QTable,"' AND pg_catalog.pg_table_is_visible(c.oid);"],
|
|||
|
+ {data,[{OID}]} = sql_query_internal(DBRef, OIDQuery),
|
|||
|
+ CheckQuery = ["SELECT a.attname FROM pg_catalog.pg_attribute a WHERE a.attrelid = '",OID,"' AND a.attnum > 0 AND NOT a.attisdropped AND a.attname ~ '^peer_.*_id$';"],
|
|||
|
+ case sql_query_internal(DBRef, CheckQuery) of
|
|||
|
+ {data, Elems} when length(Elems) == 2 ->
|
|||
|
+ ?MYDEBUG("Stats table structure is ok", []),
|
|||
|
+ ok;
|
|||
|
+ _ ->
|
|||
|
+ ?INFO_MSG("It seems like stats table structure is invalid. I will drop it and recreate", []),
|
|||
|
+ case sql_query_internal(DBRef, ["DROP TABLE ",SName,";"]) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?INFO_MSG("Successfully dropped ~p", [SName]);
|
|||
|
+ _ ->
|
|||
|
+ ?ERROR_MSG("Failed to drop ~p. You should drop it and restart module", [SName])
|
|||
|
+ end,
|
|||
|
+ error
|
|||
|
+ end;
|
|||
|
+ {error, _} -> error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+create_settings_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
|
|||
|
+ SName = settings_table(VHost, Schema),
|
|||
|
+ Query = ["CREATE TABLE ",SName," (",
|
|||
|
+ "owner_id INTEGER PRIMARY KEY, ",
|
|||
|
+ "dolog_default BOOLEAN, ",
|
|||
|
+ "dolog_list TEXT DEFAULT '', ",
|
|||
|
+ "donotlog_list TEXT DEFAULT ''",
|
|||
|
+ ");"
|
|||
|
+ ],
|
|||
|
+ case sql_query_internal_silent(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?MYDEBUG("Created settings table for ~p", [VHost]),
|
|||
|
+ ok;
|
|||
|
+ {error, Reason} ->
|
|||
|
+ case lists:keysearch(code, 1, Reason) of
|
|||
|
+ {value, {code, "42P07"}} ->
|
|||
|
+ ?MYDEBUG("Settings table for ~p already exists", [VHost]),
|
|||
|
+ ok;
|
|||
|
+ _ ->
|
|||
|
+ ?ERROR_MSG("Failed to create settings table for ~p: ~p", [VHost, Reason]),
|
|||
|
+ error
|
|||
|
+ end
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+create_users_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
|
|||
|
+ SName = users_table(VHost, Schema),
|
|||
|
+
|
|||
|
+ Fun =
|
|||
|
+ fun() ->
|
|||
|
+ Query = ["CREATE TABLE ",SName," (",
|
|||
|
+ "username TEXT UNIQUE, ",
|
|||
|
+ "user_id SERIAL PRIMARY KEY",
|
|||
|
+ ");"
|
|||
|
+ ],
|
|||
|
+ case sql_query_internal_silent(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"username_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (username);"]),
|
|||
|
+ created;
|
|||
|
+ {error, Reason} ->
|
|||
|
+ case lists:keysearch(code, 1, Reason) of
|
|||
|
+ {value, {code, "42P07"}} ->
|
|||
|
+ exists;
|
|||
|
+ _ ->
|
|||
|
+ ?ERROR_MSG("Failed to create users table for ~p: ~p", [VHost, Reason]),
|
|||
|
+ error
|
|||
|
+ end
|
|||
|
+ end
|
|||
|
+ end,
|
|||
|
+ case sql_transaction_internal(DBRef, Fun) of
|
|||
|
+ {atomic, created} ->
|
|||
|
+ ?MYDEBUG("Created users table for ~p", [VHost]),
|
|||
|
+ ok;
|
|||
|
+ {atomic, exists} ->
|
|||
|
+ ?MYDEBUG("Users table for ~p already exists", [VHost]),
|
|||
|
+ ok;
|
|||
|
+ {aborted, _} -> error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+create_servers_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
|
|||
|
+ SName = servers_table(VHost, Schema),
|
|||
|
+ Fun =
|
|||
|
+ fun() ->
|
|||
|
+ Query = ["CREATE TABLE ",SName," (",
|
|||
|
+ "server TEXT UNIQUE, ",
|
|||
|
+ "server_id SERIAL PRIMARY KEY",
|
|||
|
+ ");"
|
|||
|
+ ],
|
|||
|
+ case sql_query_internal_silent(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"server_i_",Schema,"_",escape_vhost(VHost),"\" ON ",SName," (server);"]),
|
|||
|
+ created;
|
|||
|
+ {error, Reason} ->
|
|||
|
+ case lists:keysearch(code, 1, Reason) of
|
|||
|
+ {value, {code, "42P07"}} ->
|
|||
|
+ exists;
|
|||
|
+ _ ->
|
|||
|
+ ?ERROR_MSG("Failed to create servers table for ~p: ~p", [VHost, Reason]),
|
|||
|
+ error
|
|||
|
+ end
|
|||
|
+ end
|
|||
|
+ end,
|
|||
|
+ case sql_transaction_internal(DBRef, Fun) of
|
|||
|
+ {atomic, created} ->
|
|||
|
+ ?MYDEBUG("Created servers table for ~p", [VHost]),
|
|||
|
+ ok;
|
|||
|
+ {atomic, exists} ->
|
|||
|
+ ?MYDEBUG("Servers table for ~p already exists", [VHost]),
|
|||
|
+ ok;
|
|||
|
+ {aborted, _} -> error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+create_resources_table(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
|
|||
|
+ RName = resources_table(VHost, Schema),
|
|||
|
+ Fun = fun() ->
|
|||
|
+ Query = ["CREATE TABLE ",RName," (",
|
|||
|
+ "resource TEXT UNIQUE, ",
|
|||
|
+ "resource_id SERIAL PRIMARY KEY",
|
|||
|
+ ");"
|
|||
|
+ ],
|
|||
|
+ case sql_query_internal_silent(DBRef, Query) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ {updated, _} = sql_query_internal(DBRef, ["CREATE INDEX \"resource_i_",Schema,"_",escape_vhost(VHost),"\" ON ",RName," (resource);"]),
|
|||
|
+ created;
|
|||
|
+ {error, Reason} ->
|
|||
|
+ case lists:keysearch(code, 1, Reason) of
|
|||
|
+ {value, {code, "42P07"}} ->
|
|||
|
+ exists;
|
|||
|
+ _ ->
|
|||
|
+ ?ERROR_MSG("Failed to create users table for ~p: ~p", [VHost, Reason]),
|
|||
|
+ error
|
|||
|
+ end
|
|||
|
+ end
|
|||
|
+ end,
|
|||
|
+ case sql_transaction_internal(DBRef, Fun) of
|
|||
|
+ {atomic, created} ->
|
|||
|
+ ?MYDEBUG("Created resources table for ~p", [VHost]),
|
|||
|
+ ok;
|
|||
|
+ {atomic, exists} ->
|
|||
|
+ ?MYDEBUG("Resources table for ~p already exists", [VHost]),
|
|||
|
+ ok;
|
|||
|
+ {aborted, _} -> error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+create_internals(#state{dbref=DBRef, vhost=VHost, schema=Schema}) ->
|
|||
|
+ 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);"]),
|
|||
|
+ case sql_query_internal(DBRef, [get_logmessage(VHost, Schema)]) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ ?MYDEBUG("Created logmessage for ~p", [VHost]),
|
|||
|
+ ok;
|
|||
|
+ {error, _} ->
|
|||
|
+ error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+get_user_id(DBRef, VHost, Schema, User) ->
|
|||
|
+ SQuery = ["SELECT user_id FROM ",users_table(VHost, Schema)," ",
|
|||
|
+ "WHERE username='",User,"';"],
|
|||
|
+ case sql_query_internal(DBRef, SQuery) of
|
|||
|
+ {data, []} ->
|
|||
|
+ IQuery = ["INSERT INTO ",users_table(VHost, Schema)," ",
|
|||
|
+ "VALUES ('",User,"');"],
|
|||
|
+ case sql_query_internal_silent(DBRef, IQuery) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ {data, [{DBIdNew}]} = sql_query_internal(DBRef, SQuery),
|
|||
|
+ DBIdNew;
|
|||
|
+ {error, Reason} ->
|
|||
|
+ % this can be in clustered environment
|
|||
|
+ {value, {code, "23505"}} = lists:keysearch(code, 1, Reason),
|
|||
|
+ ?ERROR_MSG("Duplicate key name for ~p", [User]),
|
|||
|
+ {data, [{ClID}]} = sql_query_internal(DBRef, SQuery),
|
|||
|
+ ClID
|
|||
|
+ end;
|
|||
|
+ {data, [{DBId}]} ->
|
|||
|
+ DBId
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+get_logmessage(VHost,Schema) ->
|
|||
|
+ UName = users_table(VHost,Schema),
|
|||
|
+ SName = servers_table(VHost,Schema),
|
|||
|
+ RName = resources_table(VHost,Schema),
|
|||
|
+ StName = stats_table(VHost,Schema),
|
|||
|
+ 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 $$
|
|||
|
+DECLARE
|
|||
|
+ ownerID INTEGER;
|
|||
|
+ peer_nameID INTEGER;
|
|||
|
+ peer_serverID INTEGER;
|
|||
|
+ peer_resourceID INTEGER;
|
|||
|
+ tablename ALIAS for $1;
|
|||
|
+ viewname ALIAS for $2;
|
|||
|
+ atdate ALIAS for $3;
|
|||
|
+BEGIN
|
|||
|
+ SELECT INTO ownerID user_id FROM ~s WHERE username = owner;
|
|||
|
+ IF NOT FOUND THEN
|
|||
|
+ INSERT INTO ~s (username) VALUES (owner);
|
|||
|
+ ownerID := lastval();
|
|||
|
+ END IF;
|
|||
|
+
|
|||
|
+ SELECT INTO peer_nameID user_id FROM ~s WHERE username = peer_name;
|
|||
|
+ IF NOT FOUND THEN
|
|||
|
+ INSERT INTO ~s (username) VALUES (peer_name);
|
|||
|
+ peer_nameID := lastval();
|
|||
|
+ END IF;
|
|||
|
+
|
|||
|
+ SELECT INTO peer_serverID server_id FROM ~s WHERE server = peer_server;
|
|||
|
+ IF NOT FOUND THEN
|
|||
|
+ INSERT INTO ~s (server) VALUES (peer_server);
|
|||
|
+ peer_serverID := lastval();
|
|||
|
+ END IF;
|
|||
|
+
|
|||
|
+ SELECT INTO peer_resourceID resource_id FROM ~s WHERE resource = peer_resource;
|
|||
|
+ IF NOT FOUND THEN
|
|||
|
+ INSERT INTO ~s (resource) VALUES (peer_resource);
|
|||
|
+ peer_resourceID := lastval();
|
|||
|
+ END IF;
|
|||
|
+
|
|||
|
+ BEGIN
|
|||
|
+ 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 || ')';
|
|||
|
+ EXCEPTION WHEN undefined_table THEN
|
|||
|
+ EXECUTE 'CREATE TABLE ' || tablename || ' (' ||
|
|||
|
+ 'owner_id INTEGER, ' ||
|
|||
|
+ 'peer_name_id INTEGER, ' ||
|
|||
|
+ 'peer_server_id INTEGER, ' ||
|
|||
|
+ 'peer_resource_id INTEGER, ' ||
|
|||
|
+ 'direction VARCHAR(4) CHECK (direction IN (''to'',''from'')), ' ||
|
|||
|
+ 'type VARCHAR(9) CHECK (type IN (''chat'',''error'',''groupchat'',''headline'',''normal'')), ' ||
|
|||
|
+ 'subject TEXT, ' ||
|
|||
|
+ 'body TEXT, ' ||
|
|||
|
+ 'timestamp DOUBLE PRECISION)';
|
|||
|
+ EXECUTE 'CREATE INDEX \"search_i_' || '~s' || '_' || atdate || '_' || '~s' || '\"' || ' ON ' || tablename || ' (owner_id, peer_name_id, peer_server_id, peer_resource_id)';
|
|||
|
+
|
|||
|
+ EXECUTE 'CREATE OR REPLACE VIEW ' || viewname || ' AS ' ||
|
|||
|
+ 'SELECT owner.username AS owner_name, ' ||
|
|||
|
+ 'peer.username AS peer_name, ' ||
|
|||
|
+ 'servers.server AS peer_server, ' ||
|
|||
|
+ 'resources.resource AS peer_resource, ' ||
|
|||
|
+ 'messages.direction, ' ||
|
|||
|
+ 'messages.type, ' ||
|
|||
|
+ 'messages.subject, ' ||
|
|||
|
+ 'messages.body, ' ||
|
|||
|
+ 'messages.timestamp ' ||
|
|||
|
+ 'FROM ' ||
|
|||
|
+ '~s owner, ' ||
|
|||
|
+ '~s peer, ' ||
|
|||
|
+ '~s servers, ' ||
|
|||
|
+ '~s resources, ' ||
|
|||
|
+ tablename || ' messages ' ||
|
|||
|
+ 'WHERE ' ||
|
|||
|
+ 'owner.user_id=messages.owner_id and ' ||
|
|||
|
+ 'peer.user_id=messages.peer_name_id and ' ||
|
|||
|
+ 'servers.server_id=messages.peer_server_id and ' ||
|
|||
|
+ 'resources.resource_id=messages.peer_resource_id ' ||
|
|||
|
+ 'ORDER BY messages.timestamp';
|
|||
|
+
|
|||
|
+ 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 || ')';
|
|||
|
+ END;
|
|||
|
+
|
|||
|
+ 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;
|
|||
|
+ IF NOT FOUND THEN
|
|||
|
+ INSERT INTO ~s (owner_id, peer_name_id, peer_server_id, at, count) VALUES (ownerID, peer_nameID, peer_serverID, atdate, 1);
|
|||
|
+ END IF;
|
|||
|
+ RETURN 0;
|
|||
|
+END;
|
|||
|
+$$ LANGUAGE plpgsql;
|
|||
|
+", [logmessage_name(VHost,Schema),UName,UName,UName,UName,SName,SName,RName,RName,Schema,escape_vhost(VHost),UName,UName,SName,RName,StName,StName]).
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% SQL internals
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+% like do_transaction/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
|
|||
|
+sql_transaction_internal(DBRef, Fun) ->
|
|||
|
+ case sql_query_internal(DBRef, ["BEGIN;"]) of
|
|||
|
+ {updated, _} ->
|
|||
|
+ case catch Fun() of
|
|||
|
+ error = Err ->
|
|||
|
+ rollback_internal(DBRef, Err);
|
|||
|
+ {error, _} = Err ->
|
|||
|
+ rollback_internal(DBRef, Err);
|
|||
|
+ {'EXIT', _} = Err ->
|
|||
|
+ rollback_internal(DBRef, Err);
|
|||
|
+ Res ->
|
|||
|
+ case sql_query_internal(DBRef, ["COMMIT;"]) of
|
|||
|
+ {error, _} -> rollback_internal(DBRef, {commit_error});
|
|||
|
+ {updated, _} ->
|
|||
|
+ case Res of
|
|||
|
+ {atomic, _} -> Res;
|
|||
|
+ _ -> {atomic, Res}
|
|||
|
+ end
|
|||
|
+ end
|
|||
|
+ end;
|
|||
|
+ {error, _} ->
|
|||
|
+ {aborted, {begin_error}}
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+% like rollback/2 in mysql_conn.erl (changeset by Yariv Sadan <yarivvv@gmail.com>)
|
|||
|
+rollback_internal(DBRef, Reason) ->
|
|||
|
+ Res = sql_query_internal(DBRef, ["ROLLBACK;"]),
|
|||
|
+ {aborted, {Reason, {rollback_result, Res}}}.
|
|||
|
+
|
|||
|
+sql_query_internal(DBRef, Query) ->
|
|||
|
+ case sql_query_internal_silent(DBRef, Query) of
|
|||
|
+ {error, undefined, Rez} ->
|
|||
|
+ ?ERROR_MSG("Got undefined result: ~p while ~p", [Rez, lists:append(Query)]),
|
|||
|
+ {error, undefined};
|
|||
|
+ {error, Error} ->
|
|||
|
+ ?ERROR_MSG("Failed: ~p while ~p", [Error, lists:append(Query)]),
|
|||
|
+ {error, Error};
|
|||
|
+ Rez -> Rez
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+sql_query_internal_silent(DBRef, Query) ->
|
|||
|
+ ?MYDEBUG("DOING: \"~s\"", [lists:append(Query)]),
|
|||
|
+ % TODO: use pquery?
|
|||
|
+ get_result(pgsql:squery(DBRef, Query)).
|
|||
|
+
|
|||
|
+get_result({ok, ["CREATE TABLE"]}) ->
|
|||
|
+ {updated, 1};
|
|||
|
+get_result({ok, ["DROP TABLE"]}) ->
|
|||
|
+ {updated, 1};
|
|||
|
+get_result({ok, ["ALTER TABLE"]}) ->
|
|||
|
+ {updated, 1};
|
|||
|
+get_result({ok,["DROP VIEW"]}) ->
|
|||
|
+ {updated, 1};
|
|||
|
+get_result({ok,["DROP FUNCTION"]}) ->
|
|||
|
+ {updated, 1};
|
|||
|
+get_result({ok, ["CREATE INDEX"]}) ->
|
|||
|
+ {updated, 1};
|
|||
|
+get_result({ok, ["CREATE FUNCTION"]}) ->
|
|||
|
+ {updated, 1};
|
|||
|
+get_result({ok, [{"SELECT", _Rows, Recs}]}) ->
|
|||
|
+ {data, [list_to_tuple(Rec) || Rec <- Recs]};
|
|||
|
+get_result({ok, ["INSERT " ++ OIDN]}) ->
|
|||
|
+ [_OID, N] = string:tokens(OIDN, " "),
|
|||
|
+ {updated, list_to_integer(N)};
|
|||
|
+get_result({ok, ["DELETE " ++ N]}) ->
|
|||
|
+ {updated, list_to_integer(N)};
|
|||
|
+get_result({ok, ["UPDATE " ++ N]}) ->
|
|||
|
+ {updated, list_to_integer(N)};
|
|||
|
+get_result({ok, ["BEGIN"]}) ->
|
|||
|
+ {updated, 1};
|
|||
|
+get_result({ok, ["LOCK TABLE"]}) ->
|
|||
|
+ {updated, 1};
|
|||
|
+get_result({ok, ["ROLLBACK"]}) ->
|
|||
|
+ {updated, 1};
|
|||
|
+get_result({ok, ["COMMIT"]}) ->
|
|||
|
+ {updated, 1};
|
|||
|
+get_result({ok, ["SET"]}) ->
|
|||
|
+ {updated, 1};
|
|||
|
+get_result({ok, [{error, Error}]}) ->
|
|||
|
+ {error, Error};
|
|||
|
+get_result(Rez) ->
|
|||
|
+ {error, undefined, Rez}.
|
|||
|
+
|
|||
|
--- mod_logdb_mnesia_old.erl.orig 2009-02-05 19:21:29.000000000 +0200
|
|||
|
+++ mod_logdb_mnesia_old.erl 2009-02-05 19:20:07.000000000 +0200
|
|||
|
@@ -0,0 +1,258 @@
|
|||
|
+%%%----------------------------------------------------------------------
|
|||
|
+%%% File : mod_logdb_mnesia_old.erl
|
|||
|
+%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
|
|||
|
+%%% Purpose : mod_logmnesia backend for mod_logdb (should be used only for copy_tables functionality)
|
|||
|
+%%% Version : trunk
|
|||
|
+%%% Id : $Id: mod_logdb_mnesia_old.erl 1169 2008-09-16 12:14:36Z malik $
|
|||
|
+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
|
|||
|
+%%%----------------------------------------------------------------------
|
|||
|
+
|
|||
|
+-module(mod_logdb_mnesia_old).
|
|||
|
+-author('o.palij@gmail.com').
|
|||
|
+
|
|||
|
+-include("ejabberd.hrl").
|
|||
|
+-include("jlib.hrl").
|
|||
|
+
|
|||
|
+-behaviour(gen_logdb).
|
|||
|
+
|
|||
|
+-export([start/2, stop/1,
|
|||
|
+ log_message/2,
|
|||
|
+ rebuild_stats/1,
|
|||
|
+ rebuild_stats_at/2,
|
|||
|
+ rebuild_stats_at1/2,
|
|||
|
+ delete_messages_by_user_at/3, delete_all_messages_by_user_at/3, delete_messages_at/2,
|
|||
|
+ get_vhost_stats/1, get_vhost_stats_at/2, get_user_stats/2, get_user_messages_at/3,
|
|||
|
+ get_dates/1,
|
|||
|
+ get_users_settings/1, get_user_settings/2, set_user_settings/3,
|
|||
|
+ drop_user/2]).
|
|||
|
+
|
|||
|
+-record(stats, {user, server, table, count}).
|
|||
|
+-record(msg, {to_user, to_server, to_resource, from_user, from_server, from_resource, id, type, subject, body, timestamp}).
|
|||
|
+
|
|||
|
+tables_prefix() -> "messages_".
|
|||
|
+% stats_table should not start with tables_prefix(VHost) !
|
|||
|
+% i.e. lists:prefix(tables_prefix(VHost), atom_to_list(stats_table())) must be /= true
|
|||
|
+stats_table() -> list_to_atom("messages-stats").
|
|||
|
+% table name as atom from Date
|
|||
|
+-define(ATABLE(Date), list_to_atom(tables_prefix() ++ Date)).
|
|||
|
+-define(LTABLE(Date), tables_prefix() ++ Date).
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% gen_logdb callbacks
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+start(_Opts, _VHost) ->
|
|||
|
+ case mnesia:system_info(is_running) of
|
|||
|
+ yes ->
|
|||
|
+ ok = create_stats_table(),
|
|||
|
+ {ok, ok};
|
|||
|
+ no ->
|
|||
|
+ ?ERROR_MSG("Mnesia not running", []),
|
|||
|
+ error;
|
|||
|
+ Status ->
|
|||
|
+ ?ERROR_MSG("Mnesia status: ~p", [Status]),
|
|||
|
+ error
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+stop(_VHost) ->
|
|||
|
+ ok.
|
|||
|
+
|
|||
|
+log_message(_VHost, _Msg) ->
|
|||
|
+ error.
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% gen_logdb callbacks (maintaince)
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+rebuild_stats(_VHost) ->
|
|||
|
+ ok.
|
|||
|
+
|
|||
|
+rebuild_stats_at(VHost, Date) ->
|
|||
|
+ Table = ?LTABLE(Date),
|
|||
|
+ {Time, Value}=timer:tc(?MODULE, rebuild_stats_at1, [VHost, Table]),
|
|||
|
+ ?INFO_MSG("rebuild_stats_at ~p elapsed ~p sec: ~p~n", [Date, Time/1000000, Value]),
|
|||
|
+ Value.
|
|||
|
+rebuild_stats_at1(VHost, Table) ->
|
|||
|
+ CFun = fun(Msg, Stats) ->
|
|||
|
+ To = Msg#msg.to_user ++ "@" ++ Msg#msg.to_server,
|
|||
|
+ Stats_to = if
|
|||
|
+ Msg#msg.to_server == VHost ->
|
|||
|
+ case lists:keysearch(To, 1, Stats) of
|
|||
|
+ {value, {Who_to, Count_to}} ->
|
|||
|
+ lists:keyreplace(To, 1, Stats, {Who_to, Count_to + 1});
|
|||
|
+ false ->
|
|||
|
+ lists:append(Stats, [{To, 1}])
|
|||
|
+ end;
|
|||
|
+ true ->
|
|||
|
+ Stats
|
|||
|
+ end,
|
|||
|
+ From = Msg#msg.from_user ++ "@" ++ Msg#msg.from_server,
|
|||
|
+ Stats_from = if
|
|||
|
+ Msg#msg.from_server == VHost ->
|
|||
|
+ case lists:keysearch(From, 1, Stats_to) of
|
|||
|
+ {value, {Who_from, Count_from}} ->
|
|||
|
+ lists:keyreplace(From, 1, Stats_to, {Who_from, Count_from + 1});
|
|||
|
+ false ->
|
|||
|
+ lists:append(Stats_to, [{From, 1}])
|
|||
|
+ end;
|
|||
|
+ true ->
|
|||
|
+ Stats_to
|
|||
|
+ end,
|
|||
|
+ Stats_from
|
|||
|
+ end,
|
|||
|
+ DFun = fun(#stats{table=STable, server=Server} = Stat, _Acc)
|
|||
|
+ when STable == Table, Server == VHost ->
|
|||
|
+ mnesia:delete_object(stats_table(), Stat, write);
|
|||
|
+ (_Stat, _Acc) -> ok
|
|||
|
+ end,
|
|||
|
+ case mnesia:transaction(fun() ->
|
|||
|
+ mnesia:write_lock_table(list_to_atom(Table)),
|
|||
|
+ mnesia:write_lock_table(stats_table()),
|
|||
|
+ % Calc stats for VHost at Date
|
|||
|
+ AStats = mnesia:foldl(CFun, [], list_to_atom(Table)),
|
|||
|
+ % Delete all stats for VHost at Date
|
|||
|
+ mnesia:foldl(DFun, [], stats_table()),
|
|||
|
+ % Write new calc'ed stats
|
|||
|
+ lists:foreach(fun({Who, Count}) ->
|
|||
|
+ Jid = jlib:string_to_jid(Who),
|
|||
|
+ JUser = Jid#jid.user,
|
|||
|
+ WStat = #stats{user=JUser, server=VHost, table=Table, count=Count},
|
|||
|
+ mnesia:write(stats_table(), WStat, write)
|
|||
|
+ end, AStats)
|
|||
|
+ end) of
|
|||
|
+ {aborted, Reason} ->
|
|||
|
+ ?ERROR_MSG("Failed to rebuild_stats_at for ~p at ~p: ~p", [VHost, Table, Reason]),
|
|||
|
+ error;
|
|||
|
+ {atomic, _} ->
|
|||
|
+ ok
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% gen_logdb callbacks (delete)
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+delete_messages_by_user_at(_VHost, _Msgs, _Date) ->
|
|||
|
+ error.
|
|||
|
+
|
|||
|
+delete_all_messages_by_user_at(_User, _VHost, _Date) ->
|
|||
|
+ error.
|
|||
|
+
|
|||
|
+delete_messages_at(VHost, Date) ->
|
|||
|
+ Table = list_to_atom(tables_prefix() ++ Date),
|
|||
|
+
|
|||
|
+ DFun = fun(#msg{to_server=To_server, from_server=From_server}=Msg, _Acc)
|
|||
|
+ when To_server == VHost; From_server == VHost ->
|
|||
|
+ mnesia:delete_object(Table, Msg, write);
|
|||
|
+ (_Msg, _Acc) -> ok
|
|||
|
+ end,
|
|||
|
+
|
|||
|
+ case mnesia:transaction(fun() ->
|
|||
|
+ mnesia:foldl(DFun, [], Table)
|
|||
|
+ end) of
|
|||
|
+ {aborted, Reason} ->
|
|||
|
+ ?ERROR_MSG("Failed to delete_messages_at for ~p at ~p: ~p", [VHost, Date, Reason]),
|
|||
|
+ error;
|
|||
|
+ {atomic, _} ->
|
|||
|
+ ok
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% gen_logdb callbacks (get)
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+get_vhost_stats(_VHost) ->
|
|||
|
+ {error, "does not emplemented"}.
|
|||
|
+
|
|||
|
+get_vhost_stats_at(VHost, Date) ->
|
|||
|
+ Fun = fun() ->
|
|||
|
+ Pat = #stats{user='$1', server=VHost, table=tables_prefix()++Date, count = '$2'},
|
|||
|
+ mnesia:select(stats_table(), [{Pat, [], [['$1', '$2']]}])
|
|||
|
+ end,
|
|||
|
+ case mnesia:transaction(Fun) of
|
|||
|
+ {atomic, Result} ->
|
|||
|
+ RFun = fun([User, Count]) ->
|
|||
|
+ {User, Count}
|
|||
|
+ end,
|
|||
|
+ {ok, lists:reverse(lists:keysort(2, lists:map(RFun, Result)))};
|
|||
|
+ {aborted, Reason} -> {error, Reason}
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+get_user_stats(_User, _VHost) ->
|
|||
|
+ {error, "does not emplemented"}.
|
|||
|
+
|
|||
|
+get_user_messages_at(User, VHost, Date) ->
|
|||
|
+ Table_name = tables_prefix() ++ Date,
|
|||
|
+ case mnesia:transaction(fun() ->
|
|||
|
+ Pat_to = #msg{to_user=User, to_server=VHost, _='_'},
|
|||
|
+ Pat_from = #msg{from_user=User, from_server=VHost, _='_'},
|
|||
|
+ mnesia:select(list_to_atom(Table_name),
|
|||
|
+ [{Pat_to, [], ['$_']},
|
|||
|
+ {Pat_from, [], ['$_']}])
|
|||
|
+ end) of
|
|||
|
+ {atomic, Result} ->
|
|||
|
+ Msgs = lists:map(fun(#msg{to_user=To_user, to_server=To_server, to_resource=To_res,
|
|||
|
+ from_user=From_user, from_server=From_server, from_resource=From_res,
|
|||
|
+ type=Type,
|
|||
|
+ subject=Subj,
|
|||
|
+ body=Body, timestamp=Timestamp} = _Msg) ->
|
|||
|
+ Subject = case Subj of
|
|||
|
+ "None" -> "";
|
|||
|
+ _ -> Subj
|
|||
|
+ end,
|
|||
|
+ {msg, To_user, To_server, To_res, From_user, From_server, From_res, Type, Subject, Body, Timestamp}
|
|||
|
+ end, Result),
|
|||
|
+ {ok, Msgs};
|
|||
|
+ {aborted, Reason} ->
|
|||
|
+ {error, Reason}
|
|||
|
+ end.
|
|||
|
+
|
|||
|
+get_dates(_VHost) ->
|
|||
|
+ Tables = mnesia:system_info(tables),
|
|||
|
+ MessagesTables =
|
|||
|
+ lists:filter(fun(Table) ->
|
|||
|
+ lists:prefix(tables_prefix(), atom_to_list(Table))
|
|||
|
+ end,
|
|||
|
+ Tables),
|
|||
|
+ lists:map(fun(Table) ->
|
|||
|
+ lists:sublist(atom_to_list(Table),
|
|||
|
+ length(tables_prefix())+1,
|
|||
|
+ length(atom_to_list(Table)))
|
|||
|
+ end,
|
|||
|
+ MessagesTables).
|
|||
|
+
|
|||
|
+get_users_settings(_VHost) ->
|
|||
|
+ {ok, []}.
|
|||
|
+get_user_settings(_User, _VHost) ->
|
|||
|
+ {ok, []}.
|
|||
|
+set_user_settings(_User, _VHost, _Set) ->
|
|||
|
+ ok.
|
|||
|
+drop_user(_User, _VHost) ->
|
|||
|
+ ok.
|
|||
|
+
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+%
|
|||
|
+% internal
|
|||
|
+%
|
|||
|
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|||
|
+% called from db_logon/2
|
|||
|
+create_stats_table() ->
|
|||
|
+ SName = stats_table(),
|
|||
|
+ case mnesia:create_table(SName,
|
|||
|
+ [{disc_only_copies, [node()]},
|
|||
|
+ {type, bag},
|
|||
|
+ {attributes, record_info(fields, stats)},
|
|||
|
+ {record_name, stats}
|
|||
|
+ ]) of
|
|||
|
+ {atomic, ok} ->
|
|||
|
+ ?INFO_MSG("Created stats table", []),
|
|||
|
+ ok;
|
|||
|
+ {aborted, {already_exists, _}} ->
|
|||
|
+ ok;
|
|||
|
+ {aborted, Reason} ->
|
|||
|
+ ?ERROR_MSG("Failed to create stats table: ~p", [Reason]),
|
|||
|
+ error
|
|||
|
+ end.
|
|||
|
--- gen_logdb.erl.orig 2009-02-05 19:21:29.000000000 +0200
|
|||
|
+++ gen_logdb.erl 2009-02-05 19:19:39.000000000 +0200
|
|||
|
@@ -0,0 +1,164 @@
|
|||
|
+%%%----------------------------------------------------------------------
|
|||
|
+%%% File : gen_logdb.erl
|
|||
|
+%%% Author : Oleg Palij (mailto,xmpp:o.palij@gmail.com)
|
|||
|
+%%% Purpose : Describes generic behaviour for mod_logdb backends.
|
|||
|
+%%% Version : trunk
|
|||
|
+%%% Id : $Id: gen_logdb.erl 1169 2008-09-16 12:14:36Z malik $
|
|||
|
+%%% Url : http://www.dp.uz.gov.ua/o.palij/mod_logdb/
|
|||
|
+%%%----------------------------------------------------------------------
|
|||
|
+
|
|||
|
+-module(gen_logdb).
|
|||
|
+-author('o.palij@gmail.com').
|
|||
|
+
|
|||
|
+-export([behaviour_info/1]).
|
|||
|
+
|
|||
|
+behaviour_info(callbacks) ->
|
|||
|
+ [
|
|||
|
+ % called from handle_info(start, _)
|
|||
|
+ % it should logon database and return reference to started instance
|
|||
|
+ % start(VHost, Opts) -> {ok, SPid} | error
|
|||
|
+ % Options - list of options to connect to db
|
|||
|
+ % Types: Options = list() -> [] |
|
|||
|
+ % [{user, "logdb"},
|
|||
|
+ % {pass, "1234"},
|
|||
|
+ % {db, "logdb"}] | ...
|
|||
|
+ % VHost = list() -> "jabber.example.org"
|
|||
|
+ {start, 2},
|
|||
|
+
|
|||
|
+ % called from cleanup/1
|
|||
|
+ % it should logoff database and do cleanup
|
|||
|
+ % stop(VHost)
|
|||
|
+ % Types: VHost = list() -> "jabber.example.org"
|
|||
|
+ {stop, 1},
|
|||
|
+
|
|||
|
+ % called from handle_call({addlog, _}, _, _)
|
|||
|
+ % it should log messages to database
|
|||
|
+ % log_message(VHost, Msg) -> ok | error
|
|||
|
+ % Types:
|
|||
|
+ % VHost = list() -> "jabber.example.org"
|
|||
|
+ % Msg = record() -> #msg
|
|||
|
+ {log_message, 2},
|
|||
|
+
|
|||
|
+ % called from ejabberdctl rebuild_stats
|
|||
|
+ % it should rebuild stats table (if used) for vhost
|
|||
|
+ % rebuild_stats(VHost)
|
|||
|
+ % Types:
|
|||
|
+ % VHost = list() -> "jabber.example.org"
|
|||
|
+ {rebuild_stats, 1},
|
|||
|
+
|
|||
|
+ % it should rebuild stats table (if used) for vhost at Date
|
|||
|
+ % rebuild_stats_at(VHost, Date)
|
|||
|
+ % Types:
|
|||
|
+ % VHost = list() -> "jabber.example.org"
|
|||
|
+ % Date = list() -> "2007-02-12"
|
|||
|
+ {rebuild_stats_at, 2},
|
|||
|
+
|
|||
|
+ % called from user_messages_at_parse_query/5
|
|||
|
+ % it should delete selected user messages at date
|
|||
|
+ % delete_messages_by_user_at(VHost, Msgs, Date) -> ok | error
|
|||
|
+ % Types:
|
|||
|
+ % VHost = list() -> "jabber.example.org"
|
|||
|
+ % Msgs = list() -> [ #msg1, msg2, ... ]
|
|||
|
+ % Date = list() -> "2007-02-12"
|
|||
|
+ {delete_messages_by_user_at, 3},
|
|||
|
+
|
|||
|
+ % called from user_messages_parse_query/4 | vhost_messages_at_parse_query/4
|
|||
|
+ % it should delete all user messages at date
|
|||
|
+ % delete_all_messages_by_user_at(User, VHost, Date) -> ok | error
|
|||
|
+ % Types:
|
|||
|
+ % User = list() -> "admin"
|
|||
|
+ % VHost = list() -> "jabber.example.org"
|
|||
|
+ % Date = list() -> "2007-02-12"
|
|||
|
+ {delete_all_messages_by_user_at, 3},
|
|||
|
+
|
|||
|
+ % called from vhost_messages_parse_query/3
|
|||
|
+ % it should delete messages for vhost at date and update stats
|
|||
|
+ % delete_messages_at(VHost, Date) -> ok | error
|
|||
|
+ % Types:
|
|||
|
+ % VHost = list() -> "jabber.example.org"
|
|||
|
+ % Date = list() -> "2007-02-12"
|
|||
|
+ {delete_messages_at, 2},
|
|||
|
+
|
|||
|
+ % called from ejabberd_web_admin:vhost_messages_stats/3
|
|||
|
+ % it should return sorted list of count of messages by dates for vhost
|
|||
|
+ % get_vhost_stats(VHost) -> {ok, [{Date1, Msgs_count1}, {Date2, Msgs_count2}, ... ]} |
|
|||
|
+ % {error, Reason}
|
|||
|
+ % Types:
|
|||
|
+ % VHost = list() -> "jabber.example.org"
|
|||
|
+ % DateN = list() -> "2007-02-12"
|
|||
|
+ % Msgs_countN = number() -> 241
|
|||
|
+ {get_vhost_stats, 1},
|
|||
|
+
|
|||
|
+ % called from ejabberd_web_admin:vhost_messages_stats_at/4
|
|||
|
+ % it should return sorted list of count of messages by users at date for vhost
|
|||
|
+ % get_vhost_stats_at(VHost, Date) -> {ok, [{User1, Msgs_count1}, {User2, Msgs_count2}, ....]} |
|
|||
|
+ % {error, Reason}
|
|||
|
+ % Types:
|
|||
|
+ % VHost = list() -> "jabber.example.org"
|
|||
|
+ % Date = list() -> "2007-02-12"
|
|||
|
+ % UserN = list() -> "admin"
|
|||
|
+ % Msgs_countN = number() -> 241
|
|||
|
+ {get_vhost_stats_at, 2},
|
|||
|
+
|
|||
|
+ % called from ejabberd_web_admin:user_messages_stats/4
|
|||
|
+ % it should return sorted list of count of messages by date for user at vhost
|
|||
|
+ % get_user_stats(User, VHost) -> {ok, [{Date1, Msgs_count1}, {Date2, Msgs_count2}, ...]} |
|
|||
|
+ % {error, Reason}
|
|||
|
+ % Types:
|
|||
|
+ % User = list() -> "admin"
|
|||
|
+ % VHost = list() -> "jabber.example.org"
|
|||
|
+ % DateN = list() -> "2007-02-12"
|
|||
|
+ % Msgs_countN = number() -> 241
|
|||
|
+ {get_user_stats, 2},
|
|||
|
+
|
|||
|
+ % called from ejabberd_web_admin:user_messages_stats_at/5
|
|||
|
+ % it should return all user messages at date
|
|||
|
+ % get_user_messages_at(User, VHost, Date) -> {ok, Msgs} | {error, Reason}
|
|||
|
+ % Types:
|
|||
|
+ % User = list() -> "admin"
|
|||
|
+ % VHost = list() -> "jabber.example.org"
|
|||
|
+ % Date = list() -> "2007-02-12"
|
|||
|
+ % Msgs = list() -> [ #msg1, msg2, ... ]
|
|||
|
+ {get_user_messages_at, 3},
|
|||
|
+
|
|||
|
+ % called from many places
|
|||
|
+ % it should return list of dates for vhost
|
|||
|
+ % get_dates(VHost) -> [Date1, Date2, ... ]
|
|||
|
+ % Types:
|
|||
|
+ % VHost = list() -> "jabber.example.org"
|
|||
|
+ % DateN = list() -> "2007-02-12"
|
|||
|
+ {get_dates, 1},
|
|||
|
+
|
|||
|
+ % called from start
|
|||
|
+ % it should return list with users settings for VHost in db
|
|||
|
+ % get_users_settings(VHost) -> [#user_settings1, #user_settings2, ... ] | error
|
|||
|
+ % Types:
|
|||
|
+ % VHost = list() -> "jabber.example.org"
|
|||
|
+ {get_users_settings, 1},
|
|||
|
+
|
|||
|
+ % called from many places
|
|||
|
+ % it should return User settings at VHost from db
|
|||
|
+ % get_user_settings(User, VHost) -> error | {ok, #user_settings}
|
|||
|
+ % Types:
|
|||
|
+ % User = list() -> "admin"
|
|||
|
+ % VHost = list() -> "jabber.example.org"
|
|||
|
+ {get_user_settings, 2},
|
|||
|
+
|
|||
|
+ % called from web admin
|
|||
|
+ % it should set User settings at VHost
|
|||
|
+ % set_user_settings(User, VHost, #user_settings) -> ok | error
|
|||
|
+ % Types:
|
|||
|
+ % User = list() -> "admin"
|
|||
|
+ % VHost = list() -> "jabber.example.org"
|
|||
|
+ {set_user_settings, 3},
|
|||
|
+
|
|||
|
+ % called from remove_user (ejabberd hook)
|
|||
|
+ % it should remove user messages and settings at VHost
|
|||
|
+ % drop_user(User, VHost) -> ok | error
|
|||
|
+ % Types:
|
|||
|
+ % User = list() -> "admin"
|
|||
|
+ % VHost = list() -> "jabber.example.org"
|
|||
|
+ {drop_user, 2}
|
|||
|
+ ];
|
|||
|
+behaviour_info(_) ->
|
|||
|
+ undefined.
|
|||
|
--- web/ejabberd_web_admin-2.0.3.erl 2009-02-03 08:27:39.000000000 +0200
|
|||
|
+++ web/ejabberd_web_admin.erl 2009-02-03 08:40:57.000000000 +0200
|
|||
|
@@ -1514,25 +1514,31 @@
|
|||
|
|
|||
|
|
|||
|
user_parse_query(User, Server, Query) ->
|
|||
|
- case lists:keysearch("chpassword", 1, Query) of
|
|||
|
- {value, _} ->
|
|||
|
- case lists:keysearch("password", 1, Query) of
|
|||
|
- {value, {_, undefined}} ->
|
|||
|
- error;
|
|||
|
- {value, {_, Password}} ->
|
|||
|
- ejabberd_auth:set_password(User, Server, Password),
|
|||
|
- ok;
|
|||
|
- _ ->
|
|||
|
- error
|
|||
|
- end;
|
|||
|
- _ ->
|
|||
|
- case lists:keysearch("removeuser", 1, Query) of
|
|||
|
- {value, _} ->
|
|||
|
- ejabberd_auth:remove_user(User, Server),
|
|||
|
- ok;
|
|||
|
- false ->
|
|||
|
- nothing
|
|||
|
- end
|
|||
|
+ lists:foldl(fun({Action, Value}, Acc) when Acc == nothing ->
|
|||
|
+ user_parse_query1(Action, User, Server, Query);
|
|||
|
+ ({Action, Value}, Acc) ->
|
|||
|
+ Acc
|
|||
|
+ end, nothing, Query).
|
|||
|
+
|
|||
|
+user_parse_query1("password", User, Server, Query) ->
|
|||
|
+ nothing;
|
|||
|
+user_parse_query1("chpassword", User, Server, Query) ->
|
|||
|
+ case lists:keysearch("password", 1, Query) of
|
|||
|
+ {value, {_, undefined}} ->
|
|||
|
+ error;
|
|||
|
+ {value, {_, Password}} ->
|
|||
|
+ ejabberd_auth:set_password(User, Server, Password),
|
|||
|
+ ok;
|
|||
|
+ _ ->
|
|||
|
+ error
|
|||
|
+ end;
|
|||
|
+user_parse_query1("removeuser", User, Server, Query) ->
|
|||
|
+ ejabberd_auth:remove_user(User, Server),
|
|||
|
+ ok;
|
|||
|
+user_parse_query1(Action, User, Server, Query) ->
|
|||
|
+ case ejabberd_hooks:run_fold(webadmin_user_parse_query, Server, [], [Action, User, Server, Query]) of
|
|||
|
+ [] -> nothing;
|
|||
|
+ Res -> Res
|
|||
|
end.
|
|||
|
|
|||
|
|
|||
|
--- mod_muc/mod_muc_room-2.0.3.erl 2009-02-03 08:27:59.000000000 +0200
|
|||
|
+++ mod_muc/mod_muc_room.erl 2009-02-03 08:37:26.000000000 +0200
|
|||
|
@@ -695,6 +695,12 @@
|
|||
|
handle_sync_event({change_config, Config}, _From, StateName, StateData) ->
|
|||
|
{result, [], NSD} = change_config(Config, StateData),
|
|||
|
{reply, {ok, NSD#state.config}, StateName, NSD};
|
|||
|
+handle_sync_event({get_jid_nick, Jid}, _From, StateName, StateData) ->
|
|||
|
+ R = case ?DICT:find(jlib:jid_tolower(Jid), StateData#state.users) of
|
|||
|
+ error -> [];
|
|||
|
+ {ok, {user, _, Nick, _, _}} -> Nick
|
|||
|
+ end,
|
|||
|
+ {reply, R, StateName, StateData};
|
|||
|
handle_sync_event(_Event, _From, StateName, StateData) ->
|
|||
|
Reply = ok,
|
|||
|
{reply, Reply, StateName, StateData}.
|
|||
|
--- msgs/uk-2.0.3.msg 2009-01-14 11:54:15.000000000 +0200
|
|||
|
+++ msgs/uk.msg 2009-02-03 08:26:20.000000000 +0200
|
|||
|
@@ -388,6 +388,35 @@
|
|||
|
% mod_offline_odbc.erl
|
|||
|
{"Your contact offline message queue is full. The message has been discarded.", "Черга повідомлень, що не були доставлені, переповнена. Повідомлення не було збережено."}.
|
|||
|
|
|||
|
+% mod_logdb
|
|||
|
+{"Users Messages", "Повідомлення користувачів"}.
|
|||
|
+{"Date", "Дата"}.
|
|||
|
+{"Count", "Кількість"}.
|
|||
|
+{"Logged messages for ", "Збережені повідомлення для "}.
|
|||
|
+{" at ", " за "}.
|
|||
|
+{"No logged messages for ", "Відсутні повідомлення для "}.
|
|||
|
+{"Date, Time", "Дата, Час"}.
|
|||
|
+{"Direction: Jid", "Напрямок: Jid"}.
|
|||
|
+{"Subject", "Тема"}.
|
|||
|
+{"Body", "Текст"}.
|
|||
|
+{"Messages", "Повідомлення"}.
|
|||
|
+{"Filter Selected", "Відфільтрувати виділені"}.
|
|||
|
+{"Do Not Log Messages", "Не зберігати повідомлення"}.
|
|||
|
+{"Log Messages", "Зберігати повідомлення"}.
|
|||
|
+{"Messages logging engine", "Система збереження повідомлень"}.
|
|||
|
+{"Default", "За замовчуванням"}.
|
|||
|
+{"Set logging preferences", "Вкажіть налагоджування збереження повідомлень"}.
|
|||
|
+{"Messages logging engine users", "Користувачі системи збереження повідомлень"}.
|
|||
|
+{"Messages logging engine settings", "Налагоджування системи збереження повідомлень"}.
|
|||
|
+{"Set run-time settings", "Вкажіть поточні налагоджування"}.
|
|||
|
+{"Groupchat messages logging", "Збереження повідомлень типу groupchat"}.
|
|||
|
+{"Jids/Domains to ignore", "Ігнорувати наступні jids/домени"}.
|
|||
|
+{"Purge messages older than (days)", "Видаляти повідомлення старіші ніж (дні)"}.
|
|||
|
+{"Poll users settings (seconds)", "Оновлювати налагоджування користувачів кожні (секунд)"}.
|
|||
|
+{"Drop", "Видаляти"}.
|
|||
|
+{"Do not drop", "Не видаляти"}.
|
|||
|
+{"Drop messages on user removal", "Видаляти повідомлення під час видалення користувача"}.
|
|||
|
+
|
|||
|
% Local Variables:
|
|||
|
% mode: erlang
|
|||
|
% End:
|
|||
|
--- msgs/ru-2.0.3.msg 2009-01-14 11:54:15.000000000 +0200
|
|||
|
+++ msgs/ru.msg 2009-02-03 08:25:31.000000000 +0200
|
|||
|
@@ -388,6 +388,35 @@
|
|||
|
% mod_offline_odbc.erl
|
|||
|
{"Your contact offline message queue is full. The message has been discarded.", "Очередь недоставленных сообщений Вашего адресата переполнена. Сообщение не было сохранено."}.
|
|||
|
|
|||
|
+% mod_logdb.erl
|
|||
|
+{"Users Messages", "Сообщения пользователей"}.
|
|||
|
+{"Date", "Дата"}.
|
|||
|
+{"Count", "Количество"}.
|
|||
|
+{"Logged messages for ", "Сохранённые cообщения для "}.
|
|||
|
+{" at ", " за "}.
|
|||
|
+{"No logged messages for ", "Отсутствуют сообщения для "}.
|
|||
|
+{"Date, Time", "Дата, Время"}.
|
|||
|
+{"Direction: Jid", "Направление: Jid"}.
|
|||
|
+{"Subject", "Тема"}.
|
|||
|
+{"Body", "Текст"}.
|
|||
|
+{"Messages", "Сообщения"}.
|
|||
|
+{"Filter Selected", "Отфильтровать выделенные"}.
|
|||
|
+{"Do Not Log Messages", "Не сохранять сообщения"}.
|
|||
|
+{"Log Messages", "Сохранять сообщения"}.
|
|||
|
+{"Messages logging engine", "Система логирования сообщений"}.
|
|||
|
+{"Default", "По умолчанию"}.
|
|||
|
+{"Set logging preferences", "Задайте настройки логирования"}.
|
|||
|
+{"Messages logging engine users", "Пользователи системы логирования сообщений"}.
|
|||
|
+{"Messages logging engine settings", "Настройки системы логирования сообщений"}.
|
|||
|
+{"Set run-time settings", "Задайте текущие настройки"}.
|
|||
|
+{"Groupchat messages logging", "Логирование сообщений типа groupchat"}.
|
|||
|
+{"Jids/Domains to ignore", "Игнорировать следующие jids/домены"}.
|
|||
|
+{"Purge messages older than (days)", "Удалять сообщения старее чем (дни)"}.
|
|||
|
+{"Poll users settings (seconds)", "Обновлять настройки пользователей через (секунд)"}.
|
|||
|
+{"Drop", "Удалять"}.
|
|||
|
+{"Do not drop", "Не удалять"}.
|
|||
|
+{"Drop messages on user removal", "Удалять сообщения при удалении пользователя"}.
|
|||
|
+
|
|||
|
% Local Variables:
|
|||
|
% mode: erlang
|
|||
|
% End:
|
|||
|
--- msgs/pl-2.0.3.msg 2009-01-14 11:54:15.000000000 +0200
|
|||
|
+++ msgs/pl.msg 2009-02-03 08:24:33.000000000 +0200
|
|||
|
@@ -408,6 +408,31 @@
|
|||
|
% mod_offline.erl
|
|||
|
{"Your contact offline message queue is full. The message has been discarded.", "Twoja kolejka wiadomoci offline jest pełna. Wiadomoć została odrzucona."}.
|
|||
|
|
|||
|
+% mod_logdb
|
|||
|
+{"Users Messages", "Wiadomości użytkownika"}.
|
|||
|
+{"Date", "Data"}.
|
|||
|
+{"Count", "Liczba"}.
|
|||
|
+{"Logged messages for ", "Zapisane wiadomości dla "}.
|
|||
|
+{" at ", " o "}.
|
|||
|
+{"No logged messages for ", "Brak zapisanych wiadomości dla "}.
|
|||
|
+{"Date, Time", "Data, Godzina"}.
|
|||
|
+{"Direction: Jid", "Kierunek: Jid"}.
|
|||
|
+{"Subject", "Temat"}.
|
|||
|
+{"Body", "Treść"}.
|
|||
|
+{"Messages","Wiadomości"}.
|
|||
|
+{"Filter Selected", "Odfiltruj zaznaczone"}.
|
|||
|
+{"Do Not Log Messages", "Nie zapisuj wiadomości"}.
|
|||
|
+{"Log Messages", "Zapisuj wiadomości"}.
|
|||
|
+{"Messages logging engine", "System zapisywania historii rozmów"}.
|
|||
|
+{"Default", "Domyślne"}.
|
|||
|
+{"Set logging preferences", "Ustaw preferencje zapisywania"}.
|
|||
|
+{"Messages logging engine settings", "Ustawienia systemu logowania"}.
|
|||
|
+{"Set run-time settings", "Zapisz ustawienia systemu logowania"}.
|
|||
|
+{"Groupchat messages logging", "Zapisywanie rozmów z konferencji"}.
|
|||
|
+{"Jids/Domains to ignore", "JID/Domena która ma być ignorowana"}.
|
|||
|
+{"Purge messages older than (days)", "Usuń wiadomości starsze niż (w dniach)"}.
|
|||
|
+{"Poll users settings (seconds)", "Czas aktualizacji preferencji użytkowników (sekundy)"}.
|
|||
|
+
|
|||
|
% Local Variables:
|
|||
|
% mode: erlang
|
|||
|
% End:
|
|||
|
--- msgs/nl-2.0.3.msg 2009-01-14 11:54:15.000000000 +0200
|
|||
|
+++ msgs/nl.msg 1970-01-01 03:00:00.000000000 +0300
|
|||
|
@@ -379,6 +379,19 @@
|
|||
|
% mod_proxy65/mod_proxy65_service.erl
|
|||
|
{"ejabberd SOCKS5 Bytestreams module", "ejabberd SOCKS5 Bytestreams module"}.
|
|||
|
|
|||
|
+% mod_logdb
|
|||
|
+{"Users Messages", "Gebruikersberichten"}.
|
|||
|
+{"Date", "Datum"}.
|
|||
|
+{"Count", "Aantal"}.
|
|||
|
+{"Logged messages for ", "Gelogde berichten van "}.
|
|||
|
+{" at ", " op "}.
|
|||
|
+{"No logged messages for ", "Geen gelogde berichten van "}.
|
|||
|
+{"Date, Time", "Datum en tijd"}.
|
|||
|
+{"Direction: Jid", "Richting: Jabber ID"}.
|
|||
|
+{"Subject", "Onderwerp"}.
|
|||
|
+{"Body", "Berichtveld"}.
|
|||
|
+{"Messages", "Berichten"}.
|
|||
|
+
|
|||
|
% Local Variables:
|
|||
|
% mode: erlang
|
|||
|
% End:
|
|||
|
--- ./mod_roster-2.0.3.erl 2009-02-03 08:28:12.000000000 +0200
|
|||
|
+++ mod_roster.erl 2009-02-03 08:32:14.000000000 +0200
|
|||
|
@@ -48,7 +48,7 @@
|
|||
|
-include("mod_roster.hrl").
|
|||
|
-include("web/ejabberd_http.hrl").
|
|||
|
-include("web/ejabberd_web_admin.hrl").
|
|||
|
-
|
|||
|
+-include("mod_logdb.hrl").
|
|||
|
|
|||
|
start(Host, Opts) ->
|
|||
|
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
|||
|
@@ -829,6 +829,14 @@
|
|||
|
Res = user_roster_parse_query(User, Server, Items1, Query),
|
|||
|
Items = mnesia:dirty_index_read(roster, US, #roster.us),
|
|||
|
SItems = lists:sort(Items),
|
|||
|
+
|
|||
|
+ Settings = case gen_mod:is_loaded(Server, mod_logdb) of
|
|||
|
+ true ->
|
|||
|
+ mod_logdb:get_user_settings(User, Server);
|
|||
|
+ false ->
|
|||
|
+ []
|
|||
|
+ end,
|
|||
|
+
|
|||
|
FItems =
|
|||
|
case SItems of
|
|||
|
[] ->
|
|||
|
@@ -876,7 +884,33 @@
|
|||
|
[?INPUTT("submit",
|
|||
|
"remove" ++
|
|||
|
ejabberd_web_admin:term_to_id(R#roster.jid),
|
|||
|
- "Remove")])])
|
|||
|
+ "Remove")]),
|
|||
|
+ case gen_mod:is_loaded(Server, mod_logdb) of
|
|||
|
+ true ->
|
|||
|
+ Peer = jlib:jid_to_string(R#roster.jid),
|
|||
|
+ A = lists:member(Peer, Settings#user_settings.dolog_list),
|
|||
|
+ B = lists:member(Peer, Settings#user_settings.donotlog_list),
|
|||
|
+ {Name, Value} =
|
|||
|
+ if
|
|||
|
+ A ->
|
|||
|
+ {"donotlog", "Do Not Log Messages"};
|
|||
|
+ B ->
|
|||
|
+ {"dolog", "Log Messages"};
|
|||
|
+ Settings#user_settings.dolog_default == true ->
|
|||
|
+ {"donotlog", "Do Not Log Messages"};
|
|||
|
+ Settings#user_settings.dolog_default == false ->
|
|||
|
+ {"dolog", "Log Messages"}
|
|||
|
+ end,
|
|||
|
+
|
|||
|
+ ?XAE("td", [{"class", "valign"}],
|
|||
|
+ [?INPUTT("submit",
|
|||
|
+ Name ++
|
|||
|
+ ejabberd_web_admin:term_to_id(R#roster.jid),
|
|||
|
+ Value)]);
|
|||
|
+ false ->
|
|||
|
+ ?X([])
|
|||
|
+ end
|
|||
|
+ ])
|
|||
|
end, SItems))])]
|
|||
|
end,
|
|||
|
[?XC("h1", ?T("Roster of ") ++ us_to_list(US))] ++
|
|||
|
@@ -958,11 +992,42 @@
|
|||
|
{"subscription", "remove"}],
|
|||
|
[]}]}}),
|
|||
|
throw(submitted);
|
|||
|
- false ->
|
|||
|
- ok
|
|||
|
- end
|
|||
|
-
|
|||
|
- end
|
|||
|
+ false ->
|
|||
|
+ case lists:keysearch(
|
|||
|
+ "donotlog" ++ ejabberd_web_admin:term_to_id(JID), 1, Query) of
|
|||
|
+ {value, _} ->
|
|||
|
+ Peer = jlib:jid_to_string(JID),
|
|||
|
+ Settings = mod_logdb:get_user_settings(User, Server),
|
|||
|
+ DNLL = case lists:member(Peer, Settings#user_settings.donotlog_list) of
|
|||
|
+ false -> lists:append(Settings#user_settings.donotlog_list, [Peer]);
|
|||
|
+ true -> Settings#user_settings.donotlog_list
|
|||
|
+ end,
|
|||
|
+ DLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.dolog_list),
|
|||
|
+ Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
|
|||
|
+ % TODO: check returned value
|
|||
|
+ ok = mod_logdb:set_user_settings(User, Server, Sett),
|
|||
|
+ throw(nothing);
|
|||
|
+ false ->
|
|||
|
+ case lists:keysearch(
|
|||
|
+ "dolog" ++ ejabberd_web_admin:term_to_id(JID), 1, Query) of
|
|||
|
+ {value, _} ->
|
|||
|
+ Peer = jlib:jid_to_string(JID),
|
|||
|
+ Settings = mod_logdb:get_user_settings(User, Server),
|
|||
|
+ DLL = case lists:member(Peer, Settings#user_settings.dolog_list) of
|
|||
|
+ false -> lists:append(Settings#user_settings.dolog_list, [Peer]);
|
|||
|
+ true -> Settings#user_settings.dolog_list
|
|||
|
+ end,
|
|||
|
+ DNLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.donotlog_list),
|
|||
|
+ Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
|
|||
|
+ % TODO: check returned value
|
|||
|
+ ok = mod_logdb:set_user_settings(User, Server, Sett),
|
|||
|
+ throw(nothing);
|
|||
|
+ false ->
|
|||
|
+ ok
|
|||
|
+ end % dolog
|
|||
|
+ end % donotlog
|
|||
|
+ end % remove
|
|||
|
+ end % validate
|
|||
|
end, Items),
|
|||
|
nothing.
|
|||
|
|
|||
|
--- ./mod_roster_odbc-2.0.3.erl 2009-02-03 08:28:26.000000000 +0200
|
|||
|
+++ mod_roster_odbc.erl 2009-02-03 08:47:04.000000000 +0200
|
|||
|
@@ -48,7 +48,7 @@
|
|||
|
-include("mod_roster.hrl").
|
|||
|
-include("web/ejabberd_http.hrl").
|
|||
|
-include("web/ejabberd_web_admin.hrl").
|
|||
|
-
|
|||
|
+-include("mod_logdb.hrl").
|
|||
|
|
|||
|
start(Host, Opts) ->
|
|||
|
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
|||
|
@@ -937,6 +937,14 @@
|
|||
|
Res = user_roster_parse_query(User, Server, Items1, Query),
|
|||
|
Items = get_roster(LUser, LServer),
|
|||
|
SItems = lists:sort(Items),
|
|||
|
+
|
|||
|
+ Settings = case gen_mod:is_loaded(Server, mod_logdb) of
|
|||
|
+ true ->
|
|||
|
+ mod_logdb:get_user_settings(User, Server);
|
|||
|
+ false ->
|
|||
|
+ []
|
|||
|
+ end,
|
|||
|
+
|
|||
|
FItems =
|
|||
|
case SItems of
|
|||
|
[] ->
|
|||
|
@@ -984,7 +992,33 @@
|
|||
|
[?INPUTT("submit",
|
|||
|
"remove" ++
|
|||
|
ejabberd_web_admin:term_to_id(R#roster.jid),
|
|||
|
- "Remove")])])
|
|||
|
+ "Remove")]),
|
|||
|
+ case gen_mod:is_loaded(Server, mod_logdb) of
|
|||
|
+ true ->
|
|||
|
+ Peer = jlib:jid_to_string(R#roster.jid),
|
|||
|
+ A = lists:member(Peer, Settings#user_settings.dolog_list),
|
|||
|
+ B = lists:member(Peer, Settings#user_settings.donotlog_list),
|
|||
|
+ {Name, Value} =
|
|||
|
+ if
|
|||
|
+ A ->
|
|||
|
+ {"donotlog", "Do Not Log Messages"};
|
|||
|
+ B ->
|
|||
|
+ {"dolog", "Log Messages"};
|
|||
|
+ Settings#user_settings.dolog_default == true ->
|
|||
|
+ {"donotlog", "Do Not Log Messages"};
|
|||
|
+ Settings#user_settings.dolog_default == false ->
|
|||
|
+ {"dolog", "Log Messages"}
|
|||
|
+ end,
|
|||
|
+
|
|||
|
+ ?XAE("td", [{"class", "valign"}],
|
|||
|
+ [?INPUTT("submit",
|
|||
|
+ Name ++
|
|||
|
+ ejabberd_web_admin:term_to_id(R#roster.jid),
|
|||
|
+ Value)]);
|
|||
|
+ false ->
|
|||
|
+ ?X([])
|
|||
|
+ end
|
|||
|
+ ])
|
|||
|
end, SItems))])]
|
|||
|
end,
|
|||
|
[?XC("h1", ?T("Roster of ") ++ us_to_list(US))] ++
|
|||
|
@@ -1066,11 +1100,42 @@
|
|||
|
{"subscription", "remove"}],
|
|||
|
[]}]}}),
|
|||
|
throw(submitted);
|
|||
|
- false ->
|
|||
|
- ok
|
|||
|
- end
|
|||
|
-
|
|||
|
- end
|
|||
|
+ false ->
|
|||
|
+ case lists:keysearch(
|
|||
|
+ "donotlog" ++ ejabberd_web_admin:term_to_id(JID), 1, Query) of
|
|||
|
+ {value, _} ->
|
|||
|
+ Peer = jlib:jid_to_string(JID),
|
|||
|
+ Settings = mod_logdb:get_user_settings(User, Server),
|
|||
|
+ DNLL = case lists:member(Peer, Settings#user_settings.donotlog_list) of
|
|||
|
+ false -> lists:append(Settings#user_settings.donotlog_list, [Peer]);
|
|||
|
+ true -> Settings#user_settings.donotlog_list
|
|||
|
+ end,
|
|||
|
+ DLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.dolog_list),
|
|||
|
+ Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
|
|||
|
+ % TODO: check returned value
|
|||
|
+ ok = mod_logdb:set_user_settings(User, Server, Sett),
|
|||
|
+ throw(nothing);
|
|||
|
+ false ->
|
|||
|
+ case lists:keysearch(
|
|||
|
+ "dolog" ++ ejabberd_web_admin:term_to_id(JID), 1, Query) of
|
|||
|
+ {value, _} ->
|
|||
|
+ Peer = jlib:jid_to_string(JID),
|
|||
|
+ Settings = mod_logdb:get_user_settings(User, Server),
|
|||
|
+ DLL = case lists:member(Peer, Settings#user_settings.dolog_list) of
|
|||
|
+ false -> lists:append(Settings#user_settings.dolog_list, [Peer]);
|
|||
|
+ true -> Settings#user_settings.dolog_list
|
|||
|
+ end,
|
|||
|
+ DNLL = lists:delete(jlib:jid_to_string(JID), Settings#user_settings.donotlog_list),
|
|||
|
+ Sett = Settings#user_settings{donotlog_list=DNLL, dolog_list=DLL},
|
|||
|
+ % TODO: check returned value
|
|||
|
+ ok = mod_logdb:set_user_settings(User, Server, Sett),
|
|||
|
+ throw(nothing);
|
|||
|
+ false ->
|
|||
|
+ ok
|
|||
|
+ end % dolog
|
|||
|
+ end % donotlog
|
|||
|
+ end % remove
|
|||
|
+ end % validate
|
|||
|
end, Items),
|
|||
|
nothing.
|
|||
|
|