Sheetshow.Store behaviour (Sheetshow v0.1.0)

Copy Markdown View Source

Where a spreadsheet file lives, and how to read and write it.

A file backend is two things that vary on their own: a format, which says how cells become bytes, and a store, which says where those bytes are and what it can promise about writing them. Sheetshow.Xlsx is the first format; Sheetshow.Store.Local is the first store.

workbook = Sheetshow.Workbook.xlsx("costs.xlsx")

A store is a value, like everything else here: the module that does the work, and whatever that module needs to find the file.

iex> store = Sheetshow.Store.local("costs.xlsx")
iex> {store.module, store.location}
{Sheetshow.Store.Local, "costs.xlsx"}

Versions, and what they are for

read/1 gives back the bytes and a version: whatever the store uses to tell one state of the file from another. write/3 takes that version back as a precondition, and a store that can refuse a write whose version no longer holds answers %Sheetshow.Error{reason: :conflict} instead of overwriting. That is :conditional_write, and writing a cell to a file replaces the whole file, so it matters more here than at Google; see What Sheetshow Can Promise. Not every store can promise it; Sheetshow.Backend.supports?/2 says which kind a workbook sits on.

A version is the store's own business, an entity tag here and a size and a modification time there, and nothing outside the store reads into it. Two atoms are reserved, so that a caller never has to guess what a missing one meant:

versionwhat it says
:absentthe file is not there. A write given it says only if it is still not there, which is what stops two processes both believing they created it.
:unknownthe store cannot tell this state of the file from another, so there is nothing to make a precondition out of. Read again, or write with :any and take the risk.

write/3 also takes :any, which writes over whatever is there.

Summary

Types

A version a write insists is still true, or :any for whatever is there.

t()

What the store uses to tell one state of a file from another.

Callbacks

Whether this store can refuse a write whose precondition no longer holds.

Whether the file is there at all.

The bytes, and the version they were at.

Writes the bytes, giving back the version they are now at. :any writes whatever is there; a version writes only if that is still what is there.

Functions

Whether this store can refuse a write whose precondition no longer holds.

Whether the file is there.

A file on the machine this is running on.

A file in a Nextcloud account, by the path you would see in the web interface. The same as webdav/2 with the URL spelled out for you.

The bytes, and the version they were at. A file that is not there is %Sheetshow.Error{reason: :not_found}, told apart from every other way a read can fail so that a caller meaning to create one can carry on.

A file on a WebDAV server: Nextcloud, ownCloud, or anything else that speaks it. See Sheetshow.Store.WebDAV for the options and for what it can promise that a local file cannot.

Writes the bytes, if the precondition still holds.

Types

precondition()

@type precondition() :: version() | :any

A version a write insists is still true, or :any for whatever is there.

t()

@type t() :: %Sheetshow.Store{location: term(), module: module(), options: keyword()}

version()

@type version() :: term()

What the store uses to tell one state of a file from another.

Callbacks

conditional_write?(t)

@callback conditional_write?(t()) :: boolean()

Whether this store can refuse a write whose precondition no longer holds.

exists?(t)

@callback exists?(t()) :: boolean()

Whether the file is there at all.

read(t)

@callback read(t()) :: {:ok, {binary(), version()}} | {:error, Sheetshow.Error.t()}

The bytes, and the version they were at.

write(t, binary, precondition)

@callback write(t(), binary(), precondition()) ::
  {:ok, version()} | {:error, Sheetshow.Error.t()}

Writes the bytes, giving back the version they are now at. :any writes whatever is there; a version writes only if that is still what is there.

Functions

conditional_write?(store)

@spec conditional_write?(t()) :: boolean()

Whether this store can refuse a write whose precondition no longer holds.

exists?(store)

@spec exists?(t()) :: boolean()

Whether the file is there.

local(path)

@spec local(Path.t()) :: t()

A file on the machine this is running on.

iex> Sheetshow.Store.local("costs.xlsx").module
Sheetshow.Store.Local

nextcloud(base_url, username, path, options \\ [])

@spec nextcloud(String.t(), String.t(), Path.t(), keyword()) :: t()

A file in a Nextcloud account, by the path you would see in the web interface. The same as webdav/2 with the URL spelled out for you.

iex> Sheetshow.Store.nextcloud("https://cloud.example.com", "user", "budget/costs.xlsx").location
"https://cloud.example.com/remote.php/dav/files/user/budget/costs.xlsx"

Nextcloud wants an app password rather than the account's own whenever the account has two-factor authentication or signs in through somewhere else: Personal settings, then Security, then Devices & sessions.

read(store)

@spec read(t()) :: {:ok, {binary(), version()}} | {:error, Sheetshow.Error.t()}

The bytes, and the version they were at. A file that is not there is %Sheetshow.Error{reason: :not_found}, told apart from every other way a read can fail so that a caller meaning to create one can carry on.

webdav(url, options \\ [])

@spec webdav(String.t(), keyword()) :: t()

A file on a WebDAV server: Nextcloud, ownCloud, or anything else that speaks it. See Sheetshow.Store.WebDAV for the options and for what it can promise that a local file cannot.

iex> Sheetshow.Store.webdav("https://cloud.example.com/x.xlsx", username: "user").module
Sheetshow.Store.WebDAV

write(store, bytes, version \\ :any)

@spec write(t(), binary(), precondition()) ::
  {:ok, version()} | {:error, Sheetshow.Error.t()}

Writes the bytes, if the precondition still holds.