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 }