bump reva to use UploadSessionLister interface
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
This commit is contained in:
@@ -13,7 +13,7 @@ require (
|
|||||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||||
github.com/coreos/go-oidc/v3 v3.8.0
|
github.com/coreos/go-oidc/v3 v3.8.0
|
||||||
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781
|
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781
|
||||||
github.com/cs3org/reva/v2 v2.16.1-0.20231128104331-ea8d1336afc9
|
github.com/cs3org/reva/v2 v2.16.1-0.20231201122033-a389ddc645c4
|
||||||
github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25
|
github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25
|
||||||
github.com/disintegration/imaging v1.6.2
|
github.com/disintegration/imaging v1.6.2
|
||||||
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
|
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
|
||||||
|
|||||||
@@ -1017,8 +1017,8 @@ github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c=
|
|||||||
github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME=
|
github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME=
|
||||||
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781 h1:BUdwkIlf8IS2FasrrPg8gGPHQPOrQ18MS1Oew2tmGtY=
|
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781 h1:BUdwkIlf8IS2FasrrPg8gGPHQPOrQ18MS1Oew2tmGtY=
|
||||||
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY=
|
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY=
|
||||||
github.com/cs3org/reva/v2 v2.16.1-0.20231128104331-ea8d1336afc9 h1:5vKQcL1hPHEZKu9e8C9rl0ap3ofMBznmoSgi4lRYXec=
|
github.com/cs3org/reva/v2 v2.16.1-0.20231201122033-a389ddc645c4 h1:61AwMfov2OxrUElWXXKHZfBsuxgNIVwZVQW4PlJoqnM=
|
||||||
github.com/cs3org/reva/v2 v2.16.1-0.20231128104331-ea8d1336afc9/go.mod h1:zcrrYVsBv/DwhpyO2/W5hoSZ/k6az6Z2EYQok65uqZY=
|
github.com/cs3org/reva/v2 v2.16.1-0.20231201122033-a389ddc645c4/go.mod h1:zcrrYVsBv/DwhpyO2/W5hoSZ/k6az6Z2EYQok65uqZY=
|
||||||
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
|||||||
@@ -3,11 +3,8 @@ package command
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
tusd "github.com/tus/tusd/pkg/handler"
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
"github.com/cs3org/reva/v2/pkg/storage"
|
"github.com/cs3org/reva/v2/pkg/storage"
|
||||||
@@ -51,20 +48,21 @@ func ListUploads(cfg *config.Config) *cli.Command {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
managingFS, ok := fs.(storage.UploadsManager)
|
managingFS, ok := fs.(storage.UploadSessionLister)
|
||||||
if !ok {
|
if !ok {
|
||||||
fmt.Fprintf(os.Stderr, "'%s' storage does not support listing expired uploads\n", cfg.Driver)
|
fmt.Fprintf(os.Stderr, "'%s' storage does not support listing expired uploads\n", cfg.Driver)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
falseValue := false
|
||||||
uploads, err := managingFS.ListUploads()
|
uploads, err := managingFS.ListUploadSessions(c.Context, storage.UploadSessionFilter{Expired: &falseValue})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Incomplete uploads:")
|
fmt.Println("Incomplete uploads:")
|
||||||
for _, u := range uploads {
|
for _, u := range uploads {
|
||||||
fmt.Printf(" - %s (%s, Size: %d, Expires: %s)\n", u.ID, u.MetaData["filename"], u.Size, expiredString(u.MetaData["expires"]))
|
ref := u.Reference()
|
||||||
|
fmt.Printf(" - %s (Space: %s, Name: %s, Size: %d/%d, Expires: %s, Processing: %t)\n", ref.GetResourceId().GetSpaceId(), u.ID(), u.Filename(), u.Offset(), u.Size(), u.Expires(), u.IsProcessing())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@@ -92,7 +90,7 @@ func PurgeExpiredUploads(cfg *config.Config) *cli.Command {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
managingFS, ok := fs.(storage.UploadsManager)
|
managingFS, ok := fs.(storage.UploadSessionLister)
|
||||||
if !ok {
|
if !ok {
|
||||||
fmt.Fprintf(os.Stderr, "'%s' storage does not support clean expired uploads\n", cfg.Driver)
|
fmt.Fprintf(os.Stderr, "'%s' storage does not support clean expired uploads\n", cfg.Driver)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -100,18 +98,23 @@ func PurgeExpiredUploads(cfg *config.Config) *cli.Command {
|
|||||||
|
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
purgedChannel := make(chan tusd.FileInfo)
|
falseValue := false
|
||||||
|
trueValue := false
|
||||||
|
uploads, err := managingFS.ListUploadSessions(c.Context, storage.UploadSessionFilter{Expired: &trueValue, Processing: &falseValue})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Println("Cleaned uploads:")
|
fmt.Println("purging uploads:")
|
||||||
go func() {
|
go func() {
|
||||||
for purged := range purgedChannel {
|
for _, u := range uploads {
|
||||||
fmt.Printf(" - %s (%s, Size: %d, Expires: %s)\n", purged.ID, purged.MetaData["filename"], purged.Size, expiredString(purged.MetaData["expires"]))
|
ref := u.Reference()
|
||||||
|
fmt.Printf(" - %s (Space: %s, Name: %s, Size: %d/%d, Expires: %s, Processing: %t)\n", ref.GetResourceId().GetSpaceId(), u.ID(), u.Filename(), u.Offset(), u.Size(), u.Expires(), u.IsProcessing())
|
||||||
|
u.Purge(c.Context)
|
||||||
}
|
}
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err = managingFS.PurgeExpiredUploads(purgedChannel)
|
|
||||||
close(purgedChannel)
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to clean expired uploads '%s'\n", err)
|
fmt.Fprintf(os.Stderr, "Failed to clean expired uploads '%s'\n", err)
|
||||||
@@ -121,12 +124,3 @@ func PurgeExpiredUploads(cfg *config.Config) *cli.Command {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func expiredString(e string) string {
|
|
||||||
expired := "N/A"
|
|
||||||
iExpires, err := strconv.Atoi(e)
|
|
||||||
if err == nil {
|
|
||||||
expired = time.Unix(int64(iExpires), 0).Format(time.RFC3339)
|
|
||||||
}
|
|
||||||
return expired
|
|
||||||
}
|
|
||||||
|
|||||||
+20
-28
@@ -23,13 +23,11 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
tusd "github.com/tus/tusd/pkg/handler"
|
tusd "github.com/tus/tusd/pkg/handler"
|
||||||
|
|
||||||
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
|
||||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||||
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net"
|
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net"
|
||||||
"github.com/cs3org/reva/v2/pkg/appctx"
|
"github.com/cs3org/reva/v2/pkg/appctx"
|
||||||
@@ -40,8 +38,8 @@ import (
|
|||||||
"github.com/cs3org/reva/v2/pkg/rhttp/datatx/metrics"
|
"github.com/cs3org/reva/v2/pkg/rhttp/datatx/metrics"
|
||||||
"github.com/cs3org/reva/v2/pkg/storage"
|
"github.com/cs3org/reva/v2/pkg/storage"
|
||||||
"github.com/cs3org/reva/v2/pkg/storage/cache"
|
"github.com/cs3org/reva/v2/pkg/storage/cache"
|
||||||
|
"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/upload"
|
||||||
"github.com/cs3org/reva/v2/pkg/storagespace"
|
"github.com/cs3org/reva/v2/pkg/storagespace"
|
||||||
"github.com/cs3org/reva/v2/pkg/utils"
|
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -103,33 +101,27 @@ func (m *manager) Handler(fs storage.FS) (http.Handler, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
if _, ok := fs.(storage.UploadSessionLister); ok {
|
||||||
for {
|
// We can currently only send updates if the fs is decomposedfs as we read very specific keys from the storage map of the tus info
|
||||||
ev := <-handler.CompleteUploads
|
go func() {
|
||||||
info := ev.Upload
|
for {
|
||||||
spaceOwner := &userv1beta1.UserId{
|
ev := <-handler.CompleteUploads
|
||||||
OpaqueId: info.Storage["SpaceOwnerOrManager"],
|
// We should be able to get the upload progress with fs.GetUploadProgress, but currently tus will erase the info files
|
||||||
}
|
// so we create a Progress instance here that is used to read the correct properties
|
||||||
owner := &userv1beta1.UserId{
|
up := upload.Progress{
|
||||||
Idp: info.Storage["Idp"],
|
Info: ev.Upload,
|
||||||
OpaqueId: info.Storage["UserId"],
|
}
|
||||||
}
|
executant := up.Executant()
|
||||||
ref := &provider.Reference{
|
ref := up.Reference()
|
||||||
ResourceId: &provider.ResourceId{
|
datatx.InvalidateCache(&executant, &ref, m.statCache)
|
||||||
StorageId: info.MetaData["providerID"],
|
if m.publisher != nil {
|
||||||
SpaceId: info.Storage["SpaceRoot"],
|
if err := datatx.EmitFileUploadedEvent(up.SpaceOwner(), &executant, &ref, m.publisher); err != nil {
|
||||||
OpaqueId: info.Storage["SpaceRoot"],
|
appctx.GetLogger(context.Background()).Error().Err(err).Msg("failed to publish FileUploaded event")
|
||||||
},
|
}
|
||||||
Path: utils.MakeRelativePath(filepath.Join(info.MetaData["dir"], info.MetaData["filename"])),
|
|
||||||
}
|
|
||||||
datatx.InvalidateCache(owner, ref, m.statCache)
|
|
||||||
if m.publisher != nil {
|
|
||||||
if err := datatx.EmitFileUploadedEvent(spaceOwner, owner, ref, m.publisher); err != nil {
|
|
||||||
appctx.GetLogger(context.Background()).Error().Err(err).Msg("failed to publish FileUploaded event")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}()
|
||||||
}()
|
}
|
||||||
|
|
||||||
h := handler.Middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
h := handler.Middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
method := r.Method
|
method := r.Method
|
||||||
|
|||||||
-18
@@ -23,16 +23,10 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
tusd "github.com/tus/tusd/pkg/handler"
|
|
||||||
|
|
||||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
|
||||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||||
registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1"
|
registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UploadFinishedFunc is a callback function used in storage drivers to indicate that an upload has finished
|
|
||||||
type UploadFinishedFunc func(spaceOwner, owner *userpb.UserId, ref *provider.Reference)
|
|
||||||
|
|
||||||
// FS is the interface to implement access to the storage.
|
// FS is the interface to implement access to the storage.
|
||||||
type FS interface {
|
type FS interface {
|
||||||
GetHome(ctx context.Context) (string, error)
|
GetHome(ctx context.Context) (string, error)
|
||||||
@@ -77,12 +71,6 @@ type FS interface {
|
|||||||
DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) error
|
DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// UploadsManager defines the interface for FS implementations that allow for managing uploads
|
|
||||||
type UploadsManager interface {
|
|
||||||
ListUploads() ([]tusd.FileInfo, error)
|
|
||||||
PurgeExpiredUploads(chan<- tusd.FileInfo) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Registry is the interface that storage registries implement
|
// Registry is the interface that storage registries implement
|
||||||
// for discovering storage providers
|
// for discovering storage providers
|
||||||
type Registry interface {
|
type Registry interface {
|
||||||
@@ -98,9 +86,3 @@ type PathWrapper interface {
|
|||||||
Unwrap(ctx context.Context, rp string) (string, error)
|
Unwrap(ctx context.Context, rp string) (string, error)
|
||||||
Wrap(ctx context.Context, rp string) (string, error)
|
Wrap(ctx context.Context, rp string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type UploadRequest struct {
|
|
||||||
Ref *provider.Reference
|
|
||||||
Body io.ReadCloser
|
|
||||||
Length int64
|
|
||||||
}
|
|
||||||
|
|||||||
+86
@@ -0,0 +1,86 @@
|
|||||||
|
// 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 storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||||
|
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||||
|
tusd "github.com/tus/tusd/pkg/handler"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UploadFinishedFunc is a callback function used in storage drivers to indicate that an upload has finished
|
||||||
|
type UploadFinishedFunc func(spaceOwner, executant *userpb.UserId, ref *provider.Reference)
|
||||||
|
|
||||||
|
// UploadRequest us used in FS.Upload() to carry required upload metadata
|
||||||
|
type UploadRequest struct {
|
||||||
|
Ref *provider.Reference
|
||||||
|
Body io.ReadCloser
|
||||||
|
Length int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadsManager defines the interface for storage drivers that allow for managing uploads
|
||||||
|
// Deprecated: No longer used. Storage drivers should implement the UploadSessionLister.
|
||||||
|
type UploadsManager interface {
|
||||||
|
ListUploads() ([]tusd.FileInfo, error)
|
||||||
|
PurgeExpiredUploads(chan<- tusd.FileInfo) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadSessionLister defines the interface for FS implementations that allow listing and purging upload sessions
|
||||||
|
type UploadSessionLister interface {
|
||||||
|
// ListUploadSessions returns the upload sessions matching the given filter
|
||||||
|
ListUploadSessions(ctx context.Context, filter UploadSessionFilter) ([]UploadSession, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadSession is the interface that storage drivers need to return whan listing upload sessions.
|
||||||
|
type UploadSession interface {
|
||||||
|
// ID returns the upload id
|
||||||
|
ID() string
|
||||||
|
// Filename returns the filename of the file
|
||||||
|
Filename() string
|
||||||
|
// Size returns the size of the upload
|
||||||
|
Size() int64
|
||||||
|
// Offset returns the current offset
|
||||||
|
Offset() int64
|
||||||
|
// Reference returns a reference for the file being uploaded. May be absolute id based or relative to e.g. a space root
|
||||||
|
Reference() provider.Reference
|
||||||
|
// Executant returns the userid of the user that created the upload
|
||||||
|
Executant() userpb.UserId
|
||||||
|
// SpaceOwner returns the owner of a space if set. optional
|
||||||
|
SpaceOwner() *userpb.UserId
|
||||||
|
// Expires returns the time when the upload can no longer be used
|
||||||
|
Expires() time.Time
|
||||||
|
|
||||||
|
// IsProcessing returns true if postprocessing has not finished, yet
|
||||||
|
// The actual postprocessing state is tracked in the postprocessing service.
|
||||||
|
IsProcessing() bool
|
||||||
|
|
||||||
|
// Purge allows completely removing an upload. Should emit a PostprocessingFinished event with a Delete outcome
|
||||||
|
Purge(ctx context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadSessionFilter can be used to filter upload sessions
|
||||||
|
type UploadSessionFilter struct {
|
||||||
|
ID *string
|
||||||
|
Processing *bool
|
||||||
|
Expired *bool
|
||||||
|
}
|
||||||
+97
-50
@@ -27,6 +27,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||||
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||||
"github.com/cs3org/reva/v2/pkg/appctx"
|
"github.com/cs3org/reva/v2/pkg/appctx"
|
||||||
@@ -35,7 +38,6 @@ import (
|
|||||||
"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes"
|
"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes"
|
||||||
"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node"
|
"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node"
|
||||||
"github.com/cs3org/reva/v2/pkg/storagespace"
|
"github.com/cs3org/reva/v2/pkg/storagespace"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Recycle items are stored inside the node folder and start with the uuid of the deleted node.
|
// Recycle items are stored inside the node folder and start with the uuid of the deleted node.
|
||||||
@@ -214,66 +216,111 @@ func readTrashLink(path string) (string, string, string, error) {
|
|||||||
|
|
||||||
func (fs *Decomposedfs) listTrashRoot(ctx context.Context, spaceID string) ([]*provider.RecycleItem, error) {
|
func (fs *Decomposedfs) listTrashRoot(ctx context.Context, spaceID string) ([]*provider.RecycleItem, error) {
|
||||||
log := appctx.GetLogger(ctx)
|
log := appctx.GetLogger(ctx)
|
||||||
items := make([]*provider.RecycleItem, 0)
|
|
||||||
|
|
||||||
trashRoot := fs.getRecycleRoot(spaceID)
|
trashRoot := fs.getRecycleRoot(spaceID)
|
||||||
matches, err := filepath.Glob(trashRoot + "/*/*/*/*/*")
|
|
||||||
|
subTrees, err := filepath.Glob(trashRoot + "/*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, itemPath := range matches {
|
numWorkers := fs.o.MaxConcurrency
|
||||||
nodePath, nodeID, timeSuffix, err := readTrashLink(itemPath)
|
if len(subTrees) < numWorkers {
|
||||||
if err != nil {
|
numWorkers = len(subTrees)
|
||||||
log.Error().Err(err).Str("trashRoot", trashRoot).Str("item", itemPath).Msg("error reading trash link, skipping")
|
}
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
md, err := os.Stat(nodePath)
|
work := make(chan string, len(subTrees))
|
||||||
if err != nil {
|
results := make(chan *provider.RecycleItem, len(subTrees))
|
||||||
log.Error().Err(err).Str("trashRoot", trashRoot).Str("item", itemPath).Str("node_path", nodePath).Msg("could not stat trash item, skipping")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
attrs, err := fs.lu.MetadataBackend().All(ctx, nodePath)
|
g, ctx := errgroup.WithContext(ctx)
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Str("trashRoot", trashRoot).Str("item", itemPath).Str("node_path", nodePath).Msg("could not get extended attributes, skipping")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeType := fs.lu.TypeFromPath(ctx, nodePath)
|
// Distribute work
|
||||||
if nodeType == provider.ResourceType_RESOURCE_TYPE_INVALID {
|
g.Go(func() error {
|
||||||
log.Error().Err(err).Str("trashRoot", trashRoot).Str("item", itemPath).Str("node_path", nodePath).Msg("invalid node type, skipping")
|
defer close(work)
|
||||||
continue
|
for _, itemPath := range subTrees {
|
||||||
}
|
select {
|
||||||
|
case work <- itemPath:
|
||||||
item := &provider.RecycleItem{
|
case <-ctx.Done():
|
||||||
Type: nodeType,
|
return ctx.Err()
|
||||||
Size: uint64(md.Size()),
|
|
||||||
Key: nodeID,
|
|
||||||
}
|
|
||||||
if deletionTime, err := time.Parse(time.RFC3339Nano, timeSuffix); err == nil {
|
|
||||||
item.DeletionTime = &types.Timestamp{
|
|
||||||
Seconds: uint64(deletionTime.Unix()),
|
|
||||||
// TODO nanos
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log.Error().Err(err).Str("trashRoot", trashRoot).Str("item", itemPath).Str("node", nodeID).Str("dtime", timeSuffix).Msg("could not parse time format, ignoring")
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
// lookup origin path in extended attributes
|
// Spawn workers that'll concurrently work the queue
|
||||||
if attr, ok := attrs[prefixes.TrashOriginAttr]; ok {
|
for i := 0; i < numWorkers; i++ {
|
||||||
item.Ref = &provider.Reference{Path: string(attr)}
|
g.Go(func() error {
|
||||||
} else {
|
for subTree := range work {
|
||||||
log.Error().Str("trashRoot", trashRoot).Str("item", itemPath).Str("node", nodeID).Str("dtime", timeSuffix).Msg("could not read origin path, skipping")
|
matches, err := filepath.Glob(subTree + "/*/*/*/*")
|
||||||
continue
|
if err != nil {
|
||||||
}
|
return err
|
||||||
// TODO filter results by permission ... on the original parent? or the trashed node?
|
}
|
||||||
// if it were on the original parent it would be possible to see files that were trashed before the current user got access
|
|
||||||
// so -> check the trash node itself
|
for _, itemPath := range matches {
|
||||||
// hmm listing trash currently lists the current users trash or the 'root' trash. from ocs only the home storage is queried for trash items.
|
nodePath, nodeID, timeSuffix, err := readTrashLink(itemPath)
|
||||||
// for now we can only really check if the current user is the owner
|
if err != nil {
|
||||||
items = append(items, item)
|
log.Error().Err(err).Str("trashRoot", trashRoot).Str("item", itemPath).Msg("error reading trash link, skipping")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
md, err := os.Stat(nodePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("trashRoot", trashRoot).Str("item", itemPath).Str("node_path", nodePath).Msg("could not stat trash item, skipping")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs, err := fs.lu.MetadataBackend().All(ctx, nodePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("trashRoot", trashRoot).Str("item", itemPath).Str("node_path", nodePath).Msg("could not get extended attributes, skipping")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeType := fs.lu.TypeFromPath(ctx, nodePath)
|
||||||
|
if nodeType == provider.ResourceType_RESOURCE_TYPE_INVALID {
|
||||||
|
log.Error().Err(err).Str("trashRoot", trashRoot).Str("item", itemPath).Str("node_path", nodePath).Msg("invalid node type, skipping")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
item := &provider.RecycleItem{
|
||||||
|
Type: nodeType,
|
||||||
|
Size: uint64(md.Size()),
|
||||||
|
Key: nodeID,
|
||||||
|
}
|
||||||
|
if deletionTime, err := time.Parse(time.RFC3339Nano, timeSuffix); err == nil {
|
||||||
|
item.DeletionTime = &types.Timestamp{
|
||||||
|
Seconds: uint64(deletionTime.Unix()),
|
||||||
|
// TODO nanos
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Error().Err(err).Str("trashRoot", trashRoot).Str("item", itemPath).Str("node", nodeID).Str("dtime", timeSuffix).Msg("could not parse time format, ignoring")
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookup origin path in extended attributes
|
||||||
|
if attr, ok := attrs[prefixes.TrashOriginAttr]; ok {
|
||||||
|
item.Ref = &provider.Reference{Path: string(attr)}
|
||||||
|
} else {
|
||||||
|
log.Error().Str("trashRoot", trashRoot).Str("item", itemPath).Str("node", nodeID).Str("dtime", timeSuffix).Msg("could not read origin path")
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case results <- item:
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for things to settle down, then close results chan
|
||||||
|
go func() {
|
||||||
|
_ = g.Wait() // error is checked later
|
||||||
|
close(results)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Collect results
|
||||||
|
items := []*provider.RecycleItem{}
|
||||||
|
for ri := range results {
|
||||||
|
items = append(items, ri)
|
||||||
}
|
}
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+66
-41
@@ -20,10 +20,10 @@ package decomposedfs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -96,14 +96,14 @@ func (fs *Decomposedfs) Upload(ctx context.Context, req storage.UploadRequest, u
|
|||||||
},
|
},
|
||||||
Path: utils.MakeRelativePath(filepath.Join(info.MetaData["dir"], info.MetaData["filename"])),
|
Path: utils.MakeRelativePath(filepath.Join(info.MetaData["dir"], info.MetaData["filename"])),
|
||||||
}
|
}
|
||||||
owner, ok := ctxpkg.ContextGetUser(uploadInfo.Ctx)
|
executant, ok := ctxpkg.ContextGetUser(uploadInfo.Ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return provider.ResourceInfo{}, errtypes.PreconditionFailed("error getting user from uploadinfo context")
|
return provider.ResourceInfo{}, errtypes.PreconditionFailed("error getting user from uploadinfo context")
|
||||||
}
|
}
|
||||||
spaceOwner := &userpb.UserId{
|
spaceOwner := &userpb.UserId{
|
||||||
OpaqueId: info.Storage["SpaceOwnerOrManager"],
|
OpaqueId: info.Storage["SpaceOwnerOrManager"],
|
||||||
}
|
}
|
||||||
uff(spaceOwner, owner.Id, uploadRef)
|
uff(spaceOwner, executant.Id, uploadRef)
|
||||||
}
|
}
|
||||||
|
|
||||||
ri := provider.ResourceInfo{
|
ri := provider.ResourceInfo{
|
||||||
@@ -243,36 +243,42 @@ func (fs *Decomposedfs) GetUpload(ctx context.Context, id string) (tusd.Upload,
|
|||||||
return upload.Get(ctx, id, fs.lu, fs.tp, fs.o.Root, fs.stream, fs.o.AsyncFileUploads, fs.o.Tokens)
|
return upload.Get(ctx, id, fs.lu, fs.tp, fs.o.Root, fs.stream, fs.o.AsyncFileUploads, fs.o.Tokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListUploads returns a list of all incomplete uploads
|
// ListUploadSessions returns the upload sessions for the given filter
|
||||||
func (fs *Decomposedfs) ListUploads() ([]tusd.FileInfo, error) {
|
func (fs *Decomposedfs) ListUploadSessions(ctx context.Context, filter storage.UploadSessionFilter) ([]storage.UploadSession, error) {
|
||||||
return fs.uploadInfos(context.Background())
|
var sessions []storage.UploadSession
|
||||||
}
|
if filter.ID != nil && *filter.ID != "" {
|
||||||
|
session, err := fs.getUploadSession(ctx, filepath.Join(fs.o.Root, "uploads", *filter.ID+".info"))
|
||||||
// PurgeExpiredUploads scans the fs for expired downloads and removes any leftovers
|
|
||||||
func (fs *Decomposedfs) PurgeExpiredUploads(purgedChan chan<- tusd.FileInfo) error {
|
|
||||||
infos, err := fs.uploadInfos(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, info := range infos {
|
|
||||||
expires, err := strconv.Atoi(info.MetaData["expires"])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sessions = []storage.UploadSession{session}
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
sessions, err = fs.uploadSessions(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filteredSessions := []storage.UploadSession{}
|
||||||
|
now := time.Now()
|
||||||
|
for _, session := range sessions {
|
||||||
|
if filter.Processing != nil && *filter.Processing != session.IsProcessing() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if int64(expires) < time.Now().Unix() {
|
if filter.Expired != nil {
|
||||||
purgedChan <- info
|
if *filter.Expired {
|
||||||
err = os.Remove(info.Storage["BinPath"])
|
if now.Before(session.Expires()) {
|
||||||
if err != nil {
|
continue
|
||||||
return err
|
}
|
||||||
}
|
} else {
|
||||||
err = os.Remove(filepath.Join(fs.o.Root, "uploads", info.ID+".info"))
|
if now.After(session.Expires()) {
|
||||||
if err != nil {
|
continue
|
||||||
return err
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
filteredSessions = append(filteredSessions, session)
|
||||||
}
|
}
|
||||||
return nil
|
return filteredSessions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AsTerminatableUpload returns a TerminatableUpload
|
// AsTerminatableUpload returns a TerminatableUpload
|
||||||
@@ -296,28 +302,47 @@ func (fs *Decomposedfs) AsConcatableUpload(up tusd.Upload) tusd.ConcatableUpload
|
|||||||
return up.(*upload.Upload)
|
return up.(*upload.Upload)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *Decomposedfs) uploadInfos(ctx context.Context) ([]tusd.FileInfo, error) {
|
func (fs *Decomposedfs) uploadSessions(ctx context.Context) ([]storage.UploadSession, error) {
|
||||||
infos := []tusd.FileInfo{}
|
uploads := []storage.UploadSession{}
|
||||||
infoFiles, err := filepath.Glob(filepath.Join(fs.o.Root, "uploads", "*.info"))
|
infoFiles, err := filepath.Glob(filepath.Join(fs.o.Root, "uploads", "*.info"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, info := range infoFiles {
|
for _, info := range infoFiles {
|
||||||
match := _idRegexp.FindStringSubmatch(info)
|
progress, err := fs.getUploadSession(ctx, info)
|
||||||
if match == nil || len(match) < 2 {
|
if err != nil {
|
||||||
|
appctx.GetLogger(ctx).Error().Interface("path", info).Msg("Decomposedfs: could not getUploadSession")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
up, err := fs.GetUpload(ctx, match[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
info, err := up.GetInfo(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
infos = append(infos, info)
|
uploads = append(uploads, progress)
|
||||||
}
|
}
|
||||||
return infos, nil
|
return uploads, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *Decomposedfs) getUploadSession(ctx context.Context, path string) (storage.UploadSession, error) {
|
||||||
|
match := _idRegexp.FindStringSubmatch(path)
|
||||||
|
if match == nil || len(match) < 2 {
|
||||||
|
return nil, fmt.Errorf("invalid upload path")
|
||||||
|
}
|
||||||
|
up, err := fs.GetUpload(ctx, match[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
info, err := up.GetInfo(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// upload processing state is stored in the node, for decomposedfs the NodeId is always set by InitiateUpload
|
||||||
|
n, err := node.ReadNode(ctx, fs.lu, info.Storage["SpaceRoot"], info.Storage["NodeId"], true, nil, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
progress := upload.Progress{
|
||||||
|
Path: path,
|
||||||
|
Info: info,
|
||||||
|
Processing: n.IsProcessing(ctx),
|
||||||
|
}
|
||||||
|
return progress, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
Vendored
+83
@@ -21,6 +21,7 @@ package upload
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
stderrors "errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
iofs "io/fs"
|
iofs "io/fs"
|
||||||
"os"
|
"os"
|
||||||
@@ -497,3 +498,85 @@ func lookupNode(ctx context.Context, spaceRoot *node.Node, path string, lu *look
|
|||||||
}
|
}
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Progress adapts the persisted upload metadata for the UploadSessionLister interface
|
||||||
|
type Progress struct {
|
||||||
|
Path string
|
||||||
|
Info tusd.FileInfo
|
||||||
|
Processing bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID implements the storage.UploadSession interface
|
||||||
|
func (p Progress) ID() string {
|
||||||
|
return p.Info.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filename implements the storage.UploadSession interface
|
||||||
|
func (p Progress) Filename() string {
|
||||||
|
return p.Info.MetaData["filename"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size implements the storage.UploadSession interface
|
||||||
|
func (p Progress) Size() int64 {
|
||||||
|
return p.Info.Size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset implements the storage.UploadSession interface
|
||||||
|
func (p Progress) Offset() int64 {
|
||||||
|
return p.Info.Offset
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reference implements the storage.UploadSession interface
|
||||||
|
func (p Progress) Reference() provider.Reference {
|
||||||
|
return provider.Reference{
|
||||||
|
ResourceId: &provider.ResourceId{
|
||||||
|
StorageId: p.Info.MetaData["providerID"],
|
||||||
|
SpaceId: p.Info.Storage["SpaceRoot"],
|
||||||
|
OpaqueId: p.Info.Storage["NodeId"], // Node id is always set in InitiateUpload
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Executant implements the storage.UploadSession interface
|
||||||
|
func (p Progress) Executant() userpb.UserId {
|
||||||
|
return userpb.UserId{
|
||||||
|
Idp: p.Info.Storage["Idp"],
|
||||||
|
OpaqueId: p.Info.Storage["UserId"],
|
||||||
|
Type: utils.UserTypeMap(p.Info.Storage["UserType"]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpaceOwner implements the storage.UploadSession interface
|
||||||
|
func (p Progress) SpaceOwner() *userpb.UserId {
|
||||||
|
return &userpb.UserId{
|
||||||
|
// idp and type do not seem to be consumed and the node currently only stores the user id anyway
|
||||||
|
OpaqueId: p.Info.Storage["SpaceOwnerOrManager"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expires implements the storage.UploadSession interface
|
||||||
|
func (p Progress) Expires() time.Time {
|
||||||
|
mt, _ := utils.MTimeToTime(p.Info.MetaData["expires"])
|
||||||
|
return mt
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsProcessing implements the storage.UploadSession interface
|
||||||
|
func (p Progress) IsProcessing() bool {
|
||||||
|
return p.Processing
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purge implements the storage.UploadSession interface
|
||||||
|
func (p Progress) Purge(ctx context.Context) error {
|
||||||
|
berr := os.Remove(p.Info.Storage["BinPath"])
|
||||||
|
if berr != nil {
|
||||||
|
appctx.GetLogger(ctx).Error().Str("id", p.Info.ID).Interface("path", p.Info.Storage["BinPath"]).Msg("Decomposedfs: could not purge bin path for upload session")
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove upload metadata
|
||||||
|
merr := os.Remove(p.Path)
|
||||||
|
if merr != nil {
|
||||||
|
appctx.GetLogger(ctx).Error().Str("id", p.Info.ID).Interface("path", p.Path).Msg("Decomposedfs: could not purge metadata path for upload session")
|
||||||
|
}
|
||||||
|
|
||||||
|
return stderrors.Join(berr, merr)
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+1
-1
@@ -357,7 +357,7 @@ github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1
|
|||||||
github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1
|
github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1
|
||||||
github.com/cs3org/go-cs3apis/cs3/tx/v1beta1
|
github.com/cs3org/go-cs3apis/cs3/tx/v1beta1
|
||||||
github.com/cs3org/go-cs3apis/cs3/types/v1beta1
|
github.com/cs3org/go-cs3apis/cs3/types/v1beta1
|
||||||
# github.com/cs3org/reva/v2 v2.16.1-0.20231128104331-ea8d1336afc9
|
# github.com/cs3org/reva/v2 v2.16.1-0.20231201122033-a389ddc645c4
|
||||||
## explicit; go 1.20
|
## explicit; go 1.20
|
||||||
github.com/cs3org/reva/v2/cmd/revad/internal/grace
|
github.com/cs3org/reva/v2/cmd/revad/internal/grace
|
||||||
github.com/cs3org/reva/v2/cmd/revad/runtime
|
github.com/cs3org/reva/v2/cmd/revad/runtime
|
||||||
|
|||||||
Reference in New Issue
Block a user