Packages

A Gleam FTP/FTPS client library with full RFC support for both passive and active mode

Current section

Files

Jump to
gftp src gftp@actor.erl
Raw

src/gftp@actor.erl

-module(gftp@actor).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gftp/actor.gleam").
-export([with_call_timeout/2, welcome_message/1, with_mode/2, with_nat_workaround/2, with_active_mode/2, with_passive_stream_builder/2, into_secure/2, clear_command_channel/1, login/3, noop/1, pwd/1, cwd/2, cdup/1, mkd/2, rmd/2, dele/2, rename/3, transfer_type/2, rest/2, abor/1, mdtm/2, size/2, feat/1, opts/3, site/2, eprt/4, custom_command/3, mlst/2, retr/3, stor/3, appe/3, list/2, nlst/2, mlsd/2, custom_data_command/4, open_retr/2, open_stor/2, open_appe/2, open_list/2, open_nlst/2, open_mlsd/2, open_data_command/3, close_data_channel/2, quit/1, start/1]).
-export_type([state/0, handle/0, message/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(
" OTP actor wrapper for gftp that serializes all FTP operations.\n"
"\n"
" Wrapping an `FtpClient` in an actor prevents protocol state corruption\n"
" by ensuring that control commands are rejected while a data channel is open.\n"
"\n"
" ## Usage\n"
"\n"
" ```gleam\n"
" import gftp\n"
" import gftp/actor as ftp_actor\n"
"\n"
" let assert Ok(client) = gftp.connect(\"ftp.example.com\", 21)\n"
" let assert Ok(started) = ftp_actor.start(client)\n"
" let handle = started.data\n"
"\n"
" let assert Ok(_) = ftp_actor.login(handle, \"user\", \"password\")\n"
" let assert Ok(cwd) = ftp_actor.pwd(handle)\n"
" let assert Ok(_) = ftp_actor.quit(handle)\n"
" ```\n"
"\n"
" ## Message-based streaming\n"
"\n"
" The actor enforces chunk protection: once a data channel is opened via\n"
" `open_retr`, `open_stor`, etc., all control commands return\n"
" `Error(DataTransferInProgress)` until `close_data_channel` is called.\n"
"\n"
" ```gleam\n"
" let assert Ok(data_stream) = ftp_actor.open_retr(handle, \"file.txt\")\n"
" // ... receive data via stream.receive_next_packet_as_message ...\n"
" let assert Ok(_) = ftp_actor.close_data_channel(handle, data_stream)\n"
" ```\n"
).
-type state() :: {state, gftp:ftp_client(), boolean()}.
-opaque handle() :: {handle, gleam@erlang@process:subject(message()), integer()}.
-opaque message() :: {welcome_message,
gleam@erlang@process:subject(gleam@option:option(binary()))} |
{with_mode, gftp@mode:mode(), gleam@erlang@process:subject(nil)} |
{with_nat_workaround, boolean(), gleam@erlang@process:subject(nil)} |
{with_active_mode, integer(), gleam@erlang@process:subject(nil)} |
{with_passive_stream_builder,
fun((binary(), integer()) -> {ok, gftp@stream:data_stream()} |
{error, gftp@result:ftp_error()}),
gleam@erlang@process:subject(nil)} |
{into_secure,
kafein:wrap_options(),
gleam@erlang@process:subject({ok, nil} |
{error, gftp@result:ftp_error()})} |
{clear_command_channel,
gleam@erlang@process:subject({ok, nil} |
{error, gftp@result:ftp_error()})} |
{login,
binary(),
binary(),
gleam@erlang@process:subject({ok, nil} |
{error, gftp@result:ftp_error()})} |
{noop,
gleam@erlang@process:subject({ok, nil} |
{error, gftp@result:ftp_error()})} |
{pwd,
gleam@erlang@process:subject({ok, binary()} |
{error, gftp@result:ftp_error()})} |
{cwd,
binary(),
gleam@erlang@process:subject({ok, nil} |
{error, gftp@result:ftp_error()})} |
{cdup,
gleam@erlang@process:subject({ok, nil} |
{error, gftp@result:ftp_error()})} |
{mkd,
binary(),
gleam@erlang@process:subject({ok, nil} |
{error, gftp@result:ftp_error()})} |
{rmd,
binary(),
gleam@erlang@process:subject({ok, nil} |
{error, gftp@result:ftp_error()})} |
{dele,
binary(),
gleam@erlang@process:subject({ok, nil} |
{error, gftp@result:ftp_error()})} |
{rename,
binary(),
binary(),
gleam@erlang@process:subject({ok, nil} |
{error, gftp@result:ftp_error()})} |
{transfer_type,
gftp@file_type:file_type(),
gleam@erlang@process:subject({ok, nil} |
{error, gftp@result:ftp_error()})} |
{rest,
integer(),
gleam@erlang@process:subject({ok, nil} |
{error, gftp@result:ftp_error()})} |
{abor,
gleam@erlang@process:subject({ok, nil} |
{error, gftp@result:ftp_error()})} |
{mdtm,
binary(),
gleam@erlang@process:subject({ok, gleam@time@timestamp:timestamp()} |
{error, gftp@result:ftp_error()})} |
{size,
binary(),
gleam@erlang@process:subject({ok, integer()} |
{error, gftp@result:ftp_error()})} |
{feat,
gleam@erlang@process:subject({ok,
gleam@dict:dict(binary(), gleam@option:option(binary()))} |
{error, gftp@result:ftp_error()})} |
{opts,
binary(),
gleam@option:option(binary()),
gleam@erlang@process:subject({ok, nil} |
{error, gftp@result:ftp_error()})} |
{site_cmd,
binary(),
gleam@erlang@process:subject({ok, gftp@response:response()} |
{error, gftp@result:ftp_error()})} |
{eprt,
binary(),
integer(),
gftp@mode:ip_version(),
gleam@erlang@process:subject({ok, nil} |
{error, gftp@result:ftp_error()})} |
{custom_command,
binary(),
list(gftp@status:status()),
gleam@erlang@process:subject({ok, gftp@response:response()} |
{error, gftp@result:ftp_error()})} |
{mlst,
gleam@option:option(binary()),
gleam@erlang@process:subject({ok, binary()} |
{error, gftp@result:ftp_error()})} |
{retr,
binary(),
fun((gftp@stream:data_stream()) -> {ok, nil} |
{error, gftp@result:ftp_error()}),
gleam@erlang@process:subject({ok, nil} |
{error, gftp@result:ftp_error()})} |
{stor,
binary(),
fun((gftp@stream:data_stream()) -> {ok, nil} |
{error, gftp@result:ftp_error()}),
gleam@erlang@process:subject({ok, nil} |
{error, gftp@result:ftp_error()})} |
{appe,
binary(),
fun((gftp@stream:data_stream()) -> {ok, nil} |
{error, gftp@result:ftp_error()}),
gleam@erlang@process:subject({ok, nil} |
{error, gftp@result:ftp_error()})} |
{list_dir,
gleam@option:option(binary()),
gleam@erlang@process:subject({ok, list(binary())} |
{error, gftp@result:ftp_error()})} |
{nlst,
gleam@option:option(binary()),
gleam@erlang@process:subject({ok, list(binary())} |
{error, gftp@result:ftp_error()})} |
{mlsd,
gleam@option:option(binary()),
gleam@erlang@process:subject({ok, list(binary())} |
{error, gftp@result:ftp_error()})} |
{custom_data_command,
binary(),
list(gftp@status:status()),
fun((gftp@stream:data_stream(), gftp@response:response()) -> {ok, nil} |
{error, gftp@result:ftp_error()}),
gleam@erlang@process:subject({ok, nil} |
{error, gftp@result:ftp_error()})} |
{open_retr,
binary(),
gleam@erlang@process:pid_(),
gleam@erlang@process:subject({ok, gftp@stream:data_stream()} |
{error, gftp@result:ftp_error()})} |
{open_stor,
binary(),
gleam@erlang@process:pid_(),
gleam@erlang@process:subject({ok, gftp@stream:data_stream()} |
{error, gftp@result:ftp_error()})} |
{open_appe,
binary(),
gleam@erlang@process:pid_(),
gleam@erlang@process:subject({ok, gftp@stream:data_stream()} |
{error, gftp@result:ftp_error()})} |
{open_list,
gleam@option:option(binary()),
gleam@erlang@process:pid_(),
gleam@erlang@process:subject({ok, gftp@stream:data_stream()} |
{error, gftp@result:ftp_error()})} |
{open_nlst,
gleam@option:option(binary()),
gleam@erlang@process:pid_(),
gleam@erlang@process:subject({ok, gftp@stream:data_stream()} |
{error, gftp@result:ftp_error()})} |
{open_mlsd,
gleam@option:option(binary()),
gleam@erlang@process:pid_(),
gleam@erlang@process:subject({ok, gftp@stream:data_stream()} |
{error, gftp@result:ftp_error()})} |
{open_data_command,
binary(),
list(gftp@status:status()),
gleam@erlang@process:pid_(),
gleam@erlang@process:subject({ok,
{gftp@stream:data_stream(), gftp@response:response()}} |
{error, gftp@result:ftp_error()})} |
{close_data_channel,
gftp@stream:data_stream(),
gleam@erlang@process:subject({ok, nil} |
{error, gftp@result:ftp_error()})} |
{quit,
gleam@erlang@process:subject({ok, nil} |
{error, gftp@result:ftp_error()})}.
-file("src/gftp/actor.gleam", 217).
?DOC(
" Set the timeout in milliseconds for actor calls. Defaults to 30000 (30 seconds).\n"
"\n"
" This timeout applies to all subsequent calls made through this handle.\n"
" For large file transfers via callback-based methods (`retr`, `stor`, `appe`),\n"
" consider increasing this value since the actor call blocks until the callback completes.\n"
"\n"
" ```gleam\n"
" let handle = ftp_actor.with_call_timeout(handle, 120_000)\n"
" ```\n"
).
-spec with_call_timeout(handle(), integer()) -> handle().
with_call_timeout(Handle, Timeout) ->
{handle, erlang:element(2, Handle), Timeout}.
-file("src/gftp/actor.gleam", 224).
?DOC(" Get the welcome message from the FTP server.\n").
-spec welcome_message(handle()) -> gleam@option:option(binary()).
welcome_message(Handle) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(Field@0) -> {welcome_message, Field@0} end
).
-file("src/gftp/actor.gleam", 229).
?DOC(" Set the data transfer mode.\n").
-spec with_mode(handle(), gftp@mode:mode()) -> nil.
with_mode(Handle, Mode) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {with_mode, Mode, _capture} end
).
-file("src/gftp/actor.gleam", 234).
?DOC(" Enable or disable the NAT workaround for passive mode.\n").
-spec with_nat_workaround(handle(), boolean()) -> nil.
with_nat_workaround(Handle, Enabled) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {with_nat_workaround, Enabled, _capture} end
).
-file("src/gftp/actor.gleam", 239).
?DOC(" Enable active mode with the specified data connection timeout.\n").
-spec with_active_mode(handle(), integer()) -> nil.
with_active_mode(Handle, Timeout) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {with_active_mode, Timeout, _capture} end
).
-file("src/gftp/actor.gleam", 244).
?DOC(" Set a custom passive stream builder.\n").
-spec with_passive_stream_builder(
handle(),
fun((binary(), integer()) -> {ok, gftp@stream:data_stream()} |
{error, gftp@result:ftp_error()})
) -> nil.
with_passive_stream_builder(Handle, Builder) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {with_passive_stream_builder, Builder, _capture} end
).
-file("src/gftp/actor.gleam", 257).
?DOC(" Switch to explicit secure mode (FTPS).\n").
-spec into_secure(handle(), kafein:wrap_options()) -> {ok, nil} |
{error, gftp@result:ftp_error()}.
into_secure(Handle, Ssl_options) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {into_secure, Ssl_options, _capture} end
).
-file("src/gftp/actor.gleam", 265).
?DOC(" Clear the command channel encryption.\n").
-spec clear_command_channel(handle()) -> {ok, nil} |
{error, gftp@result:ftp_error()}.
clear_command_channel(Handle) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(Field@0) -> {clear_command_channel, Field@0} end
).
-file("src/gftp/actor.gleam", 272).
?DOC(" Log in to the FTP server.\n").
-spec login(handle(), binary(), binary()) -> {ok, nil} |
{error, gftp@result:ftp_error()}.
login(Handle, Username, Password) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {login, Username, Password, _capture} end
).
-file("src/gftp/actor.gleam", 281).
?DOC(" Send a NOOP command.\n").
-spec noop(handle()) -> {ok, nil} | {error, gftp@result:ftp_error()}.
noop(Handle) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(Field@0) -> {noop, Field@0} end
).
-file("src/gftp/actor.gleam", 286).
?DOC(" Get the current working directory.\n").
-spec pwd(handle()) -> {ok, binary()} | {error, gftp@result:ftp_error()}.
pwd(Handle) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(Field@0) -> {pwd, Field@0} end
).
-file("src/gftp/actor.gleam", 291).
?DOC(" Change working directory.\n").
-spec cwd(handle(), binary()) -> {ok, nil} | {error, gftp@result:ftp_error()}.
cwd(Handle, Path) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {cwd, Path, _capture} end
).
-file("src/gftp/actor.gleam", 296).
?DOC(" Change to parent directory.\n").
-spec cdup(handle()) -> {ok, nil} | {error, gftp@result:ftp_error()}.
cdup(Handle) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(Field@0) -> {cdup, Field@0} end
).
-file("src/gftp/actor.gleam", 301).
?DOC(" Create a directory.\n").
-spec mkd(handle(), binary()) -> {ok, nil} | {error, gftp@result:ftp_error()}.
mkd(Handle, Path) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {mkd, Path, _capture} end
).
-file("src/gftp/actor.gleam", 306).
?DOC(" Remove a directory.\n").
-spec rmd(handle(), binary()) -> {ok, nil} | {error, gftp@result:ftp_error()}.
rmd(Handle, Path) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {rmd, Path, _capture} end
).
-file("src/gftp/actor.gleam", 311).
?DOC(" Delete a file.\n").
-spec dele(handle(), binary()) -> {ok, nil} | {error, gftp@result:ftp_error()}.
dele(Handle, Path) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {dele, Path, _capture} end
).
-file("src/gftp/actor.gleam", 316).
?DOC(" Rename a file.\n").
-spec rename(handle(), binary(), binary()) -> {ok, nil} |
{error, gftp@result:ftp_error()}.
rename(Handle, From, To) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {rename, From, To, _capture} end
).
-file("src/gftp/actor.gleam", 321).
?DOC(" Set the file transfer type.\n").
-spec transfer_type(handle(), gftp@file_type:file_type()) -> {ok, nil} |
{error, gftp@result:ftp_error()}.
transfer_type(Handle, File_type) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {transfer_type, File_type, _capture} end
).
-file("src/gftp/actor.gleam", 329).
?DOC(" Set the restart offset for the next transfer.\n").
-spec rest(handle(), integer()) -> {ok, nil} | {error, gftp@result:ftp_error()}.
rest(Handle, Offset) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {rest, Offset, _capture} end
).
-file("src/gftp/actor.gleam", 334).
?DOC(" Abort an active file transfer.\n").
-spec abor(handle()) -> {ok, nil} | {error, gftp@result:ftp_error()}.
abor(Handle) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(Field@0) -> {abor, Field@0} end
).
-file("src/gftp/actor.gleam", 339).
?DOC(" Get the modification time of a file.\n").
-spec mdtm(handle(), binary()) -> {ok, gleam@time@timestamp:timestamp()} |
{error, gftp@result:ftp_error()}.
mdtm(Handle, Pathname) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {mdtm, Pathname, _capture} end
).
-file("src/gftp/actor.gleam", 344).
?DOC(" Get the size of a file in bytes.\n").
-spec size(handle(), binary()) -> {ok, integer()} |
{error, gftp@result:ftp_error()}.
size(Handle, Pathname) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {size, Pathname, _capture} end
).
-file("src/gftp/actor.gleam", 349).
?DOC(" Retrieve server features (FEAT command).\n").
-spec feat(handle()) -> {ok,
gleam@dict:dict(binary(), gleam@option:option(binary()))} |
{error, gftp@result:ftp_error()}.
feat(Handle) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(Field@0) -> {feat, Field@0} end
).
-file("src/gftp/actor.gleam", 354).
?DOC(" Set a server option (OPTS command).\n").
-spec opts(handle(), binary(), gleam@option:option(binary())) -> {ok, nil} |
{error, gftp@result:ftp_error()}.
opts(Handle, Option, Value) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {opts, Option, Value, _capture} end
).
-file("src/gftp/actor.gleam", 363).
?DOC(" Execute a SITE command.\n").
-spec site(handle(), binary()) -> {ok, gftp@response:response()} |
{error, gftp@result:ftp_error()}.
site(Handle, Sub_command) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {site_cmd, Sub_command, _capture} end
).
-file("src/gftp/actor.gleam", 368).
?DOC(" Execute an EPRT command.\n").
-spec eprt(handle(), binary(), integer(), gftp@mode:ip_version()) -> {ok, nil} |
{error, gftp@result:ftp_error()}.
eprt(Handle, Address, Port, Ip_version) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {eprt, Address, Port, Ip_version, _capture} end
).
-file("src/gftp/actor.gleam", 383).
?DOC(" Execute a custom FTP command.\n").
-spec custom_command(handle(), binary(), list(gftp@status:status())) -> {ok,
gftp@response:response()} |
{error, gftp@result:ftp_error()}.
custom_command(Handle, Command_str, Expected_statuses) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) ->
{custom_command, Command_str, Expected_statuses, _capture}
end
).
-file("src/gftp/actor.gleam", 396).
?DOC(" Execute an MLST command.\n").
-spec mlst(handle(), gleam@option:option(binary())) -> {ok, binary()} |
{error, gftp@result:ftp_error()}.
mlst(Handle, Pathname) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {mlst, Pathname, _capture} end
).
-file("src/gftp/actor.gleam", 403).
?DOC(" Download a file using a callback.\n").
-spec retr(
handle(),
binary(),
fun((gftp@stream:data_stream()) -> {ok, nil} |
{error, gftp@result:ftp_error()})
) -> {ok, nil} | {error, gftp@result:ftp_error()}.
retr(Handle, Path, Reader) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {retr, Path, Reader, _capture} end
).
-file("src/gftp/actor.gleam", 412).
?DOC(" Upload a file using a callback.\n").
-spec stor(
handle(),
binary(),
fun((gftp@stream:data_stream()) -> {ok, nil} |
{error, gftp@result:ftp_error()})
) -> {ok, nil} | {error, gftp@result:ftp_error()}.
stor(Handle, Path, Writer) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {stor, Path, Writer, _capture} end
).
-file("src/gftp/actor.gleam", 421).
?DOC(" Append to a file using a callback.\n").
-spec appe(
handle(),
binary(),
fun((gftp@stream:data_stream()) -> {ok, nil} |
{error, gftp@result:ftp_error()})
) -> {ok, nil} | {error, gftp@result:ftp_error()}.
appe(Handle, Path, Writer) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {appe, Path, Writer, _capture} end
).
-file("src/gftp/actor.gleam", 430).
?DOC(" List directory contents.\n").
-spec list(handle(), gleam@option:option(binary())) -> {ok, list(binary())} |
{error, gftp@result:ftp_error()}.
list(Handle, Pathname) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {list_dir, Pathname, _capture} end
).
-file("src/gftp/actor.gleam", 435).
?DOC(" List file names only.\n").
-spec nlst(handle(), gleam@option:option(binary())) -> {ok, list(binary())} |
{error, gftp@result:ftp_error()}.
nlst(Handle, Pathname) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {nlst, Pathname, _capture} end
).
-file("src/gftp/actor.gleam", 440).
?DOC(" Machine-readable directory listing.\n").
-spec mlsd(handle(), gleam@option:option(binary())) -> {ok, list(binary())} |
{error, gftp@result:ftp_error()}.
mlsd(Handle, Pathname) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {mlsd, Pathname, _capture} end
).
-file("src/gftp/actor.gleam", 445).
?DOC(" Execute a custom data command with a callback.\n").
-spec custom_data_command(
handle(),
binary(),
list(gftp@status:status()),
fun((gftp@stream:data_stream(), gftp@response:response()) -> {ok, nil} |
{error, gftp@result:ftp_error()})
) -> {ok, nil} | {error, gftp@result:ftp_error()}.
custom_data_command(Handle, Command_str, Expected_statuses, On_data_stream) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) ->
{custom_data_command,
Command_str,
Expected_statuses,
On_data_stream,
_capture}
end
).
-file("src/gftp/actor.gleam", 465).
?DOC(
" Open a data channel for downloading a file.\n"
"\n"
" The returned `DataStream` has its controlling process set to the caller,\n"
" so message-based I/O (`receive_next_packet_as_message`) works correctly.\n"
).
-spec open_retr(handle(), binary()) -> {ok, gftp@stream:data_stream()} |
{error, gftp@result:ftp_error()}.
open_retr(Handle, Path) ->
Caller = erlang:self(),
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {open_retr, Path, Caller, _capture} end
).
-file("src/gftp/actor.gleam", 474).
?DOC(
" Open a data channel for uploading a file.\n"
"\n"
" The returned `DataStream` has its controlling process set to the caller,\n"
" so message-based I/O (`receive_next_packet_as_message`) works correctly.\n"
).
-spec open_stor(handle(), binary()) -> {ok, gftp@stream:data_stream()} |
{error, gftp@result:ftp_error()}.
open_stor(Handle, Path) ->
Caller = erlang:self(),
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {open_stor, Path, Caller, _capture} end
).
-file("src/gftp/actor.gleam", 483).
?DOC(
" Open a data channel for appending to a file.\n"
"\n"
" The returned `DataStream` has its controlling process set to the caller,\n"
" so message-based I/O (`receive_next_packet_as_message`) works correctly.\n"
).
-spec open_appe(handle(), binary()) -> {ok, gftp@stream:data_stream()} |
{error, gftp@result:ftp_error()}.
open_appe(Handle, Path) ->
Caller = erlang:self(),
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {open_appe, Path, Caller, _capture} end
).
-file("src/gftp/actor.gleam", 492).
?DOC(
" Open a data channel for a LIST directory listing.\n"
"\n"
" The returned `DataStream` has its controlling process set to the caller,\n"
" so message-based I/O (`receive_next_packet_as_message`) works correctly.\n"
).
-spec open_list(handle(), gleam@option:option(binary())) -> {ok,
gftp@stream:data_stream()} |
{error, gftp@result:ftp_error()}.
open_list(Handle, Pathname) ->
Caller = erlang:self(),
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {open_list, Pathname, Caller, _capture} end
).
-file("src/gftp/actor.gleam", 504).
?DOC(
" Open a data channel for an NLST file name listing.\n"
"\n"
" The returned `DataStream` has its controlling process set to the caller,\n"
" so message-based I/O (`receive_next_packet_as_message`) works correctly.\n"
).
-spec open_nlst(handle(), gleam@option:option(binary())) -> {ok,
gftp@stream:data_stream()} |
{error, gftp@result:ftp_error()}.
open_nlst(Handle, Pathname) ->
Caller = erlang:self(),
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {open_nlst, Pathname, Caller, _capture} end
).
-file("src/gftp/actor.gleam", 516).
?DOC(
" Open a data channel for an MLSD machine-readable listing.\n"
"\n"
" The returned `DataStream` has its controlling process set to the caller,\n"
" so message-based I/O (`receive_next_packet_as_message`) works correctly.\n"
).
-spec open_mlsd(handle(), gleam@option:option(binary())) -> {ok,
gftp@stream:data_stream()} |
{error, gftp@result:ftp_error()}.
open_mlsd(Handle, Pathname) ->
Caller = erlang:self(),
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {open_mlsd, Pathname, Caller, _capture} end
).
-file("src/gftp/actor.gleam", 528).
?DOC(
" Open a data channel for a custom command.\n"
"\n"
" The returned `DataStream` has its controlling process set to the caller,\n"
" so message-based I/O (`receive_next_packet_as_message`) works correctly.\n"
).
-spec open_data_command(handle(), binary(), list(gftp@status:status())) -> {ok,
{gftp@stream:data_stream(), gftp@response:response()}} |
{error, gftp@result:ftp_error()}.
open_data_command(Handle, Command_str, Expected_statuses) ->
Caller = erlang:self(),
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) ->
{open_data_command,
Command_str,
Expected_statuses,
Caller,
_capture}
end
).
-file("src/gftp/actor.gleam", 543).
?DOC(" Close a data channel and finalize the transfer.\n").
-spec close_data_channel(handle(), gftp@stream:data_stream()) -> {ok, nil} |
{error, gftp@result:ftp_error()}.
close_data_channel(Handle, Data_stream) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(_capture) -> {close_data_channel, Data_stream, _capture} end
).
-file("src/gftp/actor.gleam", 556).
?DOC(" Quit the FTP session and stop the actor.\n").
-spec quit(handle()) -> {ok, nil} | {error, gftp@result:ftp_error()}.
quit(Handle) ->
gleam@otp@actor:call(
erlang:element(2, Handle),
erlang:element(3, Handle),
fun(Field@0) -> {quit, Field@0} end
).
-file("src/gftp/actor.gleam", 745).
?DOC(
" Guard against issuing control commands while a data channel is open.\n"
" If `chunk` is `True`, replies with `DataTransferInProgress` and continues.\n"
" Otherwise, runs the operation and continues with the (possibly updated) state.\n"
).
-spec guard_chunk(
state(),
gleam@erlang@process:subject({ok, LLI} | {error, gftp@result:ftp_error()}),
fun((state()) -> {state(), {ok, LLI} | {error, gftp@result:ftp_error()}})
) -> gleam@otp@actor:next(state(), message()).
guard_chunk(State, Reply, Operation) ->
case erlang:element(3, State) of
true ->
gleam@erlang@process:send(Reply, {error, data_transfer_in_progress}),
gleam@otp@actor:continue(State);
false ->
{New_state, Result} = Operation(State),
gleam@erlang@process:send(Reply, Result),
gleam@otp@actor:continue(New_state)
end.
-file("src/gftp/actor.gleam", 765).
?DOC(
" Guard for open_* commands that return a `DataStream`.\n"
" On success, sets `chunk = True` and transfers socket ownership to the caller.\n"
).
-spec guard_chunk_open(
state(),
gleam@erlang@process:pid_(),
gleam@erlang@process:subject({ok, gftp@stream:data_stream()} |
{error, gftp@result:ftp_error()}),
fun((state()) -> {ok, gftp@stream:data_stream()} |
{error, gftp@result:ftp_error()})
) -> gleam@otp@actor:next(state(), message()).
guard_chunk_open(State, Caller, Reply, Operation) ->
case erlang:element(3, State) of
true ->
gleam@erlang@process:send(Reply, {error, data_transfer_in_progress}),
gleam@otp@actor:continue(State);
false ->
Result = Operation(State),
{New_chunk, Final_result} = case Result of
{ok, Ds} ->
case gftp@stream:controlling_process(Ds, Caller) of
{ok, _} ->
{true, {ok, Ds}};
{error, E} ->
{false, {error, {socket, E}}}
end;
{error, E@1} ->
{false, {error, E@1}}
end,
gleam@erlang@process:send(Reply, Final_result),
gleam@otp@actor:continue(
{state, erlang:element(2, State), New_chunk}
)
end.
-file("src/gftp/actor.gleam", 794).
?DOC(
" Guard for open_data_command which returns a `#(DataStream, Response)`.\n"
" On success, sets `chunk = True` and transfers socket ownership to the caller.\n"
).
-spec guard_chunk_open_pair(
state(),
gleam@erlang@process:pid_(),
gleam@erlang@process:subject({ok,
{gftp@stream:data_stream(), gftp@response:response()}} |
{error, gftp@result:ftp_error()}),
fun((state()) -> {ok, {gftp@stream:data_stream(), gftp@response:response()}} |
{error, gftp@result:ftp_error()})
) -> gleam@otp@actor:next(state(), message()).
guard_chunk_open_pair(State, Caller, Reply, Operation) ->
case erlang:element(3, State) of
true ->
gleam@erlang@process:send(Reply, {error, data_transfer_in_progress}),
gleam@otp@actor:continue(State);
false ->
Result = Operation(State),
{New_chunk, Final_result} = case Result of
{ok, {Ds, Resp}} ->
case gftp@stream:controlling_process(Ds, Caller) of
{ok, _} ->
{true, {ok, {Ds, Resp}}};
{error, E} ->
{false, {error, {socket, E}}}
end;
{error, E@1} ->
{false, {error, E@1}}
end,
gleam@erlang@process:send(Reply, Final_result),
gleam@otp@actor:continue(
{state, erlang:element(2, State), New_chunk}
)
end.
-file("src/gftp/actor.gleam", 564).
-spec handle_message(state(), message()) -> gleam@otp@actor:next(state(), message()).
handle_message(State, Message) ->
case Message of
{welcome_message, Reply} ->
gleam@erlang@process:send(
Reply,
gftp:welcome_message(erlang:element(2, State))
),
gleam@otp@actor:continue(State);
{with_mode, Mode, Reply@1} ->
Client = gftp:with_mode(erlang:element(2, State), Mode),
gleam@erlang@process:send(Reply@1, nil),
gleam@otp@actor:continue({state, Client, erlang:element(3, State)});
{with_nat_workaround, Enabled, Reply@2} ->
Client@1 = gftp:with_nat_workaround(
erlang:element(2, State),
Enabled
),
gleam@erlang@process:send(Reply@2, nil),
gleam@otp@actor:continue(
{state, Client@1, erlang:element(3, State)}
);
{with_active_mode, Timeout, Reply@3} ->
Client@2 = gftp:with_active_mode(erlang:element(2, State), Timeout),
gleam@erlang@process:send(Reply@3, nil),
gleam@otp@actor:continue(
{state, Client@2, erlang:element(3, State)}
);
{with_passive_stream_builder, Builder, Reply@4} ->
Client@3 = gftp:with_passive_stream_builder(
erlang:element(2, State),
Builder
),
gleam@erlang@process:send(Reply@4, nil),
gleam@otp@actor:continue(
{state, Client@3, erlang:element(3, State)}
);
{into_secure, Ssl_options, Reply@5} ->
guard_chunk(
State,
Reply@5,
fun(S) ->
case gftp:into_secure(erlang:element(2, S), Ssl_options) of
{ok, New_client} ->
{{state, New_client, erlang:element(3, S)},
{ok, nil}};
{error, E} ->
{S, {error, E}}
end
end
);
{clear_command_channel, Reply@6} ->
guard_chunk(
State,
Reply@6,
fun(S@1) ->
case gftp:clear_command_channel(erlang:element(2, S@1)) of
{ok, New_client@1} ->
{{state, New_client@1, erlang:element(3, S@1)},
{ok, nil}};
{error, E@1} ->
{S@1, {error, E@1}}
end
end
);
{login, Username, Password, Reply@7} ->
guard_chunk(
State,
Reply@7,
fun(S@2) ->
{S@2,
gftp:login(erlang:element(2, S@2), Username, Password)}
end
);
{noop, Reply@8} ->
guard_chunk(
State,
Reply@8,
fun(S@3) -> {S@3, gftp:noop(erlang:element(2, S@3))} end
);
{pwd, Reply@9} ->
guard_chunk(
State,
Reply@9,
fun(S@4) -> {S@4, gftp:pwd(erlang:element(2, S@4))} end
);
{cwd, Path, Reply@10} ->
guard_chunk(
State,
Reply@10,
fun(S@5) -> {S@5, gftp:cwd(erlang:element(2, S@5), Path)} end
);
{cdup, Reply@11} ->
guard_chunk(
State,
Reply@11,
fun(S@6) -> {S@6, gftp:cdup(erlang:element(2, S@6))} end
);
{mkd, Path@1, Reply@12} ->
guard_chunk(
State,
Reply@12,
fun(S@7) -> {S@7, gftp:mkd(erlang:element(2, S@7), Path@1)} end
);
{rmd, Path@2, Reply@13} ->
guard_chunk(
State,
Reply@13,
fun(S@8) -> {S@8, gftp:rmd(erlang:element(2, S@8), Path@2)} end
);
{dele, Path@3, Reply@14} ->
guard_chunk(
State,
Reply@14,
fun(S@9) -> {S@9, gftp:dele(erlang:element(2, S@9), Path@3)} end
);
{rename, From, To, Reply@15} ->
guard_chunk(
State,
Reply@15,
fun(S@10) ->
{S@10, gftp:rename(erlang:element(2, S@10), From, To)}
end
);
{transfer_type, File_type, Reply@16} ->
guard_chunk(
State,
Reply@16,
fun(S@11) ->
{S@11,
gftp:transfer_type(erlang:element(2, S@11), File_type)}
end
);
{rest, Offset, Reply@17} ->
guard_chunk(
State,
Reply@17,
fun(S@12) ->
{S@12, gftp:rest(erlang:element(2, S@12), Offset)}
end
);
{abor, Reply@18} ->
guard_chunk(
State,
Reply@18,
fun(S@13) -> {S@13, gftp:abor(erlang:element(2, S@13))} end
);
{mdtm, Pathname, Reply@19} ->
guard_chunk(
State,
Reply@19,
fun(S@14) ->
{S@14, gftp:mdtm(erlang:element(2, S@14), Pathname)}
end
);
{size, Pathname@1, Reply@20} ->
guard_chunk(
State,
Reply@20,
fun(S@15) ->
{S@15, gftp:size(erlang:element(2, S@15), Pathname@1)}
end
);
{feat, Reply@21} ->
guard_chunk(
State,
Reply@21,
fun(S@16) -> {S@16, gftp:feat(erlang:element(2, S@16))} end
);
{opts, Option, Value, Reply@22} ->
guard_chunk(
State,
Reply@22,
fun(S@17) ->
{S@17, gftp:opts(erlang:element(2, S@17), Option, Value)}
end
);
{site_cmd, Sub_command, Reply@23} ->
guard_chunk(
State,
Reply@23,
fun(S@18) ->
{S@18, gftp:site(erlang:element(2, S@18), Sub_command)}
end
);
{eprt, Address, Port, Ip_version, Reply@24} ->
guard_chunk(
State,
Reply@24,
fun(S@19) ->
{S@19,
gftp:eprt(
erlang:element(2, S@19),
Address,
Port,
Ip_version
)}
end
);
{custom_command, Command_str, Expected_statuses, Reply@25} ->
guard_chunk(
State,
Reply@25,
fun(S@20) ->
{S@20,
gftp:custom_command(
erlang:element(2, S@20),
Command_str,
Expected_statuses
)}
end
);
{mlst, Pathname@2, Reply@26} ->
guard_chunk(
State,
Reply@26,
fun(S@21) ->
{S@21, gftp:mlst(erlang:element(2, S@21), Pathname@2)}
end
);
{retr, Path@4, Reader, Reply@27} ->
guard_chunk(
State,
Reply@27,
fun(S@22) ->
{S@22, gftp:retr(erlang:element(2, S@22), Path@4, Reader)}
end
);
{stor, Path@5, Writer, Reply@28} ->
guard_chunk(
State,
Reply@28,
fun(S@23) ->
{S@23, gftp:stor(erlang:element(2, S@23), Path@5, Writer)}
end
);
{appe, Path@6, Writer@1, Reply@29} ->
guard_chunk(
State,
Reply@29,
fun(S@24) ->
{S@24, gftp:appe(erlang:element(2, S@24), Path@6, Writer@1)}
end
);
{list_dir, Pathname@3, Reply@30} ->
guard_chunk(
State,
Reply@30,
fun(S@25) ->
{S@25, gftp:list(erlang:element(2, S@25), Pathname@3)}
end
);
{nlst, Pathname@4, Reply@31} ->
guard_chunk(
State,
Reply@31,
fun(S@26) ->
{S@26, gftp:nlst(erlang:element(2, S@26), Pathname@4)}
end
);
{mlsd, Pathname@5, Reply@32} ->
guard_chunk(
State,
Reply@32,
fun(S@27) ->
{S@27, gftp:mlsd(erlang:element(2, S@27), Pathname@5)}
end
);
{custom_data_command,
Command_str@1,
Expected_statuses@1,
On_data_stream,
Reply@33} ->
guard_chunk(
State,
Reply@33,
fun(S@28) ->
{S@28,
gftp:custom_data_command(
erlang:element(2, S@28),
Command_str@1,
Expected_statuses@1,
On_data_stream
)}
end
);
{open_retr, Path@7, Caller, Reply@34} ->
guard_chunk_open(
State,
Caller,
Reply@34,
fun(S@29) ->
gftp@internal@data_channel:open_retr(
erlang:element(2, S@29),
Path@7
)
end
);
{open_stor, Path@8, Caller@1, Reply@35} ->
guard_chunk_open(
State,
Caller@1,
Reply@35,
fun(S@30) ->
gftp@internal@data_channel:open_stor(
erlang:element(2, S@30),
Path@8
)
end
);
{open_appe, Path@9, Caller@2, Reply@36} ->
guard_chunk_open(
State,
Caller@2,
Reply@36,
fun(S@31) ->
gftp@internal@data_channel:open_appe(
erlang:element(2, S@31),
Path@9
)
end
);
{open_list, Pathname@6, Caller@3, Reply@37} ->
guard_chunk_open(
State,
Caller@3,
Reply@37,
fun(S@32) ->
gftp@internal@data_channel:open_list(
erlang:element(2, S@32),
Pathname@6
)
end
);
{open_nlst, Pathname@7, Caller@4, Reply@38} ->
guard_chunk_open(
State,
Caller@4,
Reply@38,
fun(S@33) ->
gftp@internal@data_channel:open_nlst(
erlang:element(2, S@33),
Pathname@7
)
end
);
{open_mlsd, Pathname@8, Caller@5, Reply@39} ->
guard_chunk_open(
State,
Caller@5,
Reply@39,
fun(S@34) ->
gftp@internal@data_channel:open_mlsd(
erlang:element(2, S@34),
Pathname@8
)
end
);
{open_data_command,
Command_str@2,
Expected_statuses@2,
Caller@6,
Reply@40} ->
guard_chunk_open_pair(
State,
Caller@6,
Reply@40,
fun(S@35) ->
gftp@internal@data_channel:open_data_command(
erlang:element(2, S@35),
Command_str@2,
Expected_statuses@2
)
end
);
{close_data_channel, Data_stream, Reply@41} ->
Result = gftp@internal@data_channel:close_data_channel(
erlang:element(2, State),
Data_stream
),
gleam@erlang@process:send(Reply@41, Result),
gleam@otp@actor:continue({state, erlang:element(2, State), false});
{quit, Reply@42} ->
Result@1 = gftp:quit(erlang:element(2, State)),
_ = gftp:shutdown(erlang:element(2, State)),
gleam@erlang@process:send(Reply@42, Result@1),
gleam@otp@actor:stop()
end.
-file("src/gftp/actor.gleam", 193).
?DOC(
" Start an FTP actor wrapping the given client.\n"
"\n"
" The client should already be connected (via `gftp.connect`).\n"
" All subsequent operations should go through the returned `Handle`.\n"
" \n"
" You should never keep an active `FtpClient` around after starting the actor,\n"
" as it would allow bypassing the chunk protection and corrupting the protocol state.\n"
" Only interact with the FTP session via the returned `Handle` and the functions in this module.\n"
).
-spec start(gftp:ftp_client()) -> {ok, gleam@otp@actor:started(handle())} |
{error, gleam@otp@actor:start_error()}.
start(Client) ->
_pipe = {state, Client, false},
_pipe@1 = gleam@otp@actor:new(_pipe),
_pipe@2 = gleam@otp@actor:on_message(_pipe@1, fun handle_message/2),
_pipe@3 = gleam@otp@actor:start(_pipe@2),
gleam@result:map(
_pipe@3,
fun(Started) ->
{started,
erlang:element(2, Started),
{handle, erlang:element(3, Started), 30000}}
end
).