Document every exported symbol and how callers use the library.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+246
@@ -0,0 +1,246 @@
|
||||
# go-nc-files guide
|
||||
|
||||
How to use the library. Symbol signatures and doc comments are in [API](api.md). Words for the domain are in [CONTEXT.md](../CONTEXT.md).
|
||||
|
||||
Sentinel errors are `ErrNotExist`, `ErrForbidden`, and `ErrNotDir`. `IsNotExist` and `IsForbidden` report whether an error wraps the first two. The features below name which calls return them.
|
||||
|
||||
## Files seam
|
||||
|
||||
### Purpose
|
||||
|
||||
`Root` opens a folder by a User Files Root–relative path. `Folder` is flat storage inside that folder: list, read, write, exists, and remove, by basename only.
|
||||
|
||||
### When to use it
|
||||
|
||||
Write product code against `Root` and `Folder`. Pass a `Local` root in tests and a `WebDAVRoot` against Nextcloud. The rest of the call sequence stays the same.
|
||||
|
||||
### Call sequence
|
||||
|
||||
1. Obtain a `Root` (`Local` or `WebDAVRoot`).
|
||||
2. `OpenFolder` when the folder must already exist. `EnsureFolder` when missing parents should be created.
|
||||
3. On the `Folder`, call `List`, `Read`, `Write`, `Exists`, or `Remove` with a basename.
|
||||
|
||||
`List` returns first-level names. `Exists` reports a non-directory file. `Entry.IsDir` is true for a child directory.
|
||||
|
||||
### Errors
|
||||
|
||||
`OpenFolder` and `EnsureFolder` return a plain error when the relative path is invalid (`CleanRel` yields empty). A missing folder from `OpenFolder` wraps `ErrNotExist`. A path that exists and is not a directory wraps `ErrNotDir`. Permission failures wrap `ErrForbidden`.
|
||||
|
||||
`Read`, `Write`, `Exists`, and `Remove` return an error from `RequireBasename` when the name is not a single basename. `Remove` and WebDAV `Read` wrap `ErrNotExist` when the child is missing. `Exists` returns false and a nil error when the child is missing.
|
||||
|
||||
### Example
|
||||
|
||||
```go
|
||||
folder, err := root.OpenFolder("Zones")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := folder.Write("note.txt", []byte("hi")); err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := folder.Read("note.txt")
|
||||
```
|
||||
|
||||
## Local
|
||||
|
||||
### Purpose
|
||||
|
||||
`Local` is a User Files Root on the local filesystem. `RootDir` is the absolute directory that corresponds to the user's files root.
|
||||
|
||||
### When to use it
|
||||
|
||||
Use `Local` for tests and for `go run` without Nextcloud. Use `LocalFolder` when the caller already has an absolute directory and only needs a `Folder`. `LocalPath` reports that absolute directory when the `Folder` came from the local adapter.
|
||||
|
||||
### Call sequence
|
||||
|
||||
1. Set `Local.RootDir`.
|
||||
2. Call `OpenFolder` or `EnsureFolder` with a relative path. `EnsureFolder` creates the directory with mode `0755`.
|
||||
3. Or call `LocalFolder` with an absolute directory and skip the root.
|
||||
|
||||
`OpenLocal` is the Visit helper that pairs `Local` with a `FileStore`. It is described under Visit.
|
||||
|
||||
### Errors
|
||||
|
||||
`OpenFolder` wraps `ErrNotExist`, `ErrForbidden`, or `ErrNotDir`, or returns a plain error for an invalid relative path. `EnsureFolder` wraps `ErrForbidden` when the process cannot create the directory.
|
||||
|
||||
### Example
|
||||
|
||||
```go
|
||||
root := goncfiles.Local{RootDir: filesRoot}
|
||||
folder, err := root.EnsureFolder("myapp")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = folder
|
||||
```
|
||||
|
||||
## WebDAVRoot
|
||||
|
||||
### Purpose
|
||||
|
||||
`WebDAVRoot` is a User Files Root at `/remote.php/dav/files/{user}/`. `Cred` supplies the user id and AppAPI auth headers from go-nc-exapp. A nil `Client` uses `http.DefaultClient`.
|
||||
|
||||
### When to use it
|
||||
|
||||
Use `WebDAVRoot` when the Working Folder is the user's Nextcloud files. The same `Root` and `Folder` methods apply as with `Local`.
|
||||
|
||||
### Call sequence
|
||||
|
||||
1. Fill `gonexapp.Credentials`, including `BaseURL` and `UserID`.
|
||||
2. Store them on `WebDAVRoot.Cred`.
|
||||
3. `OpenFolder` checks the folder with PROPFIND and does not create it.
|
||||
4. `EnsureFolder` creates missing parents with MKCOL, then opens the folder.
|
||||
5. `List` uses PROPFIND with depth 1. `Read` uses GET. `Write` uses PUT. `Remove` uses DELETE. `Exists` uses HEAD and falls back to GET when the status is neither success, not found, nor forbidden.
|
||||
|
||||
### Errors
|
||||
|
||||
An invalid relative path returns a plain error and does not call the server. `OpenFolder` wraps `ErrNotExist` on HTTP 404, `ErrForbidden` on 401 or 403, and `ErrNotDir` when the resource is not a collection. `EnsureFolder` wraps `ErrForbidden` on 401 or 403. Other unexpected statuses are plain errors that include the HTTP status.
|
||||
|
||||
### Example
|
||||
|
||||
```go
|
||||
root := goncfiles.WebDAVRoot{Cred: cred}
|
||||
folder, err := root.OpenFolder("Zones")
|
||||
if err != nil {
|
||||
if goncfiles.IsNotExist(err) {
|
||||
return fmt.Errorf("folder missing")
|
||||
}
|
||||
return err
|
||||
}
|
||||
_ = folder
|
||||
```
|
||||
|
||||
## CleanRel
|
||||
|
||||
### Purpose
|
||||
|
||||
`CleanRel` normalizes a User Files Root–relative path. The result uses forward slashes and has no leading or trailing slash.
|
||||
|
||||
### When to use it
|
||||
|
||||
The roots call `CleanRel` themselves. Call it directly when the product must reject a path before opening a folder, or must show the normalized form.
|
||||
|
||||
### Call sequence
|
||||
|
||||
1. Pass the raw path from a query parameter or a saved preference.
|
||||
2. Treat an empty result as invalid. Do not open it.
|
||||
|
||||
Backslashes are treated as slashes. `..`, URLs, and a bare numeric Nextcloud file ID are rejected.
|
||||
|
||||
### Errors
|
||||
|
||||
`CleanRel` does not return an error. An empty string means the path is not usable. Callers that need an error, such as `OpenFolder`, turn that empty result into `invalid Working Folder path`.
|
||||
|
||||
### Example
|
||||
|
||||
```go
|
||||
rel := goncfiles.CleanRel(r.URL.Query().Get("folder"))
|
||||
if rel == "" {
|
||||
http.Error(w, "invalid folder", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
## RequireBasename
|
||||
|
||||
### Purpose
|
||||
|
||||
`RequireBasename` accepts a single file name inside a Working Folder and rejects a path.
|
||||
|
||||
### When to use it
|
||||
|
||||
`Folder` methods call it before touching a child. Call it directly when the product checks a name before building a request.
|
||||
|
||||
### Call sequence
|
||||
|
||||
1. Pass the child name.
|
||||
2. Continue only when the error is nil.
|
||||
|
||||
An empty name, a name containing `/` or `\`, a name containing `..`, or a name that is not `path.Base` of itself is rejected.
|
||||
|
||||
### Errors
|
||||
|
||||
The error text is `name must be a basename in the Working Folder`. It does not wrap `ErrNotExist` or `ErrForbidden`.
|
||||
|
||||
### Example
|
||||
|
||||
```go
|
||||
if err := goncfiles.RequireBasename(name); err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
## DefaultPathStore
|
||||
|
||||
### Purpose
|
||||
|
||||
`DefaultPathStore` persists the Saved Default Working Folder path, relative to the User Files Root. `GetDefault` returns that path, or empty when none is stored. `SetDefault` writes a new path.
|
||||
|
||||
### When to use it
|
||||
|
||||
Visit reads and writes the Saved Default through this interface. `FileStore` is a JSON file (`defaultPath`) for the local adapter. `Memory` is an in-memory store for tests. An ExApp that stores the path in Nextcloud preferences implements the same two methods around `gonexapp.AppAPIPreferences`. This library does not call OCS.
|
||||
|
||||
### Call sequence
|
||||
|
||||
1. Construct a `FileStore` with a file path, a `Memory`, or a product store.
|
||||
2. Pass it to `Open`.
|
||||
3. `GetDefault` on a missing file returns empty and a nil error.
|
||||
4. `SetDefault` writes the path. `FileStore` creates parent directories and writes indented JSON.
|
||||
|
||||
`Visit.SetDefault` is the call that also ensures the folder exists. It is described under Visit.
|
||||
|
||||
### Errors
|
||||
|
||||
`FileStore.GetDefault` returns an error when the file exists and cannot be read or is not JSON. A missing file is not an error. `FileStore.SetDefault` returns an error when the directory cannot be created or the file cannot be written. `Memory` does not return an error.
|
||||
|
||||
### Example
|
||||
|
||||
```go
|
||||
store := goncfiles.FileStore{Path: filepath.Join(dir, "settings.json")}
|
||||
saved, err := store.GetDefault()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = saved
|
||||
```
|
||||
|
||||
## Visit
|
||||
|
||||
### Purpose
|
||||
|
||||
A Visit is one opening of the ExApp: the Working Folder for this request, and the Saved Default. App-icon launch uses the Saved Default and creates the initial folder when it is missing. A Files-view folder is used only for this visit and does not change the Saved Default.
|
||||
|
||||
### When to use it
|
||||
|
||||
Call `Open` at the start of a request that needs the user's Working Folder. Use `OpenLocal` when both the files root and the settings file are on local disk.
|
||||
|
||||
### Call sequence
|
||||
|
||||
1. Call `Open` with a `Root`, a `DefaultPathStore`, the product's initial folder name, and the visit-relative path.
|
||||
2. Pass an empty visit-relative path for an app-icon launch. `Open` reads the Saved Default, uses `initialFolderName` when nothing is stored, and `EnsureFolder`s that path.
|
||||
3. Pass the Files-view folder for a visit-only path. `Open` uses `OpenFolder` and does not create it and does not fall back to the Saved Default.
|
||||
4. Use `Visit.FS` as the `Folder`, `WorkingRel` as the relative path, and `SavedDefault` as the stored default.
|
||||
5. Call `SetDefault` when the user picks a new default. It `EnsureFolder`s the path, stores it, and updates `SavedDefault`. It does not switch `FS` to that folder.
|
||||
6. `WorkingDir` returns a local absolute path when `FS` is a local folder, and `WorkingRel` otherwise.
|
||||
|
||||
### Errors
|
||||
|
||||
`Open` returns the error from `GetDefault`. An invalid saved path or an invalid visit path is a plain error (`invalid Working Folder path` or `invalid saved Working Folder path`). A missing or forbidden visit path fails closed: the error from `OpenFolder` is wrapped with the path, and the Saved Default is not opened instead.
|
||||
|
||||
`SetDefault` returns a plain error for an invalid path, or the error from `EnsureFolder` or `SetDefault` on the store.
|
||||
|
||||
### Example
|
||||
|
||||
```go
|
||||
visit, err := goncfiles.Open(root, store, "myapp", visitRelative)
|
||||
if err != nil {
|
||||
if goncfiles.IsForbidden(err) {
|
||||
http.Error(w, "forbidden", http.StatusForbidden)
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
data, err := visit.FS.Read("note.txt")
|
||||
_ = visit.WorkingRel
|
||||
_ = data
|
||||
```
|
||||
Reference in New Issue
Block a user