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