Skip to content

Commit

Permalink
Revamp the favorite manager and add the cbox sql driver (#2271)
Browse files Browse the repository at this point in the history
  • Loading branch information
ishank011 authored Nov 17, 2021
1 parent ca4b5f8 commit 2b436a9
Show file tree
Hide file tree
Showing 13 changed files with 333 additions and 71 deletions.
3 changes: 3 additions & 0 deletions changelog/unreleased/favorite-cbox-driver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Enhancement: Revamp the favorite manager and add the cbox sql driver

https://github.com/cs3org/reva/pull/2271
1 change: 1 addition & 0 deletions cmd/revad/runtime/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
_ "github.com/cs3org/reva/pkg/rhttp/datatx/manager/loader"
_ "github.com/cs3org/reva/pkg/share/cache/loader"
_ "github.com/cs3org/reva/pkg/share/manager/loader"
_ "github.com/cs3org/reva/pkg/storage/favorite/loader"
_ "github.com/cs3org/reva/pkg/storage/fs/loader"
_ "github.com/cs3org/reva/pkg/storage/registry/loader"
_ "github.com/cs3org/reva/pkg/token/manager/loader"
Expand Down
32 changes: 26 additions & 6 deletions internal/http/services/owncloud/ocdav/ocdav.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ 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/favorite"
"github.com/cs3org/reva/pkg/storage/favorite/registry"
"github.com/cs3org/reva/pkg/storage/utils/templates"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
Expand Down Expand Up @@ -98,16 +100,22 @@ 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"`
FavoriteStorageDriver string `mapstructure:"favorite_storage_driver"`
FavoriteStorageDrivers map[string]map[string]interface{} `mapstructure:"favorite_storage_drivers"`
}

func (c *Config) init() {
// note: default c.Prefix is an empty string
c.GatewaySvc = sharedconf.GetGatewaySVC(c.GatewaySvc)

if c.FavoriteStorageDriver == "" {
c.FavoriteStorageDriver = "memory"
}
}

type svc struct {
Expand All @@ -118,6 +126,13 @@ type svc struct {
client *http.Client
}

func getFavoritesManager(c *Config) (favorite.Manager, error) {
if f, ok := registry.NewFuncs[c.FavoriteStorageDriver]; ok {
return f(c.FavoriteStorageDrivers[c.FavoriteStorageDriver])
}
return nil, errtypes.NotFound("driver not found: " + c.FavoriteStorageDriver)
}

// New returns a new ocdav
func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error) {
conf := &Config{}
Expand All @@ -127,6 +142,11 @@ func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error)

conf.init()

fm, err := getFavoritesManager(conf)
if err != nil {
return nil, err
}

s := &svc{
c: conf,
webDavHandler: new(WebDavHandler),
Expand All @@ -135,7 +155,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(),
favoritesManager: fm,
}
// 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/proppatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt
return nil, nil, false
}
currentUser := ctxpkg.ContextMustGetUser(ctx)
err = s.favoritesManager.UnsetFavorite(ctx, currentUser.Id, statRes.Info.Id)
err = s.favoritesManager.UnsetFavorite(ctx, currentUser.Id, statRes.Info)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return nil, nil, false
Expand Down Expand Up @@ -281,7 +281,7 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt
return nil, nil, false
}
currentUser := ctxpkg.ContextMustGetUser(ctx)
err = s.favoritesManager.SetFavorite(ctx, currentUser.Id, statRes.Info.Id)
err = s.favoritesManager.SetFavorite(ctx, currentUser.Id, statRes.Info)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return nil, nil, false
Expand Down
17 changes: 10 additions & 7 deletions internal/http/services/owncloud/ocdav/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,17 @@ func (s *svc) doFilterFiles(w http.ResponseWriter, r *http.Request, ff *reportFi
continue
}

// The paths we receive have the format /user/<username>/<filepath>
// We only want the `<filepath>` part. Thus we remove the /user/<username>/ part.
parts := strings.SplitN(statRes.Info.Path, "/", 4)
if len(parts) != 4 {
log.Error().Str("path", statRes.Info.Path).Msg("path doesn't have the expected format")
continue
// If global URLs are not supported, return only the file path
if s.c.WebdavNamespace != "" {
// The paths we receive have the format /user/<username>/<filepath>
// We only want the `<filepath>` part. Thus we remove the /user/<username>/ part.
parts := strings.SplitN(statRes.Info.Path, "/", 4)
if len(parts) != 4 {
log.Error().Str("path", statRes.Info.Path).Msg("path doesn't have the expected format")
continue
}
statRes.Info.Path = parts[3]
}
statRes.Info.Path = parts[3]

infos = append(infos, statRes.Info)
}
Expand Down
136 changes: 136 additions & 0 deletions pkg/cbox/favorite/sql/sql.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// 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 cbox

