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 }