diff --git a/changelog/unreleased/ocs-resource-cache.md b/changelog/unreleased/ocs-resource-cache.md new file mode 100644 index 0000000000..0372fd2599 --- /dev/null +++ b/changelog/unreleased/ocs-resource-cache.md @@ -0,0 +1,8 @@ +Enhancement: Cache resources from share getter methods in OCS + +In OCS, once we retrieve the shares from the shareprovider service, we stat each +of those separately to obtain the required info, which introduces a lot of +latency. This PR introduces a resoource info cache in OCS, which would prevent +this latency. + +https://github.com/cs3org/reva/pull/1643 diff --git a/go.mod b/go.mod index 415674b8e4..70ed8d2b46 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/Masterminds/sprig v2.22.0+incompatible github.com/ReneKroon/ttlcache/v2 v2.4.0 github.com/aws/aws-sdk-go v1.38.13 + github.com/bluele/gcache v0.0.2 // indirect github.com/c-bata/go-prompt v0.2.5 github.com/cheggaaa/pb v1.0.29 github.com/coreos/go-oidc v2.2.1+incompatible diff --git a/go.sum b/go.sum index ac9d7d08c3..2b34b53633 100644 --- a/go.sum +++ b/go.sum @@ -92,6 +92,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw= +github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= github.com/bmatcuk/doublestar/v2 v2.0.3/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40 h1:y4B3+GPxKlrigF1ha5FFErxK+sr6sWxQovRMzwMhejo= diff --git a/internal/grpc/services/gateway/usershareprovider.go b/internal/grpc/services/gateway/usershareprovider.go index 1efe048526..f1541e267c 100644 --- a/internal/grpc/services/gateway/usershareprovider.go +++ b/internal/grpc/services/gateway/usershareprovider.go @@ -337,6 +337,9 @@ func (s *svc) UpdateReceivedShare(ctx context.Context, req *collaboration.Update return &collaboration.UpdateReceivedShareResponse{ Status: createRefStatus, }, err + } else if req.Field.GetState() == collaboration.ShareState_SHARE_STATE_REJECTED { + // Nothing more to do, return the original result + return res, nil } } diff --git a/internal/http/services/owncloud/ocs/config/config.go b/internal/http/services/owncloud/ocs/config/config.go index e62ba20c60..3d8f73b0b3 100644 --- a/internal/http/services/owncloud/ocs/config/config.go +++ b/internal/http/services/owncloud/ocs/config/config.go @@ -34,6 +34,8 @@ type Config struct { SharePrefix string `mapstructure:"share_prefix"` HomeNamespace string `mapstructure:"home_namespace"` AdditionalInfoAttribute string `mapstructure:"additional_info_attribute"` + ResourceInfoCacheSize int `mapstructure:"resource_info_cache_size"` + ResourceInfoCacheTTL int `mapstructure:"resource_info_cache_ttl"` } // Init sets sane defaults @@ -58,5 +60,9 @@ func (c *Config) Init() { c.AdditionalInfoAttribute = "{{.Mail}}" } + if c.ResourceInfoCacheSize == 0 { + c.ResourceInfoCacheSize = 1000000 + } + c.GatewaySvc = sharedconf.GetGatewaySVC(c.GatewaySvc) } diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go index 88ce48bf4f..e574551d4e 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go @@ -23,6 +23,7 @@ import ( "fmt" "net/http" "strconv" + "time" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" @@ -159,33 +160,43 @@ func (h *Handler) listPublicShares(r *http.Request, filters []*link.ListPublicSh return ocsDataPayload, res.Status, nil } + var info *provider.ResourceInfo for _, share := range res.GetShare() { - - statRequest := &provider.StatRequest{ - Ref: &provider.Reference{ - Spec: &provider.Reference_Id{ - Id: share.ResourceId, + key := wrapResourceID(share.ResourceId) + if infoIf, err := h.resourceInfoCache.Get(key); h.resourceInfoCacheTTL > 0 && err == nil { + log.Debug().Msgf("cache hit for resource %+v", share.ResourceId) + info = infoIf.(*provider.ResourceInfo) + } else { + statRequest := &provider.StatRequest{ + Ref: &provider.Reference{ + Spec: &provider.Reference_Id{ + Id: share.ResourceId, + }, }, - }, - } - - statResponse, err := c.Stat(ctx, statRequest) - if err != nil || res.Status.Code != rpc.Code_CODE_OK { - log.Debug().Interface("share", share).Interface("response", statResponse).Err(err).Msg("could not stat share, skipping") - continue + } + + statResponse, err := c.Stat(ctx, statRequest) + if err != nil || res.Status.Code != rpc.Code_CODE_OK { + log.Debug().Interface("share", share).Interface("response", statResponse).Err(err).Msg("could not stat share, skipping") + continue + } + info = statResponse.Info + if h.resourceInfoCacheTTL > 0 { + _ = h.resourceInfoCache.SetWithExpire(key, info, time.Second*h.resourceInfoCacheTTL) + } } sData := conversions.PublicShare2ShareData(share, r, h.publicURL) sData.Name = share.DisplayName - if err := h.addFileInfo(ctx, sData, statResponse.Info); err != nil { - log.Debug().Interface("share", share).Interface("info", statResponse.Info).Err(err).Msg("could not add file info, skipping") + if err := h.addFileInfo(ctx, sData, info); err != nil { + log.Debug().Interface("share", share).Interface("info", info).Err(err).Msg("could not add file info, skipping") continue } h.mapUserIds(ctx, c, sData) - log.Debug().Interface("share", share).Interface("info", statResponse.Info).Interface("shareData", share).Msg("mapped") + log.Debug().Interface("share", share).Interface("info", info).Interface("shareData", share).Msg("mapped") ocsDataPayload = append(ocsDataPayload, sData) diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go index c62a3409df..1e25a9e0a5 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go @@ -42,6 +42,7 @@ import ( "github.com/rs/zerolog/log" "github.com/ReneKroon/ttlcache/v2" + "github.com/bluele/gcache" "github.com/cs3org/reva/internal/http/services/owncloud/ocdav" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/config" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" @@ -58,8 +59,10 @@ type Handler struct { publicURL string sharePrefix string homeNamespace string + resourceInfoCacheTTL time.Duration additionalInfoTemplate *template.Template userIdentifierCache *ttlcache.Cache + resourceInfoCache gcache.Cache } // we only cache the minimal set of data instead of the full user metadata @@ -75,12 +78,15 @@ func (h *Handler) Init(c *config.Config) error { h.publicURL = c.Config.Host h.sharePrefix = c.SharePrefix h.homeNamespace = c.HomeNamespace + h.resourceInfoCacheTTL = time.Duration(c.ResourceInfoCacheTTL) h.additionalInfoTemplate, _ = template.New("additionalInfo").Parse(c.AdditionalInfoAttribute) h.userIdentifierCache = ttlcache.NewCache() _ = h.userIdentifierCache.SetTTL(60 * time.Second) + h.resourceInfoCache = gcache.New(c.ResourceInfoCacheSize).LFU().Build() + return nil } @@ -391,31 +397,42 @@ func (h *Handler) getShare(w http.ResponseWriter, r *http.Request, shareID strin return } - // prepare the stat request - statReq := &provider.StatRequest{ - // prepare the reference - Ref: &provider.Reference{ - // using ResourceId from the share - Spec: &provider.Reference_Id{Id: resourceID}, - }, - } + var info *provider.ResourceInfo + key := wrapResourceID(resourceID) + if infoIf, err := h.resourceInfoCache.Get(key); h.resourceInfoCacheTTL > 0 && err == nil { + logger.Debug().Msgf("cache hit for resource %+v", resourceID) + info = infoIf.(*provider.ResourceInfo) + } else { + // prepare the stat request + statReq := &provider.StatRequest{ + // prepare the reference + Ref: &provider.Reference{ + // using ResourceId from the share + Spec: &provider.Reference_Id{Id: resourceID}, + }, + } - statResponse, err := client.Stat(ctx, statReq) - if err != nil { - log.Error().Err(err).Msg("error mapping share data") - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error mapping share data", err) - return - } + statResponse, err := client.Stat(ctx, statReq) + if err != nil { + log.Error().Err(err).Msg("error mapping share data") + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error mapping share data", err) + return + } - if statResponse.Status.Code != rpc.Code_CODE_OK { - log.Error().Err(err).Str("status", statResponse.Status.Code.String()).Msg("error mapping share data") - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error mapping share data", err) - return + if statResponse.Status.Code != rpc.Code_CODE_OK { + log.Error().Err(err).Str("status", statResponse.Status.Code.String()).Msg("error mapping share data") + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error mapping share data", err) + return + } + info = statResponse.Info + if h.resourceInfoCacheTTL > 0 { + _ = h.resourceInfoCache.SetWithExpire(key, info, time.Second*h.resourceInfoCacheTTL) + } } - err = h.addFileInfo(ctx, share, statResponse.Info) + err = h.addFileInfo(ctx, share, info) if err != nil { - log.Error().Err(err).Str("status", statResponse.Status.Code.String()).Msg("error mapping share data") + log.Error().Err(err).Msg("error mapping share data") response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error mapping share data", err) } h.mapUserIds(ctx, client, share) @@ -585,6 +602,7 @@ func (h *Handler) listSharesWithMe(w http.ResponseWriter, r *http.Request) { } ctx := r.Context() + logger := appctx.GetLogger(ctx) var pinfo *provider.ResourceInfo p := r.URL.Query().Get("path") @@ -593,33 +611,41 @@ func (h *Handler) listSharesWithMe(w http.ResponseWriter, r *http.Request) { // prefix the path with the owners home, because ocs share requests are relative to the home dir target := path.Join(h.homeNamespace, r.FormValue("path")) - statReq := &provider.StatRequest{ - Ref: &provider.Reference{ - Spec: &provider.Reference_Path{ - Path: target, + if infoIf, err := h.resourceInfoCache.Get(target); h.resourceInfoCacheTTL > 0 && err == nil { + logger.Debug().Msgf("cache hit for resource %+v", target) + pinfo = infoIf.(*provider.ResourceInfo) + } else { + statReq := &provider.StatRequest{ + Ref: &provider.Reference{ + Spec: &provider.Reference_Path{ + Path: target, + }, }, - }, - } + } - statRes, err := gwc.Stat(ctx, statReq) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc stat request", err) - return - } + statRes, err := gwc.Stat(ctx, statReq) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc stat request", err) + return + } - if statRes.Status.Code != rpc.Code_CODE_OK { - switch statRes.Status.Code { - case rpc.Code_CODE_NOT_FOUND: - response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "path not found", nil) - case rpc.Code_CODE_PERMISSION_DENIED: - response.WriteOCSError(w, r, response.MetaUnauthorized.StatusCode, "permission denied", nil) - default: - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc stat request failed", nil) + if statRes.Status.Code != rpc.Code_CODE_OK { + switch statRes.Status.Code { + case rpc.Code_CODE_NOT_FOUND: + response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "path not found", nil) + case rpc.Code_CODE_PERMISSION_DENIED: + response.WriteOCSError(w, r, response.MetaUnauthorized.StatusCode, "permission denied", nil) + default: + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc stat request failed", nil) + } + return } - return - } - pinfo = statRes.GetInfo() + pinfo = statRes.GetInfo() + if h.resourceInfoCacheTTL > 0 { + _ = h.resourceInfoCache.SetWithExpire(target, pinfo, time.Second*h.resourceInfoCacheTTL) + } + } } lrsReq := collaboration.ListReceivedSharesRequest{} @@ -659,22 +685,31 @@ func (h *Handler) listSharesWithMe(w http.ResponseWriter, r *http.Request) { // we can reuse the stat info info = pinfo } else { - // we need to do a stat call - statRequest := provider.StatRequest{ - Ref: &provider.Reference{ - Spec: &provider.Reference_Id{ - Id: rs.Share.ResourceId, + key := wrapResourceID(rs.Share.ResourceId) + if infoIf, err := h.resourceInfoCache.Get(key); h.resourceInfoCacheTTL > 0 && err == nil { + logger.Debug().Msgf("cache hit for resource %+v", rs.Share.ResourceId) + info = infoIf.(*provider.ResourceInfo) + } else { + // we need to do a stat call + statRequest := provider.StatRequest{ + Ref: &provider.Reference{ + Spec: &provider.Reference_Id{ + Id: rs.Share.ResourceId, + }, }, - }, - } + } - statRes, err := gwc.Stat(r.Context(), &statRequest) - if err != nil || statRes.Status.Code != rpc.Code_CODE_OK { - h.logProblems(statRes.GetStatus(), err, "could not stat, skipping") - continue - } + statRes, err := gwc.Stat(r.Context(), &statRequest) + if err != nil || statRes.Status.Code != rpc.Code_CODE_OK { + h.logProblems(statRes.GetStatus(), err, "could not stat, skipping") + continue + } - info = statRes.GetInfo() + info = statRes.GetInfo() + if h.resourceInfoCacheTTL > 0 { + _ = h.resourceInfoCache.SetWithExpire(key, info, time.Second*h.resourceInfoCacheTTL) + } + } } data, err := conversions.CS3Share2ShareData(r.Context(), rs.Share) @@ -773,32 +808,38 @@ func (h *Handler) addFilters(w http.ResponseWriter, r *http.Request, prefix stri } target := path.Join(prefix, r.FormValue("path")) - - statReq := &provider.StatRequest{ - Ref: &provider.Reference{ - Spec: &provider.Reference_Path{ - Path: target, + if infoIf, err := h.resourceInfoCache.Get(target); h.resourceInfoCacheTTL > 0 && err == nil { + info = infoIf.(*provider.ResourceInfo) + } else { + statReq := &provider.StatRequest{ + Ref: &provider.Reference{ + Spec: &provider.Reference_Path{ + Path: target, + }, }, - }, - } + } - res, err := gwClient.Stat(ctx, statReq) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc stat request", err) - return nil, nil, err - } + res, err := gwClient.Stat(ctx, statReq) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc stat request", err) + return nil, nil, err + } - if res.Status.Code != rpc.Code_CODE_OK { - err = errors.New(res.Status.Message) - if res.Status.Code == rpc.Code_CODE_NOT_FOUND { - response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "not found", err) + if res.Status.Code != rpc.Code_CODE_OK { + err = errors.New(res.Status.Message) + if res.Status.Code == rpc.Code_CODE_NOT_FOUND { + response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "not found", err) + return nil, nil, err + } + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc stat request failed", err) return nil, nil, err } - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc stat request failed", err) - return nil, nil, err - } - info = res.Info + info = res.Info + if h.resourceInfoCacheTTL > 0 { + _ = h.resourceInfoCache.SetWithExpire(target, info, time.Second*h.resourceInfoCacheTTL) + } + } collaborationFilters = append(collaborationFilters, &collaboration.ListSharesRequest_Filter{ Type: collaboration.ListSharesRequest_Filter_TYPE_RESOURCE_ID, diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go index 5f615ba92e..cac4cf61f8 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go @@ -20,6 +20,7 @@ package shares import ( "net/http" + "time" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" @@ -146,7 +147,6 @@ func (h *Handler) removeUserShare(w http.ResponseWriter, r *http.Request, shareI } func (h *Handler) listUserShares(r *http.Request, filters []*collaboration.ListSharesRequest_Filter) ([]*conversions.ShareData, *rpc.Status, error) { - var rInfo *provider.ResourceInfo ctx := r.Context() log := appctx.GetLogger(ctx) @@ -179,26 +179,37 @@ func (h *Handler) listUserShares(r *http.Request, filters []*collaboration.ListS continue } - // prepare the stat request - statReq := &provider.StatRequest{ - Ref: &provider.Reference{ - Spec: &provider.Reference_Id{Id: s.ResourceId}, - }, - } - - statResponse, err := c.Stat(ctx, statReq) - if err != nil || statResponse.Status.Code != rpc.Code_CODE_OK { - log.Debug().Interface("share", s).Interface("response", statResponse).Interface("shareData", data).Err(err).Msg("could not stat share, skipping") - continue + var info *provider.ResourceInfo + key := wrapResourceID(s.ResourceId) + if infoIf, err := h.resourceInfoCache.Get(key); h.resourceInfoCacheTTL > 0 && err == nil { + log.Debug().Msgf("cache hit for resource %+v", s.ResourceId) + info = infoIf.(*provider.ResourceInfo) + } else { + // prepare the stat request + statReq := &provider.StatRequest{ + Ref: &provider.Reference{ + Spec: &provider.Reference_Id{Id: s.ResourceId}, + }, + } + + statResponse, err := c.Stat(ctx, statReq) + if err != nil || statResponse.Status.Code != rpc.Code_CODE_OK { + log.Debug().Interface("share", s).Interface("response", statResponse).Interface("shareData", data).Err(err).Msg("could not stat share, skipping") + continue + } + info = statResponse.Info + if h.resourceInfoCacheTTL > 0 { + _ = h.resourceInfoCache.SetWithExpire(key, info, time.Second*h.resourceInfoCacheTTL) + } } - if err := h.addFileInfo(ctx, data, statResponse.Info); err != nil { - log.Debug().Interface("share", s).Interface("info", statResponse.Info).Interface("shareData", data).Err(err).Msg("could not add file info, skipping") + if err := h.addFileInfo(ctx, data, info); err != nil { + log.Debug().Interface("share", s).Interface("info", info).Interface("shareData", data).Err(err).Msg("could not add file info, skipping") continue } h.mapUserIds(ctx, c, data) - log.Debug().Interface("share", s).Interface("info", rInfo).Interface("shareData", data).Msg("mapped") + log.Debug().Interface("share", s).Interface("info", info).Interface("shareData", data).Msg("mapped") ocsDataPayload = append(ocsDataPayload, data) } } diff --git a/pkg/cbox/share/sql/sql.go b/pkg/cbox/share/sql/sql.go index fac64aab4b..f9b0e5eb18 100644 --- a/pkg/cbox/share/sql/sql.go +++ b/pkg/cbox/share/sql/sql.go @@ -323,7 +323,7 @@ func (m *mgr) ListReceivedShares(ctx context.Context) ([]*collaboration.Received params = append(params, v) } - query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, id, stime, permissions, share_type, accepted FROM oc_share WHERE (orphan = 0 or orphan IS NULL) AND (uid_owner != ? AND uid_initiator != ?) AND id not in (SELECT distinct(id) FROM oc_share_acl WHERE rejected_by=?)" + query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, ts.id, stime, permissions, share_type, accepted, coalesce(tr.rejected_by, '') as rejected_by FROM oc_share ts LEFT JOIN oc_share_acl tr ON (ts.id = tr.id AND tr.rejected_by = ?) WHERE (orphan = 0 or orphan IS NULL) AND (uid_owner != ? AND uid_initiator != ?) " if len(user.Groups) > 0 { query += "AND (share_with=? OR share_with in (?" + strings.Repeat(",?", len(user.Groups)-1) + "))" } else { @@ -339,7 +339,7 @@ func (m *mgr) ListReceivedShares(ctx context.Context) ([]*collaboration.Received var s conversions.DBShare shares := []*collaboration.ReceivedShare{} for rows.Next() { - if err := rows.Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.ID, &s.STime, &s.Permissions, &s.ShareType, &s.State); err != nil { + if err := rows.Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.ID, &s.STime, &s.Permissions, &s.ShareType, &s.State, &s.RejectedBy); err != nil { continue } shares = append(shares, conversions.ConvertToCS3ReceivedShare(s)) @@ -355,19 +355,19 @@ func (m *mgr) getReceivedByID(ctx context.Context, id *collaboration.ShareId) (* user := user.ContextMustGetUser(ctx) uid := conversions.FormatUserID(user.Id) - params := []interface{}{id.OpaqueId, uid, uid} + params := []interface{}{uid, id.OpaqueId, uid} for _, v := range user.Groups { params = append(params, v) } s := conversions.DBShare{ID: id.OpaqueId} - query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, stime, permissions, share_type, accepted FROM oc_share WHERE (orphan = 0 or orphan IS NULL) AND id=? AND id not in (SELECT distinct(id) FROM oc_share_acl WHERE rejected_by=?)" + query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, stime, permissions, share_type, accepted, coalesce(tr.rejected_by, '') as rejected_by FROM oc_share ts LEFT JOIN oc_share_acl tr ON (ts.id = tr.id AND tr.rejected_by = ?) WHERE (orphan = 0 or orphan IS NULL) AND ts.id=? " if len(user.Groups) > 0 { query += "AND (share_with=? OR share_with in (?" + strings.Repeat(",?", len(user.Groups)-1) + "))" } else { query += "AND (share_with=?)" } - if err := m.db.QueryRow(query, params...).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.STime, &s.Permissions, &s.ShareType, &s.State); err != nil { + if err := m.db.QueryRow(query, params...).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.STime, &s.Permissions, &s.ShareType, &s.State, &s.RejectedBy); err != nil { if err == sql.ErrNoRows { return nil, errtypes.NotFound(id.OpaqueId) } @@ -381,20 +381,20 @@ func (m *mgr) getReceivedByKey(ctx context.Context, key *collaboration.ShareKey) uid := conversions.FormatUserID(user.Id) shareType, shareWith := conversions.FormatGrantee(key.Grantee) - params := []interface{}{conversions.FormatUserID(key.Owner), key.ResourceId.StorageId, key.ResourceId.OpaqueId, shareType, shareWith, shareWith, uid} + params := []interface{}{uid, conversions.FormatUserID(key.Owner), key.ResourceId.StorageId, key.ResourceId.OpaqueId, shareType, shareWith, shareWith} for _, v := range user.Groups { params = append(params, v) } s := conversions.DBShare{} - query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, id, stime, permissions, share_type, accepted FROM oc_share WHERE (orphan = 0 or orphan IS NULL) AND uid_owner=? AND fileid_prefix=? AND item_source=? AND share_type=? AND share_with=? AND id not in (SELECT distinct(id) FROM oc_share_acl WHERE rejected_by=?)" + query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, ts.id, stime, permissions, share_type, accepted, coalesce(tr.rejected_by, '') as rejected_by FROM oc_share ts LEFT JOIN oc_share_acl tr ON (ts.id = tr.id AND tr.rejected_by = ?) WHERE (orphan = 0 or orphan IS NULL) AND uid_owner=? AND fileid_prefix=? AND item_source=? AND share_type=? AND share_with=? " if len(user.Groups) > 0 { query += "AND (share_with=? OR share_with in (?" + strings.Repeat(",?", len(user.Groups)-1) + "))" } else { query += "AND (share_with=?)" } - if err := m.db.QueryRow(query, params...).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.ID, &s.STime, &s.Permissions, &s.ShareType, &s.State); err != nil { + if err := m.db.QueryRow(query, params...).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.ID, &s.STime, &s.Permissions, &s.ShareType, &s.State, &s.RejectedBy); err != nil { if err == sql.ErrNoRows { return nil, errtypes.NotFound(key.String()) } @@ -431,14 +431,14 @@ func (m *mgr) UpdateReceivedShare(ctx context.Context, ref *collaboration.ShareR return nil, err } - var query string - params := []interface{}{rs.Share.Id.OpaqueId} + var query, queryAccept string + params := []interface{}{rs.Share.Id.OpaqueId, conversions.FormatUserID(user.Id)} switch f.GetState() { case collaboration.ShareState_SHARE_STATE_REJECTED: query = "insert into oc_share_acl(id, rejected_by) values(?, ?)" - params = append(params, conversions.FormatUserID(user.Id)) case collaboration.ShareState_SHARE_STATE_ACCEPTED: - query = "update oc_share set accepted=1 where id=?" + query = "delete from oc_share_acl where id=? AND rejected_by=?" + queryAccept = "update oc_share set accepted=1 where id=?" } stmt, err := m.db.Prepare(query) @@ -450,6 +450,17 @@ func (m *mgr) UpdateReceivedShare(ctx context.Context, ref *collaboration.ShareR return nil, err } + if queryAccept != "" { + stmt, err = m.db.Prepare(queryAccept) + if err != nil { + return nil, err + } + _, err = stmt.Exec(rs.Share.Id.OpaqueId) + if err != nil { + return nil, err + } + } + rs.State = f.GetState() return rs, nil } diff --git a/pkg/cbox/utils/conversions.go b/pkg/cbox/utils/conversions.go index 784debffbf..2117282224 100644 --- a/pkg/cbox/utils/conversions.go +++ b/pkg/cbox/utils/conversions.go @@ -44,6 +44,7 @@ type DBShare struct { ShareName string STime int FileTarget string + RejectedBy string State int } @@ -199,9 +200,15 @@ func ConvertToCS3Share(s DBShare) *collaboration.Share { // ConvertToCS3ReceivedShare converts a DBShare to a CS3API collaboration received share func ConvertToCS3ReceivedShare(s DBShare) *collaboration.ReceivedShare { share := ConvertToCS3Share(s) + var state collaboration.ShareState + if s.RejectedBy != "" { + state = collaboration.ShareState_SHARE_STATE_REJECTED + } else { + state = IntToShareState(s.State) + } return &collaboration.ReceivedShare{ Share: share, - State: IntToShareState(s.State), + State: state, } } diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index da32a77d22..30854a6dae 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -26,6 +26,7 @@ import ( "net/url" "os" "path" + "path/filepath" "regexp" "strconv" "strings" @@ -578,7 +579,7 @@ func (fs *eosfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []st return nil, err } - return fs.convertToResourceInfo(ctx, eosFileInfo) + return fs.convertToResourceInfo(ctx, eosFileInfo, false) } func (fs *eosfs) getMDShareFolder(ctx context.Context, p string, mdKeys []string) (*provider.ResourceInfo, error) { @@ -601,7 +602,7 @@ func (fs *eosfs) getMDShareFolder(ctx context.Context, p string, mdKeys []string // TODO(labkode): diff between root (dir) and children (ref) if fs.isShareFolderRoot(ctx, p) { - return fs.convertToResourceInfo(ctx, eosFileInfo) + return fs.convertToResourceInfo(ctx, eosFileInfo, false) } return fs.convertToFileReference(ctx, eosFileInfo) } @@ -643,6 +644,10 @@ func (fs *eosfs) listWithNominalHome(ctx context.Context, p string) (finfos []*p } fn := fs.wrap(ctx, p) + virtualView := false + if !fs.conf.EnableHome && filepath.Dir(fn) == filepath.Clean(fs.conf.Namespace) { + virtualView = true + } eosFileInfos, err := fs.c.List(ctx, uid, gid, fn) if err != nil { @@ -660,7 +665,7 @@ func (fs *eosfs) listWithNominalHome(ctx context.Context, p string) (finfos []*p } // Remove the hidden folders in the topmost directory - if finfo, err := fs.convertToResourceInfo(ctx, eosFileInfo); err == nil && finfo.Path != "/" && !strings.HasPrefix(finfo.Path, "/.") { + if finfo, err := fs.convertToResourceInfo(ctx, eosFileInfo, virtualView); err == nil && finfo.Path != "/" && !strings.HasPrefix(finfo.Path, "/.") { finfos = append(finfos, finfo) } } @@ -714,7 +719,7 @@ func (fs *eosfs) listHome(ctx context.Context, home string) ([]*provider.Resourc } } - if finfo, err := fs.convertToResourceInfo(ctx, eosFileInfo); err == nil && finfo.Path != "/" && !strings.HasPrefix(finfo.Path, "/.") { + if finfo, err := fs.convertToResourceInfo(ctx, eosFileInfo, false); err == nil && finfo.Path != "/" && !strings.HasPrefix(finfo.Path, "/.") { finfos = append(finfos, finfo) } } @@ -1319,7 +1324,7 @@ func (fs *eosfs) convertToRecycleItem(ctx context.Context, eosDeletedItem *eoscl } func (fs *eosfs) convertToRevision(ctx context.Context, eosFileInfo *eosclient.FileInfo) (*provider.FileVersion, error) { - md, err := fs.convertToResourceInfo(ctx, eosFileInfo) + md, err := fs.convertToResourceInfo(ctx, eosFileInfo, false) if err != nil { return nil, err } @@ -1332,12 +1337,12 @@ func (fs *eosfs) convertToRevision(ctx context.Context, eosFileInfo *eosclient.F return revision, nil } -func (fs *eosfs) convertToResourceInfo(ctx context.Context, eosFileInfo *eosclient.FileInfo) (*provider.ResourceInfo, error) { - return fs.convert(ctx, eosFileInfo) +func (fs *eosfs) convertToResourceInfo(ctx context.Context, eosFileInfo *eosclient.FileInfo, virtualView bool) (*provider.ResourceInfo, error) { + return fs.convert(ctx, eosFileInfo, virtualView) } func (fs *eosfs) convertToFileReference(ctx context.Context, eosFileInfo *eosclient.FileInfo) (*provider.ResourceInfo, error) { - info, err := fs.convert(ctx, eosFileInfo) + info, err := fs.convert(ctx, eosFileInfo, false) if err != nil { return nil, err } @@ -1400,7 +1405,7 @@ func (fs *eosfs) permissionSet(ctx context.Context, eosFileInfo *eosclient.FileI return grants.GetGrantPermissionSet(perm, eosFileInfo.IsDir) } -func (fs *eosfs) convert(ctx context.Context, eosFileInfo *eosclient.FileInfo) (*provider.ResourceInfo, error) { +func (fs *eosfs) convert(ctx context.Context, eosFileInfo *eosclient.FileInfo, virtualView bool) (*provider.ResourceInfo, error) { path, err := fs.unwrap(ctx, eosFileInfo.File) if err != nil { return nil, err @@ -1411,11 +1416,13 @@ func (fs *eosfs) convert(ctx context.Context, eosFileInfo *eosclient.FileInfo) ( size = eosFileInfo.TreeSize } - owner, err := fs.getUserIDGateway(ctx, strconv.FormatUint(eosFileInfo.UID, 10)) - if err != nil { - sublog := appctx.GetLogger(ctx).With().Logger() - sublog.Warn().Uint64("uid", eosFileInfo.UID).Msg("could not lookup userid, leaving empty") - owner = &userpb.UserId{} + owner := &userpb.UserId{} + if !virtualView { + owner, err = fs.getUserIDGateway(ctx, strconv.FormatUint(eosFileInfo.UID, 10)) + if err != nil { + sublog := appctx.GetLogger(ctx).With().Logger() + sublog.Warn().Uint64("uid", eosFileInfo.UID).Msg("could not lookup userid, leaving empty") + } } info := &provider.ResourceInfo{ @@ -1499,7 +1506,6 @@ func (fs *eosfs) getUserIDGateway(ctx context.Context, uid string) (*userpb.User if userIDInterface, ok := fs.userIDCache.Load(uid); ok { return userIDInterface.(*userpb.UserId), nil } - client, err := pool.GetGatewayServiceClient(fs.conf.GatewaySvc) if err != nil { return nil, errors.Wrap(err, "eos: error getting gateway grpc client")