github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/fs/operations/lsjson.go (about)

     1  package operations
     2  
     3  import (
     4  	"context"
     5  	"path"
     6  	"time"
     7  
     8  	"github.com/ncw/rclone/backend/crypt"
     9  	"github.com/ncw/rclone/fs"
    10  	"github.com/ncw/rclone/fs/walk"
    11  	"github.com/pkg/errors"
    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  	ShowEncrypted bool `json:"showEncrypted"`
    75  	ShowOrigIDs   bool `json:"showOrigIDs"`
    76  	ShowHash      bool `json:"showHash"`
    77  	DirsOnly      bool `json:"dirsOnly"`
    78  	FilesOnly     bool `json:"filesOnly"`
    79  }
    80  
    81  // ListJSON lists fsrc using the options in opt calling callback for each item
    82  func ListJSON(ctx context.Context, fsrc fs.Fs, remote string, opt *ListJSONOpt, callback func(*ListJSONItem) error) error {
    83  	var cipher crypt.Cipher
    84  	if opt.ShowEncrypted {
    85  		fsInfo, _, _, config, err := fs.ConfigFs(fsrc.Name() + ":" + fsrc.Root())
    86  		if err != nil {
    87  			return errors.Wrap(err, "ListJSON failed to load config for crypt remote")
    88  		}
    89  		if fsInfo.Name != "crypt" {
    90  			return errors.New("The remote needs to be of type \"crypt\"")
    91  		}
    92  		cipher, err = crypt.NewCipher(config)
    93  		if err != nil {
    94  			return errors.Wrap(err, "ListJSON failed to make new crypt remote")
    95  		}
    96  	}
    97  	features := fsrc.Features()
    98  	canGetTier := features.GetTier
    99  	format := formatForPrecision(fsrc.Precision())
   100  	isBucket := features.BucketBased && remote == "" && fsrc.Root() == "" // if bucket based remote listing the root mark directories as buckets
   101  	err := walk.ListR(ctx, fsrc, remote, false, ConfigMaxDepth(opt.Recurse), walk.ListAll, func(entries fs.DirEntries) (err error) {
   102  		for _, entry := range entries {
   103  			switch entry.(type) {
   104  			case fs.Directory:
   105  				if opt.FilesOnly {
   106  					continue
   107  				}
   108  			case fs.Object:
   109  				if opt.DirsOnly {
   110  					continue
   111  				}
   112  			default:
   113  				fs.Errorf(nil, "Unknown type %T in listing", entry)
   114  			}
   115  
   116  			item := ListJSONItem{
   117  				Path:     entry.Remote(),
   118  				Name:     path.Base(entry.Remote()),
   119  				Size:     entry.Size(),
   120  				MimeType: fs.MimeTypeDirEntry(ctx, entry),
   121  			}
   122  			if !opt.NoModTime {
   123  				item.ModTime = Timestamp{When: entry.ModTime(ctx), Format: format}
   124  			}
   125  			if cipher != nil {
   126  				switch entry.(type) {
   127  				case fs.Directory:
   128  					item.EncryptedPath = cipher.EncryptDirName(entry.Remote())
   129  				case fs.Object:
   130  					item.EncryptedPath = cipher.EncryptFileName(entry.Remote())
   131  				default:
   132  					fs.Errorf(nil, "Unknown type %T in listing", entry)
   133  				}
   134  				item.Encrypted = path.Base(item.EncryptedPath)
   135  			}
   136  			if do, ok := entry.(fs.IDer); ok {
   137  				item.ID = do.ID()
   138  			}
   139  			if opt.ShowOrigIDs {
   140  				cur := entry
   141  				for {
   142  					u, ok := cur.(fs.ObjectUnWrapper)
   143  					if !ok {
   144  						break // not a wrapped object, use current id
   145  					}
   146  					next := u.UnWrap()
   147  					if next == nil {
   148  						break // no base object found, use current id
   149  					}
   150  					cur = next
   151  				}
   152  				if do, ok := cur.(fs.IDer); ok {
   153  					item.OrigID = do.ID()
   154  				}
   155  			}
   156  			switch x := entry.(type) {
   157  			case fs.Directory:
   158  				item.IsDir = true
   159  				item.IsBucket = isBucket
   160  			case fs.Object:
   161  				item.IsDir = false
   162  				if opt.ShowHash {
   163  					item.Hashes = make(map[string]string)
   164  					for _, hashType := range x.Fs().Hashes().Array() {
   165  						hash, err := x.Hash(ctx, hashType)
   166  						if err != nil {
   167  							fs.Errorf(x, "Failed to read hash: %v", err)
   168  						} else if hash != "" {
   169  							item.Hashes[hashType.String()] = hash
   170  						}
   171  					}
   172  				}
   173  				if canGetTier {
   174  					if do, ok := x.(fs.GetTierer); ok {
   175  						item.Tier = do.GetTier()
   176  					}
   177  				}
   178  			default:
   179  				fs.Errorf(nil, "Unknown type %T in listing in ListJSON", entry)
   180  			}
   181  			err = callback(&item)
   182  			if err != nil {
   183  				return errors.Wrap(err, "callback failed in ListJSON")
   184  			}
   185  
   186  		}
   187  		return nil
   188  	})
   189  	if err != nil {
   190  		return errors.Wrap(err, "error in ListJSON")
   191  	}
   192  	return nil
   193  }