ExFTP.StorageConnector behaviour (ExFTP v1.0.4)

View Source

A behaviour defining a Storage Connector.

Storage Connectors are used by the FTP interface to interact with a particular type of storage.

๐Ÿ‘€ See Also

๐Ÿ“– Resources

Summary

Types

State held onto by the server and modified by the StorageConnector.

Information about a given file, directory, or symlink

A string representing a file path (e.g "/path/to/file.txt" or "/path/to/dir/")

A Port representing a socket to communicate with an FTP client.

Callbacks

Create a function/1 that writes a stream to storage

Deletes a given directory

Deletes a given file

Whether a given path is an existing directory

Returns a stream to read the raw bytes of an object specified by a given path

Returns content_info/0 representing a single object in a given directory

Returns a list of content_info/0 representing each object in a given directory

Returns the current working directory

Creates a directory, given a path

Types

connector_state()

@type connector_state() :: %{}

State held onto by the server and modified by the StorageConnector.

It may be used by the storage connector to keep stateful values.

โš ๏ธ Reminders

Special Keys

  • current_working_directory: String.t/0 should always exist. It represents the "directory" we're operating from

๐Ÿ“– Resources

content_info()

@type content_info() :: %{
  file_name: String.t(),
  modified_datetime: DateTime.t(),
  size: integer(),
  access: :read | :write | :read_write | :none,
  type: :directory | :symlink | :file
}

Information about a given file, directory, or symlink

๐Ÿท๏ธ Keys

  • filename :: String.t/0 e.g "my_file.txt", "my_dir/" or "my_sym_link -> target/"
  • modified_datetime :: DateTime.t/0
  • size :: integer e.g 1000
  • access :: :read | :write | :read_write | :none

  • type :: :directory | :symlink | :file

๐Ÿ‘€ See Also

๐Ÿ“– Resources

path()

@type path() :: String.t()

A string representing a file path (e.g "/path/to/file.txt" or "/path/to/dir/")

โš ๏ธ Reminders

Relative paths

The Server will ensure all paths sent to the connector are absolute paths, so you don't need to worry about handling relative paths, or .. notations

๐Ÿ“– Resources

socket()

@type socket() :: port()

A Port representing a socket to communicate with an FTP client.

โš ๏ธ Reminders

Sockets are everywhere

This socket represents the TCP connection between the FTP Server and the client (often through port 21)

While related, this socket is not a PASV socket, which is a negotiated, temporary socket for sending or receiving data.

๐Ÿ“– Resources

Callbacks

create_write_func(path, connector_state, opts)

@callback create_write_func(path(), connector_state(), opts :: list()) :: function()

Create a function/1 that writes a stream to storage

๐Ÿท๏ธ Params

๐Ÿ’ป Examples

    @impl StorageConnector
    def create_write_func(path, connector_state, opts \ []) do
      fn stream ->
        fs = File.stream!(path)

        try do
          _ =
            stream
            |> chunk_stream(opts)
            |> Enum.into(fs)

          {:ok, connector_state}
        rescue
          _ ->
            {:error, "Failed to transfer"}
        end
      end
    end

delete_directory(path, connector_state)

@callback delete_directory(path(), connector_state()) ::
  {:ok, connector_state()} | {:error, term()}

Deletes a given directory

๐Ÿท๏ธ Params

โคต๏ธ Returns

โœ… On Success

  {:ok, connector_state}

โŒ On Failure

  {:error, err}

๐Ÿ’ป Examples

iex> alias ExFTP.Storage.FileConnector
iex> connector_state = %{current_working_directory: "/"}
iex> dir_to_make = File.cwd!() |> Path.join("new_dir")
iex> {:ok, connector_state} = FileConnector.make_directory(dir_to_make, connector_state)
iex> dir_to_rm = dir_to_make
iex> {:ok, connector_state} = FileConnector.delete_directory(dir_to_rm, connector_state)
iex> FileConnector.directory_exists?(dir_to_rm, connector_state)
false

๐Ÿ“– Resources

delete_file(path, connector_state)

@callback delete_file(path(), connector_state()) ::
  {:ok, connector_state()} | {:error, term()}

Deletes a given file

๐Ÿท๏ธ Params

โคต๏ธ Returns

โœ… On Success

  {:ok, connector_state}

โŒ On Failure

  {:error, err}

๐Ÿ“– Resources

