Skip to content

Commit

Permalink
add filter-files to the dav REPORT API
Browse files Browse the repository at this point in the history
I added filter-files to the dav REPORT API. This enables the listing of
favorites.
  • Loading branch information
David Christofas committed Sep 16, 2021
1 parent af7f2a2 commit bf36777
Show file tree
Hide file tree
Showing 15 changed files with 484 additions and 26 deletions.
6 changes: 6 additions & 0 deletions changelog/unreleased/list-favorites.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Enhancement: Implement listing favorites via the dav report API

Added filter-files to the dav REPORT API. This enables the listing of
favorites.

https://github.com/cs3org/reva/pull/2071
38 changes: 29 additions & 9 deletions internal/http/services/owncloud/ocdav/ocdav.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,15 @@ import (
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/pkg/appctx"
ctxpkg "github.com/cs3org/reva/pkg/ctx"
"github.com/cs3org/reva/pkg/errtypes"
"github.com/cs3org/reva/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/pkg/rhttp"
"github.com/cs3org/reva/pkg/rhttp/global"
"github.com/cs3org/reva/pkg/rhttp/router"
"github.com/cs3org/reva/pkg/sharedconf"
"github.com/cs3org/reva/pkg/storage"
"github.com/cs3org/reva/pkg/storage/favorite"
"github.com/cs3org/reva/pkg/storage/fs/registry"
"github.com/cs3org/reva/pkg/storage/utils/templates"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
Expand Down Expand Up @@ -97,11 +101,13 @@ type Config struct {
// Example: if WebdavNamespace is /users/{{substr 0 1 .Username}}/{{.Username}}
// and received path is /docs the internal path will be:
// /users/<first char of username>/<username>/docs
WebdavNamespace string `mapstructure:"webdav_namespace"`
GatewaySvc string `mapstructure:"gatewaysvc"`
Timeout int64 `mapstructure:"timeout"`
Insecure bool `mapstructure:"insecure"`
PublicURL string `mapstructure:"public_url"`
WebdavNamespace string `mapstructure:"webdav_namespace"`
GatewaySvc string `mapstructure:"gatewaysvc"`
Timeout int64 `mapstructure:"timeout"`
Insecure bool `mapstructure:"insecure"`
PublicURL string `mapstructure:"public_url"`
Driver string `mapstructure:"driver"`
Drivers map[string]map[string]interface{} `mapstructure:"drivers" docs:"url:pkg/storage/fs/localhome/localhome.go"`
}

func (c *Config) init() {
Expand All @@ -110,10 +116,18 @@ func (c *Config) init() {
}

type svc struct {
c *Config
webDavHandler *WebDavHandler
davHandler *DavHandler
client *http.Client
c *Config
webDavHandler *WebDavHandler
davHandler *DavHandler
favoritesManager favorite.Manager
client *http.Client
}

func getFS(c *Config) (storage.FS, error) {
if f, ok := registry.NewFuncs[c.Driver]; ok {
return f(c.Drivers[c.Driver])
}
return nil, errtypes.NotFound("driver not found: " + c.Driver)
}

// New returns a new ocdav
Expand All @@ -125,6 +139,11 @@ func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error)

conf.init()

fs, err := getFS(conf)
if err != nil {
return nil, err
}

s := &svc{
c: conf,
webDavHandler: new(WebDavHandler),
Expand All @@ -133,6 +152,7 @@ func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error)
rhttp.Timeout(time.Duration(conf.Timeout*int64(time.Second))),
rhttp.Insecure(conf.Insecure),
),
favoritesManager: favorite.NewInMemoryManager(fs),
}
// initialize handlers and set default configs
if err := s.webDavHandler.init(conf.WebdavNamespace, true); err != nil {
Expand Down
4 changes: 2 additions & 2 deletions internal/http/services/owncloud/ocdav/propfind.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func (s *svc) handleSpacesPropfind(w http.ResponseWriter, r *http.Request, space
}

func (s *svc) propfindResponse(ctx context.Context, w http.ResponseWriter, r *http.Request, namespace string, pf propfindXML, parentInfo *provider.ResourceInfo, resourceInfos []*provider.ResourceInfo, log zerolog.Logger) {
propRes, err := s.formatPropfind(ctx, &pf, resourceInfos, namespace)
propRes, err := s.multistatusResponse(ctx, &pf, resourceInfos, namespace)
if err != nil {
log.Error().Err(err).Msg("error formatting propfind")
w.WriteHeader(http.StatusInternalServerError)
Expand Down Expand Up @@ -342,7 +342,7 @@ func readPropfind(r io.Reader) (pf propfindXML, status int, err error) {
return pf, 0, nil
}

func (s *svc) formatPropfind(ctx context.Context, pf *propfindXML, mds []*provider.ResourceInfo, ns string) (string, error) {
func (s *svc) multistatusResponse(ctx context.Context, pf *propfindXML, mds []*provider.ResourceInfo, ns string) (string, error) {
responses := make([]*responseXML, 0, len(mds))
for i := range mds {
res, err := s.mdToPropResponse(ctx, pf, mds[i], ns)
Expand Down
28 changes: 28 additions & 0 deletions internal/http/services/owncloud/ocdav/proppatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/pkg/appctx"
ctxpkg "github.com/cs3org/reva/pkg/ctx"
rtrace "github.com/cs3org/reva/pkg/trace"
"github.com/pkg/errors"
"github.com/rs/zerolog"
Expand Down Expand Up @@ -232,6 +233,19 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt
HandleErrorStatus(&log, w, res.Status)
return nil, nil, false
}
if key == "http://owncloud.org/ns/favorite" {
statRes, err := c.Stat(ctx, &provider.StatRequest{Ref: ref})
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return nil, nil, false
}
currentUser := ctxpkg.ContextMustGetUser(ctx)
err = s.favoritesManager.UnsetFavorite(ctx, currentUser.Id, statRes.Info.Id)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return nil, nil, false
}
}
removedProps = append(removedProps, propNameXML)
} else {
sreq.ArbitraryMetadata.Metadata[key] = value
Expand Down Expand Up @@ -259,6 +273,20 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt

acceptedProps = append(acceptedProps, propNameXML)
delete(sreq.ArbitraryMetadata.Metadata, key)

if key == "http://owncloud.org/ns/favorite" {
statRes, err := c.Stat(ctx, &provider.StatRequest{Ref: ref})
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return nil, nil, false
}
currentUser := ctxpkg.ContextMustGetUser(ctx)
err = s.favoritesManager.SetFavorite(ctx, currentUser.Id, statRes.Info.Id)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return nil, nil, false
}
}
}
}
// FIXME: in case of error, need to set all properties back to the original state,
Expand Down
2 changes: 1 addition & 1 deletion internal/http/services/owncloud/ocdav/publicfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s

