Test

Hex.pm Version Hex.pm Downloads

ftpfilez

Really simple FTPS client - only put, get and delete.

This client is used in combination with filezcache and zotonic.

Distinction with other FTPS clients is:

NOTA BENE FTPS is enabled by default, because you really shouldn't use plain FTP anymore. Plain FTP can be enabled explicitly with tls => false, primarily for local testing.

Example

rebar3 shell
===> Verifying dependencies...
===> Analyzing applications...
===> Compiling ftpfilez
Erlang/OTP 23 [erts-11.1] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [hipe]

Eshell V11.1  (abort with ^G)
1> application:ensure_all_started(ftpfilez).
{ok,[ftpfilez]}
2> Cfg = #{ username => <<"anonymous">>, password => <<"test@example.com">> }.
{<<"anonymous">>, <<"test@example.com">>}
3> ftpfilez:put(Cfg, <<"ftps://ftp.example.com/LICENSE">>, {filename, "LICENSE"}).
ok
4> ftpfilez:stream(Cfg, <<"ftps://ftp.example.com/LICENSE">>, fun(X) -> io:format("!! ~p~n", [X]) end).
!! stream_start
!! <<"\n    Apache License\n", ...>>
!! eof
5> ftpfilez:delete(Cfg, <<"ftps://ftp.example.com/LICENSE">>).
ok

Request Queue

Requests use a shared pool of persistent FTP connections. The pool keeps at most four connections open per server and queues requests while all four connections for that server are busy. Connections are matched by username and the other connection settings. When a different username needs a connection, the least recently used idle connection for that server is selected. The pool first tries to authenticate the existing connection with the new username, so the TCP/TLS connection remains open when the server supports switching users. If that fails, the connection is closed and reopened with the new credentials. If all existing connections are busy, the pool opens another connection up to the per-server limit.

The pool monitors request callers. If a caller stops while its request is queued, that request is removed immediately. Caller monitors are removed when requests finish.

Connections are checked before and after use. If a server closes an idle connection, its worker reconnects when the next request needs it. A connection or login failure is returned directly to the caller, and a short backoff prevents a queued burst from repeatedly attempting the same failing connection.

Idle connections are closed after five minutes. The pool manager performs the close synchronously before processing another checkout, avoiding a race between idle expiry and connection reuse. The timeout can be configured with the connection_idle_timeout_ms application setting. A synchronous close is limited to five seconds; an unresponsive connection owner is killed. This limit can be configured with connection_close_timeout_ms.

The get, put and delete requests can be queued. A function or pid can be given as a callback for the job result. Streaming downloads can also be queued with queue_stream or queue_stream_id. Both synchronous and queued operations use the same pool, including connection setup and authentication.

Streaming callbacks run in the process calling stream/3, including function and MFA callbacks. For queued streams, this is the job process. The pool worker waits for each callback to finish before reading the next chunk, preserving event order and backpressure. Callback exceptions are raised in the caller, and the interrupted FTP connection is closed before reuse. If the caller stops while a callback is pending, the worker releases that connection as well. This preserves compatibility with process-owned resources such as filezcache writers. max_connections sets the limit per host and port (default four, capped at four); queued job processes waiting for a connection do not consume FTP connections. Use ftpfilez:status() to inspect waiting requests and active connections.

Example:

6> {ok, ReqId, JobPid} = ftpfilez:queue_put(Cfg, <<"ftps://ftp.example.com/LICENSE">>, {filename, "LICENSE"}, fun(ReqId,Result) -> io:format("!! ~p~n", [ Result ]) end).
{ok,#Ref<0.0.0.3684>,<0.854.0>}

The returned JobPid is the pid of the process in the ftpfilez queue supervisor. The callback can be a function (arity 2), {M,F,A} or a pid.

If the callback is a pid then it will receive the message {ftpfilez_done, ReqId, Result}.

Pool Status

ftpfilez:status/0 returns a sanitized snapshot of all connection workers, including their idle, busy, or disconnected state, host, port, username and TLS mode. It also reports the number of waiting and in-flight requests. Passwords and TLS options are not included.

Checking with deprecated OTP FTP APIs

Deprecation checks remain enabled by default. To allow the FTP client APIs that this library intentionally uses, run:

make xref dialyzer FTPFILEZ_ALLOW_DEPRECATED_FTP=1
# Or invoke rebar3 directly:
FTPFILEZ_ALLOW_DEPRECATED_FTP=1 rebar3 xref
FTPFILEZ_ALLOW_DEPRECATED_FTP=1 rebar3 dialyzer

This suppresses xref warnings for the existing FTP functions used by the library and their compiler deprecation warnings (also emitted during Dialyzer's compile step). Dialyzer type analysis, other deprecation warnings, and undefined-function checks remain enabled. Removed FTP functions are not ignored. Omit the option or set it to 0 to restore the default checks.