github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/fs/operations/lsjson.go (about)

     1  package operations
     2  
     3  import (
     4  	"context"
     5  	"path"
     6  	"time"
     7  
     8  	"github.com/pkg/errors"
     9  	"github.com/rclone/rclone/backend/crypt"
    10  	"github.com/rclone/rclone/fs"
    11  	"github.com/rclone/rclone/fs/walk"
    12  )
    13  
    14  // ListJSONItem in the struct which gets marshalled for each line
    15  type ListJSONItem struct {
    16  	Path          string
    17  	Name          string
    18  	EncryptedPath string `json:",omitempty"`
    19  	Encrypted     string `json:",omitempty"`
    20  	Size          int64
    21  	MimeType      string    `json:",omitempty"`
    22  	ModTime       Timestamp //`json:",omitempty"`
    23  	IsDir         bool
    24  	Hashes        map[string]string `json:",omitempty"`
    25  	ID            string            `json:",omitempty"`
    26  	OrigID        string            `json:",omitempty"`
    27  	Tier          string            `json:",omitempty"`
    28  	IsBucket      bool              `json:",omitempty"`
    29  }
    30  
    31  // Timestamp a time in the provided format
    32  type Timestamp struct {
    33  	When   time.Time
    34  	Format string
    35  }
    36  
    37  // MarshalJSON turns a Timestamp into JSON
    38  func (t Timestamp) MarshalJSON() (out []byte, err error) {
    39  	if t.When.IsZero() {
    40  		return []byte(`""`), nil
    41  	}
    42  	return []byte(`"` + t.When.Format(t.Format) + `"`), nil
    43  }
    44  
    45  // Returns a time format for the given precision
    46  func formatForPrecision(precision time.Duration) string {
    47  	switch {
    48  	case precision <= time.Nanosecond:
    49  		return "2006-01-02T15:04:05.000000000Z07:00"
    50  	case precision <= 10*time.Nanosecond:
    51  		return "2006-01-02T15:04:05.00000000Z07:00"
    52  	case precision <= 100*time.Nanosecond:
    53  		return "2006-01-02T15:04:05.0000000Z07:00"
    54  	case precision <= time.Microsecond:
    55  		return "2006-01-02T15:04:05.000000Z07:00"
    56  	case precision <= 10*time.Microsecond:
    57  		return "2006-01-02T15:04:05.00000Z07:00"
    58  	case precision <= 100*time.Microsecond:
    59  		return "2006-01-02T15:04:05.0000Z07:00"
    60  	case precision <= time.Millisecond:
    61  		return "2006-01-02T15:04:05.000Z07:00"
    62  	case precision <= 10*time.Millisecond:
    63  		return "2006-01-02T15:04:05.00Z07:00"
    64  	case precision <= 100*time.Millisecond:
    65  		return "2006-01-02T15:04:05.0Z07:00"
    66  	}
    67  	return time.RFC3339
    68  }
    69  
    70  // ListJSONOpt describes the options for ListJSON
    71  type ListJSONOpt struct {
    72  	Recurse       bool `json:"recurse"`
    73  	NoModTime     bool `json:"noModTime"`
    74  	NoMimeType    bool `json:"noMimeType"`
    75  	ShowEncrypted bool `json:"showEncrypted"`
    76  	ShowOrigIDs   bool `json:"showOrigIDs"`
    77  	ShowHash      bool `json:"showHash"`
    78  	DirsOnly      bool `json:"dirsOnly"`
    79  	FilesOnly     bool `json:"filesOnly"`
    80  }
    81  
    82  // ListJSON lists fsrc using the options in opt calling callback for each item
    83  func ListJSON(ctx context.Context, fsrc fs.Fs, remote string, opt *ListJSONOpt, callback func(*ListJSONItem) error) error {
    84  	var cipher crypt.Cipher
    85  	if opt.ShowEncrypted {
    86  		fsInfo, _, _, config, err := fs.ConfigFs(fsrc.Name() + ":" + fsrc.Root())
    87  		if err != nil {
    88  			return errors.Wrap(err, "ListJSON failed to load config for crypt remote")
    89  		}
    90  		if fsInfo.Name != "crypt" {
    91  			return errors.New("The remote needs to be of type \"crypt\"")
    92  		}
    93  		cipher, err = crypt.NewCipher(config)
    94  		if err != nil {
    95  			return errors.Wrap(err, "ListJSON failed to make new crypt remote")
    96  		}
    97  	}
    98  	features := fsrc.Features()
    99  	canGetTier := features.GetTier
   100  	format := formatForPrecision(fsrc.Precision())
   101  	isBucket := features.BucketBased && remote == "" && fsrc.Root() == "" // if bucket based remote listing the root mark directories as buckets
   102  	err := walk.ListR(ctx, fsrc, remote, false, ConfigMaxDepth(opt.Recurse), walk.ListAll, func(entries fs.DirEntries) (err error) {
   103  		for _, entry := range entries {
   104  			switch entry.(type) {
   105  			case fs.Directory:
   106  				if opt.FilesOnly {
   107  					continue
   108  				}
   109  			case fs.Object:
   110  				if opt.DirsOnly {
   111  					continue
   112  				}
   113  			default:
   114  				fs.Errorf(nil, "Unknown type %T in listing", entry)
   115  			}
   116  
   117  			item := ListJSONItem{
   118  				Path: entry.Remote(),
   119  				Name: path.Base(entry.Remote()),
   120  				Size: entry.Size(),
   121  			}
   122  			if !opt.NoModTime {
   123  				item.ModTime = Timestamp{When: entry.ModTime(ctx), Format: format}
   124  			}
   125  			if !opt.NoMimeType {
   126  				item.MimeType = fs.MimeTypeDirEntry(ctx, entry)
   127  			}
   128  			if cipher != nil {
   129  				switch entry.(type) {
   130  				case fs.Directory:
   131  					item.EncryptedPath = cipher.EncryptDirName(entry.Remote())
   132  				case fs.Object:
   133  					item.EncryptedPath = cipher.EncryptFileName(entry.Remote())
   134  				default:
   135  					fs.Errorf(nil, "Unknown type %T in listing", entry)
   136  				}
   137  				item.Encrypted = path.Base(item.EncryptedPath)
   138  			}
   139  			if do, ok := entry.(fs.IDer); ok {
   140  				item.ID = do.ID()
   141  			}
   142  			if o, ok := entry.(fs.Object); opt.ShowOrigIDs && ok {
   143  				if do, ok := fs.UnWrapObject(o).(fs.IDer); ok {
   144  					item.OrigID = do.ID()
   145  				}
   146  			}
   147  			switch x := entry.(type) {
   148  			case fs.Directory:
   149  				item.IsDir = true
   150  				item.IsBucket = isBucket
   151  			case fs.Object:
   152  				item.IsDir = false
   153  				if opt.ShowHash {
   154  					item.Hashes = make(map[string]string)
   155  					for _, hashType := range x.Fs().Hashes().Array() {
   156  						hash, err := x.Hash(ctx, hashType)
   157  						if err != nil {
   158  							fs.Errorf(x, "Failed to read hash: %v", err)
   159  						} else if hash != "" {
   160  							item.Hashes[hashType.String()] = hash
   161  						}
   162  					}
   163  				}
   164  				if canGetTier {
   165  					if do, ok := x.(fs.GetTierer); ok {
   166  						item.Tier = do.GetTier()
   167  					}
   168  				}
   169  			default:
   170  				fs.Errorf(nil, "Unknown type %T in listing in ListJSON", entry)
   171  			}
   172  			err = callback(&item)
   173  			if err != nil {
   174  				return errors.Wrap(err, "callback failed in ListJSON")
   175  			}
   176  
   177  		}
   178  		return nil
   179  	})
   180  	if err != nil {
   181  		return errors.Wrap(err, "error in ListJSON")
   182  	}
   183  	return nil
   184  }