Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manage the Nextcloud trash #4432

Merged
merged 7 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add a route to allow listing the trash bin on NextCloud
  • Loading branch information
nono committed Jun 27, 2024
commit bac0f58b568d09ac79b6372b40cb6cf038f63892
41 changes: 41 additions & 0 deletions docs/nextcloud.md
Original file line number Diff line number Diff line change
Expand Up @@ -430,3 +430,44 @@ HTTP/1.1 204 No Content
- 400 Bad Request, when the account is not configured for NextCloud
- 401 Unauthorized, when authentication to the NextCloud fails
- 404 Not Found, when the account is not found or the file is not found on the Cozy

## GET /remote/nextcloud/:account/trash/*

This route can be used to list the files and directories inside the trashbin
of NextCloud.

### Request (list)

```http
GET /remote/nextcloud/4ab2155707bb6613a8b9463daf00381b/trash/ HTTP/1.1
Host: cozy.example.net
Authorization: Bearer eyJhbG...
```

### Response (list)

```http
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
```

```json
{
"data": [
{
"type": "io.cozy.remote.nextcloud.files",
"id": "613281",
"attributes": {
"type": "directory",
"name": "Old",
"updated_at": "Tue, 25 Jun 2024 14:31:44 GMT",
"etag": "1719326384"
},
"meta": {},
"links": {
"self": "https://nextcloud.example.net/apps/files/trashbin/613281?dir=/Old"
taratatach marked this conversation as resolved.
Show resolved Hide resolved
}
}
]
}
```
63 changes: 55 additions & 8 deletions model/nextcloud/nextcloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ var _ jsonapi.Object = (*File)(nil)
type NextCloud struct {
inst *instance.Instance
accountID string
userID string
webdav *webdav.Client
}

Expand Down Expand Up @@ -99,48 +100,54 @@ func New(inst *instance.Instance, accountID string) (*NextCloud, error) {
Host: u.Host,
Username: username,
Password: password,
BasePath: "/remote.php/dav",
Logger: logger,
}
nc := &NextCloud{
inst: inst,
accountID: accountID,
webdav: webdav,
}
if err := nc.fillBasePath(&doc); err != nil {
if err := nc.fillUserID(&doc); err != nil {
return nil, err
}
return nc, nil
}

func (nc *NextCloud) Download(path string) (*webdav.Download, error) {
return nc.webdav.Get(path)
return nc.webdav.Get("/files/" + nc.userID + "/" + path)
nono marked this conversation as resolved.
Show resolved Hide resolved
}

func (nc *NextCloud) Upload(path, mime string, contentLength int64, body io.Reader) error {
headers := map[string]string{
echo.HeaderContentType: mime,
}
path = "/files/" + nc.userID + "/" + path
nono marked this conversation as resolved.
Show resolved Hide resolved
return nc.webdav.Put(path, contentLength, headers, body)
}

func (nc *NextCloud) Mkdir(path string) error {
return nc.webdav.Mkcol(path)
return nc.webdav.Mkcol("/files/" + nc.userID + "/" + path)
}

func (nc *NextCloud) Delete(path string) error {
return nc.webdav.Delete(path)
return nc.webdav.Delete("/files/" + nc.userID + "/" + path)
}

func (nc *NextCloud) Move(oldPath, newPath string) error {
oldPath = "/files/" + nc.userID + "/" + oldPath
newPath = "/files/" + nc.userID + "/" + newPath
return nc.webdav.Move(oldPath, newPath)
}

func (nc *NextCloud) Copy(oldPath, newPath string) error {
oldPath = "/files/" + nc.userID + "/" + oldPath
newPath = "/files/" + nc.userID + "/" + newPath
return nc.webdav.Copy(oldPath, newPath)
}