import (
"context"
"database/sql"
"fmt"

user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/pkg/cbox/utils"
ctxpkg "github.com/cs3org/reva/pkg/ctx"
"github.com/cs3org/reva/pkg/storage/favorite"
"github.com/cs3org/reva/pkg/storage/favorite/registry"
"github.com/mitchellh/mapstructure"
)

func init() {
registry.Register("sql", New)
}

type config struct {
DbUsername string `mapstructure:"db_username"`
DbPassword string `mapstructure:"db_password"`
DbHost string `mapstructure:"db_host"`
DbPort int `mapstructure:"db_port"`
DbName string `mapstructure:"db_name"`
}

type mgr struct {
c *config
db *sql.DB
}

// New returns an instance of the cbox sql favorites manager.
func New(m map[string]interface{}) (favorite.Manager, error) {
c := &config{}
if err := mapstructure.Decode(m, c); err != nil {
return nil, err
}

db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", c.DbUsername, c.DbPassword, c.DbHost, c.DbPort, c.DbName))
if err != nil {
return nil, err
}

return &mgr{
c: c,
db: db,
}, nil
}

func (m *mgr) ListFavorites(ctx context.Context, userID *user.UserId) ([]*provider.ResourceId, error) {
user := ctxpkg.ContextMustGetUser(ctx)
infos := []*provider.ResourceId{}
query := `SELECT fileid_prefix, fileid FROM cbox_metadata WHERE uid=? AND tag_key="fav"`
rows, err := m.db.Query(query, user.Id.OpaqueId)
if err != nil {
return nil, err
}
defer rows.Close()

for rows.Next() {
var info provider.ResourceId
if err := rows.Scan(&info.StorageId, &info.OpaqueId); err != nil {
return nil, err
}
infos = append(infos, &info)
}

if err = rows.Err(); err != nil {
return nil, err
}
return infos, nil
}

func (m *mgr) SetFavorite(ctx context.Context, userID *user.UserId, resourceInfo *provider.ResourceInfo) error {
user := ctxpkg.ContextMustGetUser(ctx)

// The primary key is just the ID in the table, it should ideally be (uid, fileid_prefix, fileid, tag_key)
// For the time being, just check if the favorite already exists. If it does, return early
var id int
query := `"SELECT id FROM cbox_metadata WHERE uid=? AND fileid_prefix=? AND fileid=? AND tag_key="fav"`
if err := m.db.QueryRow(query, user.Id.OpaqueId, resourceInfo.Id.StorageId, resourceInfo.Id.OpaqueId).Scan(&id); err == nil {
// Favorite is already set, return
return nil
}

query = `INSERT INTO cbox_metadata SET item_type=?, uid=?, fileid_prefix=?, fileid=?, tag_key="fav"`
vals := []interface{}{utils.ResourceTypeToItemInt(resourceInfo.Type), user.Id.OpaqueId, resourceInfo.Id.StorageId, resourceInfo.Id.OpaqueId}
stmt, err := m.db.Prepare(query)
if err != nil {
return err
}

if _, err = stmt.Exec(vals...); err != nil {
return err
}
return nil
}

func (m *mgr) UnsetFavorite(ctx context.Context, userID *user.UserId, resourceInfo *provider.ResourceInfo) error {
user := ctxpkg.ContextMustGetUser(ctx)
stmt, err := m.db.Prepare(`DELETE FROM cbox_metadata WHERE uid=? AND fileid_prefix=? AND fileid=? AND tag_key="fav"`)
if err != nil {
return err
}

res, err := stmt.Exec(user.Id.OpaqueId, resourceInfo.Id.StorageId, resourceInfo.Id.OpaqueId)
if err != nil {
return err
}

_, err = res.RowsAffected()
if err != nil {
return err
}
return nil
}
1 change: 1 addition & 0 deletions pkg/cbox/loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package loader

