-module(chat). -export([start/0, server/0]). % -include("/Program Files/yaws-1.68/include/yaws.hrl"). -include("/usr/local/lib/yaws/include/yaws.hrl"). -define(CHATLOGSIZE, 100). -define(DETSOPT, [{auto_save, 1000}]). start() -> application:start(inets), % proc_lib:start(?MODULE, server, []). spawn(?MODULE, server, []). server() -> % proc_lib:init_ack(ok), process_flag(trap_exit, true), register(chatserver, self()), dets:open_file(chatdata, ?DETSOPT), case dets:lookup(chatdata, logid) of [{logid, _}] -> true; % 既存セッションの後始末 _Else -> dets:insert(chatdata, {logid, 0}), % ログID最大値 dets:insert(chatdata, {log, []}), % ログ {Time, Name, Message} dets:insert(chatdata, {waiter, []}), % Comet待ち {Pid, SessionCookie, Id} dets:insert(chatdata, {member, []}) % 参加者 {SessionCookie, {Name, AccessTime}} end, loop(). loop() -> try receive {join, SessionCookie, Name} -> join(SessionCookie, Name); {part, SessionCookie} -> part(SessionCookie); {postmessage, SessionCookie, Pid, Message} -> postmessage(SessionCookie, Pid, Message); {getmessage, SessionCookie, Pid, Id} -> getmessage(SessionCookie, Pid, Id); {cancelmessage, Pid} -> cancelmessage(Pid) after 10 * 1000 -> checktimeoutmembers() end catch error:Err -> io:format("Error: ~p~p~n", [Err, erlang:get_stacktrace()]) end, checktimeoutmembers(), case get(notify) of 1 -> donotify(); _Else -> true end, loop(). join(SessionCookie, Name) -> [{member, Members}] = dets:lookup(chatdata, member), UniqName = getuniquename(Members, SessionCookie, Name, 0), dets:insert(chatdata, {member, lists:ukeymerge(1, [{SessionCookie, {UniqName, getnowsecond()}}], Members)}), case lists:keysearch(SessionCookie, 1, Members) of {value, {SessionCookie, {OldName, _AccessTime}}} -> if UniqName =:= OldName -> addmessage("System", io_lib:format("~s さんが再入室しました。", [UniqName])); true -> addmessage("System", io_lib:format("~s さんが ~s さんで再入室しました。", [OldName, UniqName])) end; _Else -> addmessage("System", io_lib:format("~s さんが入室しました。", [UniqName])) end. getuniquename(Members, SessionCookie, Name, Add) -> TryName = genname(Name, Add), NotUnique = lists:any( fun({UserSessionCookie, {UserName, _AccessTime}} = _E) -> (UserName == TryName) and (UserSessionCookie /= SessionCookie) end, Members ), case NotUnique of true -> getuniquename(Members, SessionCookie, Name, Add + 1); false -> TryName end. genname(Name, Add) -> case Add of 0 -> Name; _ -> Name ++ " (" ++ integer_to_list(Add) ++ ")" end. part(SessionCookie) -> [{member, Members}] = dets:lookup(chatdata, member), dets:insert(chatdata, {member, lists:keydelete(SessionCookie, 1, Members)}), case lists:keysearch(SessionCookie, 1, Members) of {value, {SessionCookie, {Name, _AccessTime}}} -> addmessage("System", io_lib:format("~s さんが退室しました。", [Name])); Else -> Else end. checktimeoutmembers() -> LastCheckTime = get(lastchecktime), Now = getnowsecond(), DoCheck = if LastCheckTime == undefined -> 1; LastCheckTime + 10 < Now -> 1; true -> 0 end, case DoCheck of 0 -> true; 1 -> [{member, Members}] = dets:lookup(chatdata, member), NewMembers = lists:filter( fun({_SessionCookie, {_Name, AccessTime}} = _E) -> case AccessTime + 45 >= Now of true -> true; false -> false end end, Members), dets:insert(chatdata, {member, NewMembers}), lists:filter( fun({_SessionCookie, {Name, AccessTime}} = _E) -> case AccessTime + 45 >= Now of true -> true; false -> addmessage("System", io_lib:format("~s さんがタイムアウトしました。", [Name])), false end end, Members), put(lastchecktime, Now) end. getmembers() -> [{member, Members}] = dets:lookup(chatdata, member), MemberList = lists:map( fun(E) -> {_SessionCookie, {Name, _AccessTime}} = E, {Name} end, Members), MemberList. touchmember(SessionCookie) -> [{member, Members}] = dets:lookup(chatdata, member), case lists:keysearch(SessionCookie, 1, Members) of {value, {SessionCookie, {Name, _AccessTime}}} -> NewMembers = lists:ukeymerge(1, [{SessionCookie, {Name, getnowsecond()}}], Members), dets:insert(chatdata, {member, NewMembers}); Else -> Else end. getmessage(SessionCookie, Pid, Id) -> touchmember(SessionCookie), replymessage(SessionCookie, Pid, Id). replymessage(SessionCookie, Pid, Id) -> [{log, Log}] = dets:lookup(chatdata, log), ReturnLog = lists:dropwhile( fun(E) -> {LogId, _} = E, LogId =< Id end, Log), [{waiter, Waiters}] = dets:lookup(chatdata, waiter), case length(ReturnLog) of 0 -> dets:insert(chatdata, {waiter, Waiters ++ [{Pid, SessionCookie, Id}]}); _ -> Pid ! {ok, SessionCookie, {getmembers(), ReturnLog}} end. cancelmessage(Pid) -> [{waiter, Waiters}] = dets:lookup(chatdata, waiter), dets:insert(chatdata, {waiter, lists:keydelete(Pid, 1, Waiters)}). postmessage(SessionCookie, Pid, Message) -> [{member, Members}] = dets:lookup(chatdata, member), Status = case lists:keysearch(SessionCookie, 1, Members) of {value, {SessionCookie, {Name, _AccessTime}}} -> addmessage(Name, Message), "ok"; _Else -> "err" end, Pid ! Status. addmessage(Name, Message) -> [{logid, CurrentId}] = dets:lookup(chatdata, logid), Id = CurrentId + 1, dets:insert(chatdata, {logid, Id}), [{log, Log}] = dets:lookup(chatdata, log), Log2 = if length(Log) > ?CHATLOGSIZE -> lists:nthtail(length(Log) - ?CHATLOGSIZE - 1, Log); true -> Log end, {{_Year, Month, Day}, {Hour, Minute, Second}} = calendar:now_to_local_time(now()), Time = lists:flatten(io_lib:format("~w/~w ~2.2.0w:~2.2.0w:~2.2.0w", [Month, Day, Hour, Minute, Second])), dets:insert(chatdata, {log, Log2 ++ [{Id, {Time, Name, lists:flatten(Message)}}]}), put(notify, 1). donotify() -> put(notify, 0), [{waiter, Waiters}] = dets:lookup(chatdata, waiter), lists:foreach(fun({Pid, SessionCookie, StartId} = _E) -> replymessage(SessionCookie, Pid, StartId) end, Waiters), dets:insert(chatdata, {waiter, []}). getnowsecond() -> calendar:datetime_to_gregorian_seconds(calendar:now_to_local_time(now())).