func (nc *NextCloud) ListFiles(path string) ([]jsonapi.Object, error) {
items, err := nc.webdav.List(path)
items, err := nc.webdav.List("/files/" + nc.userID + "/" + path)
if err != nil {
return nil, err
}
Expand All @@ -167,7 +174,36 @@ func (nc *NextCloud) ListFiles(path string) ([]jsonapi.Object, error) {
return files, nil
}

func (nc *NextCloud) ListTrash(path string) ([]jsonapi.Object, error) {
nono marked this conversation as resolved.
Show resolved Hide resolved
items, err := nc.webdav.List("/trashbin/" + nc.userID + "/trash/" + path)
if err != nil {
return nil, err
}

var files []jsonapi.Object
for _, item := range items {
var mime, class string
if item.Type == "file" {
mime, class = vfs.ExtractMimeAndClassFromFilename(item.TrashName)
}
file := &File{
DocID: item.ID,
Type: item.Type,
Name: item.TrashName,
Size: item.Size,
Mime: mime,
Class: class,
UpdatedAt: item.LastModified,
ETag: item.ETag,
url: nc.buildTrashURL(item, path),
}
files = append(files, file)
}
return files, nil
}

func (nc *NextCloud) Downstream(path, dirID string, kind OperationKind, cozyMetadata *vfs.FilesCozyMetadata) (*vfs.FileDoc, error) {
path = "/files/" + nc.userID + "/" + path
dl, err := nc.webdav.Get(path)
if err != nil {
return nil, err
Expand Down Expand Up @@ -215,6 +251,7 @@ func (nc *NextCloud) Downstream(path, dirID string, kind OperationKind, cozyMeta
}

func (nc *NextCloud) Upstream(path, from string, kind OperationKind) error {
path = "/files/" + nc.userID + "/" + path
fs := nc.inst.VFS()
doc, err := fs.FileByID(from)
if err != nil {
Expand All @@ -238,18 +275,18 @@ func (nc *NextCloud) Upstream(path, from string, kind OperationKind) error {
return nil
}

func (nc *NextCloud) fillBasePath(accountDoc *couchdb.JSONDoc) error {
func (nc *NextCloud) fillUserID(accountDoc *couchdb.JSONDoc) error {
userID, _ := accountDoc.M["webdav_user_id"].(string)
if userID != "" {
nc.webdav.BasePath = "/remote.php/dav/files/" + userID
nc.userID = userID
return nil
}

userID, err := nc.fetchUserID()
if err != nil {
return err
}
nc.webdav.BasePath = "/remote.php/dav/files/" + userID
nc.userID = userID

// Try to persist the userID to avoid fetching it for every WebDAV request
accountDoc.M["webdav_user_id"] = userID
Expand All @@ -269,6 +306,16 @@ func (nc *NextCloud) buildURL(item webdav.Item, path string) string {
return u.String()
}

func (nc *NextCloud) buildTrashURL(item webdav.Item, path string) string {
u := &url.URL{
Scheme: nc.webdav.Scheme,
Host: nc.webdav.Host,
Path: "/apps/files/trashbin/" + item.ID,
RawQuery: "dir=/" + path,
}
return u.String()
}

// https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-status-api.html#fetch-your-own-status
func (nc *NextCloud) fetchUserID() (string, error) {
logger := nc.webdav.Logger
Expand Down
4 changes: 4 additions & 0 deletions pkg/webdav/webdav.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ func (c *Client) List(path string) ([]Item, error) {
ID: props.FileID,
Type: "directory",
Name: props.Name,
TrashName: props.TrashName,
LastModified: props.LastModified,
ETag: props.ETag,
}
Expand All @@ -264,6 +265,7 @@ type Item struct {
ID string
Type string
Name string
TrashName string
Size uint64
ContentType string
LastModified string
Expand All @@ -284,6 +286,7 @@ type props struct {
Status string `xml:"status"`
Type xml.Name `xml:"prop>resourcetype>collection"`
Name string `xml:"prop>displayname"`
TrashName string `xml:"prop>trashbin-filename"`
Size string `xml:"prop>getcontentlength"`
ContentType string `xml:"prop>getcontenttype"`
LastModified string `xml:"prop>getlastmodified"`
Expand All @@ -301,6 +304,7 @@ const ListFilesPayload = `<?xml version="1.0"?>
<d:getcontentlength />
<d:getcontenttype />
<oc:fileid />
<nc:trashbin-filename />
</d:prop>
</d:propfind>
`
Expand Down
23 changes: 22 additions & 1 deletion web/remote/nextcloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,29 @@ import (
"github.com/ncw/swift/v2"
)

func nextcloudGetTrash(c echo.Context) error {
inst := middlewares.GetInstance(c)
if err := middlewares.AllowWholeType(c, permission.GET, consts.Files); err != nil {
return err
}

accountID := c.Param("account")
nc, err := nextcloud.New(inst, accountID)
if err != nil {
return wrapNextcloudErrors(err)
}

path := c.Param("*")
files, err := nc.ListTrash(path)
if err != nil {
return wrapNextcloudErrors(err)
}
return jsonapi.DataList(c, http.StatusOK, files, nil)
}

func nextcloudGet(c echo.Context) error {
inst := middlewares.GetInstance(c)
if err := middlewares.AllowWholeType(c, permission.PUT, consts.Files); err != nil {
if err := middlewares.AllowWholeType(c, permission.GET, consts.Files); err != nil {
return err
}

Expand Down Expand Up @@ -247,6 +267,7 @@ func nextcloudUpstream(c echo.Context) error {

func nextcloudRoutes(router *echo.Group) {
group := router.Group("/nextcloud/:account")
group.GET("/trash/*", nextcloudGetTrash)
group.GET("/*", nextcloudGet)
group.PUT("/*", nextcloudPut)
group.DELETE("/*", nextcloudDelete)
Expand Down