A spreadsheet file on a WebDAV server: Nextcloud, ownCloud, or anything else that speaks it.
This is the store that can do what a local file cannot: refuse a write that
would clobber somebody. A read brings back the file's ETag, a write sends it
back as If-Match, and the server compares the two before it writes anything.
A file that changed in between gets 412 Precondition Failed, which arrives
here as %Sheetshow.Error{reason: :conflict}, and because nothing was
written, the work can be re-read, re-planned and tried again.
store = Sheetshow.Store.nextcloud("https://cloud.example.com", "user", "budget/costs.xlsx",
username: "user", password: System.fetch_env!("NEXTCLOUD_APP_PASSWORD"))
{:ok, workbook} = Sheetshow.Workbook.xlsx(store) |> Sheetshow.connect()App passwords
Nextcloud takes an ordinary Basic auth header, but an account with two-factor authentication or an external identity provider will not accept the login password over WebDAV. It wants an app password, made under Personal settings → Security → Devices & sessions. That is the usual reason a set of credentials that works in a browser gets a 401 here.
Weak entity tags, and the 412 that never goes away
If-Match uses strong comparison: RFC 7232 says both tags must be non-weak
and match exactly, so an entity tag marked weak (W/"…") can never satisfy
it. Servers hand one back legitimately whenever the response body was
compressed (nginx with gzip on, Cloudflare, Traefik, PHP's
zlib.output_compression), and a client that sends it back anyway gets 412
forever, no matter how many times it re-reads.
Two things here about that. Reads ask for Accept-Encoding: identity, so the
server has no reason to compress and every reason to give a strong tag. And a
write holding a weak one is refused here, with an error that says what to
do, rather than sent off to fail in a way that looks like a conflict and is
not one.