-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.