livebook/lib/livebook/file_system.ex
Jonatan Kłosko 982a345ddc
Improve notebook file locking to work across nodes (#675)
* Improve notebook file locking to work across nodes

* Add node check for local file system opreations

* Replace node with host id

* Refactor process down cleanup

* Scope local file system with node

* local? -> type
2021-11-05 00:29:04 +01:00

169 lines
4.7 KiB
Elixir

defprotocol Livebook.FileSystem do
@moduledoc false
# This protocol defines an interface for file systems
# that can be plugged into Livebook.
@typedoc """
A path uniquely idenfies file in the file system.
Path has most of the semantics of regular file paths,
with the following exceptions:
* path must be be absolute for consistency
* directory path must have a trailing slash, whereas
regular file path must not have a trailing slash.
Rationale: some file systems allow a directory and
a file with the same name to co-exist, while path
needs to distinguish between them
"""
@type path :: String.t()
@typedoc """
A human-readable error message clarifying the operation
failure reason.
"""
@type error :: String.t()
@type access :: :read | :write | :read_write | :none
@doc """
Returns a term uniquely identifying the resource used as a file
system.
"""
@spec resource_identifier(t()) :: term()
def resource_identifier(file_system)
@doc """
Returns the file system type.
Based on the underlying resource, the type can be either:
* `:local` - if the resource is local to its node
* `:global` - if the resource is external and available
accessible from any node
"""
@spec type(t()) :: :local | :global
def type(file_system)
@doc """
Returns the default directory path.
To some extent this is similar to current working directory
in a regular file system. For most file systems this
will just be the root path.
"""
@spec default_path(t()) :: path()
def default_path(file_system)
@doc """
Returns a list of files located in the given directory.
When `recursive` is set to `true`, nested directories
are traversed and the final list includes all the paths.
"""
@spec list(t(), path(), boolean()) :: {:ok, list(path())} | {:error, error()}
def list(file_system, path, recursive)
@doc """
Returns binary content of the given file.
"""
@spec read(t(), path()) :: {:ok, binary()} | {:error, error()}
def read(file_system, path)
@doc """
Writes the given binary content to the given file.
If the file exists, it gets overridden.
If the file doesn't exist, it gets created along with
all the necessary directories.
"""
@spec write(t(), path(), binary()) :: :ok | {:error, error()}
def write(file_system, path, content)
@doc """
Returns the current access level to the given file.
If determining the access is costly, then this function may
always return the most liberal access, since all access
functions return error on an invalid attempt.
"""
@spec access(t(), path()) :: {:ok, access()} | {:error, error()}
def access(file_system, path)
@doc """
Creates the given directory unless it already exists.
All necessary parent directories are created as well.
"""
@spec create_dir(t(), path()) :: :ok | {:error, error()}
def create_dir(file_system, path)
@doc """
Removes the given file.
If a directory is given, all of its contents are removed
recursively.
If the file doesn't exist, no error is returned.
"""
@spec remove(t(), path()) :: :ok | {:error, error()}
def remove(file_system, path)
@doc """
Copies the given file.
The given files must be of the same type.
If regular files are given, the contents are copied,
potentially overriding the destination if it already exists.
If directories are given, the directory contents are copied
recursively.
"""
@spec copy(t(), path(), path()) :: :ok | {:error, error()}
def copy(file_system, source_path, destination_path)
@doc """
Renames the given file.
If a directory is given, it gets renamed as expected and
consequently all of the child paths change.
If the destination exists, an error is returned.
"""
@spec rename(t(), path(), path()) :: :ok | {:error, error()}
def rename(file_system, source_path, destination_path)
@doc """
Returns a version identifier for the given file.
The resulting value must be a string of ASCII characters
placed between double quotes, suitable for use as the
value of the ETag HTTP header.
"""
@spec etag_for(t(), path()) :: {:ok, String.t()} | {:error, error()}
def etag_for(file_system, path)
@doc """
Checks if the given path exists in the file system.
"""
@spec exists?(t(), path()) :: {:ok, boolean()} | {:error, error()}
def exists?(file_system, path)
@doc """
Resolves `subject` against a valid directory path.
The `subject` may be either relative or absolute,
contain special sequences such as ".." and ".",
but the interpretation is left up to the file system.
In other words, this has the semantics of path join
followed by expand.
"""
@spec resolve_path(t(), path(), String.t()) :: path()
def resolve_path(file_system, dir_path, subject)
end