2018-10-26 15:50:55 +02:00
|
|
|
package operations
|
|
|
|
|
|
|
|
import (
|
2019-06-17 10:34:30 +02:00
|
|
|
"context"
|
2018-10-26 15:50:55 +02:00
|
|
|
"path"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2019-07-28 19:47:38 +02:00
|
|
|
"github.com/rclone/rclone/backend/crypt"
|
|
|
|
"github.com/rclone/rclone/fs"
|
|
|
|
"github.com/rclone/rclone/fs/walk"
|
2018-10-26 15:50:55 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// ListJSONItem in the struct which gets marshalled for each line
|
|
|
|
type ListJSONItem struct {
|
2019-04-15 19:12:09 +02:00
|
|
|
Path string
|
|
|
|
Name string
|
|
|
|
EncryptedPath string `json:",omitempty"`
|
|
|
|
Encrypted string `json:",omitempty"`
|
|
|
|
Size int64
|
|
|
|
MimeType string `json:",omitempty"`
|
|
|
|
ModTime Timestamp //`json:",omitempty"`
|
|
|
|
IsDir bool
|
|
|
|
Hashes map[string]string `json:",omitempty"`
|
|
|
|
ID string `json:",omitempty"`
|
|
|
|
OrigID string `json:",omitempty"`
|
2019-03-20 13:45:06 +01:00
|
|
|
Tier string `json:",omitempty"`
|
2019-06-07 18:28:15 +02:00
|
|
|
IsBucket bool `json:",omitempty"`
|
2018-10-26 15:50:55 +02:00
|
|
|
}
|
|
|
|
|
2019-02-03 12:01:52 +01:00
|
|
|
// Timestamp a time in the provided format
|
|
|
|
type Timestamp struct {
|
|
|
|
When time.Time
|
|
|
|
Format string
|
|
|
|
}
|
2018-10-26 15:50:55 +02:00
|
|
|
|
|
|
|
// MarshalJSON turns a Timestamp into JSON
|
|
|
|
func (t Timestamp) MarshalJSON() (out []byte, err error) {
|
2019-02-03 12:01:52 +01:00
|
|
|
if t.When.IsZero() {
|
2018-10-26 15:50:55 +02:00
|
|
|
return []byte(`""`), nil
|
|
|
|
}
|
2019-02-03 12:01:52 +01:00
|
|
|
return []byte(`"` + t.When.Format(t.Format) + `"`), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns a time format for the given precision
|
|
|
|
func formatForPrecision(precision time.Duration) string {
|
|
|
|
switch {
|
|
|
|
case precision <= time.Nanosecond:
|
|
|
|
return "2006-01-02T15:04:05.000000000Z07:00"
|
|
|
|
case precision <= 10*time.Nanosecond:
|
|
|
|
return "2006-01-02T15:04:05.00000000Z07:00"
|
|
|
|
case precision <= 100*time.Nanosecond:
|
|
|
|
return "2006-01-02T15:04:05.0000000Z07:00"
|
|
|
|
case precision <= time.Microsecond:
|
|
|
|
return "2006-01-02T15:04:05.000000Z07:00"
|
|
|
|
case precision <= 10*time.Microsecond:
|
|
|
|
return "2006-01-02T15:04:05.00000Z07:00"
|
|
|
|
case precision <= 100*time.Microsecond:
|
|
|
|
return "2006-01-02T15:04:05.0000Z07:00"
|
|
|
|
case precision <= time.Millisecond:
|
|
|
|
return "2006-01-02T15:04:05.000Z07:00"
|
|
|
|
case precision <= 10*time.Millisecond:
|
|
|
|
return "2006-01-02T15:04:05.00Z07:00"
|
|
|
|
case precision <= 100*time.Millisecond:
|
|
|
|
return "2006-01-02T15:04:05.0Z07:00"
|
|
|
|
}
|
|
|
|
return time.RFC3339
|
2018-10-26 15:50:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// ListJSONOpt describes the options for ListJSON
|
|
|
|
type ListJSONOpt struct {
|
|
|
|
Recurse bool `json:"recurse"`
|
|
|
|
NoModTime bool `json:"noModTime"`
|
|
|
|
ShowEncrypted bool `json:"showEncrypted"`
|
|
|
|
ShowOrigIDs bool `json:"showOrigIDs"`
|
|
|
|
ShowHash bool `json:"showHash"`
|
2019-04-05 22:24:09 +02:00
|
|
|
DirsOnly bool `json:"dirsOnly"`
|
|
|
|
FilesOnly bool `json:"filesOnly"`
|
2018-10-26 15:50:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// ListJSON lists fsrc using the options in opt calling callback for each item
|
2019-06-17 10:34:30 +02:00
|
|
|
func ListJSON(ctx context.Context, fsrc fs.Fs, remote string, opt *ListJSONOpt, callback func(*ListJSONItem) error) error {
|
2018-10-26 15:50:55 +02:00
|
|
|
var cipher crypt.Cipher
|
|
|
|
if opt.ShowEncrypted {
|
|
|
|
fsInfo, _, _, config, err := fs.ConfigFs(fsrc.Name() + ":" + fsrc.Root())
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "ListJSON failed to load config for crypt remote")
|
|
|
|
}
|
|
|
|
if fsInfo.Name != "crypt" {
|
|
|
|
return errors.New("The remote needs to be of type \"crypt\"")
|
|
|
|
}
|
|
|
|
cipher, err = crypt.NewCipher(config)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "ListJSON failed to make new crypt remote")
|
|
|
|
}
|
|
|
|
}
|
2019-06-07 18:28:15 +02:00
|
|
|
features := fsrc.Features()
|
|
|
|
canGetTier := features.GetTier
|
2019-02-03 12:01:52 +01:00
|
|
|
format := formatForPrecision(fsrc.Precision())
|
2019-06-07 18:28:15 +02:00
|
|
|
isBucket := features.BucketBased && remote == "" && fsrc.Root() == "" // if bucket based remote listing the root mark directories as buckets
|
2019-06-17 10:34:30 +02:00
|
|
|
err := walk.ListR(ctx, fsrc, remote, false, ConfigMaxDepth(opt.Recurse), walk.ListAll, func(entries fs.DirEntries) (err error) {
|
2018-10-26 15:50:55 +02:00
|
|
|
for _, entry := range entries {
|
2019-04-05 22:24:09 +02:00
|
|
|
switch entry.(type) {
|
|
|
|
case fs.Directory:
|
|
|
|
if opt.FilesOnly {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
case fs.Object:
|
|
|
|
if opt.DirsOnly {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
fs.Errorf(nil, "Unknown type %T in listing", entry)
|
|
|
|
}
|
|
|
|
|
2018-10-26 15:50:55 +02:00
|
|
|
item := ListJSONItem{
|
|
|
|
Path: entry.Remote(),
|
|
|
|
Name: path.Base(entry.Remote()),
|
|
|
|
Size: entry.Size(),
|
2019-06-17 10:34:30 +02:00
|
|
|
MimeType: fs.MimeTypeDirEntry(ctx, entry),
|
2018-10-26 15:50:55 +02:00
|
|
|
}
|
|
|
|
if !opt.NoModTime {
|
2019-06-17 10:34:30 +02:00
|
|
|
item.ModTime = Timestamp{When: entry.ModTime(ctx), Format: format}
|
2018-10-26 15:50:55 +02:00
|
|
|
}
|
|
|
|
if cipher != nil {
|
|
|
|
switch entry.(type) {
|
|
|
|
case fs.Directory:
|
2019-04-15 19:12:09 +02:00
|
|
|
item.EncryptedPath = cipher.EncryptDirName(entry.Remote())
|
2018-10-26 15:50:55 +02:00
|
|
|
case fs.Object:
|
2019-04-15 19:12:09 +02:00
|
|
|
item.EncryptedPath = cipher.EncryptFileName(entry.Remote())
|
2018-10-26 15:50:55 +02:00
|
|
|
default:
|
|
|
|
fs.Errorf(nil, "Unknown type %T in listing", entry)
|
|
|
|
}
|
2019-04-15 19:12:09 +02:00
|
|
|
item.Encrypted = path.Base(item.EncryptedPath)
|
2018-10-26 15:50:55 +02:00
|
|
|
}
|
|
|
|
if do, ok := entry.(fs.IDer); ok {
|
|
|
|
item.ID = do.ID()
|
|
|
|
}
|
2019-08-12 23:03:53 +02:00
|
|
|
if o, ok := entry.(fs.Object); opt.ShowOrigIDs && ok {
|
|
|
|
if do, ok := fs.UnWrapObject(o).(fs.IDer); ok {
|
2018-10-26 15:50:55 +02:00
|
|
|
item.OrigID = do.ID()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
switch x := entry.(type) {
|
|
|
|
case fs.Directory:
|
|
|
|
item.IsDir = true
|
2019-06-07 18:28:15 +02:00
|
|
|
item.IsBucket = isBucket
|
2018-10-26 15:50:55 +02:00
|
|
|
case fs.Object:
|
|
|
|
item.IsDir = false
|
|
|
|
if opt.ShowHash {
|
|
|
|
item.Hashes = make(map[string]string)
|
|
|
|
for _, hashType := range x.Fs().Hashes().Array() {
|
2019-06-17 10:34:30 +02:00
|
|
|
hash, err := x.Hash(ctx, hashType)
|
2018-10-26 15:50:55 +02:00
|
|
|
if err != nil {
|
|
|
|
fs.Errorf(x, "Failed to read hash: %v", err)
|
|
|
|
} else if hash != "" {
|
|
|
|
item.Hashes[hashType.String()] = hash
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-03-20 13:45:06 +01:00
|
|
|
if canGetTier {
|
|
|
|
if do, ok := x.(fs.GetTierer); ok {
|
|
|
|
item.Tier = do.GetTier()
|
|
|
|
}
|
|
|
|
}
|
2018-10-26 15:50:55 +02:00
|
|
|
default:
|
|
|
|
fs.Errorf(nil, "Unknown type %T in listing in ListJSON", entry)
|
|
|
|
}
|
|
|
|
err = callback(&item)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "callback failed in ListJSON")
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "error in ListJSON")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|