Created
February 28, 2018 15:42
-
-
Save maxlapshin/f9880426be9eb9063d07c64b43869093 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-module(config2_lexer). | |
-export([tokens/1, ast/1, ast2/1, maybe_int/1]). | |
-export([parse/1, parse/2]). | |
-export([cidr/1]). | |
parse(Bin) -> | |
parse(Bin, #{}). | |
parse(Bin, #{} = Options) when is_binary(Bin) -> | |
try ast3(Bin, Options) | |
catch | |
throw:E -> E; | |
error:E -> {error, {E, erlang:get_stacktrace()}} | |
end. | |
cidr(Bin) -> | |
ast2_parse_ipcidr(Bin, 0). | |
tokens(Path) when is_list(Path) -> | |
case file:read_file(Path) of | |
{ok, Bin} -> | |
tokens(Bin); | |
{error, E} -> | |
{error, E} | |
end; | |
tokens(Bin) -> | |
config(Bin, 1). | |
ast(Bin) -> | |
Tokens = tokens(Bin), | |
if | |
is_list(Tokens) -> | |
Tokens1 = [T || T <- Tokens, element(1,T) =/= comment], | |
ast_top(Tokens1); | |
true -> | |
Tokens | |
end. | |
% ast2 уже знает про конкретные опции и знает, когда и кто каким может быть, | |
% но идет строго по одной строчке | |
ast2(Bin) -> | |
AST1 = ast(Bin), | |
ast2_top(AST1). | |
% ast3 склеивает соседние строчки в более сложные структуры | |
ast3(Bin, Options) -> | |
AST2 = ast2(Bin), | |
run_ast3_transform(AST2, Options). | |
config(<<>>, _) -> []; | |
config(<<" ", Bin/binary>>, L) -> config(Bin, L); | |
config(<<"\t", Bin/binary>>, L) -> config(Bin, L); | |
config(<<"\n", Bin/binary>>, L) -> config(Bin, L+1); | |
config(<<"\r", Bin/binary>>, L) -> config(Bin, L); | |
config(<<";", Bin/binary>>, L) -> config(Bin, L); | |
config(<<"#", Bin/binary>>, L) -> comment(Bin, L, <<>>); | |
config(<<"%", Bin/binary>>, L) -> comment(Bin, L, <<>>); | |
config(<<"//", Bin/binary>>, L) -> comment(Bin, L, <<>>); | |
config(<<"}", Bin/binary>>, L) -> [{'}', L}|config(Bin, L)]; | |
config(<<C, Bin/binary>>, L) when C >= $a andalso C =< $z -> | |
command(Bin, L, <<C>>); | |
config(<<C, _/binary>>, L) -> throw({error, {invalid_symbol,L,C}}). | |
comment(<<"\n", Bin/binary>>, L, Acc) -> [{comment, L, Acc}|config(Bin, L+1)]; | |
comment(<<C, Bin/binary>>, L, Acc) -> comment(Bin, L, <<Acc/binary, C>>); | |
comment(<<>>, L, Acc) -> [{comment, L, Acc}]. | |
command(<<"\n", Bin/binary>>, L, Acc) -> [{command, L, binary_to_atom(Acc,latin1)}|params(Bin, L+1)]; | |
command(<<"{", _/binary>> = Bin, L, Acc) -> [{command, L, binary_to_atom(Acc,latin1)}|params(Bin, L)]; | |
command(<<";", Bin/binary>>, L, Acc) -> [{command, L, binary_to_atom(Acc,latin1)},{';', L}|config(Bin, L)]; | |
command(<<"}",_/binary>> = Bin, L, Acc) -> [{command, L, binary_to_atom(Acc,latin1)},{';', L}|config(Bin, L)]; | |
command(<<C, Bin/binary>>, L, Acc) when C == $\t orelse C == $\r orelse C == $\s -> [{command, L, binary_to_atom(Acc,latin1)}|params(Bin, L)]; | |
command(<<C, Bin/binary>>, L, Acc) -> command(Bin, L, <<Acc/binary, C>>); | |
command(<<>>, L, Acc) -> throw({error, {invalid_command,L,first20(Acc)}}). | |
first20(<<Acc:20/binary,Rest/binary>>) -> <<Acc/binary,"(",(integer_to_binary(size(Rest)))/binary," more bytes)">>; | |
first20(Acc) -> Acc. | |
params(<<"#", Bin/binary>>, L) -> comment(Bin, L, <<>>); | |
params(<<"{", Bin/binary>>, L) -> [{'{', L}|config(Bin, L)]; | |
params(<<"}", Bin/binary>>, L) -> [{'}', L}|config(Bin, L)]; | |
params(<<";", Bin/binary>>, L) -> [{';', L}|config(Bin, L)]; | |
params(<<"\n", Bin/binary>>, L) -> params(Bin, L+1); | |
params(<<C, Bin/binary>>, L) when C == $\t orelse C == $\r orelse C == $\s -> params(Bin, L); | |
params(<<$", Bin/binary>>, L) -> quoted_string(Bin, L, L, <<>>); | |
params(<<>>, _L) -> []; | |
params(<<C, Bin/binary>>, L) -> param(Bin, L, <<C>>). | |
param(<<"\n", Bin/binary>>, L, Acc) -> [{string, L, Acc}|params(Bin, L+1)]; | |
param(<<"{", _/binary>> = Bin, L, Acc) -> [{string, L, Acc}|params(Bin, L)]; | |
param(<<";", Bin/binary>>, L, Acc) -> [{string, L, Acc},{';',L}|config(Bin, L)]; | |
param(<<"}", _/binary>> = Bin, L, Acc) -> [{string, L, Acc},{';',L}|config(Bin, L)]; | |
param(<<C, Bin/binary>>, L, Acc) when C == $\t orelse C == $\r orelse C == $\s -> [{string, L, Acc}|params(Bin, L)]; | |
param(<<$=,$", Bin/binary>>, L, Acc) -> quoted_param(Bin, L, L, <<Acc/binary, "=">>); | |
param(<<C, Bin/binary>>, L, Acc) -> param(Bin, L, <<Acc/binary, C>>); | |
param(<<>>, L, Acc) -> [{string,L,Acc}]. | |
quoted_param(<<$", Bin/binary>>, L0, L, Acc) -> [{string, L0, Acc}|params(Bin, L)]; | |
quoted_param(<<"\n", Bin/binary>>, L0, L, Acc) -> quoted_param(Bin, L0, L+1, <<Acc/binary,"\n">>); | |
quoted_param(<<$\\,$", Bin/binary>>, L0, L, Acc) -> quoted_param(Bin, L0, L, <<Acc/binary,$">>); | |
quoted_param(<<C, Bin/binary>>, L0, L, Acc) -> quoted_param(Bin, L0, L, <<Acc/binary,C>>). | |
quoted_string(<<$", Bin/binary>>, L0, L, Acc) -> [{string, L0, Acc}|params(Bin, L)]; | |
quoted_string(<<"\n", Bin/binary>>, L0, L, Acc) -> quoted_string(Bin, L0, L+1, <<Acc/binary,"\n">>); | |
quoted_string(<<$\\,$", Bin/binary>>, L0, L, Acc) -> quoted_string(Bin, L0, L, <<Acc/binary,$">>); | |
quoted_string(<<$\\,$\\, Bin/binary>>, L0, L, Acc) -> quoted_string(Bin, L0, L, <<Acc/binary,$\\>>); | |
quoted_string(<<C, Bin/binary>>, L0, L, Acc) -> quoted_string(Bin, L0, L, <<Acc/binary,C>>). | |
maybe_int(<<"16#", Val/binary>>) -> binary_to_integer(Val, 16); | |
maybe_int(<<"0x", Val/binary>>) -> binary_to_integer(Val, 16); | |
maybe_int(<<I, _/binary>> = Bin) when I >= $0 andalso I =< $9 -> | |
case maybe_int(Bin, 0) of | |
false -> | |
Bin; | |
Result -> | |
Result | |
end; | |
maybe_int(<<"true">>) -> true; | |
maybe_int(<<"false">>) -> false; | |
maybe_int(<<"off">>) -> false; | |
maybe_int(V) -> V. | |
maybe_int(<<I, Bin/binary>>, Acc) when I >= $0 andalso I =< $9 -> | |
maybe_int(Bin, Acc*10 + I - $0); | |
maybe_int(<<"G">>, Acc) -> {size,Acc*1024*1024*1024}; | |
maybe_int(<<"M">>, Acc) -> {size,Acc*1024*1024}; | |
maybe_int(<<"m">>, Acc) -> {size,Acc*1000*1000}; | |
maybe_int(<<"K">>, Acc) -> {size,Acc*1024}; | |
maybe_int(<<"k">>, Acc) -> {size,Acc*1000}; | |
maybe_int(<<"d">>, Acc) -> {time,Acc*24*3600}; | |
maybe_int(<<"h">>, Acc) -> {time,Acc*3600}; | |
maybe_int(<<"s">>, Acc) -> {time,Acc}; | |
maybe_int(<<"%">>, Acc) -> {percent,Acc}; | |
maybe_int(<<_, _/binary>>, _Acc) -> false; | |
maybe_int(<<>>, Acc) -> Acc. | |
-record(entry, { | |
command, | |
line, | |
name, | |
options | |
}). | |
-record(section, { | |
command, | |
line, | |
name, | |
options, | |
section = [] | |
}). | |
ast_top([{command, L, Command}, {string, _, S}, {';', _}|Tokens]) -> | |
[#entry{command = Command, line = L, name = S}|ast_top(Tokens)]; | |
ast_top([{command, L, Command}, {';', _}|Tokens]) -> | |
[#entry{command = Command, line = L}|ast_top(Tokens)]; | |
ast_top([{command, L, Command}, {'{', _}|Tokens]) -> | |
{Section, Tokens1} = ast_section(Tokens), | |
[#section{command = Command,line = L, section = Section}|ast_top(Tokens1)]; | |
ast_top([{command, L, Command}, {string, _, S}, {'{', _}|Tokens]) -> | |
{Section, Tokens1} = ast_section(Tokens), | |
[#section{command = Command,line = L,name = S,section = Section}|ast_top(Tokens1)]; | |
ast_top([{command, L, Command}, {string, _, S} |Tokens]) -> | |
case ast_options(Tokens) of | |
{Options, Tokens1} -> | |
[#entry{command = Command,line = L, name = S, options = Options}|ast_top(Tokens1)]; | |
{section, Options, Tokens1} -> | |
{Section, Tokens2} = ast_section(Tokens1), | |
[#section{command = Command, line = L, name = S, options = Options, section = Section}|ast_top(Tokens2)] | |
end; | |
ast_top([]) -> | |
[]. | |
ast_section([{'}',_}| Tokens]) -> | |
{[], Tokens}; | |
ast_section([{command, L, Command}, {string, _, S}, {';', _}|Tokens]) -> | |
{Section,Tokens1} = ast_section(Tokens), | |
{[#entry{command = Command, line = L, name = S}|Section], Tokens1}; | |
ast_section([{command, L, Command}, {';', _}|Tokens]) -> | |
{Section,Tokens1} = ast_section(Tokens), | |
{[#entry{command = Command, line = L}|Section], Tokens1}; | |
ast_section([{command, L, Command}, {'}', _}|Tokens]) -> | |
{[#entry{command = Command, line = L}], Tokens}; | |
ast_section([{command, L, Command}, {string, _, S} |Tokens]) -> | |
{Options, Tokens1} = ast_options(Tokens), | |
{Section,Tokens2} = ast_section(Tokens1), | |
{ [#entry{command = Command,line=L, name=S,options=Options}|Section], Tokens2}. | |
ast_options([{'{',_}|Tokens]) -> | |
{section, [], Tokens}; | |
ast_options([{'}',_}|_] = Tokens) -> | |
{[], Tokens}; | |
ast_options([{command,_,_}|_] = Tokens) -> | |
{[], Tokens}; | |
ast_options([]) -> | |
{[], []}; | |
ast_options([{';',_}|Tokens]) -> | |
{[], Tokens}; | |
ast_options([{string,_,S}|Tokens]) -> | |
case ast_options(Tokens) of | |
{Opts,Tokens1} -> | |
{[S|Opts], Tokens1}; | |
{section, Opts, Tokens1} -> | |
{section, [S|Opts], Tokens1} | |
end. | |
ast2_top([#entry{} = Entry|AST]) -> | |
[ast2_top_entry(Entry)|ast2_top(AST)]; | |
ast2_top([#section{} = S|AST]) -> | |
[ast2_top_section(S)|ast2_top(AST)]; | |
ast2_top([]) -> | |
[]. | |
ast2_top_entry(#entry{command=C,line=L,name=V}) when | |
C == http; C == https; C == admin_http; C == admin_https; C == rtsp; C == rtsps; | |
C == rtmp; C == rtmps; C == mysql; C == snmp -> | |
case parse_listener(V) of | |
{ok, I} -> | |
{C,I}; | |
undefined -> | |
throw({error, {invalid_listener,L,C,V}}) | |
end; | |
ast2_top_entry(#entry{command=C,line=L,name=V}) when | |
C == max_sessions; C == channel_limit; C == cpu_limit -> | |
try binary_to_integer(V) of | |
I -> | |
{C,I} | |
catch | |
_:_ -> throw({error, {invalid_spec,L,C,V}}) | |
end; | |
ast2_top_entry(#entry{command = log_level} = E) -> | |
ast2_top_entry(E#entry{command = loglevel}); | |
ast2_top_entry(#entry{command = loglevel, line = L, name = V}) -> | |
Level = case V of | |
<<"debug">> -> debug; | |
<<"info">> -> info; | |
<<"notice">> -> notice; | |
<<"warning">> -> warning; | |
<<"error">> -> error; | |
<<"alert">> -> alert; | |
<<"critical">> -> critical; | |
_ -> throw({error, {invalid_loglevel,L,V}}) | |
end, | |
{loglevel, Level}; | |
ast2_top_entry(#entry{command =C,line=L,name=V}) when C == logrequests; C == log_requests -> | |
{log_requests, ast2_bool(V,L)}; | |
ast2_top_entry(#entry{command = no_auto_token}) -> | |
{autogenerate_token, false}; | |
ast2_top_entry(#entry{command = auto_token, line = L, name = V}) -> | |
Flag = case V of | |
<<"false">> -> false; | |
<<"blank">> -> blank; | |
_ -> throw({error, {invalid_auto_token,L,V}}) | |
end, | |
{autogenerate_token, Flag}; | |
ast2_top_entry(#entry{command=C,line=L,name=V1,options=Values}) when C == edit_auth; C == view_auth -> | |
case Values of | |
[V2] -> {C,#{login => V1, password => V2}}; | |
_ -> throw({error, {invalid_auth,L,C,V1,Values}}) | |
end; | |
ast2_top_entry(#entry{command=web_script=C,line=L,name=V1,options=Values}) -> | |
case Values of | |
[V2|Opts] -> {web_script, #{prefix => V1, path => V2, extra => maps:from_list(ast2_keyvalues(Opts,L))}}; | |
_ -> throw({error, {invalid_web_script,L,C,V1,Values}}) | |
end; | |
ast2_top_entry(#entry{command = total_bandwidth=C, line = L,name = V1}) -> | |
case maybe_int(V1) of | |
{size,S} -> {total_bandwidth,S}; | |
_ -> throw({error, {invalid_bandwidth,L,C,V1}}) | |
end; | |
ast2_top_entry(#entry{command = plugin, line = L, name = V, options = undefined}) -> | |
ast2_top_section(#section{command = plugin, name = binary_to_atom(V,latin1), line = L, section = []}); | |
ast2_top_entry(#entry{command = dvr, line = L, name = Name, options = Values}) -> | |
case Values of | |
[_|_] -> | |
{dvr, Name, parse_dvr(Values, L)}; | |
_ -> | |
throw({error, {invalid_dvr,L,Name}}) | |
end; | |
ast2_top_entry(#entry{command = http_proxy, name = V, line = L, options = Values}) -> | |
case Values of | |
[URL|KVlist] -> | |
{http_proxy, maps:merge(#{prefix => V, url => URL}, maps:from_list(ast2_keyvalues(KVlist, L)))}; | |
_ -> | |
throw({error, {invalid_http_proxy,L,Values}}) | |
end; | |
ast2_top_entry(#entry{command = server} = Entry) -> | |
ast2_top_entry(Entry#entry{command = peer}); | |
ast2_top_entry(#entry{command = peer, name = V, options = undefined}) -> | |
case re:run(V, "^(.+)\\:(\\d+)$", [{capture,all_but_first,binary}]) of | |
nomatch -> {peer, V, #{}}; | |
{match, [V1,P1]} -> {peer, V1, #{port => binary_to_integer(P1)}} | |
end; | |
ast2_top_entry(#entry{command = live, name = V}) -> | |
apply_stream_options(live, V, [{static,false}]); | |
ast2_top_entry(#entry{command = cdnproxy}) -> | |
{cdnproxy, true}; | |
ast2_top_entry(#entry{command = vsaas}) -> | |
{plugin, vsaas, #{}}; | |
ast2_top_entry(#entry{command = ondemand, name = V, options = [U|Opts], line = L}) -> | |
apply_stream_options(stream, V, [{urls, [maps:from_list([{url,U}] ++ ast2_url_options(ast2_keyvalues(Opts,L)))]},{static,false}]); | |
ast2_top_entry(#entry{command = stream, name = V, options = [U|Opts], line = L}) -> | |
apply_stream_options(stream, V, [{urls, [maps:from_list([{url,U}] ++ ast2_url_options(ast2_keyvalues(Opts,L)))]},{static,true}]); | |
ast2_top_entry(#entry{command = file, name = V, options = [U|Opts], line = L}) -> | |
apply_stream_options(file, V, [{urls, [maps:from_list([{url,remove_last_slash(U)}] ++ ast2_url_options(ast2_keyvalues(Opts,L)))]}]); | |
ast2_top_entry(#entry{command = rewrite, name = N, options = Opts} = E) -> | |
case re:run(N, <<"(.+)/\\*$">>, [{capture,all_but_first,binary}]) of | |
nomatch -> | |
ast2_top_entry(E#entry{command = ondemand}); | |
{match, [N1]} when Opts =/= undefined -> | |
apply_stream_options(dynamic, N1, [{urls, [#{url => V} || V <- Opts]},{static,false}] ) | |
end; | |
ast2_top_entry(#entry{command = cache, name = Name, options = Opts, line = L}) -> | |
{cache, Name, parse_cache(Opts, L)}; | |
ast2_top_entry(#entry{command = api_allowed_from, line = L} = Entry) -> | |
{api_allowed_from, [ast2_parse_ipcidr(V, L) || V <- ast2_values(Entry)]}; | |
ast2_top_entry(#entry{command = notify, name = URL, options = Opts, line = L}) when Opts == undefined orelse Opts == [] -> | |
ast2_top_section(#section{command = notify, name = URL, line = L, section = [ | |
#entry{command = sink, name = URL, line = L}, | |
#entry{command = buffer, name = <<"off">>, line = L} | |
]}); | |
ast2_top_entry(#entry{command = source, name = N, options = Options, line = L}) -> | |
ast2_top_section(#section{command = source, name=N, options=Options, line=L}); | |
ast2_top_entry(#entry{command = C} = Entry) when C == retroview; C == auth; C == wwwroot; C == url_prefix; C == init_script; | |
C == aliaser; C == auth_token; C == pulsedb; C == session_log; C == include; C == cluster_key; | |
C == allowed_countries; C == disallowed_countries; C == domain; C == domains; C == meta | |
-> | |
ast2_entry(Entry); | |
ast2_top_entry(#entry{} = Entry) -> | |
error({invalid_entry,Entry#entry.line, Entry#entry.command, Entry}). | |
% ast2_entry(Entry). | |
ast2_top_section(#section{command = vsaas, name = V, line = L, section = Entries}) -> | |
V == undefined orelse throw({error,{invalid_vsaas_name,L,V}}), | |
{plugin, vsaas, maps:from_list([ast2_entry(E) || E <- Entries])}; | |
ast2_top_section(#section{command = C, name = N, section = Entries}) when C == stream; C == ondemand; C == file; C == live -> | |
Opts = case C of | |
ondemand -> [{static,false}]; | |
stream -> [{static,true}]; | |
live -> [{static,false}]; | |
file -> [] | |
end ++ [ast2_entry(E) || E <- Entries], | |
Cmd1 = case C of | |
ondemand -> stream; | |
live -> live; | |
_ -> C | |
end, | |
apply_stream_options(Cmd1, N, Opts); | |
ast2_top_section(#section{command = rewrite, name = N, section = Entries} = Section) -> | |
case re:run(N, <<"(.+)/\\*$">>, [{capture,all_but_first,binary}]) of | |
nomatch -> | |
ast2_top_section(Section#section{command = ondemand}); | |
{match, [N1]} -> | |
apply_stream_options(dynamic, N1, [{static,false}] ++ [ast2_entry(E) || E <- Entries]) | |
end; | |
ast2_top_section(#section{command = notify, name = N, options = undefined, section = Entries}) -> | |
{notify, N, make_notify([ast2_entry(E) || E <- Entries])}; | |
ast2_top_section(#section{command = source, name = N, options = Options, section = Entries}) -> | |
URLs = [N] ++ case Options of | |
undefined -> []; | |
_ -> Options | |
end, | |
Prefixes = [case re:run(URL, "[^:]+://[^/]+/(.*)$", [{capture,all_but_first,binary}]) of | |
{match, [Prefix]} -> <<Prefix/binary, "/">>; | |
nomatch -> <<>> | |
end || URL <- URLs], | |
Prefix = hd(Prefixes), | |
{source, URLs, make_options(config2_helpers:replace_meta([ast2_entry(E) || E <- Entries]++[{prefix,Prefix}]))}; | |
ast2_top_section(#section{command = plugin, name = N, section = Entries}) -> | |
Name = if is_binary(N) -> binary_to_atom(N,latin1); | |
is_atom(N) -> N | |
end, | |
{plugin,Name,maps:from_list([case ast2_entry(E) of | |
{auth, #{url := U}} -> {auth, U}; | |
Else -> Else | |
end || E <- Entries])}; | |
ast2_top_section(#section{command = server} = Section) -> | |
ast2_top_section(Section#section{command = peer}); | |
ast2_top_section(#section{command = peer, name = N, section = Entries}) -> | |
Opts = maps:from_list([ast2_entry(E) || E <- Entries]), | |
case re:run(N, "^(.+)\\:(\\d+)$", [{capture,all_but_first,binary}]) of | |
nomatch -> {peer, N, Opts}; | |
{match, [V1,P1]} -> {peer, V1, Opts#{port => binary_to_integer(P1)}} | |
end; | |
ast2_top_section(#section{command = C, name = N, section = Entries}) when C == none | |
-> | |
{C,N,[ast2_entry(E) || E <- Entries]}. | |
ast2_keyvalues(undefined,_L) -> | |
[]; | |
ast2_keyvalues(Opts,L) -> | |
[ast2_keyvalue(Opt, L, Opts) || Opt <- Opts]. | |
ast2_keyvalue(Opt, L, Opts) -> | |
case binary:split(Opt,<<"=">>) of | |
[<<"vb">>,<<"copy">>] -> | |
{video_bitrate,copy}; | |
[<<"ab">>,<<"copy">>] -> | |
{audio_bitrate,copy}; | |
[<<"vb">>,V] -> | |
case maybe_int(V) of | |
{size,S} -> {video_bitrate,S}; | |
_ -> {vb, V} | |
end; | |
[<<"ab">>,V] -> | |
case maybe_int(V) of | |
{size,S} -> {audio_bitrate,S}; | |
_ -> {ab, V} | |
end; | |
[K,V] -> {binary_to_atom(K,latin1),V}; | |
[_] -> throw({error, {invalid_key_value,L,Opt,Opts}}) | |
end. | |
ast2_entry(#entry{command = disabled}) -> {disabled, true}; | |
ast2_entry(#entry{command = dvr_offline} = E) -> | |
{dvr, DVR} = ast2_entry(E#entry{command = dvr}), | |
{dvr, DVR#{dvr_offline => true}}; | |
ast2_entry(#entry{command = dvr, name = <<"@", Name/binary>>}) -> | |
{external_dvr, Name}; | |
ast2_entry(#entry{command = dvr, line = L, name = Name, options = Values}) -> | |
Control = case Values of | |
undefined -> [Name]; | |
_ -> [Name|Values] | |
end, | |
{dvr, parse_dvr(Control, L)}; | |
ast2_entry(#entry{command = record_input, line = L, name = Name, options = Values}) -> | |
Control = case Values of | |
undefined -> [Name]; | |
_ -> [Name|Values] | |
end, | |
{record_input, parse_dvr(Control, L)}; | |
ast2_entry(#entry{command = C} = Entry) when C == only; C == except -> | |
{C, ast2_values(Entry)}; | |
ast2_entry(#entry{command = path, name = V, options = Options, line = L}) -> | |
Opts = ast2_url_options(ast2_keyvalues(Options, L)), | |
{path, maps:from_list([{url, remove_last_slash(V)}] ++ Opts)}; | |
ast2_entry(#entry{command = url, name = V, options = Options, line = L}) -> | |
Opts = ast2_url_options(ast2_keyvalues(Options, L)), | |
{url, maps:from_list([{url,V}] ++ Opts)}; | |
ast2_entry(#entry{command = drm, line = L, name = V, options = Opts}) -> | |
{drm, maps:merge(#{vendor => binary_to_atom(V,latin1)}, maps:from_list(ast2_keyvalues(Opts, L)))}; | |
ast2_entry(#entry{command = C, name = V, line = L}) when C == hds; C == hls; C == mpegts; C == rtmp; | |
C == rtsp; C == dash; C == m4s; C == m4f; C == mseld; C == webrtc; C == pulse -> | |
C1 = atom_to_binary(C,latin1), | |
{binary_to_atom(<<C1/binary, "_off">>, latin1), not ast2_bool(V,L)}; | |
ast2_entry(#entry{command = mpegts_pids, name = Pid1, options = Options, line = L}) -> | |
{mpegts_pids, parse_mpegts_pids(Pid1, Options, L)}; | |
ast2_entry(#entry{command = segments_count} = E) -> | |
ast2_entry(E#entry{command = segment_count}); | |
ast2_entry(#entry{command = client_timeout} = E) -> | |
ast2_entry(E#entry{command = clients_timeout}); | |
ast2_entry(#entry{command = segments} = E) -> | |
ast2_entry(E#entry{command = segment_count}); | |
ast2_entry(#entry{command = gop_duration, name = N}) -> | |
{gop_duration, binary_to_integer(N)*1000}; | |
ast2_entry(#entry{command = C, name = <<"false">>}) when C == source_timeout; C == clients_timeout; C == prepush -> | |
{C, false}; | |
ast2_entry(#entry{command = C, name = <<"off">>}) when C == source_timeout; C == clients_timeout; C == prepush -> | |
{C, false}; | |
ast2_entry(#entry{command = rtp, name = <<"udp">>}) -> | |
{rtp,udp}; | |
ast2_entry(#entry{command = C, line = L, name = I}) when C == retry_limit; C == max_bitrate; C == prepush; | |
C == clients_timeout; C == source_timeout; C == hls_buffer; C == segment_count; C == read_queue; | |
C == fetch_timeout; C == stale_timeout; | |
C == max_readers; C == channel_limit; C == cpu_limit; C == max_sessions -> | |
try binary_to_integer(I) of | |
V -> {C,V} | |
catch | |
_:_ -> throw({error, {invalid_value,L,C,I}}) | |
end; | |
ast2_entry(#entry{command = ad_inject, line = L} = Entry) -> | |
{ad_inject, maps:from_list(ast2_keyvalues(ast2_values(Entry),L))}; | |
ast2_entry(#entry{command = C, line = L} = Entry) when C == cluster_ingest; C == motion_detector -> | |
Opts = ast2_keyvalues(ast2_values(Entry),L), | |
Opts0 = case C of | |
motion_detector -> [{enabled, true}]; | |
_ -> [] | |
end, | |
Opts1 = Opts0 ++ case proplists:get_all_values(tag, Opts) of | |
[] -> Opts; | |
Tags -> [{tags,Tags}] ++ [{K,V} || {K,V} <- Opts, K =/= tag] | |
end, | |
Opts2 = case proplists:get_all_values(notify, Opts1) of | |
[] -> Opts1; | |
Notify -> [{notify,Notify}] ++ [{K,V} || {K,V} <- Opts1, K =/= notify] | |
end, | |
{C, maps:from_list(Opts2)}; | |
ast2_entry(#entry{command = C, line = L} = Entry) when C == transcoder -> | |
{C, ast2_keyvalues(ast2_values(Entry),L)}; | |
ast2_entry(#entry{command = no_prepush}) -> {prepush, false}; | |
ast2_entry(#entry{command = download}) -> {download, true}; | |
ast2_entry(#entry{command = publish_enabled}) -> {publish_enabled, true}; | |
ast2_entry(#entry{command = add_audio_only}) -> {add_audio_only, true}; | |
ast2_entry(#entry{command = thumbnails, line = L} = E) -> | |
Opts = maps:from_list(ast2_keyvalues(ast2_values(E), L)), | |
{thumbnails, Opts#{enabled => true}}; | |
ast2_entry(#entry{command = logo, line = L} = E) -> | |
Opts = maps:from_list(ast2_keyvalues(ast2_values(E), L)), | |
{logo, Opts}; | |
ast2_entry(#entry{command = url_prefix, name = <<"false">>}) -> | |
{url_prefix, false}; | |
ast2_entry(#entry{command = domains} = Entry) -> | |
{domains, ast2_values(Entry)}; | |
ast2_entry(#entry{command = C} = Entry) when C == allowed_countries; C == disallowed_countries -> | |
{C, [to_upcase(X) || X <- ast2_values(Entry)]}; | |
ast2_entry(#entry{command = C} = Entry) when C == paths; C == urls -> | |
{C, [#{url => E} || E <- ast2_values(Entry)]}; | |
ast2_entry(#entry{command = udp, name = UDP}) -> | |
{push, <<"udp://",UDP/binary>>}; | |
ast2_entry(#entry{command = tracks} = Entry) -> | |
{tracks, [binary_to_integer(I) || I <- ast2_values(Entry)]}; | |
ast2_entry(#entry{command = auth, name = URL, line = L, options = Opts}) -> | |
M1 = case Opts of | |
undefined -> #{}; | |
[] -> #{}; | |
_ -> #{extra => maps:from_list(ast2_keyvalues(Opts, L))} | |
end, | |
U = case URL of | |
<<"true">> -> true; | |
<<"false">> -> false; | |
<<"mocked">> -> mocked; | |
_ -> URL | |
end, | |
{auth, M1#{url => U}}; | |
ast2_entry(#entry{command = ad_schedule, name = N1, options = [N2]}) -> | |
{ad_schedule, #{schedule => N1, alias => N2}}; | |
ast2_entry(#entry{command = meta, name = N1, options = [N2]}) -> | |
{meta, binary_to_atom(N1,latin1), N2}; | |
ast2_entry(#entry{command = cache, name = <<"@",Cache/binary>>}) -> | |
{external_cache, Cache}; | |
ast2_entry(#entry{command = cache, line = L} = Entry) -> | |
{cache, parse_cache(ast2_values(Entry), L)}; | |
ast2_entry(#entry{command = segment_cache, line = L} = Entry) -> | |
{segment_cache, parse_cache(ast2_values(Entry), L)}; | |
ast2_entry(#entry{command = C, name = V, options = undefined}) -> | |
{C, V}; | |
ast2_entry(#entry{command = C, name = V, options = []}) -> | |
{C, V}; | |
ast2_entry(#entry{command = C, name = V, options = Options, line = L}) -> | |
{C,V, ast2_keyvalues(Options, L)}. | |
ast2_values(#entry{name = N, options = Opts}) -> | |
case N of | |
undefined -> []; | |
_ -> [N] | |
end ++ case Opts of | |
undefined -> []; | |
_ -> Opts | |
end. | |
ast2_url_options([{rtp,P}|List]) -> [{rtp,binary_to_atom(P,latin1)}|ast2_url_options(List)]; | |
ast2_url_options([{priority,P}|List]) -> [{priority,binary_to_integer(P)}|ast2_url_options(List)]; | |
ast2_url_options([{source_timeout,P}|List]) -> [{source_timeout,binary_to_integer(P)}|ast2_url_options(List)]; | |
ast2_url_options([{no_clients_reconnect_delay,P}|List]) -> [{no_clients_reconnect_delay,binary_to_integer(P)}|ast2_url_options(List)]; | |
ast2_url_options([KV|List]) -> [KV|ast2_url_options(List)]; | |
ast2_url_options([]) -> []. | |
ast2_bool(V,L) -> | |
case V of | |
<<"true">> -> true; | |
<<"on">> -> true; | |
<<"off">> -> false; | |
<<"false">> -> false; | |
_ -> throw({error, {invalid_boolean,L,V}}) | |
end. | |
parse_listener(Bin) -> | |
try binary_to_integer(Bin) of | |
Int -> {ok, Int} | |
catch | |
_:_ -> | |
case binary:split(Bin,<<":">>) of | |
[_H,P] -> | |
try binary_to_integer(P) of | |
_ -> {ok, Bin} | |
catch _:_ -> | |
undefined | |
end; | |
_ -> | |
undefined | |
end | |
end. | |
ast2_parse_ipcidr(Bin, _L) -> | |
{Addr, Len} = case binary:split(Bin, <<"/">>) of | |
[A,Ln] -> {A,binary_to_integer(Ln)}; | |
[A] -> {A,32} | |
end, | |
case binary:match(Addr, <<":">>) of | |
nomatch -> | |
Address_ = [binary_to_integer(I) || I <- binary:split(Addr,<<".">>,[global])], | |
Address = list_to_tuple(lists:sublist(Address_ ++ [0,0,0,0], 4)), | |
{Address, Len}; | |
_ -> | |
{ok, Address} = inet_parse:address(binary_to_list(Addr)), | |
{Address, Len} | |
end. | |
parse_cache([Path|Opts], L) -> | |
parse_cache0(Opts, #{path => Path}, L). | |
parse_cache0([], Cache, _) -> Cache; | |
parse_cache0([<<"misses=",M/binary>>|Opts], Cache, L) -> | |
parse_cache0(Opts, Cache#{misses => binary_to_integer(M)}, L); | |
parse_cache0([I|Opts], Cache, L) -> | |
case maybe_int(I) of | |
{size,S} -> | |
parse_cache0(Opts, Cache#{disk_limit => S}, L); | |
{time,S} -> | |
parse_cache0(Opts, Cache#{time_limit => S}, L); | |
_ -> | |
throw({error,{invalid_cache_option,L,I}}) | |
end. | |
parse_mpegts_pids(E1, undefined, L) -> parse_mpegts_pids(E1, [], L); | |
parse_mpegts_pids(E1, MorePids, L) -> parse_mpegts_pids([E1|MorePids], L). | |
parse_mpegts_pids(PidEntries, L) -> | |
FlatMap = [parse_mpegts_pid(E, L) || E <- PidEntries], | |
lists:foldl(fun populate_mpegts_map/2, #{}, FlatMap). | |
parse_mpegts_pid(Entry, L) -> | |
case binary:split(Entry, <<"=">>) of | |
[Key, Value] -> | |
Pid = maybe_int(Value), | |
is_integer(Pid) orelse throw({error, {invalid_mpegts_pid, L, Value}}), | |
{mpegts_id(Key, L), Pid}; | |
[_] -> | |
throw({error, {invalid_mpegts_pid_spec, L, Entry}}) | |
end. | |
mpegts_id(<<T:8, BinIndex/binary>>, L) when T == $a; T == $v; T == $t -> | |
Type = case T of | |
$a -> audio; | |
$v -> video; | |
$t -> text | |
end, | |
Index = maybe_int(BinIndex), | |
is_integer(Index) orelse throw({error, {invalid_track_id, L, <<T:8, BinIndex/binary>>}}), | |
{Type, Index}; | |
mpegts_id(BinID, _) -> | |
binary_to_atom(BinID, latin1). | |
populate_mpegts_map({{Type, Index}, Pid}, Map) -> | |
TMap = maps:get(Type, Map, #{}), | |
Map#{Type => TMap#{Index => Pid}}; | |
populate_mpegts_map({Key, Pid}, Map) -> | |
Map#{Key => Pid}. | |
parse_dvr([Path|Opts], L) -> | |
parse_dvr0(Opts, #{root => Path}, L). | |
parse_dvr0([], DVR, _L) -> DVR; | |
parse_dvr0([<<I,"%">>|Opts], DVR, L) -> parse_dvr0(Opts, DVR#{disk_limit => I-$0}, L); | |
parse_dvr0([<<I:2/binary,"%">>|Opts], DVR, L) -> parse_dvr0(Opts, DVR#{disk_limit => binary_to_integer(I)}, L); | |
parse_dvr0([<<"replication_port=",Port/binary>>|Opts], DVR, L) -> parse_dvr0(Opts, DVR#{replication_port => binary_to_integer(Port)}, L); | |
parse_dvr0([<<"replicate=",Speed/binary>>|Opts], DVR, L) -> | |
{size,S} = maybe_int(Speed), | |
parse_dvr0(Opts, DVR#{replication_speed => S, dvr_replicate => true}, L); | |
parse_dvr0([<<"replicate">>|Opts], DVR, L) -> | |
parse_dvr0(Opts, DVR#{dvr_replicate => true}, L); | |
parse_dvr0([<<"schedule=", DvrSchedule/binary>>|Opts], DVR, L) -> | |
Schedule = parse_dvr_schedule(DvrSchedule, L), | |
parse_dvr0(Opts, DVR#{schedule => Schedule}, L); | |
parse_dvr0([<<"copy=", Copy/binary>>|Opts], DVR, L) -> | |
parse_dvr0(Opts, DVR#{copy => Copy}, L); | |
parse_dvr0([Num|Opts], DVR, L) -> | |
case maybe_int(Num) of | |
T when is_integer(T) andalso T > 0 -> parse_dvr0(Opts, DVR#{dvr_limit => T}, L); | |
{percent, N} -> parse_dvr0(Opts, DVR#{disk_limit => N}, L); | |
{size, Size} -> parse_dvr0(Opts, DVR#{disk_space => Size}, L); | |
{time, T} -> parse_dvr0(Opts, DVR#{dvr_limit => T}, L) | |
end. | |
parse_dvr_schedule(S, L) -> | |
Lexems = dvr_schedule_lexems(S), | |
Lexems2 = dvr_schedule_parse2(Lexems,L), | |
dvr_schedule_parse3(Lexems2, L). | |
dvr_schedule_lexems(<<>>) -> []; | |
dvr_schedule_lexems(<<",",S/binary>>) -> [','|dvr_schedule_lexems(S)]; | |
dvr_schedule_lexems(<<":",S/binary>>) -> [':'|dvr_schedule_lexems(S)]; | |
dvr_schedule_lexems(<<"-",S/binary>>) -> ['-'|dvr_schedule_lexems(S)]; | |
dvr_schedule_lexems(<<I1,S/binary>>) when I1 >= $0, I1 =< $9 -> dvr_schedule_int(S, I1 - $0). | |
dvr_schedule_int(<<I,S/binary>>, Acc) when I >= $0, I =< $9 -> dvr_schedule_int(S, Acc*10 + I - $0); | |
dvr_schedule_int(S, Acc) -> [Acc|dvr_schedule_lexems(S)]. | |
dvr_schedule_parse2([I1,':',I2|List], L) when is_integer(I1), is_integer(I2) -> | |
[{int,I1*100+I2}|dvr_schedule_parse2(List, L)]; | |
dvr_schedule_parse2([I1|List], L) when is_integer(I1) -> | |
I2 = if | |
I1 > 100 -> I1; | |
true -> I1*100 | |
end, | |
[{int,I2}|dvr_schedule_parse2(List, L)]; | |
dvr_schedule_parse2([','|List], L) -> | |
[','|dvr_schedule_parse2(List, L)]; | |
dvr_schedule_parse2(['-'|List], L) -> | |
['-'|dvr_schedule_parse2(List, L)]; | |
dvr_schedule_parse2([], _) -> | |
[]. | |
dvr_schedule_parse3([{int,I1},'-',{int,I2}|List], L) -> | |
[[I1,I2]|dvr_schedule_parse3(List,L)]; | |
dvr_schedule_parse3([','|List], L) -> | |
dvr_schedule_parse3(List, L); | |
dvr_schedule_parse3([], _) -> []. | |
-record(notify, { | |
sink, | |
only = [], | |
except = [], | |
owner, | |
verbose, | |
buffer, | |
throttle_delay, | |
args = #{} | |
}). | |
make_notify(Entries) -> | |
Notify = make_notify0(Entries, #notify{}), | |
maps:from_list(case Notify#notify.only of | |
[] -> []; | |
Only -> [{only,Only}] | |
end ++ | |
case Notify#notify.except of | |
[] -> []; | |
Except -> [{except,Except}] | |
end ++ | |
case Notify#notify.owner of | |
undefined -> []; | |
Owner -> [{owner,Owner}] | |
end ++ | |
case Notify#notify.verbose of | |
undefined -> []; | |
Verbose -> [{verbose,Verbose}] | |
end ++ | |
case Notify#notify.buffer of | |
undefined -> []; | |
Buffer -> [{buffer,Buffer}] | |
end ++ | |
case Notify#notify.throttle_delay of | |
undefined -> []; | |
Delay -> [{throttle_delay,Delay}] | |
end ++ | |
[{sink, Notify#notify.sink}, | |
{args, Notify#notify.args}]). | |
make_notify0([{only, Conditions}|Entries], #notify{only = Only} = Notify) -> | |
make_notify0(Entries, Notify#notify{only = Only ++ [notify_conditions(Conditions)] }); | |
make_notify0([{except, Conditions}|Entries], #notify{except = Except} = Notify) -> | |
make_notify0(Entries, Notify#notify{except = Except ++ [notify_conditions(Conditions)] }); | |
make_notify0([{buffer, <<"off">>}|Entries], #notify{} = Notify) -> | |
make_notify0(Entries, Notify#notify{buffer = false}); | |
make_notify0([{buffer, Buffer}|Entries], #notify{} = Notify) -> | |
make_notify0(Entries, Notify#notify{buffer = binary_to_integer(Buffer)}); | |
make_notify0([{throttle_delay, Delay}|Entries], #notify{} = Notify) -> | |
make_notify0(Entries, Notify#notify{throttle_delay = binary_to_integer(Delay)}); | |
make_notify0([{owner, Owner}|Entries], #notify{} = Notify) -> | |
make_notify0(Entries, Notify#notify{owner = Owner}); | |
make_notify0([{verbose, Verbose}|Entries], #notify{} = Notify) -> | |
make_notify0(Entries, Notify#notify{verbose = binary_to_atom(Verbose,latin1)}); | |
make_notify0([{sink, Sink}|Entries], #notify{} = Notify) -> | |
make_notify0(Entries, Notify#notify{sink = Sink}); | |
make_notify0([{Key, Value} | Entries], #notify{args = Args} = Notify) -> | |
make_notify0(Entries, Notify#notify{args = maps:put(Key, Value, Args)}); | |
make_notify0([], #notify{} = Notify) -> | |
Notify. | |
notify_conditions(Conditions) -> | |
notify_conditions0(Conditions, #{}). | |
notify_conditions0([], And) -> And; | |
notify_conditions0([Condition|Conditions], And) -> | |
case binary:split(Condition, <<"=">>) of | |
[_] -> | |
events:error("Invalid notify condition: ~p", [Condition]), | |
error({invalid_notify_condition, Condition}); | |
[Key,Value] -> | |
Values = case binary:split(Value, <<",">>, [global]) of | |
[Value] -> Value; | |
List -> List | |
end, | |
And1 = maps:put(binary_to_atom(Key,latin1), Values, And), | |
notify_conditions0(Conditions, And1) | |
end. | |
-record(opts, { | |
prefix, | |
options = #{}, | |
auth = #{}, | |
groups = [], | |
urls = [], | |
push = [], | |
meta = [] | |
}). | |
apply_stream_options(Command, Name, Options) -> | |
#opts{} = Opts = apply_options(Options), | |
case Command of | |
live -> | |
{Command, Name, maps:merge(make_options(Opts#opts{prefix = Name}), #{publish_enabled => true})}; | |
file -> | |
{Command, Name, make_options(Opts#opts{prefix = Name})}; | |
dynamic -> | |
{Command, Name, make_options(Opts#opts{prefix = Name})}; | |
stream -> | |
{Command, Name, make_options(Opts)} | |
end. | |
% l(_, undefined) -> []; | |
% l(Key, Value) -> [{Key,Value}]. | |
make_options([{_,_}|_] = Options) -> | |
make_options(apply_options(Options)); | |
make_options(#opts{push = Push, urls = URLs, auth = Auth, meta = Meta, prefix = Prefix, groups = Groups, options = #{} = Options}) -> | |
UnsortedOptions = case Meta of | |
[] -> []; | |
_ -> [{meta,maps:from_list(Meta)}] | |
end ++ | |
case Groups of | |
[] -> []; | |
_ -> [{groups,Groups}] | |
end ++ | |
case Push of | |
[] -> []; | |
_ -> [{push,Push}] | |
end ++ | |
case URLs of | |
[] -> []; | |
_ -> [{urls,URLs}] | |
end ++ | |
case Prefix of | |
undefined -> []; | |
_ -> [{prefix,Prefix}] | |
end ++ | |
case maps:to_list(Auth) of | |
[] -> []; | |
_ -> [{auth,Auth}] | |
end, | |
maps:merge(Options, maps:from_list(UnsortedOptions)). | |
apply_options(Options) -> apply_options(#opts{}, Options). | |
apply_options(#opts{urls = URLs} = O, [{url,URL}|Acc]) -> | |
apply_options(O#opts{urls = URLs ++ [URL]}, Acc); | |
apply_options(#opts{} = O, [{urls,URLs2}|Acc]) -> | |
apply_options(O#opts{urls = URLs2}, Acc); | |
apply_options(#opts{urls = URLs} = O, [{path,URL}|Acc]) -> | |
apply_options(O#opts{urls = URLs ++ [URL]}, Acc); | |
apply_options(#opts{} = O, [{paths,URLs2}|Acc]) -> | |
apply_options(O#opts{urls = URLs2}, Acc); | |
apply_options(#opts{auth = Auth} = O, [{domain,Domain}|Acc]) -> | |
Auth1 = case Auth of | |
#{domains := [_|_] = D} -> Auth#{domains := D ++ [clean_domain(Domain)]}; | |
#{} -> Auth#{domains => [clean_domain(Domain)]} | |
end, | |
apply_options(O#opts{auth = Auth1}, Acc); | |
apply_options(#opts{auth = Auth} = O, [{domains,Domains}|Acc]) -> | |
apply_options(O#opts{auth = Auth#{domains => [clean_domain(D) || D <- Domains]}}, Acc); | |
apply_options(#opts{meta = []} = O, [{meta,KVList}|Acc]) when is_list(KVList) -> | |
apply_options(O#opts{meta = KVList}, Acc); | |
apply_options(#opts{meta = Meta} = O, [{meta,K,V}|Acc]) -> | |
apply_options(O#opts{meta = Meta ++ [{K,V}]}, Acc); | |
apply_options(#opts{push = Push} = O, [{push,P}|Acc]) -> | |
apply_options(O#opts{push = Push ++ [P]}, Acc); | |
apply_options(#opts{groups = Groups} = O, [{group,G}|Acc]) -> | |
apply_options(O#opts{groups = Groups ++ [G]}, Acc); | |
apply_options(#opts{auth = Auth} = O, [{auth,#{} = Auth2}|Acc]) -> | |
apply_options(O#opts{auth = maps:merge(Auth,Auth2)}, Acc); | |
apply_options(#opts{auth = Auth} = O, [{allowed_countries,Countries}|Acc]) -> | |
apply_options(O#opts{auth = Auth#{allowed_countries => Countries}}, Acc); | |
apply_options(#opts{auth = Auth} = O, [{disallowed_countries,Countries}|Acc]) -> | |
apply_options(O#opts{auth = Auth#{disallowed_countries => Countries}}, Acc); | |
apply_options(#opts{options = Opts} = O, [{K,V}|Acc]) -> | |
case lists:member(K, config2_render:all_options()) of | |
true -> | |
apply_options(O#opts{options = maps:put(K,V,Opts)}, Acc); | |
false -> | |
error({invalid_option, K,V}) | |
end; | |
apply_options(#opts{} = O, [_|Acc]) -> apply_options(O, Acc); | |
apply_options(#opts{} = O, []) -> O. | |
clean_domain(<<"*.", Domain/binary>>) -> <<".",Domain/binary>>; | |
clean_domain(Domain) -> Domain. | |
remove_last_slash(Path) -> | |
Length = byte_size(Path)-1, | |
case Path of | |
<<Path1:Length/binary, "/">> -> Path1; | |
_ -> Path | |
end. | |
to_upcase(Binary) -> to_upcase(Binary, <<>>). | |
to_upcase(<<>>, Acc) -> Acc; | |
to_upcase(<<C, Binary/binary>>, Acc) when C >= 97 andalso C =< 122 -> to_upcase(Binary, <<Acc/binary, (C-32)>>); | |
to_upcase(<<C, Binary/binary>>, Acc) -> to_upcase(Binary, <<Acc/binary, C>>). | |
% Весь трешак ниже скопирован из config2_format.peg | |
run_ast3_transform(AST, Options) -> | |
GlobalAuth = read_global_auth(AST), | |
AST2 = replace_global_auth(AST, GlobalAuth), | |
AST8 = case maps:get(add_global, Options, true) of | |
true -> | |
% 1. Надо вычислить глобальные настройки auth и вмержить их в каждый стрим | |
% учитываются опции auth, domains, (dis)allowed_countries, autogenerate_token | |
AST3 = apply_global_auth(AST2, add_suffix_to_keys(GlobalAuth, <<"_global">>)), | |
% 2. Теперь надо наложить глобальные переменные | |
AST5 = apply_global_variables(AST3, [cluster_key, url_prefix]), | |
% 3. Раньше здесь было добавление ad scheduled стримов, теперь выкидываем, только руками | |
% 5. Проставляем global dvr | |
AST7 = add_global_dvrs(AST5), | |
add_global_caches(AST7); | |
false -> | |
AST | |
end, | |
AST9 = add_positions(AST8), | |
case validate_config(AST9) of | |
ok -> | |
AST9; | |
{error, E} -> | |
{error, E} | |
end. | |
read_global_auth(AST) -> read_global_auth2(AST, #{}). | |
read_global_auth2([], Auth) -> Auth; | |
read_global_auth2([{auth,A}|Lines], Auth) -> read_global_auth2(Lines, maps:merge(Auth, A)); | |
read_global_auth2([{domain,Domain}|Lines], Auth) -> | |
Auth1 = case Auth of | |
#{domains := [_|_] = D} -> Auth#{domains := D ++ [clean_domain(Domain)]}; | |
#{} -> Auth#{domains => [clean_domain(Domain)]} | |
end, | |
read_global_auth2(Lines, Auth1); | |
read_global_auth2([{domains,Domains}|Lines], Auth) -> | |
read_global_auth2(Lines, Auth#{domains => [clean_domain(D) || D <- Domains]}); | |
read_global_auth2([{Tag,Countries}|Lines], Auth) when | |
Tag == allowed_countries; Tag == disallowed_countries; Tag == autogenerate_token-> | |
read_global_auth2(Lines, maps:put(Tag, Countries, Auth)); | |
read_global_auth2([_|Lines], Auth) -> read_global_auth2(Lines, Auth). | |
apply_global_auth([{Tag,Name,Config}|AST], GlobalAuth) when | |
Tag == stream; Tag == file; Tag == source; Tag == live; Tag == dynamic -> | |
Auth = add_suffix_to_keys(maps:get(auth, Config, #{}), <<"_orig">>), | |
Config1 = maps:put(auth, maps:merge(GlobalAuth, Auth), Config), | |
[{Tag,Name,Config1}|apply_global_auth(AST, GlobalAuth)]; | |
apply_global_auth([KV|AST], GlobalAuth) -> | |
[KV|apply_global_auth(AST, GlobalAuth)]; | |
apply_global_auth([], _) -> []. | |
add_suffix_to_keys(Map, Suffix) -> | |
maps:merge( | |
maps:from_list([{list_to_atom(atom_to_list(K) ++ binary_to_list(Suffix)),V} || {K,V} <- maps:to_list(Map)]), | |
Map | |
). | |
replace_global_auth(Lines, Auth) -> | |
case maps:to_list(Auth) == [{url,true}] of | |
true -> Lines; | |
false -> replace_global_auth0(Lines, Auth) | |
end. | |
replace_global_auth0([{K,_}|Lines], Auth) when K == auth; K == domain; K == domains; K == allowed_countries; K == disallowed_countries; K == autogenerate_token -> | |
[{auth,Auth}|clean_global_auth(Lines)]; | |
replace_global_auth0([Line|Lines], Auth) -> | |
[Line|replace_global_auth0(Lines, Auth)]; | |
replace_global_auth0([], _) -> []. | |
clean_global_auth(Lines) -> | |
[L || L <- Lines, not lists:member(element(1,L), [auth, domain, domains, allowed_countries, disallowed_countries, autogenerate_token])]. | |
apply_global_variables(AST, []) -> AST; | |
apply_global_variables(AST, [K|Lines]) -> | |
AST1 = case lists:keyfind(K, 1, AST) of | |
{K,V} -> apply_global_variable(AST, K, V); | |
false -> AST | |
end, | |
apply_global_variables(AST1, Lines). | |
apply_global_variable([{Tag,Name,Config}|AST], K, V) when | |
Tag == stream; Tag == file; Tag == source; Tag == live; Tag == dynamic -> | |
Config1 = maps:merge( | |
maps:from_list([ {K,V}, {list_to_atom(atom_to_list(K) ++ "_global"),V} ]), | |
Config), | |
[{Tag,Name,Config1}|apply_global_variable(AST, K, V)]; | |
apply_global_variable([Line|AST], K, V) -> [Line|apply_global_variable(AST, K, V)]; | |
apply_global_variable([], _, _) -> []. | |
validate_config(AST) -> | |
Validations = [fun validate_unique_names/1, fun validate_source_prefixes/1, fun validate_push_targets/1], | |
run_validations(Validations, AST). | |
run_validations([], _AST) -> | |
ok; | |
run_validations([Fun1|MoreFuns], AST) -> | |
case Fun1(AST) of | |
ok -> run_validations(MoreFuns, AST); | |
{error, _} = Error -> Error | |
end. | |
validate_unique_names(AST) -> | |
Names = [N || {Tag,N,_} <- AST, Tag == stream orelse Tag == file orelse Tag == live], | |
case Names -- lists:usort(Names) of | |
[_|_] = NonUnique -> {error, {non_unique_names, NonUnique}}; | |
[] -> ok | |
end. | |
validate_source_prefixes([{source,URLs,_}|Config]) -> | |
Prefixes = [case re:run(URL, "[^:]+://[^/]+/(.*)$", [{capture,all_but_first,binary}]) of | |
{match, [Prefix]} -> Prefix; | |
nomatch -> <<>> | |
end || URL <- URLs], | |
case lists:usort(Prefixes) of | |
[_] -> validate_source_prefixes(Config); | |
[_|_] -> {error, {different_source_prefixes,URLs}} | |
end; | |
validate_source_prefixes([]) -> ok; | |
validate_source_prefixes([_|Config]) -> validate_source_prefixes(Config). | |
validate_push_targets(AST) -> | |
BadTargets = [{S, Target} || {stream, S, #{push := STargets}} <- AST, Target <- STargets, not config2_helpers:is_valid_url(Target)], | |
case BadTargets of | |
[] -> ok; | |
_ -> {error, {bad_push_targets, BadTargets}} | |
end. | |
add_global_dvrs(AST) -> | |
add_global_dvrs2(AST, AST). | |
add_global_dvrs2([{Tag,Name,#{external_dvr := D} = Options}|AST], Config) -> | |
Opts1 = case [DVR || {dvr,N,#{} = DVR} <- Config, N == D] of | |
[#{} = DVR] -> Options#{dvr => DVR, dvr_global => DVR}; | |
[] -> Options | |
end, | |
[{Tag,Name,Opts1}|add_global_dvrs2(AST, Config)]; | |
add_global_dvrs2([], _) -> []; | |
add_global_dvrs2([Line|AST], Config) -> [Line|add_global_dvrs2(AST, Config)]. | |
add_global_caches(AST) -> | |
add_global_caches2(AST, AST). | |
add_global_caches2([{Tag,Name,#{external_cache := C} = Options}|AST], Config) -> | |
Opts1 = case [Cache || {cache,N,#{} = Cache} <- Config, N == C] of | |
[#{} = Cache] -> Options#{cache => Cache, cache_global => Cache}; | |
[] -> Options | |
end, | |
[{Tag,Name,Opts1}|add_global_caches2(AST, Config)]; | |
add_global_caches2([], _) -> []; | |
add_global_caches2([Line|AST], Config) -> [Line|add_global_caches2(AST, Config)]. | |
add_positions(AST0) -> | |
AST1 = set_entry_positions(AST0, stream, 1), | |
AST2 = set_entry_positions(AST1, notify, 1), | |
AST2. | |
set_entry_positions([{E1,N,Opts}|Config], E, P) when E1 == E -> | |
[{E1,N,Opts#{position => P}}|set_entry_positions(Config, E, P+1)]; | |
set_entry_positions([Line|Config], E, P) -> | |
[Line|set_entry_positions(Config, E, P)]; | |
set_entry_positions([], _, _) -> | |
[]. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment