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 }