import (
// Load cbox specific drivers.
_ "github.com/cs3org/reva/pkg/cbox/favorite/sql"
_ "github.com/cs3org/reva/pkg/cbox/group/rest"
_ "github.com/cs3org/reva/pkg/cbox/publicshare/sql"
_ "github.com/cs3org/reva/pkg/cbox/share/sql"
Expand Down
14 changes: 13 additions & 1 deletion pkg/cbox/utils/conversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func ExtractGrantee(t int, g string) *provider.Grantee {
return &grantee
}

// ResourceTypeToItem maps a resource type to an integer
// ResourceTypeToItem maps a resource type to a string
func ResourceTypeToItem(r provider.ResourceType) string {
switch r {
case provider.ResourceType_RESOURCE_TYPE_FILE:
Expand All @@ -99,6 +99,18 @@ func ResourceTypeToItem(r provider.ResourceType) string {
}
}

// ResourceTypeToItemInt maps a resource type to an integer
func ResourceTypeToItemInt(r provider.ResourceType) int {
switch r {
case provider.ResourceType_RESOURCE_TYPE_CONTAINER:
return 0
case provider.ResourceType_RESOURCE_TYPE_FILE:
return 1
default:
return -1
}
}

// SharePermToInt maps read/write permissions to an integer
func SharePermToInt(p *provider.ResourcePermissions) int {
var perm int
Expand Down
47 changes: 2 additions & 45 deletions pkg/storage/favorite/favorite.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ package favorite

import (
"context"
"sync"

user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
Expand All @@ -31,49 +30,7 @@ type Manager interface {
// ListFavorites returns all resources that were favorited by a user.
ListFavorites(ctx context.Context, userID *user.UserId) ([]*provider.ResourceId, error)
// SetFavorite marks a resource as favorited by a user.
SetFavorite(ctx context.Context, userID *user.UserId, resourceID *provider.ResourceId) error
SetFavorite(ctx context.Context, userID *user.UserId, resourceInfo *provider.ResourceInfo) error
// UnsetFavorite unmarks a resource as favorited by a user.
UnsetFavorite(ctx context.Context, userID *user.UserId, resourceID *provider.ResourceId) error
}

// NewInMemoryManager returns an instance of the in-memory favorites manager.
func NewInMemoryManager() *InMemoryManager {
return &InMemoryManager{favorites: make(map[string]map[string]*provider.ResourceId)}
}

// InMemoryManager implements the Manager interface to manage favorites using an in-memory storage.
// This should not be used in production but can be used for tests.
type InMemoryManager struct {
sync.RWMutex
favorites map[string]map[string]*provider.ResourceId
}

// ListFavorites returns all resources that were favorited by a user.
func (m *InMemoryManager) ListFavorites(ctx context.Context, userID *user.UserId) ([]*provider.ResourceId, error) {
m.RLock()
defer m.RUnlock()
favorites := make([]*provider.ResourceId, 0, len(m.favorites[userID.OpaqueId]))
for _, id := range m.favorites[userID.OpaqueId] {
favorites = append(favorites, id)
}
return favorites, nil
}

// SetFavorite marks a resource as favorited by a user.
func (m *InMemoryManager) SetFavorite(_ context.Context, userID *user.UserId, resourceID *provider.ResourceId) error {
m.Lock()
defer m.Unlock()
if m.favorites[userID.OpaqueId] == nil {
m.favorites[userID.OpaqueId] = make(map[string]*provider.ResourceId)
}
m.favorites[userID.OpaqueId][resourceID.OpaqueId] = resourceID
return nil
}

// UnsetFavorite unmarks a resource as favorited by a user.
func (m *InMemoryManager) UnsetFavorite(_ context.Context, userID *user.UserId, resourceID *provider.ResourceId) error {
m.Lock()
defer m.Unlock()
delete(m.favorites[userID.OpaqueId], resourceID.OpaqueId)
return nil
UnsetFavorite(ctx context.Context, userID *user.UserId, resourceInfo *provider.ResourceInfo) error
}
25 changes: 25 additions & 0 deletions pkg/storage/favorite/loader/loader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// 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 loader

import (
// Load share cache drivers.
_ "github.com/cs3org/reva/pkg/storage/favorite/memory"
// Add your own here
)
Loading

0 comments on commit 2b436a9

Please sign in to comment.