directory_exists?(path, connector_state)

@callback directory_exists?(path(), connector_state()) :: boolean()

Whether a given path is an existing directory

๐Ÿท๏ธ Params

โคต๏ธ Returns

โœ… On Success

  `true` or `false`

๐Ÿ’ป Examples

iex> alias ExFTP.Storage.FileConnector
iex> FileConnector.directory_exists?("/tmp", %{current_working_directory: "/"})
true
iex> FileConnector.directory_exists?("/does-not-exist", %{current_working_directory: "/"})
false

๐Ÿ“– Resources

get_content(path, connector_state)

@callback get_content(path(), connector_state()) :: {:ok, any()} | {:error, term()}

Returns a stream to read the raw bytes of an object specified by a given path

๐Ÿท๏ธ Params

โคต๏ธ Returns

โœ… On Success

  {:ok, data}

โŒ On Failure

  {:error, err}

๐Ÿ’ป Examples

iex> alias ExFTP.Storage.FileConnector
iex> connector_state = %{current_working_directory: "/"}
iex> file_to_get_content = File.cwd!() |> File.ls!() |> Enum.filter(&String.contains?(&1,".")) |> hd()
iex> path = Path.join(File.cwd!(), file_to_get_content)
iex> {:ok, _data} = FileConnector.get_content(path, connector_state)

๐Ÿ“– Resources

get_content_info(path, connector_state)

@callback get_content_info(path(), connector_state()) ::
  {:ok, content_info()} | {:error, term()}

Returns content_info/0 representing a single object in a given directory

๐Ÿท๏ธ Params

โคต๏ธ Returns

โœ… On Success

  {:ok, %{...}}

โŒ On Failure

  {:error, err}

๐Ÿ’ป Examples

iex> alias ExFTP.Storage.FileConnector
iex> connector_state = %{current_working_directory: "/"}
iex> file_to_get_info = File.cwd!() |> File.ls!() |> hd()
iex> path = Path.join(File.cwd!(), file_to_get_info)
iex> {:ok, content_info} = FileConnector.get_content_info(path, connector_state)

๐Ÿ“– Resources

get_directory_contents(path, connector_state)

@callback get_directory_contents(path(), connector_state()) ::
  {:ok, [content_info()]} | {:error, term()}

Returns a list of content_info/0 representing each object in a given directory

๐Ÿท๏ธ Params

โคต๏ธ Returns

โœ… On Success

  {:ok, [%{...}, ...]}

โŒ On Failure

  {:error, err}

๐Ÿ’ป Examples

iex> alias ExFTP.Storage.FileConnector
iex> connector_state = %{current_working_directory: "/"}
iex> dir = File.cwd!()
iex> {:ok, _content_infos} = FileConnector.get_directory_contents(dir, connector_state)

๐Ÿ“– Resources

get_working_directory(connector_state)

@callback get_working_directory(connector_state()) :: String.t()

Returns the current working directory

๐Ÿท๏ธ Params

โคต๏ธ Returns

โœ… On Success

  (The working directory)

๐Ÿ’ป Examples

iex> alias ExFTP.Storage.FileConnector
iex> FileConnector.get_working_directory(%{current_working_directory: "/"})
"/"

โš ๏ธ Reminders

Doesn't the connector_state already have it?

Most Storage Connectors will just return what's already in the connector_state. However, this method is implemented just in case a Connector has a different way of determining the current working directory.

๐Ÿ“– Resources

make_directory(path, connector_state)

@callback make_directory(path(), connector_state()) ::
  {:ok, connector_state()} | {:error, term()}

Creates a directory, given a path

๐Ÿท๏ธ Params

โคต๏ธ Returns

โœ… On Success

  {:ok, connector_state}

โŒ On Failure

  {:error, err}

๐Ÿ’ป Examples

iex> alias ExFTP.Storage.FileConnector
iex> connector_state = %{current_working_directory: "/"}
iex> dir_to_make = File.cwd!() |> Path.join("new_dir")
iex> {:ok, connector_state} = FileConnector.make_directory(dir_to_make, connector_state)
iex> FileConnector.directory_exists?(dir_to_make, connector_state)
true
iex> {:ok, _connector_state} = FileConnector.delete_directory(dir_to_make, connector_state)
iex> FileConnector.directory_exists?(dir_to_rm, connector_state)
false

๐Ÿ“– Resources