summaryrefslogtreecommitdiff
path: root/src/mod_muc
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_muc')
-rw-r--r--src/mod_muc/mod_muc_room.erl277
1 files changed, 268 insertions, 9 deletions
diff --git a/src/mod_muc/mod_muc_room.erl b/src/mod_muc/mod_muc_room.erl
index f62880c4..7f7b8957 100644
--- a/src/mod_muc/mod_muc_room.erl
+++ b/src/mod_muc/mod_muc_room.erl
@@ -63,7 +63,7 @@
config = #config{},
users = ?DICT:new(),
affiliations = ?DICT:new(),
- history = lqueue_new(10),
+ history = lqueue_new(20),
subject = "",
subject_author = ""}).
@@ -831,7 +831,8 @@ add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) ->
add_online_user(From, Nick, Role, StateData)),
send_new_presence(From, NewState),
send_existing_presences(From, NewState),
- case send_history(From, NewState) of
+ Shift = count_stanza_shift(Nick, Els, NewState),
+ case send_history(From, Shift, NewState) of
true ->
ok;
_ ->
@@ -879,7 +880,259 @@ extract_password([{xmlelement, Name, Attrs, SubEls} = El | Els]) ->
extract_password([_ | Els]) ->
extract_password(Els).
+count_stanza_shift(Nick, Els, StateData) ->
+ HL = lqueue_to_list(StateData#state.history),
+ Since = extract_history(Els, "since"),
+ Shift0 = case Since of
+ false ->
+ 0;
+ _ ->
+ Sin = calendar:datetime_to_gregorian_seconds(Since),
+ count_seconds_shift(Sin, HL)
+ end,
+ Seconds = extract_history(Els, "seconds"),
+ Shift1 = case Seconds of
+ false ->
+ 0;
+ _ ->
+ Sec = calendar:datetime_to_gregorian_seconds(
+ calendar:now_to_universal_time(now())) - Seconds,
+ count_seconds_shift(Sec, HL)
+ end,
+ MaxStanzas = extract_history(Els, "maxstanzas"),
+ Shift2 = case MaxStanzas of
+ false ->
+ 0;
+ _ ->
+ count_maxstanzas_shift(MaxStanzas, HL)
+ end,
+ MaxChars = extract_history(Els, "maxchars"),
+ Shift3 = case MaxChars of
+ false ->
+ 0;
+ _ ->
+ count_maxchars_shift(Nick, MaxChars, HL)
+ end,
+ lists:max([Shift0, Shift1, Shift2, Shift3]).
+
+count_seconds_shift(Seconds, HistoryList) ->
+ lists:sum(
+ lists:map(
+ fun({_Nick, _Packet, _HaveSubject, TimeStamp, _Size}) ->
+ T = calendar:datetime_to_gregorian_seconds(TimeStamp),
+ if
+ T < Seconds ->
+ 1;
+ true ->
+ 0
+ end
+ end, HistoryList)).
+
+count_maxstanzas_shift(MaxStanzas, HistoryList) ->
+ S = length(HistoryList) - MaxStanzas,
+ if
+ S =< 0 ->
+ 0;
+ true ->
+ S
+ end.
+
+count_maxchars_shift(Nick, MaxSize, HistoryList) ->
+ NLen = string:len(Nick) + 1,
+ Sizes = lists:map(
+ fun({_Nick, _Packet, _HaveSubject, _TimeStamp, Size}) ->
+ Size + NLen
+ end, HistoryList),
+ calc_shift(MaxSize, Sizes).
+
+calc_shift(MaxSize, Sizes) ->
+ Total = lists:sum(Sizes),
+ calc_shift(MaxSize, Total, 0, Sizes).
+
+calc_shift(_MaxSize, _Size, Shift, []) ->
+ Shift;
+calc_shift(MaxSize, Size, Shift, [S | TSizes]) ->
+ if
+ MaxSize >= Size ->
+ Shift;
+ true ->
+ calc_shift(MaxSize, Size - S, Shift + 1, TSizes)
+ end.
+
+extract_history([], Type) ->
+ false;
+extract_history([{xmlelement, Name, Attrs, SubEls} = El | Els], Type) ->
+ case xml:get_attr_s("xmlns", Attrs) of
+ ?NS_MUC ->
+ AttrVal = xml:get_path_s(El,
+ [{elem, "history"}, {attr, Type}]),
+ case Type of
+ "since" ->
+ parse_datetime(AttrVal);
+ _ ->
+ case catch list_to_integer(AttrVal) of
+ {'EXIT', _} ->
+ false;
+ IntVal ->
+ if
+ IntVal >= 0 ->
+ IntVal;
+ true ->
+ false
+ end
+ end
+ end;
+ _ ->
+ extract_history(Els, Type)
+ end;
+extract_history([_ | Els], Type) ->
+ extract_history(Els, Type).
+
+% JEP-0082
+% yyyy-mm-ddThh:mm:ss[.sss]{Z|{+|-}hh:mm} -> {{yyyy, mm, dd}, {hh, mm, ss}} (UTC)
+parse_datetime(TimeStr) ->
+ DateTime = string:tokens(TimeStr, "T"),
+ case DateTime of
+ [Date, Time] ->
+ case parse_date(Date) of
+ false ->
+ false;
+ D ->
+ case parse_time(Time) of
+ false ->
+ false;
+ {T, TZH, TZM} ->
+ S = calendar:datetime_to_gregorian_seconds(
+ {D, T}),
+ calendar:gregorian_seconds_to_datetime(
+ S - TZH * 60 * 60 - TZM * 60 * 30)
+ end
+ end;
+ _ ->
+ false
+ end.
+
+% yyyy-mm-dd
+parse_date(Date) ->
+ YearMonthDay = string:tokens(Date, "-"),
+ case length(YearMonthDay) of
+ 3 ->
+ [Y, M, D] = lists:map(
+ fun(L)->
+ case catch list_to_integer(L) of
+ {'EXIT', _} ->
+ false;
+ Int ->
+ Int
+ end
+ end, YearMonthDay),
+ case catch calendar:valid_date(Y, M, D) of
+ true ->
+ {Y, M, D};
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end.
+% hh:mm:ss[.sss]TZD
+parse_time(Time) ->
+ case string:str(Time, "Z") of
+ 0 ->
+ parse_time_with_timezone(Time);
+ _ ->
+ [T | _] = string:tokens(Time, "Z"),
+ case parse_time1(T) of
+ false ->
+ false;
+ TT ->
+ {TT, 0, 0}
+ end
+ end.
+
+parse_time_with_timezone(Time) ->
+ case string:str(Time, "+") of
+ 0 ->
+ case string:str(Time, "-") of
+ 0 ->
+ false;
+ _ ->
+ parse_time_with_timezone(Time, "-")
+ end;
+ _ ->
+ parse_time_with_timezone(Time, "+")
+ end.
+
+parse_time_with_timezone(Time, Delim) ->
+ TTZ = string:tokens(Time, Delim),
+ case TTZ of
+ [T, TZ] ->
+ case parse_timezone(TZ) of
+ false ->
+ false;
+ {TZH, TZM} ->
+ case parse_time1(T) of
+ false ->
+ false;
+ TT ->
+ case Delim of
+ "-" ->
+ {TT, -TZH, -TZM};
+ "+" ->
+ {TT, TZH, TZM};
+ _ ->
+ false
+ end
+ end
+ end;
+ _ ->
+ false
+ end.
+
+parse_timezone(TZ) ->
+ case string:tokens(TZ, ":") of
+ [H, M] ->
+ case check_list([{H, 12}, {M, 60}]) of
+ {[H, M], true} ->
+ {H, M};
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end.
+
+parse_time1(Time) ->
+ case string:tokens(Time, ".") of
+ [HMS | _] ->
+ case string:tokens(HMS, ":") of
+ [H, M, S] ->
+ case check_list([{H, 24}, {M, 60}, {S, 60}]) of
+ {[H1, M1, S1], true} ->
+ {H1, M1, S1};
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end.
+
+check_list(List) ->
+ lists:mapfoldl(
+ fun({L, N}, B)->
+ case catch list_to_integer(L) of
+ {'EXIT', _} ->
+ {false, false};
+ Int when (Int >= 0) and (Int =< N) ->
+ {Int, B};
+ _ ->
+ {false, false}
+ end
+ end, true, List).
send_update_presence(JID, StateData) ->
LJID = jlib:jid_tolower(JID),
@@ -1087,22 +1340,28 @@ add_message_to_history(FromNick, Packet, StateData) ->
_ ->
true
end,
+ TimeStamp = calendar:now_to_universal_time(now()),
TSPacket = append_subtags(Packet,
- [jlib:timestamp_to_xml(
- calendar:now_to_universal_time(
- now()))]),
- Q1 = lqueue_in({FromNick, TSPacket, HaveSubject}, StateData#state.history),
+ [jlib:timestamp_to_xml(TimeStamp)]),
+ {xmlelement, Name, Attrs, Els} = TSPacket,
+ SPacket = jlib:replace_from_to(
+ jlib:jid_replace_resource(StateData#state.jid, FromNick),
+ StateData#state.jid,
+ TSPacket),
+ Size = string:len(xml:element_to_string(SPacket)),
+ Q1 = lqueue_in({FromNick, TSPacket, HaveSubject, TimeStamp, Size},
+ StateData#state.history),
StateData#state{history = Q1}.
-send_history(JID, StateData) ->
+send_history(JID, Shift, StateData) ->
lists:foldl(
- fun({Nick, Packet, HaveSubject}, B) ->
+ fun({Nick, Packet, HaveSubject, _TimeStamp, _Size}, B) ->
ejabberd_router:route(
jlib:jid_replace_resource(StateData#state.jid, Nick),
JID,
Packet),
B or HaveSubject
- end, false, lqueue_to_list(StateData#state.history)).
+ end, false, lists:nthtail(Shift, lqueue_to_list(StateData#state.history))).
send_subject(JID, StateData) ->