mirror of
https://github.com/rclone/rclone.git
synced 2024-12-22 15:11:56 +01:00
about: complete other providers and re-work internals
* Implement about for: * local, crypt, cache, drive, swift, hubic, onedrive, pcloud, dropbox * Implement `--json` and `---full` flag for `rclone about` * change About interface to return a Usage structure * Remove operations.About as it is too thin an interface * Implement Integration test Relates to #1138 and #1564
This commit is contained in:
parent
94e277d759
commit
1ac6dacf0f
7
backend/cache/cache.go
vendored
7
backend/cache/cache.go
vendored
@ -1414,14 +1414,11 @@ func (f *Fs) CleanUp() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// About gets quota information from the Fs
|
// About gets quota information from the Fs
|
||||||
func (f *Fs) About() error {
|
func (f *Fs) About() (*fs.Usage, error) {
|
||||||
f.CleanUpCache(false)
|
|
||||||
|
|
||||||
do := f.Fs.Features().About
|
do := f.Fs.Features().About
|
||||||
if do == nil {
|
if do == nil {
|
||||||
return nil
|
return nil, errors.New("About not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
return do()
|
return do()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -452,6 +452,15 @@ func (f *Fs) CleanUp() error {
|
|||||||
return do()
|
return do()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// About gets quota information from the Fs
|
||||||
|
func (f *Fs) About() (*fs.Usage, error) {
|
||||||
|
do := f.Fs.Features().About
|
||||||
|
if do == nil {
|
||||||
|
return nil, errors.New("About not supported")
|
||||||
|
}
|
||||||
|
return do()
|
||||||
|
}
|
||||||
|
|
||||||
// UnWrap returns the Fs that this Fs is wrapping
|
// UnWrap returns the Fs that this Fs is wrapping
|
||||||
func (f *Fs) UnWrap() fs.Fs {
|
func (f *Fs) UnWrap() fs.Fs {
|
||||||
return f.Fs
|
return f.Fs
|
||||||
@ -699,6 +708,7 @@ var (
|
|||||||
_ fs.CleanUpper = (*Fs)(nil)
|
_ fs.CleanUpper = (*Fs)(nil)
|
||||||
_ fs.UnWrapper = (*Fs)(nil)
|
_ fs.UnWrapper = (*Fs)(nil)
|
||||||
_ fs.ListRer = (*Fs)(nil)
|
_ fs.ListRer = (*Fs)(nil)
|
||||||
|
_ fs.Abouter = (*Fs)(nil)
|
||||||
_ fs.ObjectInfo = (*ObjectInfo)(nil)
|
_ fs.ObjectInfo = (*ObjectInfo)(nil)
|
||||||
_ fs.Object = (*Object)(nil)
|
_ fs.Object = (*Object)(nil)
|
||||||
_ fs.ObjectUnWrapper = (*Object)(nil)
|
_ fs.ObjectUnWrapper = (*Object)(nil)
|
||||||
|
@ -1051,7 +1051,7 @@ func (f *Fs) CleanUp() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// About gets quota information
|
// About gets quota information
|
||||||
func (f *Fs) About() error {
|
func (f *Fs) About() (*fs.Usage, error) {
|
||||||
var about *drive.About
|
var about *drive.About
|
||||||
var err error
|
var err error
|
||||||
err = f.pacer.Call(func() (bool, error) {
|
err = f.pacer.Call(func() (bool, error) {
|
||||||
@ -1059,18 +1059,19 @@ func (f *Fs) About() error {
|
|||||||
return shouldRetry(err)
|
return shouldRetry(err)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fs.Errorf(f, "Failed to get Drive storageQuota: %v", err)
|
return nil, errors.Wrap(err, "failed to get Drive storageQuota")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
quota := float64(about.StorageQuota.Limit) / (1 << 30)
|
q := about.StorageQuota
|
||||||
usagetotal := float64(about.StorageQuota.Usage) / (1 << 30)
|
usage := &fs.Usage{
|
||||||
usagedrive := float64(about.StorageQuota.UsageInDrive) / (1 << 30)
|
Used: fs.NewUsageValue(q.UsageInDrive), // bytes in use
|
||||||
usagetrash := float64(about.StorageQuota.UsageInDriveTrash) / (1 << 30)
|
Trashed: fs.NewUsageValue(q.UsageInDriveTrash), // bytes in trash
|
||||||
fmt.Printf("Quota: %.0f GiB | Used: %.1f GiB (Trash: %.1f GiB) | Available: %.1f GiB | Usage: %d%%\n",
|
Other: fs.NewUsageValue(q.Usage - q.UsageInDrive), // other usage eg gmail in drive
|
||||||
quota, usagedrive, usagetrash, quota-usagedrive, int((usagedrive/quota)*100))
|
}
|
||||||
fmt.Printf("Space used in other Google services (such as Gmail): %.2f GiB\n",
|
if q.Limit > 0 {
|
||||||
usagetotal-usagedrive)
|
usage.Total = fs.NewUsageValue(q.Limit) // quota of bytes that can be used
|
||||||
return nil
|
usage.Free = fs.NewUsageValue(q.Limit - q.Usage) // bytes which can be uploaded before reaching the quota
|
||||||
|
}
|
||||||
|
return usage, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move src to this remote using server side move operations.
|
// Move src to this remote using server side move operations.
|
||||||
@ -1664,6 +1665,7 @@ var (
|
|||||||
_ fs.PutUncheckeder = (*Fs)(nil)
|
_ fs.PutUncheckeder = (*Fs)(nil)
|
||||||
_ fs.PublicLinker = (*Fs)(nil)
|
_ fs.PublicLinker = (*Fs)(nil)
|
||||||
_ fs.MergeDirser = (*Fs)(nil)
|
_ fs.MergeDirser = (*Fs)(nil)
|
||||||
|
_ fs.Abouter = (*Fs)(nil)
|
||||||
_ fs.Object = (*Object)(nil)
|
_ fs.Object = (*Object)(nil)
|
||||||
_ fs.MimeTyper = &Object{}
|
_ fs.MimeTyper = (*Object)(nil)
|
||||||
)
|
)
|
||||||
|
@ -33,6 +33,7 @@ import (
|
|||||||
"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
|
"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
|
||||||
"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/files"
|
"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/files"
|
||||||
"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/sharing"
|
"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/sharing"
|
||||||
|
"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/users"
|
||||||
"github.com/ncw/rclone/fs"
|
"github.com/ncw/rclone/fs"
|
||||||
"github.com/ncw/rclone/fs/config"
|
"github.com/ncw/rclone/fs/config"
|
||||||
"github.com/ncw/rclone/fs/config/flags"
|
"github.com/ncw/rclone/fs/config/flags"
|
||||||
@ -128,6 +129,7 @@ type Fs struct {
|
|||||||
features *fs.Features // optional features
|
features *fs.Features // optional features
|
||||||
srv files.Client // the connection to the dropbox server
|
srv files.Client // the connection to the dropbox server
|
||||||
sharingClient sharing.Client // as above, but for generating sharing links
|
sharingClient sharing.Client // as above, but for generating sharing links
|
||||||
|
users users.Client // as above, but for accessing user information
|
||||||
slashRoot string // root with "/" prefix, lowercase
|
slashRoot string // root with "/" prefix, lowercase
|
||||||
slashRootSlash string // root with "/" prefix and postfix, lowercase
|
slashRootSlash string // root with "/" prefix and postfix, lowercase
|
||||||
pacer *pacer.Pacer // To pace the API calls
|
pacer *pacer.Pacer // To pace the API calls
|
||||||
@ -209,11 +211,13 @@ func NewFs(name, root string) (fs.Fs, error) {
|
|||||||
}
|
}
|
||||||
srv := files.New(config)
|
srv := files.New(config)
|
||||||
sharingClient := sharing.New(config)
|
sharingClient := sharing.New(config)
|
||||||
|
users := users.New(config)
|
||||||
|
|
||||||
f := &Fs{
|
f := &Fs{
|
||||||
name: name,
|
name: name,
|
||||||
srv: srv,
|
srv: srv,
|
||||||
sharingClient: sharingClient,
|
sharingClient: sharingClient,
|
||||||
|
users: users,
|
||||||
pacer: pacer.New().SetMinSleep(minSleep).SetMaxSleep(maxSleep).SetDecayConstant(decayConstant),
|
pacer: pacer.New().SetMinSleep(minSleep).SetMaxSleep(maxSleep).SetDecayConstant(decayConstant),
|
||||||
}
|
}
|
||||||
f.features = (&fs.Features{
|
f.features = (&fs.Features{
|
||||||
@ -727,6 +731,33 @@ func (f *Fs) DirMove(src fs.Fs, srcRemote, dstRemote string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// About gets quota information
|
||||||
|
func (f *Fs) About() (usage *fs.Usage, err error) {
|
||||||
|
var q *users.SpaceUsage
|
||||||
|
err = f.pacer.Call(func() (bool, error) {
|
||||||
|
q, err = f.users.GetSpaceUsage()
|
||||||
|
return shouldRetry(err)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "about failed")
|
||||||
|
}
|
||||||
|
var total uint64
|
||||||
|
if q.Allocation != nil {
|
||||||
|
if q.Allocation.Individual != nil {
|
||||||
|
total += q.Allocation.Individual.Allocated
|
||||||
|
}
|
||||||
|
if q.Allocation.Team != nil {
|
||||||
|
total += q.Allocation.Team.Allocated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
usage = &fs.Usage{
|
||||||
|
Total: fs.NewUsageValue(int64(total)), // quota of bytes that can be used
|
||||||
|
Used: fs.NewUsageValue(int64(q.Used)), // bytes in use
|
||||||
|
Free: fs.NewUsageValue(int64(total - q.Used)), // bytes which can be uploaded before reaching the quota
|
||||||
|
}
|
||||||
|
return usage, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Hashes returns the supported hash sets.
|
// Hashes returns the supported hash sets.
|
||||||
func (f *Fs) Hashes() hash.Set {
|
func (f *Fs) Hashes() hash.Set {
|
||||||
return hash.Set(hash.Dropbox)
|
return hash.Set(hash.Dropbox)
|
||||||
@ -1012,5 +1043,6 @@ var (
|
|||||||
_ fs.Mover = (*Fs)(nil)
|
_ fs.Mover = (*Fs)(nil)
|
||||||
_ fs.PublicLinker = (*Fs)(nil)
|
_ fs.PublicLinker = (*Fs)(nil)
|
||||||
_ fs.DirMover = (*Fs)(nil)
|
_ fs.DirMover = (*Fs)(nil)
|
||||||
|
_ fs.Abouter = (*Fs)(nil)
|
||||||
_ fs.Object = (*Object)(nil)
|
_ fs.Object = (*Object)(nil)
|
||||||
)
|
)
|
||||||
|
29
backend/local/about_unix.go
Normal file
29
backend/local/about_unix.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// +build darwin dragonfly freebsd linux
|
||||||
|
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/ncw/rclone/fs"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// About gets quota information
|
||||||
|
func (f *Fs) About() (*fs.Usage, error) {
|
||||||
|
var s syscall.Statfs_t
|
||||||
|
err := syscall.Statfs(f.root, &s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to read disk usage")
|
||||||
|
}
|
||||||
|
bs := int64(s.Bsize)
|
||||||
|
usage := &fs.Usage{
|
||||||
|
Total: fs.NewUsageValue(bs * int64(s.Blocks)), // quota of bytes that can be used
|
||||||
|
Used: fs.NewUsageValue(bs * int64(s.Blocks-s.Bfree)), // bytes in use
|
||||||
|
Free: fs.NewUsageValue(bs * int64(s.Bavail)), // bytes which can be uploaded before reaching the quota
|
||||||
|
}
|
||||||
|
return usage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check interface
|
||||||
|
var _ fs.Abouter = &Fs{}
|
36
backend/local/about_windows.go
Normal file
36
backend/local/about_windows.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/ncw/rclone/fs"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var getFreeDiskSpace = syscall.NewLazyDLL("kernel32.dll").NewProc("GetDiskFreeSpaceExW")
|
||||||
|
|
||||||
|
// About gets quota information
|
||||||
|
func (f *Fs) About() (*fs.Usage, error) {
|
||||||
|
var available, total, free int64
|
||||||
|
_, _, e1 := getFreeDiskSpace.Call(
|
||||||
|
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(f.root))),
|
||||||
|
uintptr(unsafe.Pointer(&available)), // lpFreeBytesAvailable - for this user
|
||||||
|
uintptr(unsafe.Pointer(&total)), // lpTotalNumberOfBytes
|
||||||
|
uintptr(unsafe.Pointer(&free)), // lpTotalNumberOfFreeBytes
|
||||||
|
)
|
||||||
|
if e1 != syscall.Errno(0) {
|
||||||
|
return nil, errors.Wrap(e1, "failed to read disk usage")
|
||||||
|
}
|
||||||
|
usage := &fs.Usage{
|
||||||
|
Total: fs.NewUsageValue(total), // quota of bytes that can be used
|
||||||
|
Used: fs.NewUsageValue(total - free), // bytes in use
|
||||||
|
Free: fs.NewUsageValue(available), // bytes which can be uploaded before reaching the quota
|
||||||
|
}
|
||||||
|
return usage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check interface
|
||||||
|
var _ fs.Abouter = &Fs{}
|
@ -50,10 +50,10 @@ type IdentitySet struct {
|
|||||||
|
|
||||||
// Quota groups storage space quota-related information on OneDrive into a single structure.
|
// Quota groups storage space quota-related information on OneDrive into a single structure.
|
||||||
type Quota struct {
|
type Quota struct {
|
||||||
Total int `json:"total"`
|
Total int64 `json:"total"`
|
||||||
Used int `json:"used"`
|
Used int64 `json:"used"`
|
||||||
Remaining int `json:"remaining"`
|
Remaining int64 `json:"remaining"`
|
||||||
Deleted int `json:"deleted"`
|
Deleted int64 `json:"deleted"`
|
||||||
State string `json:"state"` // normal | nearing | critical | exceeded
|
State string `json:"state"` // normal | nearing | critical | exceeded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -922,6 +922,31 @@ func (f *Fs) DirCacheFlush() {
|
|||||||
f.dirCache.ResetRoot()
|
f.dirCache.ResetRoot()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// About gets quota information
|
||||||
|
func (f *Fs) About() (usage *fs.Usage, err error) {
|
||||||
|
var drive api.Drive
|
||||||
|
opts := rest.Opts{
|
||||||
|
Method: "GET",
|
||||||
|
Path: "",
|
||||||
|
}
|
||||||
|
var resp *http.Response
|
||||||
|
err = f.pacer.Call(func() (bool, error) {
|
||||||
|
resp, err = f.srv.CallJSON(&opts, nil, &drive)
|
||||||
|
return shouldRetry(resp, err)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "about failed")
|
||||||
|
}
|
||||||
|
q := drive.Quota
|
||||||
|
usage = &fs.Usage{
|
||||||
|
Total: fs.NewUsageValue(q.Total), // quota of bytes that can be used
|
||||||
|
Used: fs.NewUsageValue(q.Used), // bytes in use
|
||||||
|
Trashed: fs.NewUsageValue(q.Deleted), // bytes in trash
|
||||||
|
Free: fs.NewUsageValue(q.Remaining), // bytes which can be uploaded before reaching the quota
|
||||||
|
}
|
||||||
|
return usage, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Hashes returns the supported hash sets.
|
// Hashes returns the supported hash sets.
|
||||||
func (f *Fs) Hashes() hash.Set {
|
func (f *Fs) Hashes() hash.Set {
|
||||||
return hash.Set(hash.SHA1)
|
return hash.Set(hash.SHA1)
|
||||||
@ -1273,6 +1298,7 @@ var (
|
|||||||
_ fs.Mover = (*Fs)(nil)
|
_ fs.Mover = (*Fs)(nil)
|
||||||
// _ fs.DirMover = (*Fs)(nil)
|
// _ fs.DirMover = (*Fs)(nil)
|
||||||
_ fs.DirCacheFlusher = (*Fs)(nil)
|
_ fs.DirCacheFlusher = (*Fs)(nil)
|
||||||
|
_ fs.Abouter = (*Fs)(nil)
|
||||||
_ fs.Object = (*Object)(nil)
|
_ fs.Object = (*Object)(nil)
|
||||||
_ fs.MimeTyper = &Object{}
|
_ fs.MimeTyper = &Object{}
|
||||||
)
|
)
|
||||||
|
@ -151,3 +151,35 @@ type ChecksumFileResult struct {
|
|||||||
Hashes
|
Hashes
|
||||||
Metadata Item `json:"metadata"`
|
Metadata Item `json:"metadata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserInfo is returned from /userinfo
|
||||||
|
type UserInfo struct {
|
||||||
|
Error
|
||||||
|
Cryptosetup bool `json:"cryptosetup"`
|
||||||
|
Plan int `json:"plan"`
|
||||||
|
CryptoSubscription bool `json:"cryptosubscription"`
|
||||||
|
PublicLinkQuota int64 `json:"publiclinkquota"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
UserID int `json:"userid"`
|
||||||
|
Result int `json:"result"`
|
||||||
|
Quota int64 `json:"quota"`
|
||||||
|
TrashRevretentionDays int `json:"trashrevretentiondays"`
|
||||||
|
Premium bool `json:"premium"`
|
||||||
|
PremiumLifetime bool `json:"premiumlifetime"`
|
||||||
|
EmailVerified bool `json:"emailverified"`
|
||||||
|
UsedQuota int64 `json:"usedquota"`
|
||||||
|
Language string `json:"language"`
|
||||||
|
Business bool `json:"business"`
|
||||||
|
CryptoLifetime bool `json:"cryptolifetime"`
|
||||||
|
Registered string `json:"registered"`
|
||||||
|
Journey struct {
|
||||||
|
Claimed bool `json:"claimed"`
|
||||||
|
Steps struct {
|
||||||
|
VerifyMail bool `json:"verifymail"`
|
||||||
|
UploadFile bool `json:"uploadfile"`
|
||||||
|
AutoUpload bool `json:"autoupload"`
|
||||||
|
DownloadApp bool `json:"downloadapp"`
|
||||||
|
DownloadDrive bool `json:"downloaddrive"`
|
||||||
|
} `json:"steps"`
|
||||||
|
} `json:"journey"`
|
||||||
|
}
|
||||||
|
@ -806,6 +806,30 @@ func (f *Fs) DirCacheFlush() {
|
|||||||
f.dirCache.ResetRoot()
|
f.dirCache.ResetRoot()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// About gets quota information
|
||||||
|
func (f *Fs) About() (usage *fs.Usage, err error) {
|
||||||
|
opts := rest.Opts{
|
||||||
|
Method: "POST",
|
||||||
|
Path: "/userinfo",
|
||||||
|
}
|
||||||
|
var resp *http.Response
|
||||||
|
var q api.UserInfo
|
||||||
|
err = f.pacer.Call(func() (bool, error) {
|
||||||
|
resp, err = f.srv.CallJSON(&opts, nil, &q)
|
||||||
|
err = q.Error.Update(err)
|
||||||
|
return shouldRetry(resp, err)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "about failed")
|
||||||
|
}
|
||||||
|
usage = &fs.Usage{
|
||||||
|
Total: fs.NewUsageValue(q.Quota), // quota of bytes that can be used
|
||||||
|
Used: fs.NewUsageValue(q.UsedQuota), // bytes in use
|
||||||
|
Free: fs.NewUsageValue(q.Quota - q.UsedQuota), // bytes which can be uploaded before reaching the quota
|
||||||
|
}
|
||||||
|
return usage, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Hashes returns the supported hash sets.
|
// Hashes returns the supported hash sets.
|
||||||
func (f *Fs) Hashes() hash.Set {
|
func (f *Fs) Hashes() hash.Set {
|
||||||
return hash.Set(hash.MD5 | hash.SHA1)
|
return hash.Set(hash.MD5 | hash.SHA1)
|
||||||
@ -1107,5 +1131,6 @@ var (
|
|||||||
_ fs.Mover = (*Fs)(nil)
|
_ fs.Mover = (*Fs)(nil)
|
||||||
_ fs.DirMover = (*Fs)(nil)
|
_ fs.DirMover = (*Fs)(nil)
|
||||||
_ fs.DirCacheFlusher = (*Fs)(nil)
|
_ fs.DirCacheFlusher = (*Fs)(nil)
|
||||||
|
_ fs.Abouter = (*Fs)(nil)
|
||||||
_ fs.Object = (*Object)(nil)
|
_ fs.Object = (*Object)(nil)
|
||||||
)
|
)
|
||||||
|
@ -498,6 +498,24 @@ func (f *Fs) ListR(dir string, callback fs.ListRCallback) (err error) {
|
|||||||
return list.Flush()
|
return list.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// About gets quota information
|
||||||
|
func (f *Fs) About() (*fs.Usage, error) {
|
||||||
|
containers, err := f.c.ContainersAll(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "container listing failed")
|
||||||
|
}
|
||||||
|
var total, objects int64
|
||||||
|
for _, c := range containers {
|
||||||
|
total += c.Bytes
|
||||||
|
objects += c.Count
|
||||||
|
}
|
||||||
|
usage := &fs.Usage{
|
||||||
|
Used: fs.NewUsageValue(total), // bytes in use
|
||||||
|
Objects: fs.NewUsageValue(objects), // objects in use
|
||||||
|
}
|
||||||
|
return usage, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Put the object into the container
|
// Put the object into the container
|
||||||
//
|
//
|
||||||
// Copy the reader in to the new object which is returned
|
// Copy the reader in to the new object which is returned
|
||||||
|
@ -1,27 +1,112 @@
|
|||||||
package about
|
package about
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/ncw/rclone/cmd"
|
"github.com/ncw/rclone/cmd"
|
||||||
"github.com/ncw/rclone/fs/operations"
|
"github.com/ncw/rclone/fs"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
jsonOutput bool
|
||||||
|
fullOutput bool
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cmd.Root.AddCommand(commandDefintion)
|
cmd.Root.AddCommand(commandDefinition)
|
||||||
|
commandDefinition.Flags().BoolVar(&jsonOutput, "json", false, "Format output as JSON")
|
||||||
|
commandDefinition.Flags().BoolVar(&fullOutput, "full", false, "Full numbers instead of SI units")
|
||||||
}
|
}
|
||||||
|
|
||||||
var commandDefintion = &cobra.Command{
|
// printValue formats uv to be output
|
||||||
|
func printValue(what string, uv *int64) {
|
||||||
|
what += ":"
|
||||||
|
if uv == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var val string
|
||||||
|
if fullOutput {
|
||||||
|
val = fmt.Sprintf("%d", *uv)
|
||||||
|
} else {
|
||||||
|
val = fs.SizeSuffix(*uv).String()
|
||||||
|
}
|
||||||
|
fmt.Printf("%-9s%v\n", what, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandDefinition = &cobra.Command{
|
||||||
Use: "about remote:",
|
Use: "about remote:",
|
||||||
Short: `Get quota information from the remote.`,
|
Short: `Get quota information from the remote.`,
|
||||||
Long: `
|
Long: `
|
||||||
Get quota information from the remote, like bytes used/free/quota and bytes
|
Get quota information from the remote, like bytes used/free/quota and bytes
|
||||||
used in the trash. Not supported by all remotes.
|
used in the trash. Not supported by all remotes.
|
||||||
|
|
||||||
|
This will print to stdout something like this:
|
||||||
|
|
||||||
|
Total: 17G
|
||||||
|
Used: 7.444G
|
||||||
|
Free: 1.315G
|
||||||
|
Trashed: 100.000M
|
||||||
|
Other: 8.241G
|
||||||
|
|
||||||
|
Where the fields are:
|
||||||
|
|
||||||
|
* Total: total size available.
|
||||||
|
* Used: total size used
|
||||||
|
* Free: total amount this user could upload.
|
||||||
|
* Trashed: total amount in the trash
|
||||||
|
* Other: total amount in other storage (eg Gmail, Google Photos)
|
||||||
|
* Objects: total number of objects in the storage
|
||||||
|
|
||||||
|
Note that not all the backends provide all the fields - they will be
|
||||||
|
missing if they are not known for that backend. Where it is known
|
||||||
|
that the value is unlimited the value will also be omitted.
|
||||||
|
|
||||||
|
Use the --full flag to see the numbers written out in full, eg
|
||||||
|
|
||||||
|
Total: 18253611008
|
||||||
|
Used: 7993453766
|
||||||
|
Free: 1411001220
|
||||||
|
Trashed: 104857602
|
||||||
|
Other: 8849156022
|
||||||
|
|
||||||
|
Use the --json flag for a computer readable output, eg
|
||||||
|
|
||||||
|
{
|
||||||
|
"total": 18253611008,
|
||||||
|
"used": 7993453766,
|
||||||
|
"trashed": 104857602,
|
||||||
|
"other": 8849156022,
|
||||||
|
"free": 1411001220
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
Run: func(command *cobra.Command, args []string) {
|
Run: func(command *cobra.Command, args []string) {
|
||||||
cmd.CheckArgs(1, 1, command, args)
|
cmd.CheckArgs(1, 1, command, args)
|
||||||
fsrc := cmd.NewFsSrc(args)
|
f := cmd.NewFsSrc(args)
|
||||||
cmd.Run(true, false, command, func() error {
|
cmd.Run(true, false, command, func() error {
|
||||||
return operations.About(fsrc)
|
doAbout := f.Features().About
|
||||||
|
if doAbout == nil {
|
||||||
|
return errors.Errorf("%v doesn't support about", f)
|
||||||
|
}
|
||||||
|
u, err := doAbout()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "About call failed")
|
||||||
|
}
|
||||||
|
if jsonOutput {
|
||||||
|
out := json.NewEncoder(os.Stdout)
|
||||||
|
out.SetIndent("", "\t")
|
||||||
|
return out.Encode(u)
|
||||||
|
}
|
||||||
|
printValue("Total", u.Total)
|
||||||
|
printValue("Used", u.Used)
|
||||||
|
printValue("Free", u.Free)
|
||||||
|
printValue("Trashed", u.Trashed)
|
||||||
|
printValue("Other", u.Other)
|
||||||
|
printValue("Objects", u.Objects)
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -125,21 +125,21 @@ operations more efficient.
|
|||||||
| Amazon S3 | No | Yes | No | No | No | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
| Amazon S3 | No | Yes | No | No | No | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
||||||
| Backblaze B2 | No | No | No | No | Yes | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
| Backblaze B2 | No | No | No | No | Yes | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
||||||
| Box | Yes | Yes | Yes | Yes | No [#575](https://github.com/ncw/rclone/issues/575) | No | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
| Box | Yes | Yes | Yes | Yes | No [#575](https://github.com/ncw/rclone/issues/575) | No | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
||||||
| Dropbox | Yes | Yes | Yes | Yes | No [#575](https://github.com/ncw/rclone/issues/575) | No | Yes | Yes | No |
|
| Dropbox | Yes | Yes | Yes | Yes | No [#575](https://github.com/ncw/rclone/issues/575) | No | Yes | Yes | Yes |
|
||||||
| FTP | No | No | Yes | Yes | No | No | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
| FTP | No | No | Yes | Yes | No | No | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
||||||
| Google Cloud Storage | Yes | Yes | No | No | No | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
| Google Cloud Storage | Yes | Yes | No | No | No | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
||||||
| Google Drive | Yes | Yes | Yes | Yes | Yes | No | Yes | Yes | Yes |
|
| Google Drive | Yes | Yes | Yes | Yes | Yes | No | Yes | Yes | Yes |
|
||||||
| HTTP | No | No | No | No | No | No | No | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
| HTTP | No | No | No | No | No | No | No | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
||||||
| Hubic | Yes † | Yes | No | No | No | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
| Hubic | Yes † | Yes | No | No | No | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | Yes |
|
||||||
| Microsoft Azure Blob Storage | Yes | Yes | No | No | No | Yes | No | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
| Microsoft Azure Blob Storage | Yes | Yes | No | No | No | Yes | No | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
||||||
| Microsoft OneDrive | Yes | Yes | Yes | No [#197](https://github.com/ncw/rclone/issues/197) | No [#575](https://github.com/ncw/rclone/issues/575) | No | No | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
| Microsoft OneDrive | Yes | Yes | Yes | No [#197](https://github.com/ncw/rclone/issues/197) | No [#575](https://github.com/ncw/rclone/issues/575) | No | No | No [#2178](https://github.com/ncw/rclone/issues/2178) | Yes |
|
||||||
| Openstack Swift | Yes † | Yes | No | No | No | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
| Openstack Swift | Yes † | Yes | No | No | No | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | Yes |
|
||||||
| pCloud | Yes | Yes | Yes | Yes | Yes | No | No | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
| pCloud | Yes | Yes | Yes | Yes | Yes | No | No | No [#2178](https://github.com/ncw/rclone/issues/2178) | Yes |
|
||||||
| QingStor | No | Yes | No | No | No | Yes | No | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
| QingStor | No | Yes | No | No | No | Yes | No | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
||||||
| SFTP | No | No | Yes | Yes | No | No | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
| SFTP | No | No | Yes | Yes | No | No | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
||||||
| WebDAV | Yes | Yes | Yes | Yes | No | No | Yes ‡ | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
| WebDAV | Yes | Yes | Yes | Yes | No | No | Yes ‡ | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
||||||
| Yandex Disk | Yes | No | No | No | Yes | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
| Yandex Disk | Yes | No | No | No | Yes | Yes | Yes | No [#2178](https://github.com/ncw/rclone/issues/2178) | No |
|
||||||
| The local filesystem | Yes | No | Yes | Yes | No | No | Yes | No | No |
|
| The local filesystem | Yes | No | Yes | Yes | No | No | Yes | No | Yes |
|
||||||
|
|
||||||
### Purge ###
|
### Purge ###
|
||||||
|
|
||||||
|
25
fs/fs.go
25
fs/fs.go
@ -262,6 +262,25 @@ type ListRCallback func(entries DirEntries) error
|
|||||||
// ListRFn is defines the call used to recursively list a directory
|
// ListRFn is defines the call used to recursively list a directory
|
||||||
type ListRFn func(dir string, callback ListRCallback) error
|
type ListRFn func(dir string, callback ListRCallback) error
|
||||||
|
|
||||||
|
// NewUsageValue makes a valid value
|
||||||
|
func NewUsageValue(value int64) *int64 {
|
||||||
|
p := new(int64)
|
||||||
|
*p = value
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage is returned by the About call
|
||||||
|
//
|
||||||
|
// If a value is nil then it isn't supported by that backend
|
||||||
|
type Usage struct {
|
||||||
|
Total *int64 `json:"total,omitempty"` // quota of bytes that can be used
|
||||||
|
Used *int64 `json:"used,omitempty"` // bytes in use
|
||||||
|
Trashed *int64 `json:"trashed,omitempty"` // bytes in trash
|
||||||
|
Other *int64 `json:"other,omitempty"` // other usage eg gmail in drive
|
||||||
|
Free *int64 `json:"free,omitempty"` // bytes which can be uploaded before reaching the quota
|
||||||
|
Objects *int64 `json:"objects,omitempty"` // objects in the storage system
|
||||||
|
}
|
||||||
|
|
||||||
// Features describe the optional features of the Fs
|
// Features describe the optional features of the Fs
|
||||||
type Features struct {
|
type Features struct {
|
||||||
// Feature flags, whether Fs
|
// Feature flags, whether Fs
|
||||||
@ -378,8 +397,8 @@ type Features struct {
|
|||||||
// of listing recursively that doing a directory traversal.
|
// of listing recursively that doing a directory traversal.
|
||||||
ListR ListRFn
|
ListR ListRFn
|
||||||
|
|
||||||
// Get quota information from the Fs
|
// About gets quota information from the Fs
|
||||||
About func() error
|
About func() (*Usage, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable nil's out the named feature. If it isn't found then it
|
// Disable nil's out the named feature. If it isn't found then it
|
||||||
@ -718,7 +737,7 @@ type RangeSeeker interface {
|
|||||||
// Abouter is an optional interface for Fs
|
// Abouter is an optional interface for Fs
|
||||||
type Abouter interface {
|
type Abouter interface {
|
||||||
// About gets quota information from the Fs
|
// About gets quota information from the Fs
|
||||||
About() error
|
About() (*Usage, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObjectsChan is a channel of Objects
|
// ObjectsChan is a channel of Objects
|
||||||
|
@ -1049,19 +1049,6 @@ func CleanUp(f fs.Fs) error {
|
|||||||
return doCleanUp()
|
return doCleanUp()
|
||||||
}
|
}
|
||||||
|
|
||||||
// About gets quota information from the remote
|
|
||||||
func About(f fs.Fs) error {
|
|
||||||
doAbout := f.Features().About
|
|
||||||
if doAbout == nil {
|
|
||||||
return errors.Errorf("%v doesn't support about", f)
|
|
||||||
}
|
|
||||||
if fs.Config.DryRun {
|
|
||||||
fs.Logf(f, "Not running about as --dry-run set")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return doAbout()
|
|
||||||
}
|
|
||||||
|
|
||||||
// wrap a Reader and a Closer together into a ReadCloser
|
// wrap a Reader and a Closer together into a ReadCloser
|
||||||
type readCloser struct {
|
type readCloser struct {
|
||||||
io.Reader
|
io.Reader
|
||||||
|
@ -1079,6 +1079,23 @@ func Run(t *testing.T, opt *Opt) {
|
|||||||
file.Check(t, obj, remote.Precision())
|
file.Check(t, obj, remote.Precision())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// TestAbout tests the About optional interface
|
||||||
|
t.Run("TestObjectAbout", func(t *testing.T) {
|
||||||
|
skipIfNotOk(t)
|
||||||
|
|
||||||
|
// Check have About
|
||||||
|
doAbout := remote.Features().About
|
||||||
|
if doAbout == nil {
|
||||||
|
t.Skip("FS does not support About")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't really check the output much!
|
||||||
|
usage, err := doAbout()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, usage)
|
||||||
|
assert.NotEqual(t, int64(0), usage.Total)
|
||||||
|
})
|
||||||
|
|
||||||
// TestObjectPurge tests Purge
|
// TestObjectPurge tests Purge
|
||||||
t.Run("TestObjectPurge", func(t *testing.T) {
|
t.Run("TestObjectPurge", func(t *testing.T) {
|
||||||
skipIfNotOk(t)
|
skipIfNotOk(t)
|
||||||
|
Loading…
Reference in New Issue
Block a user