infos := s.getPublicFileInfos(onContainer, depth == "0", tokenStatInfo)

propRes, err := s.formatPropfind(ctx, &pf, infos, ns)
propRes, err := s.multistatusResponse(ctx, &pf, infos, ns)
if err != nil {
sublog.Error().Err(err).Msg("error formatting propfind")
w.WriteHeader(http.StatusInternalServerError)
Expand Down
62 changes: 61 additions & 1 deletion internal/http/services/owncloud/ocdav/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ import (
"net/http"

"github.com/cs3org/reva/pkg/appctx"
ctxpkg "github.com/cs3org/reva/pkg/ctx"
)

const (
elementNameSearchFiles = "search-files"
elementNameFilterFiles = "filter-files"
)

func (s *svc) handleReport(w http.ResponseWriter, r *http.Request, ns string) {
Expand All @@ -42,6 +48,11 @@ func (s *svc) handleReport(w http.ResponseWriter, r *http.Request, ns string) {
return
}

if rep.FilterFiles != nil {
s.doFilterFiles(w, r, rep.FilterFiles, ns)
return
}

// TODO(jfd): implement report

w.WriteHeader(http.StatusNotImplemented)
Expand All @@ -59,9 +70,39 @@ func (s *svc) doSearchFiles(w http.ResponseWriter, r *http.Request, sf *reportSe
w.WriteHeader(http.StatusNotImplemented)
}

func (s *svc) doFilterFiles(w http.ResponseWriter, r *http.Request, ff *reportFilterFiles, namespace string) {
ctx := r.Context()
log := appctx.GetLogger(ctx)

if ff.Rules.Favorite {
// List the users favorite resources.
currentUser := ctxpkg.ContextMustGetUser(ctx)
favorites, err := s.favoritesManager.ListFavorites(ctx, currentUser.Id)
if err != nil {
log.Error().Err(err).Msg("error getting favorites")
w.WriteHeader(http.StatusInternalServerError)
return
}

responsesXML, err := s.multistatusResponse(ctx, &propfindXML{Prop: ff.Prop}, favorites, namespace)
if err != nil {
log.Error().Err(err).Msg("error formatting propfind")
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set(HeaderDav, "1, 3, extended-mkcol")
w.Header().Set(HeaderContentType, "application/xml; charset=utf-8")
w.WriteHeader(http.StatusMultiStatus)
if _, err := w.Write([]byte(responsesXML)); err != nil {
log.Err(err).Msg("error writing response")
}
}
}

type report struct {
SearchFiles *reportSearchFiles
// FilterFiles TODO add this for tag based search
FilterFiles *reportFilterFiles `xml:"filter-files"`
}
type reportSearchFiles struct {
XMLName xml.Name `xml:"search-files"`
Expand All @@ -75,6 +116,18 @@ type reportSearchFilesSearch struct {
Offset int `xml:"offset"`
}

type reportFilterFiles struct {
XMLName xml.Name `xml:"filter-files"`
Lang string `xml:"xml:lang,attr,omitempty"`
Prop propfindProps `xml:"DAV: prop"`
Rules reportFilterFilesRules `xml:"filter-rules"`
}

type reportFilterFilesRules struct {
Favorite bool `xml:"favorite"`
SystemTag int `xml:"systemtag"`
}

func readReport(r io.Reader) (rep *report, status int, err error) {
decoder := xml.NewDecoder(r)
rep = &report{}
Expand All @@ -89,13 +142,20 @@ func readReport(r io.Reader) (rep *report, status int, err error) {
}

if v, ok := t.(xml.StartElement); ok {
if v.Name.Local == "search-files" {
if v.Name.Local == elementNameSearchFiles {
var repSF reportSearchFiles
err = decoder.DecodeElement(&repSF, &v)
if err != nil {
return nil, http.StatusBadRequest, err
}
rep.SearchFiles = &repSF
} else if v.Name.Local == elementNameFilterFiles {
var repFF reportFilterFiles
err = decoder.DecodeElement(&repFF, &v)
if err != nil {
return nil, http.StatusBadRequest, err
}
rep.FilterFiles = &repFF
}
}
}
Expand Down
63 changes: 63 additions & 0 deletions internal/http/services/owncloud/ocdav/report_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.

package ocdav

import (
"strings"
"testing"
)

func TestUnmarshallReportFilterFiles(t *testing.T) {
ffXML := `<oc:filter-files xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
<d:prop>
<d:getlastmodified />
<d:getetag />
<d:getcontenttype />
<d:resourcetype />
<oc:fileid />
<oc:permissions />
<oc:size />
<d:getcontentlength />
<oc:tags />
<oc:favorite />
<d:lockdiscovery />
<oc:comments-unread />
<oc:owner-display-name />
<oc:share-types />
</d:prop>
<oc:filter-rules>
<oc:favorite>1</oc:favorite>
</oc:filter-rules>
</oc:filter-files>`

reader := strings.NewReader(ffXML)

report, status, err := readReport(reader)
if status != 0 || err != nil {
t.Error("Failed to unmarshal filter-files xml")
}

if report.FilterFiles == nil {
t.Error("Failed to unmarshal filter-files xml. FilterFiles is nil")
}

if report.FilterFiles.Rules.Favorite == false {
t.Error("Failed to correctly unmarshal filter-rules. Favorite is expected to be true.")
}
}
2 changes: 1 addition & 1 deletion internal/http/services/owncloud/ocdav/versions.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func (h *VersionsHandler) doListVersions(w http.ResponseWriter, r *http.Request,
infos = append(infos, vi)
}

propRes, err := s.formatPropfind(ctx, &pf, infos, "")
propRes, err := s.multistatusResponse(ctx, &pf, infos, "")
if err != nil {
sublog.Error().Err(err).Msg("error formatting propfind")
w.WriteHeader(http.StatusInternalServerError)
Expand Down
Loading

0 comments on commit bf36777

Please sign in to comment.