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
|
_Avoid_: absolute WebDAV URLs, numeric file IDs
|
||||||
|
|
||||||
**Working Folder**:
|
**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
|
_Avoid_: catalog folder, zones directory, app-specific suffixes in Library code
|
||||||
|
|
||||||
**Saved Default**:
|
**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
|
_Avoid_: silently switching folders, creating a visit path that was not saved as default
|
||||||
|
|
||||||
**Files seam**:
|
**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
|
_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
|
Write(name string, data []byte) error
|
||||||
// Exists reports whether name is a non-directory file in the Working Folder.
|
// Exists reports whether name is a non-directory file in the Working Folder.
|
||||||
Exists(name string) (bool, error)
|
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.
|
// Root is User Files Root–relative folder access.
|
||||||
@@ -181,6 +183,18 @@ func (f localFolder) Exists(name string) (bool, error) {
|
|||||||
return !info.IsDir(), nil
|
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.
|
// RequireBasename rejects path segments that are not a single basename.
|
||||||
func RequireBasename(name string) error {
|
func RequireBasename(name string) error {
|
||||||
if name == "" || name != path.Base(name) || strings.ContainsAny(name, `/\`) || strings.Contains(name, "..") {
|
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)
|
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).
|
// Exists implements Folder via WebDAV HEAD (falls back to GET when needed).
|
||||||
func (f webdavFolder) Exists(name string) (bool, error) {
|
func (f webdavFolder) Exists(name string) (bool, error) {
|
||||||
u, err := f.childURL(name)
|
u, err := f.childURL(name)
|
||||||
|
|||||||
@@ -56,6 +56,13 @@ func TestWebDAVReadWriteListExists(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.WriteHeader(http.StatusOK)
|
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":
|
case "MKCOL":
|
||||||
dirs[strings.TrimSuffix(path, "/")] = true
|
dirs[strings.TrimSuffix(path, "/")] = true
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
@@ -102,6 +109,13 @@ func TestWebDAVReadWriteListExists(t *testing.T) {
|
|||||||
if !found {
|
if !found {
|
||||||
t.Fatalf("list: %+v", entries)
|
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) {
|
func TestWebDAVOpenMissingIsNotExist(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user