2022-08-28 11:21:57 +00:00
|
|
|
// Package upstream provides utility functionality to union.
|
2019-11-30 14:41:39 +00:00
|
|
|
package upstream
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2021-11-04 10:12:57 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2019-11-30 14:41:39 +00:00
|
|
|
"io"
|
|
|
|
"math"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"sync/atomic"
|
|
|
|
"time"
|
|
|
|
|
2022-04-03 12:39:42 +00:00
|
|
|
"github.com/rclone/rclone/backend/union/common"
|
2019-11-30 14:41:39 +00:00
|
|
|
"github.com/rclone/rclone/fs"
|
|
|
|
"github.com/rclone/rclone/fs/cache"
|
2021-02-10 14:32:44 +00:00
|
|
|
"github.com/rclone/rclone/fs/fspath"
|
2023-07-22 07:23:57 +00:00
|
|
|
"github.com/rclone/rclone/fs/operations"
|
2019-11-30 14:41:39 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// ErrUsageFieldNotSupported stats the usage field is not supported by the backend
|
|
|
|
ErrUsageFieldNotSupported = errors.New("this usage field is not supported")
|
|
|
|
)
|
|
|
|
|
|
|
|
// Fs is a wrap of any fs and its configs
|
|
|
|
type Fs struct {
|
|
|
|
fs.Fs
|
|
|
|
RootFs fs.Fs
|
|
|
|
RootPath string
|
2022-04-03 12:39:42 +00:00
|
|
|
Opt *common.Options
|
2019-11-30 14:41:39 +00:00
|
|
|
writable bool
|
|
|
|
creatable bool
|
|
|
|
usage *fs.Usage // Cache the usage
|
|
|
|
cacheTime time.Duration // cache duration
|
2023-08-20 22:20:23 +00:00
|
|
|
cacheExpiry atomic.Int64 // usage cache expiry time
|
2019-11-30 14:41:39 +00:00
|
|
|
cacheMutex sync.RWMutex
|
|
|
|
cacheOnce sync.Once
|
|
|
|
cacheUpdate bool // if the cache is updating
|
2023-07-22 07:23:57 +00:00
|
|
|
writeback bool // writeback to this upstream
|
|
|
|
writebackFs *Fs // if non zero, writeback to this upstream
|
2019-11-30 14:41:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Directory describes a wrapped Directory
|
|
|
|
//
|
|
|
|
// This is a wrapped Directory which contains the upstream Fs
|
|
|
|
type Directory struct {
|
|
|
|
fs.Directory
|
|
|
|
f *Fs
|
|
|
|
}
|
|
|
|
|
|
|
|
// Object describes a wrapped Object
|
|
|
|
//
|
|
|
|
// This is a wrapped Object which contains the upstream Fs
|
|
|
|
type Object struct {
|
|
|
|
fs.Object
|
|
|
|
f *Fs
|
|
|
|
}
|
|
|
|
|
Spelling fixes
Fix spelling of: above, already, anonymous, associated,
authentication, bandwidth, because, between, blocks, calculate,
candidates, cautious, changelog, cleaner, clipboard, command,
completely, concurrently, considered, constructs, corrupt, current,
daemon, dependencies, deprecated, directory, dispatcher, download,
eligible, ellipsis, encrypter, endpoint, entrieslist, essentially,
existing writers, existing, expires, filesystem, flushing, frequently,
hierarchy, however, implementation, implements, inaccurate,
individually, insensitive, longer, maximum, metadata, modified,
multipart, namedirfirst, nextcloud, obscured, opened, optional,
owncloud, pacific, passphrase, password, permanently, persimmon,
positive, potato, protocol, quota, receiving, recommends, referring,
requires, revisited, satisfied, satisfies, satisfy, semver,
serialized, session, storage, strategies, stringlist, successful,
supported, surprise, temporarily, temporary, transactions, unneeded,
update, uploads, wrapped
Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
2020-10-09 00:17:24 +00:00
|
|
|
// Entry describe a wrapped fs.DirEntry interface with the
|
2019-11-30 14:41:39 +00:00
|
|
|
// information of upstream Fs
|
|
|
|
type Entry interface {
|
|
|
|
fs.DirEntry
|
|
|
|
UpstreamFs() *Fs
|
|
|
|
}
|
|
|
|
|
|
|
|
// New creates a new Fs based on the
|
|
|
|
// string formatted `type:root_path(:ro/:nc)`
|
2022-04-03 12:39:42 +00:00
|
|
|
func New(ctx context.Context, remote, root string, opt *common.Options) (*Fs, error) {
|
2021-02-10 14:32:44 +00:00
|
|
|
configName, fsPath, err := fspath.SplitFs(remote)
|
2019-11-30 14:41:39 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
f := &Fs{
|
2023-08-20 22:20:23 +00:00
|
|
|
RootPath: strings.TrimRight(root, "/"),
|
|
|
|
Opt: opt,
|
|
|
|
writable: true,
|
|
|
|
creatable: true,
|
|
|
|
cacheTime: time.Duration(opt.CacheTime) * time.Second,
|
|
|
|
usage: &fs.Usage{},
|
|
|
|
}
|
|
|
|
f.cacheExpiry.Store(time.Now().Unix())
|
2019-11-30 14:41:39 +00:00
|
|
|
if strings.HasSuffix(fsPath, ":ro") {
|
|
|
|
f.writable = false
|
|
|
|
f.creatable = false
|
|
|
|
fsPath = fsPath[0 : len(fsPath)-3]
|
|
|
|
} else if strings.HasSuffix(fsPath, ":nc") {
|
|
|
|
f.writable = true
|
|
|
|
f.creatable = false
|
|
|
|
fsPath = fsPath[0 : len(fsPath)-3]
|
2023-07-22 07:23:57 +00:00
|
|
|
} else if strings.HasSuffix(fsPath, ":writeback") {
|
|
|
|
f.writeback = true
|
|
|
|
fsPath = fsPath[0 : len(fsPath)-len(":writeback")]
|
2019-11-30 14:41:39 +00:00
|
|
|
}
|
2021-02-10 14:32:44 +00:00
|
|
|
remote = configName + fsPath
|
|
|
|
rFs, err := cache.Get(ctx, remote)
|
2019-11-30 14:41:39 +00:00
|
|
|
if err != nil && err != fs.ErrorIsFile {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
f.RootFs = rFs
|
2021-11-19 17:07:57 +00:00
|
|
|
rootString := fspath.JoinRootPath(remote, root)
|
2020-11-05 15:18:51 +00:00
|
|
|
myFs, err := cache.Get(ctx, rootString)
|
2019-11-30 14:41:39 +00:00
|
|
|
if err != nil && err != fs.ErrorIsFile {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
f.Fs = myFs
|
2020-08-31 16:46:58 +00:00
|
|
|
cache.PinUntilFinalized(f.Fs, f)
|
2019-11-30 14:41:39 +00:00
|
|
|
return f, err
|
|
|
|
}
|
|
|
|
|
2023-07-22 07:23:57 +00:00
|
|
|
// Prepare the configured upstreams as a group
|
|
|
|
func Prepare(fses []*Fs) error {
|
|
|
|
writebacks := 0
|
|
|
|
var writebackFs *Fs
|
|
|
|
for _, f := range fses {
|
|
|
|
if f.writeback {
|
|
|
|
writebackFs = f
|
|
|
|
writebacks++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if writebacks == 0 {
|
|
|
|
return nil
|
|
|
|
} else if writebacks > 1 {
|
|
|
|
return fmt.Errorf("can only have 1 :writeback not %d", writebacks)
|
|
|
|
}
|
|
|
|
for _, f := range fses {
|
|
|
|
if !f.writeback {
|
|
|
|
f.writebackFs = writebackFs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-05-20 10:39:20 +00:00
|
|
|
// WrapDirectory wraps an fs.Directory to include the info
|
2019-11-30 14:41:39 +00:00
|
|
|
// of the upstream Fs
|
|
|
|
func (f *Fs) WrapDirectory(e fs.Directory) *Directory {
|
|
|
|
if e == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return &Directory{
|
|
|
|
Directory: e,
|
|
|
|
f: f,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-20 10:39:20 +00:00
|
|
|
// WrapObject wraps an fs.Object to include the info
|
2019-11-30 14:41:39 +00:00
|
|
|
// of the upstream Fs
|
|
|
|
func (f *Fs) WrapObject(o fs.Object) *Object {
|
|
|
|
if o == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return &Object{
|
|
|
|
Object: o,
|
|
|
|
f: f,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-20 10:39:20 +00:00
|
|
|
// WrapEntry wraps an fs.DirEntry to include the info
|
2019-11-30 14:41:39 +00:00
|
|
|
// of the upstream Fs
|
|
|
|
func (f *Fs) WrapEntry(e fs.DirEntry) (Entry, error) {
|
2022-06-24 14:01:19 +00:00
|
|
|
switch e := e.(type) {
|
2019-11-30 14:41:39 +00:00
|
|
|
case fs.Object:
|
2022-06-24 14:01:19 +00:00
|
|
|
return f.WrapObject(e), nil
|
2019-11-30 14:41:39 +00:00
|
|
|
case fs.Directory:
|
2022-06-24 14:01:19 +00:00
|
|
|
return f.WrapDirectory(e), nil
|
2019-11-30 14:41:39 +00:00
|
|
|
default:
|
2021-11-04 10:12:57 +00:00
|
|
|
return nil, fmt.Errorf("unknown object type %T", e)
|
2019-11-30 14:41:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpstreamFs get the upstream Fs the entry is stored in
|
|
|
|
func (e *Directory) UpstreamFs() *Fs {
|
|
|
|
return e.f
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpstreamFs get the upstream Fs the entry is stored in
|
|
|
|
func (o *Object) UpstreamFs() *Fs {
|
|
|
|
return o.f
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnWrap returns the Object that this Object is wrapping or
|
|
|
|
// nil if it isn't wrapping anything
|
|
|
|
func (o *Object) UnWrap() fs.Object {
|
|
|
|
return o.Object
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsCreatable return if the fs is allowed to create new objects
|
|
|
|
func (f *Fs) IsCreatable() bool {
|
|
|
|
return f.creatable
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsWritable return if the fs is allowed to write
|
|
|
|
func (f *Fs) IsWritable() bool {
|
|
|
|
return f.writable
|
|
|
|
}
|
|
|
|
|
|
|
|
// Put in to the remote path with the modTime given of the given size
|
|
|
|
//
|
|
|
|
// May create the object even if it returns an error - if so
|
|
|
|
// will return the object and the error, otherwise will return
|
|
|
|
// nil and the error
|
|
|
|
func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
|
|
|
|
o, err := f.Fs.Put(ctx, in, src, options...)
|
|
|
|
if err != nil {
|
|
|
|
return o, err
|
|
|
|
}
|
|
|
|
f.cacheMutex.Lock()
|
|
|
|
defer f.cacheMutex.Unlock()
|
|
|
|
size := src.Size()
|
|
|
|
if f.usage.Used != nil {
|
|
|
|
*f.usage.Used += size
|
|
|
|
}
|
|
|
|
if f.usage.Free != nil {
|
|
|
|
*f.usage.Free -= size
|
|
|
|
}
|
|
|
|
if f.usage.Objects != nil {
|
|
|
|
*f.usage.Objects++
|
|
|
|
}
|
|
|
|
return o, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// PutStream uploads to the remote path with the modTime given of indeterminate size
|
|
|
|
//
|
|
|
|
// May create the object even if it returns an error - if so
|
|
|
|
// will return the object and the error, otherwise will return
|
|
|
|
// nil and the error
|
|
|
|
func (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
|
|
|
|
do := f.Features().PutStream
|
|
|
|
if do == nil {
|
|
|
|
return nil, fs.ErrorNotImplemented
|
|
|
|
}
|
|
|
|
o, err := do(ctx, in, src, options...)
|
|
|
|
if err != nil {
|
|
|
|
return o, err
|
|
|
|
}
|
|
|
|
f.cacheMutex.Lock()
|
|
|
|
defer f.cacheMutex.Unlock()
|
|
|
|
size := o.Size()
|
|
|
|
if f.usage.Used != nil {
|
|
|
|
*f.usage.Used += size
|
|
|
|
}
|
|
|
|
if f.usage.Free != nil {
|
|
|
|
*f.usage.Free -= size
|
|
|
|
}
|
|
|
|
if f.usage.Objects != nil {
|
|
|
|
*f.usage.Objects++
|
|
|
|
}
|
|
|
|
return o, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update in to the object with the modTime given of the given size
|
|
|
|
//
|
2020-05-25 06:05:53 +00:00
|
|
|
// When called from outside an Fs by rclone, src.Size() will always be >= 0.
|
2019-11-30 14:41:39 +00:00
|
|
|
// But for unknown-sized objects (indicated by src.Size() == -1), Upload should either
|
|
|
|
// return an error or update the object properly (rather than e.g. calling panic).
|
|
|
|
func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error {
|
|
|
|
size := o.Size()
|
|
|
|
err := o.Object.Update(ctx, in, src, options...)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
o.f.cacheMutex.Lock()
|
|
|
|
defer o.f.cacheMutex.Unlock()
|
|
|
|
delta := o.Size() - size
|
|
|
|
if delta <= 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if o.f.usage.Used != nil {
|
|
|
|
*o.f.usage.Used += size
|
|
|
|
}
|
|
|
|
if o.f.usage.Free != nil {
|
|
|
|
*o.f.usage.Free -= size
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-06-29 13:29:15 +00:00
|
|
|
// GetTier returns storage tier or class of the Object
|
|
|
|
func (o *Object) GetTier() string {
|
|
|
|
do, ok := o.Object.(fs.GetTierer)
|
|
|
|
if !ok {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return do.GetTier()
|
|
|
|
}
|
|
|
|
|
|
|
|
// ID returns the ID of the Object if known, or "" if not
|
|
|
|
func (o *Object) ID() string {
|
|
|
|
do, ok := o.Object.(fs.IDer)
|
|
|
|
if !ok {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return do.ID()
|
|
|
|
}
|
|
|
|
|
|
|
|
// MimeType returns the content type of the Object if known
|
|
|
|
func (o *Object) MimeType(ctx context.Context) (mimeType string) {
|
|
|
|
if do, ok := o.Object.(fs.MimeTyper); ok {
|
|
|
|
mimeType = do.MimeType(ctx)
|
|
|
|
}
|
|
|
|
return mimeType
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetTier performs changing storage tier of the Object if
|
|
|
|
// multiple storage classes supported
|
|
|
|
func (o *Object) SetTier(tier string) error {
|
|
|
|
do, ok := o.Object.(fs.SetTierer)
|
|
|
|
if !ok {
|
|
|
|
return errors.New("underlying remote does not support SetTier")
|
|
|
|
}
|
|
|
|
return do.SetTier(tier)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Metadata returns metadata for an object
|
|
|
|
//
|
|
|
|
// It should return nil if there is no Metadata
|
|
|
|
func (o *Object) Metadata(ctx context.Context) (fs.Metadata, error) {
|
|
|
|
do, ok := o.Object.(fs.Metadataer)
|
|
|
|
if !ok {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
return do.Metadata(ctx)
|
|
|
|
}
|
|
|
|
|
2024-05-08 16:06:55 +00:00
|
|
|
// SetMetadata sets metadata for an Object
|
|
|
|
//
|
|
|
|
// It should return fs.ErrorNotImplemented if it can't set metadata
|
|
|
|
func (o *Object) SetMetadata(ctx context.Context, metadata fs.Metadata) error {
|
|
|
|
do, ok := o.Object.(fs.SetMetadataer)
|
|
|
|
if !ok {
|
|
|
|
return fs.ErrorNotImplemented
|
|
|
|
}
|
|
|
|
return do.SetMetadata(ctx, metadata)
|
|
|
|
}
|
|
|
|
|
2024-02-27 11:04:38 +00:00
|
|
|
// Metadata returns metadata for an DirEntry
|
|
|
|
//
|
|
|
|
// It should return nil if there is no Metadata
|
|
|
|
func (e *Directory) Metadata(ctx context.Context) (fs.Metadata, error) {
|
|
|
|
do, ok := e.Directory.(fs.Metadataer)
|
|
|
|
if !ok {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
return do.Metadata(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetMetadata sets metadata for an DirEntry
|
|
|
|
//
|
|
|
|
// It should return fs.ErrorNotImplemented if it can't set metadata
|
|
|
|
func (e *Directory) SetMetadata(ctx context.Context, metadata fs.Metadata) error {
|
|
|
|
do, ok := e.Directory.(fs.SetMetadataer)
|
|
|
|
if !ok {
|
|
|
|
return fs.ErrorNotImplemented
|
|
|
|
}
|
|
|
|
return do.SetMetadata(ctx, metadata)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetModTime sets the metadata on the DirEntry to set the modification date
|
|
|
|
//
|
|
|
|
// If there is any other metadata it does not overwrite it.
|
|
|
|
func (e *Directory) SetModTime(ctx context.Context, t time.Time) error {
|
|
|
|
do, ok := e.Directory.(fs.SetModTimer)
|
|
|
|
if !ok {
|
|
|
|
return fs.ErrorNotImplemented
|
|
|
|
}
|
|
|
|
return do.SetModTime(ctx, t)
|
|
|
|
}
|
|
|
|
|
2023-07-22 07:23:57 +00:00
|
|
|
// Writeback writes the object back and returns a new object
|
|
|
|
//
|
|
|
|
// If it returns nil, nil then the original object is OK
|
|
|
|
func (o *Object) Writeback(ctx context.Context) (*Object, error) {
|
|
|
|
if o.f.writebackFs == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
newObj, err := operations.Copy(ctx, o.f.writebackFs.Fs, nil, o.Object.Remote(), o.Object)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// newObj could be nil here
|
|
|
|
if newObj == nil {
|
|
|
|
fs.Errorf(o, "nil Object returned from operations.Copy")
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
return &Object{
|
|
|
|
Object: newObj,
|
|
|
|
f: o.f,
|
|
|
|
}, err
|
|
|
|
}
|
|
|
|
|
2019-11-30 14:41:39 +00:00
|
|
|
// About gets quota information from the Fs
|
|
|
|
func (f *Fs) About(ctx context.Context) (*fs.Usage, error) {
|
2023-08-20 22:20:23 +00:00
|
|
|
if f.cacheExpiry.Load() <= time.Now().Unix() {
|
2019-11-30 14:41:39 +00:00
|
|
|
err := f.updateUsage()
|
|
|
|
if err != nil {
|
|
|
|
return nil, ErrUsageFieldNotSupported
|
|
|
|
}
|
|
|
|
}
|
|
|
|
f.cacheMutex.RLock()
|
|
|
|
defer f.cacheMutex.RUnlock()
|
|
|
|
return f.usage, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetFreeSpace get the free space of the fs
|
2022-04-04 08:32:50 +00:00
|
|
|
//
|
|
|
|
// This is returned as 0..math.MaxInt64-1 leaving math.MaxInt64 as a sentinel
|
2019-11-30 14:41:39 +00:00
|
|
|
func (f *Fs) GetFreeSpace() (int64, error) {
|
2023-08-20 22:20:23 +00:00
|
|
|
if f.cacheExpiry.Load() <= time.Now().Unix() {
|
2019-11-30 14:41:39 +00:00
|
|
|
err := f.updateUsage()
|
|
|
|
if err != nil {
|
2022-04-04 08:32:50 +00:00
|
|
|
return math.MaxInt64 - 1, ErrUsageFieldNotSupported
|
2019-11-30 14:41:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
f.cacheMutex.RLock()
|
|
|
|
defer f.cacheMutex.RUnlock()
|
|
|
|
if f.usage.Free == nil {
|
2022-04-04 08:32:50 +00:00
|
|
|
return math.MaxInt64 - 1, ErrUsageFieldNotSupported
|
|
|
|
}
|
2022-06-24 13:52:30 +00:00
|
|
|
return *f.usage.Free, nil
|
2019-11-30 14:41:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetUsedSpace get the used space of the fs
|
2022-04-04 08:32:50 +00:00
|
|
|
//
|
|
|
|
// This is returned as 0..math.MaxInt64-1 leaving math.MaxInt64 as a sentinel
|
2019-11-30 14:41:39 +00:00
|
|
|
func (f *Fs) GetUsedSpace() (int64, error) {
|
2023-08-20 22:20:23 +00:00
|
|
|
if f.cacheExpiry.Load() <= time.Now().Unix() {
|
2019-11-30 14:41:39 +00:00
|
|
|
err := f.updateUsage()
|
|
|
|
if err != nil {
|
|
|
|
return 0, ErrUsageFieldNotSupported
|
|
|
|
}
|
|
|
|
}
|
|
|
|
f.cacheMutex.RLock()
|
|
|
|
defer f.cacheMutex.RUnlock()
|
|
|
|
if f.usage.Used == nil {
|
|
|
|
return 0, ErrUsageFieldNotSupported
|
|
|
|
}
|
2022-06-24 13:52:30 +00:00
|
|
|
return *f.usage.Used, nil
|
2019-11-30 14:41:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetNumObjects get the number of objects of the fs
|
|
|
|
func (f *Fs) GetNumObjects() (int64, error) {
|
2023-08-20 22:20:23 +00:00
|
|
|
if f.cacheExpiry.Load() <= time.Now().Unix() {
|
2019-11-30 14:41:39 +00:00
|
|
|
err := f.updateUsage()
|
|
|
|
if err != nil {
|
|
|
|
return 0, ErrUsageFieldNotSupported
|
|
|
|
}
|
|
|
|
}
|
|
|
|
f.cacheMutex.RLock()
|
|
|
|
defer f.cacheMutex.RUnlock()
|
|
|
|
if f.usage.Objects == nil {
|
|
|
|
return 0, ErrUsageFieldNotSupported
|
|
|
|
}
|
|
|
|
return *f.usage.Objects, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *Fs) updateUsage() (err error) {
|
|
|
|
if do := f.RootFs.Features().About; do == nil {
|
|
|
|
return ErrUsageFieldNotSupported
|
|
|
|
}
|
|
|
|
done := false
|
|
|
|
f.cacheOnce.Do(func() {
|
|
|
|
f.cacheMutex.Lock()
|
|
|
|
err = f.updateUsageCore(false)
|
|
|
|
f.cacheMutex.Unlock()
|
|
|
|
done = true
|
|
|
|
})
|
|
|
|
if done {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !f.cacheUpdate {
|
|
|
|
f.cacheUpdate = true
|
|
|
|
go func() {
|
|
|
|
_ = f.updateUsageCore(true)
|
|
|
|
f.cacheUpdate = false
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *Fs) updateUsageCore(lock bool) error {
|
|
|
|
// Run in background, should not be cancelled by user
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
|
|
defer cancel()
|
|
|
|
usage, err := f.RootFs.Features().About(ctx)
|
|
|
|
if err != nil {
|
|
|
|
f.cacheUpdate = false
|
2021-11-04 10:12:57 +00:00
|
|
|
if errors.Is(err, fs.ErrorDirNotFound) {
|
2020-08-19 17:04:16 +00:00
|
|
|
err = nil
|
|
|
|
}
|
2019-11-30 14:41:39 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if lock {
|
|
|
|
f.cacheMutex.Lock()
|
|
|
|
defer f.cacheMutex.Unlock()
|
|
|
|
}
|
|
|
|
// Store usage
|
2023-08-20 22:20:23 +00:00
|
|
|
f.cacheExpiry.Store(time.Now().Add(f.cacheTime).Unix())
|
2019-11-30 14:41:39 +00:00
|
|
|
f.usage = usage
|
|
|
|
return nil
|
|
|
|
}
|
2022-06-29 13:29:15 +00:00
|
|
|
|
|
|
|
// Check the interfaces are satisfied
|
|
|
|
var (
|
2024-02-27 11:04:38 +00:00
|
|
|
_ fs.FullObject = (*Object)(nil)
|
|
|
|
_ fs.FullDirectory = (*Directory)(nil)
|
2022-06-29 13:29:15 +00:00
|
|
|
)
|