-module(lightspeed@upload).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/lightspeed/upload.gleam").
-export([allow/5, name/1, mode/1, entries/1, errors/1, add_entry/5, entry_status_label/1, prepare_external/3, start_upload/2, progress/3, complete/3, fail/3, cancel/3, error_to_string/1]).
-export_type([upload_mode/0, entry_status/0, upload_entry/0, upload_error/0, upload/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(" LiveView-style upload compatibility model.\n").
-type upload_mode() :: direct_server | {external_client, binary()}.
-type entry_status() :: selected |
{external_prepared, binary()} |
{uploading, integer()} |
{completed, binary()} |
{cancelled, binary()} |
{failed, binary()}.
-type upload_entry() :: {upload_entry,
binary(),
binary(),
integer(),
binary(),
entry_status()}.
-type upload_error() :: {too_many_files, integer()} |
{too_large, binary(), integer(), integer()} |
{not_accepted, binary(), list(binary())} |
{duplicate_ref, binary()} |
{unknown_ref, binary()} |
{invalid_progress, binary(), integer()} |
{invalid_transition, binary(), binary(), binary()}.
-opaque upload() :: {upload,
binary(),
upload_mode(),
list(binary()),
integer(),
integer(),
list(upload_entry()),
list(upload_error())}.
-file("src/lightspeed/upload.gleam", 59).
?DOC(" Build one upload configuration.\n").
-spec allow(binary(), upload_mode(), list(binary()), integer(), integer()) -> upload().
allow(Name, Mode, Accept, Max_entries, Max_file_size) ->
{upload, Name, Mode, Accept, Max_entries, Max_file_size, [], []}.
-file("src/lightspeed/upload.gleam", 78).
?DOC(" Upload name.\n").
-spec name(upload()) -> binary().
name(Upload) ->
erlang:element(2, Upload).
-file("src/lightspeed/upload.gleam", 83).
?DOC(" Upload mode.\n").
-spec mode(upload()) -> upload_mode().
mode(Upload) ->
erlang:element(3, Upload).
-file("src/lightspeed/upload.gleam", 88).
?DOC(" Entries in selection order.\n").
-spec entries(upload()) -> list(upload_entry()).
entries(Upload) ->
lists:reverse(erlang:element(7, Upload)).
-file("src/lightspeed/upload.gleam", 93).
?DOC(" Errors in occurrence order.\n").
-spec errors(upload()) -> list(upload_error()).
errors(Upload) ->
lists:reverse(erlang:element(8, Upload)).
-file("src/lightspeed/upload.gleam", 370).
-spec reject(upload(), upload_error()) -> {upload(),
{ok, upload_entry()} | {error, upload_error()}}.
reject(Upload, Error) ->
{{upload,
erlang:element(2, Upload),
erlang:element(3, Upload),
erlang:element(4, Upload),
erlang:element(5, Upload),
erlang:element(6, Upload),
erlang:element(7, Upload),
[Error | erlang:element(8, Upload)]},
{error, Error}}.
-file("src/lightspeed/upload.gleam", 421).
-spec accepted_pattern(binary(), binary(), binary()) -> boolean().
accepted_pattern(Pattern, Client_name, Client_type) ->
case Pattern of
<<"*/*"/utf8>> ->
true;
_ ->
case gleam_stdlib:string_starts_with(Pattern, <<"."/utf8>>) of
true ->
gleam_stdlib:string_ends_with(Client_name, Pattern);
false ->
case gleam_stdlib:string_ends_with(Pattern, <<"/*"/utf8>>) of
true ->
gleam_stdlib:string_starts_with(
Client_type,
gleam@string:replace(
Pattern,
<<"*"/utf8>>,
<<""/utf8>>
)
);
false ->
Client_type =:= Pattern
end
end
end.
-file("src/lightspeed/upload.gleam", 404).
-spec accepted_any(list(binary()), binary(), binary()) -> boolean().
accepted_any(Patterns, Client_name, Client_type) ->
case Patterns of
[] ->
false;
[Pattern | Rest] ->
case accepted_pattern(
string:lowercase(Pattern),
Client_name,
Client_type
) of
true ->
true;
false ->
accepted_any(Rest, Client_name, Client_type)
end
end.
-file("src/lightspeed/upload.gleam", 388).
-spec accepted(list(binary()), binary(), binary()) -> boolean().
accepted(Accept, Client_name, Client_type) ->
case Accept of
[] ->
true;
_ ->
accepted_any(
Accept,
string:lowercase(Client_name),
string:lowercase(Client_type)
)
end.
-file("src/lightspeed/upload.gleam", 377).
-spec has_ref(list(upload_entry()), binary()) -> boolean().
has_ref(Entries_rev, Ref) ->
case Entries_rev of
[] ->
false;
[Entry | Rest] ->
case erlang:element(2, Entry) =:= Ref of
true ->
true;
false ->
has_ref(Rest, Ref)
end
end.
-file("src/lightspeed/upload.gleam", 98).
?DOC(" Add one selected file entry with validation.\n").
-spec add_entry(upload(), binary(), binary(), integer(), binary()) -> {upload(),
{ok, upload_entry()} | {error, upload_error()}}.
add_entry(Upload, Ref, Client_name, Client_size, Client_type) ->
case erlang:length(erlang:element(7, Upload)) >= erlang:element(5, Upload) of
true ->
reject(Upload, {too_many_files, erlang:element(5, Upload)});
false ->
case has_ref(erlang:element(7, Upload), Ref) of
true ->
reject(Upload, {duplicate_ref, Ref});
false ->
case Client_size > erlang:element(6, Upload) of
true ->
reject(
Upload,
{too_large,
Ref,
Client_size,
erlang:element(6, Upload)}
);
false ->
case accepted(
erlang:element(4, Upload),
Client_name,
Client_type
) of
false ->
reject(
Upload,
{not_accepted,
Ref,
erlang:element(4, Upload)}
);
true ->
Entry = {upload_entry,
Ref,
Client_name,
Client_size,
Client_type,
selected},
{{upload,
erlang:element(2, Upload),
erlang:element(3, Upload),
erlang:element(4, Upload),
erlang:element(5, Upload),
erlang:element(6, Upload),
[Entry | erlang:element(7, Upload)],
erlang:element(8, Upload)},
{ok, Entry}}
end
end
end
end.
-file("src/lightspeed/upload.gleam", 300).
?DOC(" Stable status label for assertions and logs.\n").
-spec entry_status_label(entry_status()) -> binary().
entry_status_label(Status) ->
case Status of
selected ->
<<"selected"/utf8>>;
{external_prepared, _} ->
<<"external_prepared"/utf8>>;
{uploading, _} ->
<<"uploading"/utf8>>;
{completed, _} ->
<<"completed"/utf8>>;
{cancelled, _} ->
<<"cancelled"/utf8>>;
{failed, _} ->
<<"failed"/utf8>>
end.
-file("src/lightspeed/upload.gleam", 441).
-spec reverse_into(list(UOA), list(UOA)) -> list(UOA).
reverse_into(Left, Right) ->
case Left of
[] ->
Right;
[Entry | Rest] ->
reverse_into(Rest, [Entry | Right])
end.
-file("src/lightspeed/upload.gleam", 347).
-spec update_entry(
list(upload_entry()),
binary(),
fun((upload_entry()) -> {ok, upload_entry()} | {error, upload_error()}),
list(upload_entry())
) -> {ok, {list(upload_entry()), upload_entry()}} | {error, upload_error()}.
update_entry(Entries_rev, Ref, With, Seen_rev) ->
case Entries_rev of
[] ->
{error, {unknown_ref, Ref}};
[Entry | Rest] ->
case erlang:element(2, Entry) =:= Ref of
true ->
case With(Entry) of
{error, Error} ->
{error, Error};
{ok, Updated} ->
{ok,
{reverse_into(Seen_rev, [Updated | Rest]),
Updated}}
end;
false ->
update_entry(Rest, Ref, With, [Entry | Seen_rev])
end
end.
-file("src/lightspeed/upload.gleam", 332).
-spec transition(
upload(),
binary(),
binary(),
fun((upload_entry()) -> {ok, upload_entry()} | {error, upload_error()})
) -> {upload(), {ok, upload_entry()} | {error, upload_error()}}.
transition(Upload, Ref, _, With) ->
case update_entry(erlang:element(7, Upload), Ref, With, []) of
{error, Error} ->
reject(Upload, Error);
{ok, {Entries_rev, Entry}} ->
{{upload,
erlang:element(2, Upload),
erlang:element(3, Upload),
erlang:element(4, Upload),
erlang:element(5, Upload),
erlang:element(6, Upload),
Entries_rev,
erlang:element(8, Upload)},
{ok, Entry}}
end.
-file("src/lightspeed/upload.gleam", 150).
?DOC(" Prepare one external upload entry with a preflight URL.\n").
-spec prepare_external(upload(), binary(), binary()) -> {upload(),
{ok, upload_entry()} | {error, upload_error()}}.
prepare_external(Upload, Ref, Upload_url) ->
case erlang:element(3, Upload) of
direct_server ->
reject(
Upload,
{invalid_transition,
Ref,
<<"direct_mode"/utf8>>,
<<"prepare_external"/utf8>>}
);
{external_client, _} ->
transition(
Upload,
Ref,
<<"prepare_external"/utf8>>,
fun(Entry) -> case erlang:element(6, Entry) of
selected ->
{ok,
{upload_entry,
erlang:element(2, Entry),
erlang:element(3, Entry),
erlang:element(4, Entry),
erlang:element(5, Entry),
{external_prepared, Upload_url}}};
_ ->
{error,
{invalid_transition,
Ref,
entry_status_label(erlang:element(6, Entry)),
<<"prepare_external"/utf8>>}}
end end
)
end.
-file("src/lightspeed/upload.gleam", 189).
?DOC(" Start uploading one entry.\n").
-spec start_upload(upload(), binary()) -> {upload(),
{ok, upload_entry()} | {error, upload_error()}}.
start_upload(Upload, Ref) ->
transition(
Upload,
Ref,
<<"start_upload"/utf8>>,
fun(Entry) ->
case {erlang:element(3, Upload), erlang:element(6, Entry)} of
{direct_server, selected} ->
{ok,
{upload_entry,
erlang:element(2, Entry),
erlang:element(3, Entry),
erlang:element(4, Entry),
erlang:element(5, Entry),
{uploading, 0}}};
{{external_client, _}, {external_prepared, _}} ->
{ok,
{upload_entry,
erlang:element(2, Entry),
erlang:element(3, Entry),
erlang:element(4, Entry),
erlang:element(5, Entry),
{uploading, 0}}};
{_, _} ->
{error,
{invalid_transition,
Ref,
entry_status_label(erlang:element(6, Entry)),
<<"start_upload"/utf8>>}}
end
end
).
-file("src/lightspeed/upload.gleam", 212).
?DOC(" Update upload progress.\n").
-spec progress(upload(), binary(), integer()) -> {upload(),
{ok, upload_entry()} | {error, upload_error()}}.
progress(Upload, Ref, Progress) ->
case (Progress < 0) orelse (Progress > 100) of
true ->
reject(Upload, {invalid_progress, Ref, Progress});
false ->
transition(
Upload,
Ref,
<<"progress"/utf8>>,
fun(Entry) -> case erlang:element(6, Entry) of
{external_prepared, _} ->
{ok,
{upload_entry,
erlang:element(2, Entry),
erlang:element(3, Entry),
erlang:element(4, Entry),
erlang:element(5, Entry),
{uploading, Progress}}};
{uploading, _} ->
{ok,
{upload_entry,
erlang:element(2, Entry),
erlang:element(3, Entry),
erlang:element(4, Entry),
erlang:element(5, Entry),
{uploading, Progress}}};
_ ->
{error,
{invalid_transition,
Ref,
entry_status_label(erlang:element(6, Entry)),
<<"progress"/utf8>>}}
end end
)
end.
-file("src/lightspeed/upload.gleam", 238).
?DOC(" Complete one upload entry.\n").
-spec complete(upload(), binary(), binary()) -> {upload(),
{ok, upload_entry()} | {error, upload_error()}}.
complete(Upload, Ref, Location) ->
transition(
Upload,
Ref,
<<"complete"/utf8>>,
fun(Entry) -> case erlang:element(6, Entry) of
{uploading, _} ->
{ok,
{upload_entry,
erlang:element(2, Entry),
erlang:element(3, Entry),
erlang:element(4, Entry),
erlang:element(5, Entry),
{completed, Location}}};
_ ->
{error,
{invalid_transition,
Ref,
entry_status_label(erlang:element(6, Entry)),
<<"complete"/utf8>>}}
end end
).
-file("src/lightspeed/upload.gleam", 258).
?DOC(" Mark one upload entry as failed.\n").
-spec fail(upload(), binary(), binary()) -> {upload(),
{ok, upload_entry()} | {error, upload_error()}}.
fail(Upload, Ref, Reason) ->
transition(
Upload,
Ref,
<<"fail"/utf8>>,
fun(Entry) -> case erlang:element(6, Entry) of
{uploading, _} ->
{ok,
{upload_entry,
erlang:element(2, Entry),
erlang:element(3, Entry),
erlang:element(4, Entry),
erlang:element(5, Entry),
{failed, Reason}}};
_ ->
{error,
{invalid_transition,
Ref,
entry_status_label(erlang:element(6, Entry)),
<<"fail"/utf8>>}}
end end
).
-file("src/lightspeed/upload.gleam", 277).
?DOC(" Cancel one upload entry.\n").
-spec cancel(upload(), binary(), binary()) -> {upload(),
{ok, upload_entry()} | {error, upload_error()}}.
cancel(Upload, Ref, Reason) ->
transition(
Upload,
Ref,
<<"cancel"/utf8>>,
fun(Entry) -> case erlang:element(6, Entry) of
selected ->
{ok,
{upload_entry,
erlang:element(2, Entry),
erlang:element(3, Entry),
erlang:element(4, Entry),
erlang:element(5, Entry),
{cancelled, Reason}}};
{external_prepared, _} ->
{ok,
{upload_entry,
erlang:element(2, Entry),
erlang:element(3, Entry),
erlang:element(4, Entry),
erlang:element(5, Entry),
{cancelled, Reason}}};
{uploading, _} ->
{ok,
{upload_entry,
erlang:element(2, Entry),
erlang:element(3, Entry),
erlang:element(4, Entry),
erlang:element(5, Entry),
{cancelled, Reason}}};
_ ->
{error,
{invalid_transition,
Ref,
entry_status_label(erlang:element(6, Entry)),
<<"cancel"/utf8>>}}
end end
).
-file("src/lightspeed/upload.gleam", 312).
?DOC(" Stable error label for assertions and logs.\n").
-spec error_to_string(upload_error()) -> binary().
error_to_string(Error) ->
case Error of
{too_many_files, Max_entries} ->
<<"too_many_files:"/utf8,
(erlang:integer_to_binary(Max_entries))/binary>>;
{too_large, Ref, Size_bytes, Max_bytes} ->
<<<<<<<<<<"too_large:"/utf8, Ref/binary>>/binary, ":"/utf8>>/binary,
(erlang:integer_to_binary(Size_bytes))/binary>>/binary,
":"/utf8>>/binary,
(erlang:integer_to_binary(Max_bytes))/binary>>;
{not_accepted, Ref@1, _} ->
<<"not_accepted:"/utf8, Ref@1/binary>>;
{duplicate_ref, Ref@2} ->
<<"duplicate_ref:"/utf8, Ref@2/binary>>;
{unknown_ref, Ref@3} ->
<<"unknown_ref:"/utf8, Ref@3/binary>>;
{invalid_progress, Ref@4, Progress} ->
<<<<<<"invalid_progress:"/utf8, Ref@4/binary>>/binary, ":"/utf8>>/binary,
(erlang:integer_to_binary(Progress))/binary>>;
{invalid_transition, Ref@5, Status, Action} ->
<<<<<<<<<<"invalid_transition:"/utf8, Ref@5/binary>>/binary,
":"/utf8>>/binary,
Status/binary>>/binary,
":"/utf8>>/binary,
Action/binary>>
end.