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

Revamp the favorite manager and add the cbox sql driver #2271

Merged
merged 4 commits into from
Nov 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
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
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