Packages
livebook
0.17.0
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook/file_system/local.ex
defmodule Livebook.FileSystem.Local do
# File system backed by local disk.
defstruct [:default_path, id: "local"]
alias Livebook.FileSystem
@type t :: %__MODULE__{
default_path: FileSystem.path()
}
@doc """
Returns a new file system struct.
## Options
* `:default_path` - the default directory path. Defaults
to the current working directory
"""
@spec new(keyword()) :: t()
def new(opts \\ []) do
default_path =
Keyword.get_lazy(opts, :default_path, fn ->
File.cwd!() |> FileSystem.Utils.ensure_dir_path()
end)
FileSystem.Utils.assert_dir_path!(default_path)
%__MODULE__{default_path: default_path}
end
end
defimpl Livebook.FileSystem, for: Livebook.FileSystem.Local do
alias Livebook.FileSystem
@stream_chunk_size_in_bytes 16384
def type(_file_system) do
:local
end
def default_path(file_system) do
file_system.default_path
end
def list(file_system, path, recursive) do
FileSystem.Utils.assert_dir_path!(path)
case file_ls(path) do
{:ok, filenames} ->
paths =
Enum.map(filenames, fn name ->
path = Path.join(path, name)
if File.dir?(path), do: path <> "/", else: path
end)
to_traverse =
if recursive do
Enum.filter(paths, &FileSystem.Utils.dir_path?/1)
else
[]
end
Enum.reduce(to_traverse, {:ok, paths}, fn path, result ->
with {:ok, current_paths} <- result,
{:ok, new_paths} <- list(file_system, path, recursive) do
{:ok, current_paths ++ new_paths}
end
end)
{:error, error} ->
FileSystem.Utils.posix_error(error)
end
end
defp file_ls(dir) do
# TODO: use File.ls/1 back once this is fixed in the OTP version
# that we require. See https://github.com/erlang/otp/issues/4779
#
# Erlang currently chokes on emoji directories on Windows, so we
# discard those
case :file.list_dir(dir) do
{:ok, names} ->
{:ok,
for(
name <- names,
string =
try do
IO.chardata_to_string(name)
rescue
_ -> nil
end,
do: string
)}
{:error, error} ->
{:error, error}
end
end
def read(_file_system, path) do
FileSystem.Utils.assert_regular_path!(path)
case File.read(path) do
{:ok, binary} -> {:ok, binary}
{:error, error} -> FileSystem.Utils.posix_error(error)
end
end
def write(_file_system, path, content) do
FileSystem.Utils.assert_regular_path!(path)
dir = Path.dirname(path)
with :ok <- File.mkdir_p(dir),
:ok <- File.write(path, content, [:sync]) do
:ok
else
{:error, error} -> FileSystem.Utils.posix_error(error)
end
end
def access(_file_system, path) do
case File.stat(path) do
{:ok, stat} -> {:ok, stat.access}
{:error, error} -> FileSystem.Utils.posix_error(error)
end
end
def create_dir(_file_system, path) do
FileSystem.Utils.assert_dir_path!(path)
case File.mkdir_p(path) do
:ok -> :ok
{:error, error} -> FileSystem.Utils.posix_error(error)
end
end
def remove(_file_system, path) do
case File.rm_rf(path) do
{:ok, _paths} -> :ok
{:error, error, _paths} -> FileSystem.Utils.posix_error(error)
end
end
def copy(_file_system, source_path, destination_path) do
FileSystem.Utils.assert_same_type!(source_path, destination_path)
containing_dir = Path.dirname(destination_path)
case File.mkdir_p(containing_dir) do
:ok ->
case File.cp_r(source_path, destination_path) do
{:ok, _paths} -> :ok
{:error, error, _path} -> FileSystem.Utils.posix_error(error)
end
{:error, error} ->
FileSystem.Utils.posix_error(error)
end
end
def rename(_file_system, source_path, destination_path) do
FileSystem.Utils.assert_same_type!(source_path, destination_path)
if File.exists?(destination_path) do
FileSystem.Utils.posix_error(:eexist)
else
containing_dir = Path.dirname(destination_path)
with :ok <- File.mkdir_p(containing_dir),
:ok <- rename_or_move(source_path, destination_path) do
:ok
else
{:error, error} ->
FileSystem.Utils.posix_error(error)
end
end
end
defp rename_or_move(source_path, destination_path) do
with {:error, :exdev} <- File.rename(source_path, destination_path) do
# For files on different file systems, try to copy and remove instead
with {:ok, _paths} <- File.cp_r(source_path, destination_path),
{:ok, _paths} <- File.rm_rf(source_path) do
:ok
else
{:error, error, _paths} -> {:error, error}
end
end
end
def etag_for(_file_system, path) do
case File.stat(path) do
{:ok, stat} ->
%{size: size, mtime: mtime} = stat
hash = {size, mtime} |> :erlang.phash2() |> Integer.to_string(16)
etag = <<?", hash::binary, ?">>
{:ok, etag}
{:error, error} ->
FileSystem.Utils.posix_error(error)
end
end
def exists?(_file_system, path) do
if FileSystem.Utils.dir_path?(path) do
{:ok, File.dir?(path)}
else
{:ok, File.exists?(path)}
end
end
def resolve_path(_file_system, dir_path, subject) do
FileSystem.Utils.assert_dir_path!(dir_path)
if subject == "" do
dir_path
else
dir? = FileSystem.Utils.dir_path?(subject) or Path.basename(subject) in [".", ".."]
expanded_path = Path.expand(subject, dir_path)
if dir? do
FileSystem.Utils.ensure_dir_path(expanded_path)
else
expanded_path
end
end
end
def write_stream_init(_file_system, path, _opts) do
FileSystem.Utils.assert_regular_path!(path)
downloads_dir = Path.join(Livebook.Config.tmp_path(), "downloads")
download_path = Path.join(downloads_dir, Livebook.Utils.random_long_id())
with :ok <- File.mkdir_p(downloads_dir),
{:ok, device} <- File.open(download_path, [:write]) do
{:ok, %{path: path, download_path: download_path, device: device}}
else
{:error, error} -> FileSystem.Utils.posix_error(error)
end
end
def write_stream_chunk(_file_system, state, chunk) when is_binary(chunk) do
:ok = IO.binwrite(state.device, chunk)
{:ok, state}
end
def write_stream_finish(_file_system, state) do
File.close(state.device)
with :ok <- File.mkdir_p(Path.dirname(state.path)),
:ok <- rename_or_move(state.download_path, state.path) do
:ok
else
{:error, error} ->
File.rm(state.download_path)
FileSystem.Utils.posix_error(error)
end
end
def write_stream_halt(_file_system, state) do
File.close(state.device)
File.rm(state.download_path)
:ok
end
def read_stream_into(_file_system, path, collectable) do
FileSystem.Utils.assert_regular_path!(path)
try do
result =
path
|> File.stream!(@stream_chunk_size_in_bytes, [])
|> Enum.into(collectable)
{:ok, result}
rescue
error in File.Error -> FileSystem.Utils.posix_error(error.reason)
end
end
def load(_file_system, _fields), do: raise("not implemented")
def dump(_file_system), do: raise("not implemented")
def external_metadata(_file_system), do: raise("not implemented")
end