-module(ftpasta). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/ftpasta.gleam"). -export([describe_error/1, new/1, connect/1, auth/3, port/2, encryption/2, verification/2, append_bytes/3, change_working_dir/2, close/1, create_dir/2, delete_dir/2, delete_file/2, download_bytes/2, get_working_dir/1, rename/3, upload_bytes/3, list/2, delete_recursive/2, append_file/3, download_file/3, download_recursive/3, upload_file/3, upload_recursive/3]). -export_type([connection/0, listing/0, config/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. -opaque connection() :: {connection, gleam@erlang@process:pid_()}. -type listing() :: {directory, binary()} | {file, binary(), integer()} | {link, binary(), binary()}. -type config() :: {config, binary(), integer(), ftpasta@tls_config:t_l_s_config(), boolean(), binary(), binary()}. -file("src/ftpasta.gleam", 29). ?DOC(" Convert an FTPError into a descriptive string.\n"). -spec describe_error(ftpasta@ftp_error:f_t_p_error()) -> binary(). describe_error(Error) -> case Error of dir_parse_error -> <<"Parsing directory contents failed."/utf8>>; {local_file_error, E} -> <<"Local file error: "/utf8, (simplifile:describe_error(E))/binary>>; _ -> ftpasta_ffi:formaterror(Error) end. -file("src/ftpasta.gleam", 38). ?DOC(" Compare two remote files by name.\n"). -spec compare_listings(listing(), listing()) -> gleam@order:order(). compare_listings(A, B) -> gleam@string:compare(erlang:element(2, A), erlang:element(2, B)). -file("src/ftpasta.gleam", 57). ?DOC(" Start building an FTP connection.\n"). -spec new(binary()) -> config(). new(Host) -> {config, Host, 21, plaintext, true, <<"anonymous"/utf8>>, <<""/utf8>>}. -file("src/ftpasta.gleam", 69). ?DOC(" Connect to the server.\n"). -spec connect(config()) -> {ok, connection()} | {error, ftpasta@ftp_error:f_t_p_error()}. connect(Config) -> gleam@result:'try'( ftpasta_ffi:open( erlang:element(2, Config), erlang:element(3, Config), erlang:element(4, Config), erlang:element(5, Config) ), fun(Pid) -> gleam@result:map( ftpasta_ffi:user( Pid, erlang:element(6, Config), erlang:element(7, Config) ), fun(_) -> {connection, Pid} end ) end ). -file("src/ftpasta.gleam", 81). ?DOC(" Provide a username and password.\n"). -spec auth(config(), binary(), binary()) -> config(). auth(Config, Username, Password) -> {config, erlang:element(2, Config), erlang:element(3, Config), erlang:element(4, Config), erlang:element(5, Config), Username, Password}. -file("src/ftpasta.gleam", 86). ?DOC(" Use a non-default port.\n"). -spec port(config(), integer()) -> config(). port(Config, Port) -> {config, erlang:element(2, Config), Port, erlang:element(4, Config), erlang:element(5, Config), erlang:element(6, Config), erlang:element(7, Config)}. -file("src/ftpasta.gleam", 91). ?DOC(" Use encryption.\n"). -spec encryption(config(), ftpasta@tls_config:t_l_s_config()) -> config(). encryption(Config, Tls) -> {config, erlang:element(2, Config), erlang:element(3, Config), Tls, erlang:element(5, Config), erlang:element(6, Config), erlang:element(7, Config)}. -file("src/ftpasta.gleam", 96). ?DOC(" If certificates should be verified or accepted blindly.\n"). -spec verification(config(), boolean()) -> config(). verification(Config, Enabled) -> {config, erlang:element(2, Config), erlang:element(3, Config), erlang:element(4, Config), Enabled, erlang:element(6, Config), erlang:element(7, Config)}. -file("src/ftpasta.gleam", 103). ?DOC(" Append some data onto a file on the server.\n"). -spec append_bytes(connection(), bitstring(), binary()) -> {ok, nil} | {error, ftpasta@ftp_error:f_t_p_error()}. append_bytes(Connection, Data, Remote_file) -> ftpasta_ffi:append_bin(erlang:element(2, Connection), Data, Remote_file). -file("src/ftpasta.gleam", 122). ?DOC(" Change directories on the remote server.\n"). -spec change_working_dir(connection(), binary()) -> {ok, nil} | {error, ftpasta@ftp_error:f_t_p_error()}. change_working_dir(Connection, Remote_dir) -> ftpasta_ffi:cd(erlang:element(2, Connection), Remote_dir). -file("src/ftpasta.gleam", 130). ?DOC(" Close the connection.\n"). -spec close(connection()) -> nil. close(Connection) -> ftpasta_ffi:close(erlang:element(2, Connection)). -file("src/ftpasta.gleam", 135). ?DOC(" Create a new directory on the server.\n"). -spec create_dir(connection(), binary()) -> {ok, nil} | {error, ftpasta@ftp_error:f_t_p_error()}. create_dir(Connection, Remote_dir) -> ftpasta_ffi:mkdir(erlang:element(2, Connection), Remote_dir). -file("src/ftpasta.gleam", 143). ?DOC(" Remove an empty directory from the server.\n"). -spec delete_dir(connection(), binary()) -> {ok, nil} | {error, ftpasta@ftp_error:f_t_p_error()}. delete_dir(Connection, Remote_dir) -> ftpasta_ffi:rmdir(erlang:element(2, Connection), Remote_dir). -file("src/ftpasta.gleam", 151). ?DOC(" Remove a file from the server.\n"). -spec delete_file(connection(), binary()) -> {ok, nil} | {error, ftpasta@ftp_error:f_t_p_error()}. delete_file(Connection, Remote_file) -> ftpasta_ffi:delete(erlang:element(2, Connection), Remote_file). -file("src/ftpasta.gleam", 177). ?DOC(" Fetch a file from the server, and return it as a BitArray.\n"). -spec download_bytes(connection(), binary()) -> {ok, bitstring()} | {error, ftpasta@ftp_error:f_t_p_error()}. download_bytes(Connection, Remote_file) -> ftpasta_ffi:recv_bin(erlang:element(2, Connection), Remote_file). -file("src/ftpasta.gleam", 217). ?DOC(" Get the current remote path used for the connection.\n"). -spec get_working_dir(connection()) -> {ok, binary()} | {error, ftpasta@ftp_error:f_t_p_error()}. get_working_dir(Connection) -> ftpasta_ffi:pwd(erlang:element(2, Connection)). -file("src/ftpasta.gleam", 239). ?DOC(" Rename a file or directory on the server.\n"). -spec rename(connection(), binary(), binary()) -> {ok, nil} | {error, ftpasta@ftp_error:f_t_p_error()}. rename(Connection, Old_path, New_path) -> ftpasta_ffi:rename(erlang:element(2, Connection), Old_path, New_path). -file("src/ftpasta.gleam", 248). ?DOC(" Send a data in memory to the server.\n"). -spec upload_bytes(connection(), bitstring(), binary()) -> {ok, nil} | {error, ftpasta@ftp_error:f_t_p_error()}. upload_bytes(Connection, Data, Remote_file) -> ftpasta_ffi:send_bin(erlang:element(2, Connection), Data, Remote_file). -file("src/ftpasta.gleam", 293). ?DOC( " Parse a line from the output of \"ls\".\n" " This is obviously gross, but FTP lacks better options without extensions.\n" ). -spec parse_ls_line(binary()) -> {ok, listing()} | {error, ftpasta@ftp_error:f_t_p_error()}. parse_ls_line(Text) -> Whitespace@1 = case gleam@regexp:from_string(<<" +"/utf8>>) of {ok, Whitespace} -> Whitespace; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"ftpasta"/utf8>>, function => <<"parse_ls_line"/utf8>>, line => 294, value => _assert_fail, start => 7692, 'end' => 7744, pattern_start => 7703, pattern_end => 7717}) end, Filename@1 = case gleam@regexp:from_string(<<"(?:\\S+ +){8}(.+)"/utf8>>) of {ok, Filename} -> Filename; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"ftpasta"/utf8>>, function => <<"parse_ls_line"/utf8>>, line => 295, value => _assert_fail@1, start => 7747, 'end' => 7812, pattern_start => 7758, pattern_end => 7770}) end, gleam@result:'try'(case gleam@regexp:scan(Filename@1, Text) of [{match, _, [{some, X}]}] -> {ok, X}; _ -> {error, dir_parse_error} end, fun(Name) -> case gleam@regexp:split(Whitespace@1, Text) of [<<"d"/utf8, _/binary>> | _] -> {ok, {directory, Name}}; [<<"-"/utf8, _/binary>>, _, _, _, Size_text | _] -> case gleam_stdlib:parse_int(Size_text) of {ok, Size} -> {ok, {file, Name, Size}}; {error, _} -> {error, dir_parse_error} end; [<<"l"/utf8, _/binary>> | _] -> case gleam@string:split_once(Name, <<" -> "/utf8>>) of {ok, {Name@1, Target}} -> {ok, {link, Name@1, Target}}; {error, _} -> {error, dir_parse_error} end; _ -> {error, dir_parse_error} end end). -file("src/ftpasta.gleam", 222). ?DOC(" List all files, symlinks, and subdirectories in a directory.\n"). -spec list(connection(), binary()) -> {ok, list(listing())} | {error, ftpasta@ftp_error:f_t_p_error()}. list(Connection, Remote_dir) -> Re@1 = case gleam@regexp:from_string(<<"[\\r\\n]+"/utf8>>) of {ok, Re} -> Re; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"ftpasta"/utf8>>, function => <<"list"/utf8>>, line => 226, value => _assert_fail, start => 5718, 'end' => 5769, pattern_start => 5729, pattern_end => 5735}) end, Arg = case Remote_dir of <<""/utf8>> -> <<"-Al"/utf8>>; Dir -> <<"-Al "/utf8, Dir/binary>> end, gleam@result:'try'( ftpasta_ffi:ls(erlang:element(2, Connection), Arg), fun(Text) -> _pipe = gleam@regexp:split(Re@1, Text), _pipe@1 = gleam@list:filter( _pipe, fun(S) -> not gleam@string:is_empty(S) end ), _pipe@2 = gleam@list:try_map(_pipe@1, fun parse_ls_line/1), gleam@result:map( _pipe@2, fun(_capture) -> gleam@list:sort(_capture, fun compare_listings/2) end ) end ). -file("src/ftpasta.gleam", 159). ?DOC(" Remove a directory and all contents from the server.\n"). -spec delete_recursive(connection(), binary()) -> {ok, nil} | {error, ftpasta@ftp_error:f_t_p_error()}. delete_recursive(Connection, Remote_dir) -> gleam@result:'try'( list(Connection, Remote_dir), fun(Items) -> gleam@result:'try'( gleam@list:try_map( Items, fun(Item) -> Path = <<<>/binary, (erlang:element(2, Item))/binary>>, case Item of {file, _, _} -> delete_file(Connection, Path); {link, _, _} -> delete_file(Connection, Path); {directory, _} -> delete_recursive(Connection, Path) end end ), fun(_) -> delete_dir(Connection, Remote_dir) end ) end ). -file("src/ftpasta.gleam", 324). ?DOC(" Update the local path stored in the FTP library.\n"). -spec reset_local_dir(connection()) -> {ok, nil} | {error, ftpasta@ftp_error:f_t_p_error()}. reset_local_dir(Connection) -> case simplifile:current_directory() of {ok, Path} -> ftpasta_ffi:lcd(erlang:element(2, Connection), Path); {error, E} -> {error, {local_file_error, E}} end. -file("src/ftpasta.gleam", 112). ?DOC(" Append a file to another file on the server.\n"). -spec append_file(connection(), binary(), binary()) -> {ok, nil} | {error, ftpasta@ftp_error:f_t_p_error()}. append_file(Connection, Local_file, Remote_file) -> gleam@result:'try'( reset_local_dir(Connection), fun(_) -> ftpasta_ffi:append( erlang:element(2, Connection), Local_file, Remote_file ) end ). -file("src/ftpasta.gleam", 185). ?DOC(" Fetch a file from the server.\n"). -spec download_file(connection(), binary(), binary()) -> {ok, nil} | {error, ftpasta@ftp_error:f_t_p_error()}. download_file(Connection, Remote_file, Local_file) -> gleam@result:'try'( reset_local_dir(Connection), fun(_) -> ftpasta_ffi:recv( erlang:element(2, Connection), Remote_file, Local_file ) end ). -file("src/ftpasta.gleam", 195). ?DOC(" Fetch a directory and all contents recursively from the server.\n"). -spec download_recursive(connection(), binary(), binary()) -> {ok, nil} | {error, ftpasta@ftp_error:f_t_p_error()}. download_recursive(Connection, Remote_dir, Local_dir) -> gleam@result:'try'( begin _pipe = simplifile_erl:create_directory(Local_dir), gleam@result:map_error( _pipe, fun(Field@0) -> {local_file_error, Field@0} end ) end, fun(_) -> gleam@result:'try'( list(Connection, Remote_dir), fun(Items) -> _pipe@1 = gleam@list:try_map( Items, fun(Item) -> Local_path = filepath:join( Local_dir, erlang:element(2, Item) ), Remote_path = <<<>/binary, (erlang:element(2, Item))/binary>>, case Item of {file, _, _} -> download_file( Connection, Remote_path, Local_path ); {link, _, _} -> download_file( Connection, Remote_path, Local_path ); {directory, _} -> download_recursive( Connection, Remote_path, Local_path ) end end ), gleam@result:replace(_pipe@1, nil) end ) end ). -file("src/ftpasta.gleam", 257). ?DOC(" Send a file to the server.\n"). -spec upload_file(connection(), binary(), binary()) -> {ok, nil} | {error, ftpasta@ftp_error:f_t_p_error()}. upload_file(Connection, Local_file, Remote_file) -> gleam@result:'try'( reset_local_dir(Connection), fun(_) -> ftpasta_ffi:send( erlang:element(2, Connection), Local_file, Remote_file ) end ). -file("src/ftpasta.gleam", 267). ?DOC(" Send a directory and all contents to the server.\n"). -spec upload_recursive(connection(), binary(), binary()) -> {ok, nil} | {error, ftpasta@ftp_error:f_t_p_error()}. upload_recursive(Connection, Local_dir, Remote_dir) -> gleam@result:'try'( create_dir(Connection, Remote_dir), fun(_) -> gleam@result:'try'( begin _pipe = simplifile_erl:read_directory(Local_dir), gleam@result:map_error( _pipe, fun(Field@0) -> {local_file_error, Field@0} end ) end, fun(Items) -> _pipe@1 = gleam@list:try_map( Items, fun(Name) -> Local_path = filepath:join(Local_dir, Name), Remote_path = <<<>/binary, Name/binary>>, case simplifile_erl:is_file(Local_path) of {ok, true} -> upload_file( Connection, Local_path, Remote_path ); {ok, false} -> upload_recursive( Connection, Local_path, Remote_path ); {error, E} -> {error, {local_file_error, E}} end end ), gleam@result:replace(_pipe@1, nil) end ) end ).