Let a Working Folder delete a basename.
CheckDNS needs that to Remove Domain without a second storage path. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+2
-2
@@ -9,7 +9,7 @@ The root of that Nextcloud user's files tree. Working Folder paths are relative
|
||||
_Avoid_: absolute WebDAV URLs, numeric file IDs
|
||||
|
||||
**Working Folder**:
|
||||
The single folder an ExApp uses for the current operation. Its path is relative to the User Files Root. Each ExApp defines what files inside that folder mean; this Library only opens, lists, reads, and writes there.
|
||||
The single folder an ExApp uses for the current operation. Its path is relative to the User Files Root. Each ExApp defines what files inside that folder mean; this Library only opens, lists, reads, writes, and removes there.
|
||||
_Avoid_: catalog folder, zones directory, app-specific suffixes in Library code
|
||||
|
||||
**Saved Default**:
|
||||
@@ -21,5 +21,5 @@ One inbound ExApp request bound to one Working Folder. App-icon launch uses the
|
||||
_Avoid_: silently switching folders, creating a visit path that was not saved as default
|
||||
|
||||
**Files seam**:
|
||||
The `Root` and `Folder` interfaces: open or ensure a relative path, then list, read, write, and test existence. Local disk and WebDAV are two implementations of the same seam.
|
||||
The `Root` and `Folder` interfaces: open or ensure a relative path, then list, read, write, remove, and test existence. Local disk and WebDAV are two implementations of the same seam.
|
||||
_Avoid_: coupling the seam to a specific on-disk layout beyond basename rules
|
||||
|
||||
@@ -32,6 +32,8 @@ type Folder interface {
|
||||
Write(name string, data []byte) error
|
||||
// Exists reports whether name is a non-directory file in the Working Folder.
|
||||
Exists(name string) (bool, error)
|
||||
// Remove deletes a basename in the Working Folder.
|
||||
Remove(name string) error
|
||||
}
|
||||
|
||||
// Root is User Files Root–relative folder access.
|
||||
@@ -181,6 +183,18 @@ func (f localFolder) Exists(name string) (bool, error) {
|
||||
return !info.IsDir(), nil
|
||||
}
|
||||
|
||||
// Remove implements Folder for a local directory.
|
||||
func (f localFolder) Remove(name string) error {
|
||||
if err := RequireBasename(name); err != nil {
|
||||
return err
|
||||
}
|
||||
err := os.Remove(filepath.Join(f.dir, name))
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
return fmt.Errorf("%w: %s", ErrNotExist, name)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// RequireBasename rejects path segments that are not a single basename.
|
||||
func RequireBasename(name string) error {
|
||||
if name == "" || name != path.Base(name) || strings.ContainsAny(name, `/\`) || strings.Contains(name, "..") {
|
||||
|
||||
@@ -335,6 +335,41 @@ func (f webdavFolder) Write(name string, data []byte) error {
|
||||
return fmt.Errorf("webdav PUT %s: %s", name, res.Status)
|
||||
}
|
||||
|
||||
// Remove implements Folder via WebDAV DELETE.
|
||||
func (f webdavFolder) Remove(name string) error {
|
||||
u, err := f.childURL(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodDelete, u, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, vs := range f.root.Cred.AuthHeaders() {
|
||||
for _, v := range vs {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
}
|
||||
res, err := f.root.client().Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if _, err := io.Copy(io.Discard, res.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
switch res.StatusCode {
|
||||
case http.StatusOK, http.StatusNoContent, http.StatusAccepted:
|
||||
return nil
|
||||
case http.StatusNotFound:
|
||||
return fmt.Errorf("%w: %s", ErrNotExist, name)
|
||||
case http.StatusForbidden, http.StatusUnauthorized:
|
||||
return fmt.Errorf("%w", ErrForbidden)
|
||||
default:
|
||||
return fmt.Errorf("webdav DELETE %s: %s", name, res.Status)
|
||||
}
|
||||
}
|
||||
|
||||
// Exists implements Folder via WebDAV HEAD (falls back to GET when needed).
|
||||
func (f webdavFolder) Exists(name string) (bool, error) {
|
||||
u, err := f.childURL(name)
|
||||
|
||||
@@ -56,6 +56,13 @@ func TestWebDAVReadWriteListExists(t *testing.T) {
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
case http.MethodDelete:
|
||||
if _, ok := store[path]; !ok {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
delete(store, path)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
case "MKCOL":
|
||||
dirs[strings.TrimSuffix(path, "/")] = true
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
@@ -102,6 +109,13 @@ func TestWebDAVReadWriteListExists(t *testing.T) {
|
||||
if !found {
|
||||
t.Fatalf("list: %+v", entries)
|
||||
}
|
||||
if err := folder.Remove("note.txt"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok, err = folder.Exists("note.txt")
|
||||
if err != nil || ok {
|
||||
t.Fatalf("after remove: ok=%v err=%v", ok, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebDAVOpenMissingIsNotExist(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user