PageRenderTime 166ms CodeModel.GetById 60ms app.highlight 72ms RepoModel.GetById 29ms app.codeStats 0ms

/ucengine/src/models/uce_user.erl

http://github.com/AF83/ucengine
Erlang | 365 lines | 277 code | 43 blank | 45 comment | 2 complexity | 891d8de3a043c01ca2de43e3f4519158 MD5 | raw file
  1%%
  2%%  U.C.Engine - Unified Collaboration Engine
  3%%  Copyright (C) 2011 af83
  4%%
  5%%  This program is free software: you can redistribute it and/or modify
  6%%  it under the terms of the GNU Affero General Public License as published by
  7%%  the Free Software Foundation, either version 3 of the License, or
  8%%  (at your option) any later version.
  9%%
 10%%  This program is distributed in the hope that it will be useful,
 11%%  but WITHOUT ANY WARRANTY; without even the implied warranty of
 12%%  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13%%  GNU Affero General Public License for more details.
 14%%
 15%%  You should have received a copy of the GNU Affero General Public License
 16%%  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 17%%
 18-module(uce_user).
 19
 20% public api
 21-export([add/2,
 22         delete/2,
 23         update/2,
 24         list/1,
 25         get/2,
 26         get_by_name/2,
 27         exists/2,
 28         acl/3,
 29         add_role/3,
 30         delete_role/3,
 31         start_link/2]).
 32
 33% gen_server callbacks
 34-export([init/1,
 35         code_change/3,
 36         handle_call/3,
 37         handle_cast/2,
 38         handle_info/2,
 39         terminate/2]).
 40
 41-behaviour(gen_server).
 42
 43-include("uce.hrl").
 44
 45%
 46% Public api
 47%
 48-spec add(domain(), user()) -> {ok, uid()} | erlang:throw({error, conflict}).
 49add(Domain, #uce_user{id=none} = User) ->
 50    add(Domain, User#uce_user{id=utils:random()});
 51add(Domain, #uce_user{id=UId, name=Name} = User) ->
 52    case exists_by_name(Domain, Name) of
 53        true ->
 54            throw({error,conflict});
 55        false ->
 56            uce_role:add(Domain, #uce_role{id=UId}),
 57            DefaultRoles = [{"default", ""}, {UId, ""}],
 58            (db:get(?MODULE, Domain)):add(Domain,
 59                                          User#uce_user{roles=User#uce_user.roles ++ DefaultRoles}),
 60            {ok, UId}
 61    end.
 62
 63-spec delete(domain(), uid()) -> {ok, deleted} | erlang:throw({error, not_found}) | erlang:throw({error, any()}).
 64delete(Domain, Uid) ->
 65    case exists(Domain, Uid) of
 66        true ->
 67            % delete the default role
 68            case catch uce_role:delete(Domain, Uid) of
 69                {error, Reason} when Reason /= not_found ->
 70                    throw({error, Reason});
 71                {ok, deleted}->
 72                    case get_pid_of(Domain, Uid) of
 73                        {error, not_found} ->
 74                            ok;
 75                        {ok, Pid} ->
 76                            ok = gen_server:call(Pid, stop)
 77                    end,
 78                    (db:get(?MODULE, Domain)):delete(Domain, Uid)
 79            end;
 80        false ->
 81            throw({error, not_found})
 82    end.
 83
 84-spec update(domain(), user()) -> {ok, updated} | erlang:throw({error, not_found}).
 85update(Domain, #uce_user{id=Uid} = User) ->
 86    case exists(Domain, Uid) of
 87        true ->
 88            case get_pid_of(Domain, Uid) of
 89                {error, not_found} ->
 90                    ok;
 91                {ok, Pid} ->
 92                    gen_server:cast(Pid, {update_user, User})
 93            end,
 94            (db:get(?MODULE, Domain)):update(Domain, User);
 95        false ->
 96            throw({error, not_found})
 97   end.
 98
 99-spec list(domain()) -> {ok, list(user())}.
100list(Domain) ->
101    (db:get(?MODULE, Domain)):list(Domain).
102
103-spec get(domain(), uid()) -> {ok, user()} | erlang:throw({error, not_found}).
104get(Domain, Uid) ->
105    case get_pid_of(Domain, Uid) of
106        {error, not_found} ->
107            (db:get(?MODULE, Domain)):get(Domain, Uid);
108        {ok, Pid} ->
109            gen_server:call(Pid, get_user)
110    end.
111
112-spec get_by_name(domain(), list()) -> {ok, user()} | erlang:throw({error, not_found}).
113get_by_name(Domain, Name) ->
114    (db:get(?MODULE, Domain)):get_by_name(Domain, Name).
115
116-spec exists(domain(), uid()) -> true | false | erlang:throw({error, any()}).
117% "" value are used in uce_event:add
118% From or To can be empty
119exists(_Domain, "") ->
120    true;
121exists(Domain, Uid) ->
122    case get_pid_of(Domain, Uid) of
123        {error, not_found} ->
124            case catch get(Domain, Uid) of
125                {error, not_found} ->
126                    false;
127                {error, Reason} ->
128                    throw({error, Reason});
129                {ok, _User}->
130                    true
131            end;
132        {ok, _Pid} ->
133            true
134    end.
135
136-spec add_role(domain(), uid(), {role(), meeting_id()}) -> {ok, updated} | erlang:throw({error, not_found}).
137add_role(Domain, Uid, {Role, Location}) ->
138    % Just ensure the role and location exists
139    case uce_meeting:exists(Domain, Location) of
140        true ->
141            case uce_role:exists(Domain, Role) of
142                true ->
143                    {ok, User} = get(Domain, Uid),
144                    case lists:member({Role, Location}, User#uce_user.roles) of
145                        true ->
146                            {ok, updated};
147                        false ->
148                            update(Domain, User#uce_user{roles=(User#uce_user.roles ++ [{Role, Location}])})
149                    end;
150                false ->
151                    throw({error, not_found})
152            end;
153        false ->
154            throw({error, not_found})
155    end.
156
157-spec delete_role(domain(), uid(), {role(), meeting_id()}) -> {ok, updated} | erlang:throw({error, not_found}).
158delete_role(Domain, Id, {Role, Location}) ->
159    {ok, User} = get(Domain, Id),
160    Roles = case lists:member({Role, Location}, User#uce_user.roles) of
161                true ->
162                    lists:delete({Role, Location}, User#uce_user.roles);
163                false ->
164                    throw({error, not_found})
165            end,
166    update(Domain, User#uce_user{roles=Roles}).
167
168-spec acl(domain(), uid(), list()) -> {ok, list()}.
169acl(Domain, User, Location) ->
170    {ok, Record} = get(Domain, User),
171    ACL = lists:map(fun({RoleName, RoleLocation}) ->
172                            {ok, RoleACL} =
173                                if
174                                    RoleLocation == "" ->
175                                        uce_role:acl(Domain, RoleName);
176                                    RoleLocation == Location ->
177                                        uce_role:acl(Domain, RoleName);
178                                    true ->
179                                        {ok, []}
180                                end,
181                            RoleACL
182                    end,
183                    Record#uce_user.roles),
184    {ok, lists:flatten(ACL)}.
185
186%
187% gen server callbacks
188%
189
190-record(state, {
191          domain,
192          user,
193          presences = []
194         }).
195
196init([Domain, #uce_user{id=Uid} = User]) ->
197    gproc:add_local_name({Domain, uid, Uid}),
198    timer:send_after(config:get(timeout_refresh) * 1000, check_timeout),
199    {ok, #state{domain=Domain, user=User}}.
200
201handle_call(get_user, _From, #state{user=User} = State) ->
202    {reply, {ok, User}, State};
203
204handle_call({add_presence, #uce_presence{id=Sid}=Presence}, _From, #state{domain=Domain, presences=Presences} = State) ->
205    gproc:add_local_name({Domain, sid, Sid}),
206    {reply, ok, State#state{presences=[Presence|Presences]}};
207
208handle_call({get_presence, Sid}, _From, #state{presences=Presences} = State) ->
209    {reply, get_presence_by_sid(Sid, Presences), State};
210
211handle_call({update_presence, #uce_presence{id=Sid}=NewPresence}, _From, #state{presences=Presences} = State) ->
212    {Presence, NewPresences} = delete_presence_from_sid(Sid, Presences),
213    {reply, {ok, Presence}, State#state{presences=[NewPresence|NewPresences]}};
214
215%%
216%% supervisor:terminate_child doesn't work with simple_one_for_one in erlang < R14BO3
217%%
218handle_call(stop, _From, State) ->
219    {stop, "normal", ok, State}.
220
221handle_cast({update_user, User}, State) ->
222    {noreply, State#state{user=User}};
223
224handle_cast({add_stream, Sid}, #state{presences=Presences} = State) ->
225    {Presence, NewPresences} = delete_presence_from_sid(Sid, Presences),
226    NbStream = Presence#uce_presence.streams + 1,
227    {noreply, State#state{presences=[Presence#uce_presence{streams=NbStream}|NewPresences]}};
228
229handle_cast({remove_stream, Sid}, #state{presences=Presences} = State) ->
230    {Presence, NewPresences} = delete_presence_from_sid(Sid, Presences),
231    NbStream = Presence#uce_presence.streams - 1,
232    {noreply, State#state{presences=[Presence#uce_presence{streams=NbStream,
233                                                           last_activity=utils:now()}|NewPresences]}};
234
235handle_cast({delete_presence, Sid}, #state{domain=Domain, user=User, presences=Presences} = State) ->
236    {PresenceToDelete, NewPresences} = delete_presence_from_sid(Sid, Presences),
237    ok = disconnect_from_meetings(Domain, User, PresenceToDelete, NewPresences),
238    case NewPresences of
239        [] ->
240            {stop, normal, State#state{presences=NewPresences}};
241        _Other ->
242            {noreply, State#state{presences=NewPresences}}
243    end.
244
245handle_info(check_timeout, #state{domain=Domain,
246                                  presences=Presences} = State) ->
247    cleanup_presence(Domain, Presences, utils:now()),
248    timer:send_after(config:get(timeout_refresh) * 1000, check_timeout),
249    {noreply, State}.
250
251terminate(_Reason, _State) ->
252    ok.
253
254code_change(_OldVsn, State, _Extra) ->
255    {ok, State}.
256
257
258%
259% Private function
260%
261
262start_link(Domain, User) ->
263    gen_server:start_link(?MODULE, [Domain, User], []).
264
265-spec exists_by_name(domain(), list()) -> true | false | erlang:throw({error, any()}).
266exists_by_name(Domain, Name) ->
267    case catch get_by_name(Domain, Name) of
268        {error, not_found} ->
269            false;
270        {error, Reason} ->
271            throw({error, Reason});
272        {ok, _User}->
273            true
274    end.
275
276-spec get_presence_by_sid(sid(), list(presence())) -> {ok, presence()} | {error, not_found}.
277get_presence_by_sid(_Sid, []) ->
278    {error, not_found};
279get_presence_by_sid(Sid, [#uce_presence{id=Sid} = Presence|_Presences]) ->
280    {ok, Presence};
281get_presence_by_sid(Sid, [_Presence|Presences]) ->
282    get_presence_by_sid(Sid, Presences).
283
284get_pid_of(Domain, Uid) ->
285    case gproc:lookup_local_name({Domain, uid, Uid}) of
286        undefined ->
287            {error, not_found};
288        Pid ->
289            {ok, Pid}
290    end.
291
292%
293% Cleanup old presence
294%
295cleanup_presence(Domain, [#uce_presence{id=Sid,
296                                        streams=0,
297                                        last_activity=LastActivity,
298                                        timeout=Timeout}|Rest], Now) ->
299    if
300        LastActivity + (Timeout * 1000) < Now ->
301            {ok, deleted} = uce_presence:delete(Domain, Sid),
302            ?COUNTER(timeout);
303        true ->
304            nothing
305    end,
306    cleanup_presence(Domain, Rest, Now);
307cleanup_presence(Domain, [_Presence|Rest], Now) ->
308    cleanup_presence(Domain, Rest, Now);
309cleanup_presence(_Domain, [], _Now) ->
310    ok.
311
312
313%
314% Return all presence associated to the user
315%
316-spec get_all_meetings_of_user(list(presence()), uid()) -> list(meeting_id()) | [].
317get_all_meetings_of_user(Presences) ->
318    get_all_meetings_of_user(Presences, []).
319
320get_all_meetings_of_user([#uce_presence{meetings=Meetings}|Rest], Result) ->
321    get_all_meetings_of_user(Rest, Meetings ++ Result);
322% the end
323get_all_meetings_of_user([], Result) ->
324    Result.
325
326%
327% Cleanup presence in meetings
328%
329disconnect_from_meetings(Domain, #uce_user{id=Uid}, #uce_presence{meetings=Meetings}, Presences) ->
330    UserMeetings = sets:to_list(sets:subtract(sets:from_list(Meetings), sets:from_list(get_all_meetings_of_user(Presences)))),
331    clean_meetings(Domain, Uid, UserMeetings).
332
333clean_meetings(Domain, Uid, Meetings) ->
334    clean_meeting(Domain, Meetings, Uid),
335    uce_event:add(Domain, #uce_event{id=none,
336                                     from=Uid,
337                                     type="internal.presence.delete",
338                                     location=""}),
339    ok.
340
341clean_meeting(_Domain, [], _Uid) ->
342    ok;
343clean_meeting(Domain, [Meeting|Meetings], Uid) ->
344    try uce_event:add(Domain, #uce_event{id=none,
345                                         from=Uid,
346                                         type="internal.roster.delete",
347                                         location=Meeting}) of
348        {ok, _Id} ->
349            try uce_meeting:leave(Domain, Meeting, Uid) of
350                {ok, updated} ->
351                    ok
352            catch
353                {error, Reason} ->
354                    ?ERROR_MSG("Error when cleanup meeting presence of ~p : ~p", [Uid, Reason])
355            end
356    catch
357        {error, Reason} ->
358            ?ERROR_MSG("Error when cleanup roster presence of ~p : ~p", [Uid, Reason])
359    end,
360    clean_meeting(Domain, Meetings, Uid).
361
362delete_presence_from_sid(Sid, Presences) ->
363    {ok, Presence} = get_presence_by_sid(Sid, Presences),
364    NewPresences = lists:delete(Presence, Presences),
365    {Presence, NewPresences}.