-
Notifications
You must be signed in to change notification settings - Fork 9
/
repeat_times.ex
146 lines (121 loc) · 3.9 KB
/
repeat_times.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
defmodule ExSDP.RepeatTimes do
@moduledoc """
This module represents the field of SDP that specifies
rebroadcasts of a session. Works directly in conjunction
with timing `t` parameter.
- active_duration - how long session will last
- repeat_interval - interval of session rebroadcast
- offsets - offset between scheduled rebroadcast
If `start_time` of `t` is set to today 3 pm, `active_duration` is set
to `3h`, `repeat_interval` is set to `14d` and `offsets` are `0 4d`
then the session will be rebroadcasted today at 3 pm and on Thursday 3 pm
every two week until `end_time` of param `t`.
For more details please see [RFC4566 Section 5.10](https://tools.ietf.org/html/rfc4566#section-5.10).
"""
use Bunch.Access
@enforce_keys [:repeat_interval, :active_duration, :offsets]
defstruct @enforce_keys
@type t :: %__MODULE__{
repeat_interval: non_neg_integer(),
active_duration: non_neg_integer(),
offsets: [non_neg_integer()]
}
@unit_mappings %{
"d" => 86_400,
"h" => 3600,
"m" => 60,
"s" => 1
}
@valid_keys Map.keys(@unit_mappings)
@type reason ::
:duration_nan
| :interval_nan
| :no_offsets
| :malformed_repeat
| {:invalid_offset | :invalid_unit, binary()}
@spec parse(binary()) :: {:ok, t()} | {:error, reason}
def parse(repeat) do
case String.split(repeat, " ") do
[interval, duration | offsets] = as_list ->
if compact?(as_list) do
parse_compact(as_list)
else
parse_explicit(interval, duration, offsets)
end
_malformed_repeat ->
{:error, :malformed_repeat}
end
end
defp compact?(parts) do
Enum.any?(parts, fn time ->
Enum.any?(@valid_keys, fn unit -> String.ends_with?(time, unit) end)
end)
end
defp parse_explicit(_interval, _duration, []), do: {:error, :no_offsets}
defp parse_explicit(interval, duration, offsets) do
with {interval, ""} <- Integer.parse(interval),
{duration, ""} <- Integer.parse(duration),
{:ok, offsets} <- process_offsets(offsets) do
explicit_repeat = %__MODULE__{
repeat_interval: interval,
active_duration: duration,
offsets: offsets
}
{:ok, explicit_repeat}
else
{:error, _} = error -> error
_other_error -> {:error, :malformed_repeat}
end
end
defp process_offsets(offsets, acc \\ [])
defp process_offsets([], acc), do: {:ok, Enum.reverse(acc)}
defp process_offsets([offset | rest], acc) do
case Integer.parse(offset) do
{offset, ""} when offset >= 0 -> process_offsets(rest, [offset | acc])
{_, _} -> {:error, {:invalid_offset, offset}}
end
end
defp parse_compact(list) do
with [_ | _] = result <- decode_compact(list) do
result
|> Enum.reverse()
|> build_compact()
end
end
defp decode_compact(list) do
Enum.reduce_while(list, [], fn
"0", acc ->
{:cont, [0 | acc]}
elem, acc ->
case Integer.parse(elem) do
{value, unit} when unit in @valid_keys ->
time = value * @unit_mappings[unit]
{:cont, [time | acc]}
{_, invalid_unit} ->
{:halt, {:error, {:invalid_unit, invalid_unit}}}
end
end)
end
defp build_compact(list)
defp build_compact([_, _]), do: {:error, :no_offsets}
defp build_compact([interval, duration | offsets]) do
compact = %__MODULE__{
repeat_interval: interval,
active_duration: duration,
offsets: offsets
}
{:ok, compact}
end
end
defimpl String.Chars, for: ExSDP.RepeatTimes do
@impl true
def to_string(repeat_times) do
serialized_fields =
[
Integer.to_string(repeat_times.repeat_interval),
Integer.to_string(repeat_times.active_duration)
] ++
Enum.map(repeat_times.offsets, &Integer.to_string/1)
Enum.join(serialized_fields, " ")
end
end