# 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 ```