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  }