github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/backend/putio/object.go (about) 1 package putio 2 3 import ( 4 "context" 5 "io" 6 "net/http" 7 "net/url" 8 "path" 9 "strconv" 10 "time" 11 12 "github.com/pkg/errors" 13 "github.com/putdotio/go-putio/putio" 14 "github.com/rclone/rclone/fs" 15 "github.com/rclone/rclone/fs/fserrors" 16 "github.com/rclone/rclone/fs/hash" 17 ) 18 19 // Object describes a Putio object 20 // 21 // Putio Objects always have full metadata 22 type Object struct { 23 fs *Fs // what this object is part of 24 file *putio.File 25 remote string // The remote path 26 modtime time.Time 27 } 28 29 // NewObject finds the Object at remote. If it can't be found 30 // it returns the error fs.ErrorObjectNotFound. 31 func (f *Fs) NewObject(ctx context.Context, remote string) (o fs.Object, err error) { 32 // defer log.Trace(f, "remote=%v", remote)("o=%+v, err=%v", &o, &err) 33 obj := &Object{ 34 fs: f, 35 remote: remote, 36 } 37 err = obj.readEntryAndSetMetadata(ctx) 38 if err != nil { 39 return nil, err 40 } 41 return obj, err 42 } 43 44 // Return an Object from a path 45 // 46 // If it can't be found it returns the error fs.ErrorObjectNotFound. 47 func (f *Fs) newObjectWithInfo(ctx context.Context, remote string, info putio.File) (o fs.Object, err error) { 48 // defer log.Trace(f, "remote=%v, info=+v", remote, &info)("o=%+v, err=%v", &o, &err) 49 obj := &Object{ 50 fs: f, 51 remote: remote, 52 } 53 err = obj.setMetadataFromEntry(info) 54 if err != nil { 55 return nil, err 56 } 57 return obj, err 58 } 59 60 // Fs returns the parent Fs 61 func (o *Object) Fs() fs.Info { 62 return o.fs 63 } 64 65 // Return a string version 66 func (o *Object) String() string { 67 if o == nil { 68 return "<nil>" 69 } 70 return o.remote 71 } 72 73 // Remote returns the remote path 74 func (o *Object) Remote() string { 75 return o.remote 76 } 77 78 // Hash returns the dropbox special hash 79 func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) { 80 if t != hash.CRC32 { 81 return "", hash.ErrUnsupported 82 } 83 err := o.readEntryAndSetMetadata(ctx) 84 if err != nil { 85 return "", errors.Wrap(err, "failed to read hash from metadata") 86 } 87 return o.file.CRC32, nil 88 } 89 90 // Size returns the size of an object in bytes 91 func (o *Object) Size() int64 { 92 if o.file == nil { 93 return 0 94 } 95 return o.file.Size 96 } 97 98 // ID returns the ID of the Object if known, or "" if not 99 func (o *Object) ID() string { 100 if o.file == nil { 101 return "" 102 } 103 return itoa(o.file.ID) 104 } 105 106 // MimeType returns the content type of the Object if 107 // known, or "" if not 108 func (o *Object) MimeType(ctx context.Context) string { 109 err := o.readEntryAndSetMetadata(ctx) 110 if err != nil { 111 return "" 112 } 113 return o.file.ContentType 114 } 115 116 // setMetadataFromEntry sets the fs data from a putio.File 117 // 118 // This isn't a complete set of metadata and has an inacurate date 119 func (o *Object) setMetadataFromEntry(info putio.File) error { 120 o.file = &info 121 o.modtime = info.UpdatedAt.Time 122 return nil 123 } 124 125 // Reads the entry for a file from putio 126 func (o *Object) readEntry(ctx context.Context) (f *putio.File, err error) { 127 // defer log.Trace(o, "")("f=%+v, err=%v", f, &err) 128 leaf, directoryID, err := o.fs.dirCache.FindRootAndPath(ctx, o.remote, false) 129 if err != nil { 130 if err == fs.ErrorDirNotFound { 131 return nil, fs.ErrorObjectNotFound 132 } 133 return nil, err 134 } 135 var resp struct { 136 File putio.File `json:"file"` 137 } 138 err = o.fs.pacer.Call(func() (bool, error) { 139 // fs.Debugf(o, "requesting child. directoryID: %s, name: %s", directoryID, leaf) 140 req, err := o.fs.client.NewRequest(ctx, "GET", "/v2/files/"+directoryID+"/child?name="+url.QueryEscape(o.fs.opt.Enc.FromStandardName(leaf)), nil) 141 if err != nil { 142 return false, err 143 } 144 _, err = o.fs.client.Do(req, &resp) 145 if perr, ok := err.(*putio.ErrorResponse); ok && perr.Response.StatusCode == 404 { 146 return false, fs.ErrorObjectNotFound 147 } 148 return shouldRetry(err) 149 }) 150 if err != nil { 151 return nil, err 152 } 153 if resp.File.IsDir() { 154 return nil, fs.ErrorNotAFile 155 } 156 return &resp.File, err 157 } 158 159 // Read entry if not set and set metadata from it 160 func (o *Object) readEntryAndSetMetadata(ctx context.Context) error { 161 if o.file != nil { 162 return nil 163 } 164 entry, err := o.readEntry(ctx) 165 if err != nil { 166 return err 167 } 168 return o.setMetadataFromEntry(*entry) 169 } 170 171 // Returns the remote path for the object 172 func (o *Object) remotePath() string { 173 return path.Join(o.fs.root, o.remote) 174 } 175 176 // ModTime returns the modification time of the object 177 // 178 // It attempts to read the objects mtime and if that isn't present the 179 // LastModified returned in the http headers 180 func (o *Object) ModTime(ctx context.Context) time.Time { 181 if o.modtime.IsZero() { 182 err := o.readEntryAndSetMetadata(ctx) 183 if err != nil { 184 fs.Debugf(o, "Failed to read metadata: %v", err) 185 return time.Now() 186 } 187 } 188 return o.modtime 189 } 190 191 // SetModTime sets the modification time of the local fs object 192 // 193 // Commits the datastore 194 func (o *Object) SetModTime(ctx context.Context, modTime time.Time) (err error) { 195 // defer log.Trace(o, "modTime=%v", modTime.String())("err=%v", &err) 196 req, err := o.fs.client.NewRequest(ctx, "POST", "/v2/files/touch?file_id="+strconv.FormatInt(o.file.ID, 10)+"&updated_at="+url.QueryEscape(modTime.Format(time.RFC3339)), nil) 197 if err != nil { 198 return err 199 } 200 // fs.Debugf(o, "setting modtime: %s", modTime.String()) 201 _, err = o.fs.client.Do(req, nil) 202 if err != nil { 203 return err 204 } 205 o.modtime = modTime 206 if o.file != nil { 207 o.file.UpdatedAt.Time = modTime 208 } 209 return nil 210 } 211 212 // Storable returns whether this object is storable 213 func (o *Object) Storable() bool { 214 return true 215 } 216 217 // Open an object for read 218 func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.ReadCloser, err error) { 219 // defer log.Trace(o, "")("err=%v", &err) 220 var storageURL string 221 err = o.fs.pacer.Call(func() (bool, error) { 222 storageURL, err = o.fs.client.Files.URL(ctx, o.file.ID, true) 223 return shouldRetry(err) 224 }) 225 if err != nil { 226 return 227 } 228 229 var resp *http.Response 230 headers := fs.OpenOptionHeaders(options) 231 err = o.fs.pacer.Call(func() (bool, error) { 232 req, err := http.NewRequest(http.MethodGet, storageURL, nil) 233 if err != nil { 234 return shouldRetry(err) 235 } 236 req = req.WithContext(ctx) // go1.13 can use NewRequestWithContext 237 req.Header.Set("User-Agent", o.fs.client.UserAgent) 238 239 // merge headers with extra headers 240 for header, value := range headers { 241 req.Header.Set(header, value) 242 } 243 // fs.Debugf(o, "opening file: id=%d", o.file.ID) 244 resp, err = o.fs.httpClient.Do(req) 245 return shouldRetry(err) 246 }) 247 if perr, ok := err.(*putio.ErrorResponse); ok && perr.Response.StatusCode >= 400 && perr.Response.StatusCode <= 499 { 248 _ = resp.Body.Close() 249 return nil, fserrors.NoRetryError(err) 250 } 251 if err != nil { 252 return nil, err 253 } 254 return resp.Body, nil 255 } 256 257 // Update the already existing object 258 // 259 // Copy the reader into the object updating modTime and size 260 // 261 // The new object may have been created if an error is returned 262 func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (err error) { 263 // defer log.Trace(o, "src=%+v", src)("err=%v", &err) 264 remote := o.remotePath() 265 if ignoredFiles.MatchString(remote) { 266 fs.Logf(o, "File name disallowed - not uploading") 267 return nil 268 } 269 err = o.Remove(ctx) 270 if err != nil { 271 return err 272 } 273 newObj, err := o.fs.PutUnchecked(ctx, in, src, options...) 274 if err != nil { 275 return err 276 } 277 *o = *(newObj.(*Object)) 278 return err 279 } 280 281 // Remove an object 282 func (o *Object) Remove(ctx context.Context) (err error) { 283 // defer log.Trace(o, "")("err=%v", &err) 284 return o.fs.pacer.Call(func() (bool, error) { 285 // fs.Debugf(o, "removing file: id=%d", o.file.ID) 286 err = o.fs.client.Files.Delete(ctx, o.file.ID) 287 return shouldRetry(err) 288 }) 289 }