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