drive: restructure Object type

This commit is contained in:
Fabian Möller 2018-08-21 12:49:33 +02:00 committed by Nick Craig-Wood
parent 171e39b230
commit 0b2fc621fc

View File

@ -337,18 +337,27 @@ type Fs struct {
isTeamDrive bool // true if this is a team drive isTeamDrive bool // true if this is a team drive
} }
// Object describes a drive object type baseObject struct {
type Object struct {
fs *Fs // what this object is part of fs *Fs // what this object is part of
remote string // The remote path remote string // The remote path
id string // Drive Id of this object id string // Drive Id of this object
modifiedDate string // RFC3339 time it was last modified
mimeType string // The object MIME type
bytes int64 // size of the object
}
type documentObject struct {
baseObject
url string // Download URL of this object
documentMimeType string // the original document MIME type
extLen int // The length of the added export extension
}
// Object describes a drive object
type Object struct {
baseObject
url string // Download URL of this object url string // Download URL of this object
md5sum string // md5sum of the object md5sum string // md5sum of the object
bytes int64 // size of the object
modifiedDate string // RFC3339 time it was last modified
documentMimeType string // if set this is a Google doc
v2Download bool // generate v2 download link ondemand v2Download bool // generate v2 download link ondemand
mimeType string
} }
// ------------------------------------------------------------ // ------------------------------------------------------------
@ -822,54 +831,138 @@ func NewFs(name, path string, m configmap.Mapper) (fs.Fs, error) {
return f, nil return f, nil
} }
// Return an Object from a path func (f *Fs) newBaseObject(remote string, info *drive.File) baseObject {
// modifiedDate := info.ModifiedTime
// If it can't be found it returns the error fs.ErrorObjectNotFound. if f.opt.UseCreatedDate {
func (f *Fs) newObjectWithInfo(remote string, info *drive.File) (fs.Object, error) { modifiedDate = info.CreatedTime
o := &Object{ }
return baseObject{
fs: f, fs: f,
remote: remote, remote: remote,
id: info.Id,
modifiedDate: modifiedDate,
mimeType: info.MimeType,
bytes: info.Size,
} }
if info != nil {
o.setMetaData(info)
} else {
err := o.readMetaData() // reads info and meta, returning an error
if err != nil {
return nil, err
}
}
return o, nil
} }
func (f *Fs) newDocumentObjectWithInfo(remote, extension, mimeType string, info *drive.File) (fs.Object, error) { // newRegularObject creates a fs.Object for a normal drive.File
o, err := f.newObjectWithInfo(remote, info) func (f *Fs) newRegularObject(remote string, info *drive.File) fs.Object {
return &Object{
baseObject: f.newBaseObject(remote, info),
url: fmt.Sprintf("%sfiles/%s?alt=media", f.svc.BasePath, info.Id),
md5sum: strings.ToLower(info.Md5Checksum),
v2Download: f.opt.V2DownloadMinSize != -1 && info.Size >= int64(f.opt.V2DownloadMinSize),
}
}
// newDocumentObject creates a fs.Object for a google docs drive.File
func (f *Fs) newDocumentObject(remote string, info *drive.File, extension, exportMimeType string) (fs.Object, error) {
mediaType, _, err := mime.ParseMediaType(exportMimeType)
if err != nil { if err != nil {
return nil, err return nil, err
} }
o.(*Object).setGdocsMetaData(info, extension, mimeType) url := fmt.Sprintf("%sfiles/%s/export?mimeType=%s", f.svc.BasePath, info.Id, url.QueryEscape(mediaType))
return o, nil if f.opt.AlternateExport {
switch info.MimeType {
case "application/vnd.google-apps.drawing":
url = fmt.Sprintf("https://docs.google.com/drawings/d/%s/export/%s", info.Id, extension[1:])
case "application/vnd.google-apps.document":
url = fmt.Sprintf("https://docs.google.com/document/d/%s/export?format=%s", info.Id, extension[1:])
case "application/vnd.google-apps.spreadsheet":
url = fmt.Sprintf("https://docs.google.com/spreadsheets/d/%s/export?format=%s", info.Id, extension[1:])
case "application/vnd.google-apps.presentation":
url = fmt.Sprintf("https://docs.google.com/presentation/d/%s/export/%s", info.Id, extension[1:])
}
}
baseObject := f.newBaseObject(remote+extension, info)
baseObject.bytes = -1
baseObject.mimeType = exportMimeType
return &documentObject{
baseObject: baseObject,
url: url,
documentMimeType: info.MimeType,
extLen: len(extension),
}, nil
}
// newObjectWithInfo creates a fs.Object for any drive.File
//
// When the drive.File cannot be represented as a fs.Object it will return (nil, nil).
func (f *Fs) newObjectWithInfo(remote string, info *drive.File) (fs.Object, error) {
// If item has MD5 sum or a length it is a file stored on drive
if info.Md5Checksum != "" || info.Size > 0 {
return f.newRegularObject(remote, info), nil
}
extension, exportName, exportMimeType, isDocument := f.findExportFormat(info)
return f.newObjectWithExportInfo(remote, info, extension, exportName, exportMimeType, isDocument)
}
// newObjectWithExportInfo creates a fs.Object for any drive.File and the result of findExportFormat
//
// When the drive.File cannot be represented as a fs.Object it will return (nil, nil).
func (f *Fs) newObjectWithExportInfo(
remote string, info *drive.File,
extension, exportName, exportMimeType string, isDocument bool) (fs.Object, error) {
switch {
case info.Md5Checksum != "" || info.Size > 0:
// If item has MD5 sum or a length it is a file stored on drive
return f.newRegularObject(remote, info), nil
case f.opt.SkipGdocs:
fs.Debugf(remote, "Skipping google document type %q", info.MimeType)
return nil, nil
default:
// If item MimeType is in the ExportFormats then it is a google doc
if !isDocument {
fs.Debugf(remote, "Ignoring unknown document type %q", info.MimeType)
return nil, nil
}
if extension == "" {
fs.Debugf(remote, "No export formats found for %q", info.MimeType)
return nil, nil
}
return f.newDocumentObject(remote, info, extension, exportMimeType)
}
} }
// NewObject finds the Object at remote. If it can't be found // NewObject finds the Object at remote. If it can't be found
// it returns the error fs.ErrorObjectNotFound. // it returns the error fs.ErrorObjectNotFound.
func (f *Fs) NewObject(remote string) (fs.Object, error) { func (f *Fs) NewObject(remote string) (fs.Object, error) {
return f.newObjectWithInfo(remote, nil) info, extension, exportName, exportMimeType, isDocument, err := f.getRemoteInfoWithExport(remote)
if err != nil {
return nil, err
}
remote = remote[:len(remote)-len(extension)]
obj, err := f.newObjectWithExportInfo(remote, info, extension, exportName, exportMimeType, isDocument)
switch {
case err != nil:
return nil, err
case obj == nil:
return nil, fs.ErrorObjectNotFound
default:
return obj, nil
}
} }
// FindLeaf finds a directory of name leaf in the folder with ID pathID // FindLeaf finds a directory of name leaf in the folder with ID pathID
func (f *Fs) FindLeaf(pathID, leaf string) (pathIDOut string, found bool, err error) { func (f *Fs) FindLeaf(pathID, leaf string) (pathIDOut string, found bool, err error) {
// Find the leaf in pathID // Find the leaf in pathID
found, err = f.list([]string{pathID}, leaf, true, false, false, func(item *drive.File) bool { found, err = f.list([]string{pathID}, leaf, true, false, false, func(item *drive.File) bool {
if item.Name == leaf {
pathIDOut = item.Id
return true
}
if !f.opt.SkipGdocs { if !f.opt.SkipGdocs {
_, exportName, _, _ := f.findExportFormat(item) _, exportName, _, isDocument := f.findExportFormat(item)
if exportName == leaf { if exportName == leaf {
pathIDOut = item.Id pathIDOut = item.Id
return true return true
} }
if isDocument {
return false
}
}
if item.Name == leaf {
pathIDOut = item.Id
return true
} }
return false return false
}) })
@ -1217,6 +1310,9 @@ func (f *Fs) ListR(dir string, callback fs.ListRCallback) (err error) {
return list.Flush() return list.Flush()
} }
// itemToDirEntry converts a drive.File to a fs.DirEntry.
// When the drive.File cannot be represented as a fs.DirEntry
// (nil, nil) is returned.
func (f *Fs) itemToDirEntry(remote string, item *drive.File) (fs.DirEntry, error) { func (f *Fs) itemToDirEntry(remote string, item *drive.File) (fs.DirEntry, error) {
switch { switch {
case item.MimeType == driveFolderType: case item.MimeType == driveFolderType:
@ -1227,50 +1323,19 @@ func (f *Fs) itemToDirEntry(remote string, item *drive.File) (fs.DirEntry, error
return d, nil return d, nil
case f.opt.AuthOwnerOnly && !isAuthOwned(item): case f.opt.AuthOwnerOnly && !isAuthOwned(item):
// ignore object // ignore object
case item.Md5Checksum != "" || item.Size > 0:
// If item has MD5 sum or a length it is a file stored on drive
o, err := f.newObjectWithInfo(remote, item)
if err != nil {
return nil, err
}
return o, nil
case f.opt.SkipGdocs:
fs.Debugf(remote, "Skipping google document type %q", item.MimeType)
default: default:
// If item MimeType is in the ExportFormats then it is a google doc return f.newObjectWithInfo(remote, item)
extension, _, exportMimeType, isDocument := f.findExportFormat(item)
if !isDocument {
fs.Debugf(remote, "Ignoring unknown document type %q", item.MimeType)
break
}
if extension == "" {
fs.Debugf(remote, "No export formats found for %q", item.MimeType)
break
}
o, err := f.newDocumentObjectWithInfo(remote, extension, exportMimeType, item)
if err != nil {
return nil, err
}
return o, nil
} }
return nil, nil return nil, nil
} }
// Creates a drive.File info from the parameters passed in and a half // Creates a drive.File info from the parameters passed in.
// finished Object which must have setMetaData called on it
// //
// Used to create new objects // Used to create new objects
func (f *Fs) createFileInfo(remote string, modTime time.Time, size int64) (*Object, *drive.File, error) { func (f *Fs) createFileInfo(remote string, modTime time.Time) (*drive.File, error) {
// Temporary Object under construction
o := &Object{
fs: f,
remote: remote,
bytes: size,
}
leaf, directoryID, err := f.dirCache.FindRootAndPath(remote, true) leaf, directoryID, err := f.dirCache.FindRootAndPath(remote, true)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
// Define the metadata for the file we are going to create. // Define the metadata for the file we are going to create.
@ -1281,7 +1346,7 @@ func (f *Fs) createFileInfo(remote string, modTime time.Time, size int64) (*Obje
MimeType: fs.MimeTypeFromName(remote), MimeType: fs.MimeTypeFromName(remote),
ModifiedTime: modTime.Format(timeFormatOut), ModifiedTime: modTime.Format(timeFormatOut),
} }
return o, createInfo, nil return createInfo, nil
} }
// Put the object // Put the object
@ -1290,7 +1355,7 @@ func (f *Fs) createFileInfo(remote string, modTime time.Time, size int64) (*Obje
// //
// The new object may have been created if an error is returned // The new object may have been created if an error is returned
func (f *Fs) Put(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { func (f *Fs) Put(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
exisitingObj, err := f.newObjectWithInfo(src.Remote(), nil) exisitingObj, err := f.NewObject(src.Remote())
switch err { switch err {
case nil: case nil:
return exisitingObj, exisitingObj.Update(in, src, options...) return exisitingObj, exisitingObj.Update(in, src, options...)
@ -1319,7 +1384,6 @@ func (f *Fs) PutUnchecked(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOpt
srcExt := path.Ext(remote) srcExt := path.Ext(remote)
exportExt := "" exportExt := ""
importMimeType := "" importMimeType := ""
exportMimeType := ""
if f.importMimeTypes != nil && !f.opt.SkipGdocs { if f.importMimeTypes != nil && !f.opt.SkipGdocs {
importMimeType = f.findImportFormat(srcMimeType) importMimeType = f.findImportFormat(srcMimeType)
@ -1327,7 +1391,7 @@ func (f *Fs) PutUnchecked(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOpt
if isInternalMimeType(importMimeType) { if isInternalMimeType(importMimeType) {
remote = remote[:len(remote)-len(srcExt)] remote = remote[:len(remote)-len(srcExt)]
exportExt, exportMimeType, _ = f.findExportFormatByMimeType(importMimeType) exportExt, _, _ = f.findExportFormatByMimeType(importMimeType)
if exportExt == "" { if exportExt == "" {
return nil, errors.Errorf("No export format found for %q", importMimeType) return nil, errors.Errorf("No export format found for %q", importMimeType)
} }
@ -1337,7 +1401,7 @@ func (f *Fs) PutUnchecked(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOpt
} }
} }
o, createInfo, err := f.createFileInfo(remote, modTime, size) createInfo, err := f.createFileInfo(remote, modTime)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1359,20 +1423,16 @@ func (f *Fs) PutUnchecked(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOpt
return shouldRetry(err) return shouldRetry(err)
}) })
if err != nil { if err != nil {
return o, err return nil, err
} }
} else { } else {
// Upload the file in chunks // Upload the file in chunks
info, err = f.Upload(in, size, srcMimeType, "", createInfo, remote) info, err = f.Upload(in, size, srcMimeType, "", createInfo, remote)
if err != nil { if err != nil {
return o, err return nil, err
} }
} }
if isInternalMimeType(importMimeType) { return f.newObjectWithInfo(remote, info)
return f.newDocumentObjectWithInfo(remote, exportExt, exportMimeType, info)
}
o.setMetaData(info)
return o, nil
} }
// MergeDirs merges the contents of all the directories passed // MergeDirs merges the contents of all the directories passed
@ -1510,23 +1570,36 @@ func (f *Fs) Precision() time.Duration {
// //
// If it isn't possible then return fs.ErrorCantCopy // If it isn't possible then return fs.ErrorCantCopy
func (f *Fs) Copy(src fs.Object, remote string) (fs.Object, error) { func (f *Fs) Copy(src fs.Object, remote string) (fs.Object, error) {
srcObj, ok := src.(*Object) var srcObj *baseObject
if !ok { srcRemote := src.Remote()
ext := ""
switch src := src.(type) {
case *Object:
srcObj = &src.baseObject
case *documentObject:
srcObj = &src.baseObject
srcRemote, ext = srcRemote[:len(srcRemote)-src.extLen], srcRemote[len(srcRemote)-src.extLen:]
default:
fs.Debugf(src, "Can't copy - not same remote type") fs.Debugf(src, "Can't copy - not same remote type")
return nil, fs.ErrorCantCopy return nil, fs.ErrorCantCopy
} }
if srcObj.documentMimeType != "" {
return nil, errors.New("can't copy a Google document") if ext != "" {
if !strings.HasSuffix(remote, ext) {
fs.Debugf(src, "Can't copy - not same document type")
return nil, fs.ErrorCantCopy
}
remote = remote[:len(remote)-len(ext)]
} }
o, createInfo, err := f.createFileInfo(remote, srcObj.ModTime(), srcObj.bytes) createInfo, err := f.createFileInfo(remote, src.ModTime())
if err != nil { if err != nil {
return nil, err return nil, err
} }
var info *drive.File var info *drive.File
err = o.fs.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
info, err = o.fs.svc.Files.Copy(srcObj.id, createInfo). info, err = f.svc.Files.Copy(srcObj.id, createInfo).
Fields(googleapi.Field(partialFields)). Fields(googleapi.Field(partialFields)).
SupportsTeamDrives(f.isTeamDrive). SupportsTeamDrives(f.isTeamDrive).
KeepRevisionForever(f.opt.KeepRevisionForever). KeepRevisionForever(f.opt.KeepRevisionForever).
@ -1536,9 +1609,7 @@ func (f *Fs) Copy(src fs.Object, remote string) (fs.Object, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return f.newObjectWithInfo(remote, info)
o.setMetaData(info)
return o, nil
} }
// Purge deletes all the files and the container // Purge deletes all the files and the container
@ -1629,21 +1700,35 @@ func (f *Fs) About() (*fs.Usage, error) {
// //
// If it isn't possible then return fs.ErrorCantMove // If it isn't possible then return fs.ErrorCantMove
func (f *Fs) Move(src fs.Object, remote string) (fs.Object, error) { func (f *Fs) Move(src fs.Object, remote string) (fs.Object, error) {
srcObj, ok := src.(*Object) var srcObj *baseObject
if !ok { srcRemote := src.Remote()
ext := ""
switch src := src.(type) {
case *Object:
srcObj = &src.baseObject
case *documentObject:
srcObj = &src.baseObject
srcRemote, ext = srcRemote[:len(srcRemote)-src.extLen], srcRemote[len(srcRemote)-src.extLen:]
default:
fs.Debugf(src, "Can't move - not same remote type") fs.Debugf(src, "Can't move - not same remote type")
return nil, fs.ErrorCantMove return nil, fs.ErrorCantMove
} }
if srcObj.documentMimeType != "" {
return nil, errors.New("can't move a Google document") if ext != "" {
if !strings.HasSuffix(remote, ext) {
fs.Debugf(src, "Can't move - not same document type")
return nil, fs.ErrorCantMove
} }
remote = remote[:len(remote)-len(ext)]
}
_, srcParentID, err := srcObj.fs.dirCache.FindPath(src.Remote(), false) _, srcParentID, err := srcObj.fs.dirCache.FindPath(src.Remote(), false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Temporary Object under construction // Temporary Object under construction
dstObj, dstInfo, err := f.createFileInfo(remote, srcObj.ModTime(), srcObj.bytes) dstInfo, err := f.createFileInfo(remote, src.ModTime())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1665,8 +1750,7 @@ func (f *Fs) Move(src fs.Object, remote string) (fs.Object, error) {
return nil, err return nil, err
} }
dstObj.setMetaData(info) return f.newObjectWithInfo(remote, info)
return dstObj, nil
} }
// PublicLink adds a "readable by anyone with link" permission on the given file or folder. // PublicLink adds a "readable by anyone with link" permission on the given file or folder.
@ -1676,14 +1760,11 @@ func (f *Fs) PublicLink(remote string) (link string, err error) {
fs.Debugf(f, "attempting to share directory '%s'", remote) fs.Debugf(f, "attempting to share directory '%s'", remote)
} else { } else {
fs.Debugf(f, "attempting to share single file '%s'", remote) fs.Debugf(f, "attempting to share single file '%s'", remote)
o := &Object{ o, err := f.NewObject(remote)
fs: f, if err != nil {
remote: remote, return "", err
} }
if err = o.readMetaData(); err != nil { id = o.(fs.IDer).ID()
return
}
id = o.id
} }
permission := &drive.Permission{ permission := &drive.Permission{
@ -1967,10 +2048,15 @@ func (f *Fs) Hashes() hash.Set {
// ------------------------------------------------------------ // ------------------------------------------------------------
// Fs returns the parent Fs // Fs returns the parent Fs
func (o *Object) Fs() fs.Info { func (o *baseObject) Fs() fs.Info {
return o.fs return o.fs
} }
// Return a string version
func (o *baseObject) String() string {
return o.remote
}
// Return a string version // Return a string version
func (o *Object) String() string { func (o *Object) String() string {
if o == nil { if o == nil {
@ -1980,7 +2066,7 @@ func (o *Object) String() string {
} }
// Remote returns the remote path // Remote returns the remote path
func (o *Object) Remote() string { func (o *baseObject) Remote() string {
return o.remote return o.remote
} }
@ -1991,85 +2077,59 @@ func (o *Object) Hash(t hash.Type) (string, error) {
} }
return o.md5sum, nil return o.md5sum, nil
} }
func (o *baseObject) Hash(t hash.Type) (string, error) {
if t != hash.MD5 {
return "", hash.ErrUnsupported
}
return "", nil
}
// Size returns the size of an object in bytes // Size returns the size of an object in bytes
func (o *Object) Size() int64 { func (o *baseObject) Size() int64 {
return o.bytes return o.bytes
} }
// setMetaData sets the fs data from a drive.File // getRemoteInfo returns a drive.File for the remote
func (o *Object) setMetaData(info *drive.File) { func (f *Fs) getRemoteInfo(remote string) (info *drive.File, err error) {
o.id = info.Id info, _, _, _, _, err = f.getRemoteInfoWithExport(remote)
o.url = fmt.Sprintf("%sfiles/%s?alt=media", o.fs.svc.BasePath, info.Id) return
o.md5sum = strings.ToLower(info.Md5Checksum)
o.bytes = info.Size
if o.bytes != -1 && o.fs.opt.V2DownloadMinSize != -1 && o.bytes >= int64(o.fs.opt.V2DownloadMinSize) {
o.v2Download = true
}
if o.fs.opt.UseCreatedDate {
o.modifiedDate = info.CreatedTime
} else {
o.modifiedDate = info.ModifiedTime
}
o.mimeType = info.MimeType
} }
// setGdocsMetaData only sets the gdocs related fields // getRemoteInfoWithExport returns a drive.File and the export settings for the remote
func (o *Object) setGdocsMetaData(info *drive.File, extension, exportMimeType string) { func (f *Fs) getRemoteInfoWithExport(remote string) (
o.url = fmt.Sprintf("%sfiles/%s/export?mimeType=%s", o.fs.svc.BasePath, info.Id, url.QueryEscape(exportMimeType)) info *drive.File, extension, exportName, exportMimeType string, isDocument bool, err error) {
if o.fs.opt.AlternateExport { leaf, directoryID, err := f.dirCache.FindRootAndPath(remote, false)
switch info.MimeType {
case "application/vnd.google-apps.drawing":
o.url = fmt.Sprintf("https://docs.google.com/drawings/d/%s/export/%s", info.Id, extension[1:])
case "application/vnd.google-apps.document":
o.url = fmt.Sprintf("https://docs.google.com/document/d/%s/export?format=%s", info.Id, extension[1:])
case "application/vnd.google-apps.spreadsheet":
o.url = fmt.Sprintf("https://docs.google.com/spreadsheets/d/%s/export?format=%s", info.Id, extension[1:])
case "application/vnd.google-apps.presentation":
o.url = fmt.Sprintf("https://docs.google.com/presentation/d/%s/export/%s", info.Id, extension[1:])
}
}
o.documentMimeType = o.mimeType
o.mimeType = exportMimeType
o.bytes = -1
}
// readMetaData gets the info if it hasn't already been fetched
func (o *Object) readMetaData() (err error) {
if o.id != "" {
return nil
}
leaf, directoryID, err := o.fs.dirCache.FindRootAndPath(o.remote, false)
if err != nil { if err != nil {
if err == fs.ErrorDirNotFound { if err == fs.ErrorDirNotFound {
return fs.ErrorObjectNotFound return nil, "", "", "", false, fs.ErrorObjectNotFound
} }
return err return nil, "", "", "", false, err
} }
found, err := o.fs.list([]string{directoryID}, leaf, false, true, false, func(item *drive.File) bool { found, err := f.list([]string{directoryID}, leaf, false, true, false, func(item *drive.File) bool {
if item.Name == leaf { if !f.opt.SkipGdocs {
o.setMetaData(item) extension, exportName, exportMimeType, isDocument = f.findExportFormat(item)
return true
}
if !o.fs.opt.SkipGdocs {
extension, exportName, exportMimeType, _ := o.fs.findExportFormat(item)
if exportName == leaf { if exportName == leaf {
o.setMetaData(item) info = item
o.setGdocsMetaData(item, extension, exportMimeType)
return true return true
} }
if isDocument {
return false
}
}
if item.Name == leaf {
info = item
return true
} }
return false return false
}) })
if err != nil { if err != nil {
return err return nil, "", "", "", false, err
} }
if !found { if !found {
return fs.ErrorObjectNotFound return nil, "", "", "", false, fs.ErrorObjectNotFound
} }
return nil return
} }
// ModTime returns the modification time of the object // ModTime returns the modification time of the object
@ -2077,12 +2137,7 @@ func (o *Object) readMetaData() (err error) {
// //
// It attempts to read the objects mtime and if that isn't present the // It attempts to read the objects mtime and if that isn't present the
// LastModified returned in the http headers // LastModified returned in the http headers
func (o *Object) ModTime() time.Time { func (o *baseObject) ModTime() time.Time {
err := o.readMetaData()
if err != nil {
fs.Debugf(o, "Failed to read metadata: %v", err)
return time.Now()
}
modTime, err := time.Parse(timeFormatIn, o.modifiedDate) modTime, err := time.Parse(timeFormatIn, o.modifiedDate)
if err != nil { if err != nil {
fs.Debugf(o, "Failed to read mtime from object: %v", err) fs.Debugf(o, "Failed to read mtime from object: %v", err)
@ -2092,18 +2147,15 @@ func (o *Object) ModTime() time.Time {
} }
// SetModTime sets the modification time of the drive fs object // SetModTime sets the modification time of the drive fs object
func (o *Object) SetModTime(modTime time.Time) error { func (o *baseObject) SetModTime(modTime time.Time) error {
err := o.readMetaData()
if err != nil {
return err
}
// New metadata // New metadata
updateInfo := &drive.File{ updateInfo := &drive.File{
ModifiedTime: modTime.Format(timeFormatOut), ModifiedTime: modTime.Format(timeFormatOut),
} }
// Set modified date // Set modified date
var info *drive.File var info *drive.File
err = o.fs.pacer.Call(func() (bool, error) { err := o.fs.pacer.Call(func() (bool, error) {
var err error
info, err = o.fs.svc.Files.Update(o.id, updateInfo). info, err = o.fs.svc.Files.Update(o.id, updateInfo).
Fields(googleapi.Field(partialFields)). Fields(googleapi.Field(partialFields)).
SupportsTeamDrives(o.fs.isTeamDrive). SupportsTeamDrives(o.fs.isTeamDrive).
@ -2114,46 +2166,22 @@ func (o *Object) SetModTime(modTime time.Time) error {
return err return err
} }
// Update info from read data // Update info from read data
o.setMetaData(info) o.modifiedDate = info.ModifiedTime
return nil return nil
} }
// Storable returns a boolean as to whether this object is storable // Storable returns a boolean as to whether this object is storable
func (o *Object) Storable() bool { func (o *baseObject) Storable() bool {
return true return true
} }
// httpResponse gets an http.Response object for the object o.url // httpResponse gets an http.Response object for the object
// using the method passed in // using the url and method passed in
func (o *Object) httpResponse(method string, options []fs.OpenOption) (req *http.Request, res *http.Response, err error) { func (o *baseObject) httpResponse(url, method string, options []fs.OpenOption) (req *http.Request, res *http.Response, err error) {
if o.url == "" { if url == "" {
return nil, nil, errors.New("forbidden to download - check sharing permission") return nil, nil, errors.New("forbidden to download - check sharing permission")
} }
if o.documentMimeType != "" { req, err = http.NewRequest(method, url, nil)
for _, o := range options {
// https://developers.google.com/drive/v3/web/manage-downloads#partial_download
if _, ok := o.(*fs.RangeOption); ok {
return nil, nil, errors.New("partial downloads are not supported while exporting Google Documents")
}
}
}
if o.v2Download {
f := o.fs
var v2File *drive_v2.File
err = f.pacer.Call(func() (bool, error) {
v2File, err = o.fs.v2Svc.Files.Get(o.id).
Fields("downloadUrl").
SupportsTeamDrives(f.isTeamDrive).
Do()
return shouldRetry(err)
})
if err == nil {
fs.Debugf(o, "Using v2 download: %v", v2File.DownloadUrl)
o.url = v2File.DownloadUrl
o.v2Download = false
}
}
req, err = http.NewRequest(method, o.url, nil)
if err != nil { if err != nil {
return req, nil, err return req, nil, err
} }
@ -2174,9 +2202,10 @@ func (o *Object) httpResponse(method string, options []fs.OpenOption) (req *http
return req, res, nil return req, res, nil
} }
// openFile represents an Object open for reading // openDocumentFile represents an documentObject open for reading.
type openFile struct { // Updates the object size after read successfully.
o *Object // Object we are reading for type openDocumentFile struct {
o *documentObject // Object we are reading for
in io.ReadCloser // reading from here in io.ReadCloser // reading from here
bytes int64 // number of bytes read on this connection bytes int64 // number of bytes read on this connection
eof bool // whether we have read end of file eof bool // whether we have read end of file
@ -2184,7 +2213,7 @@ type openFile struct {
} }
// Read bytes from the object - see io.Reader // Read bytes from the object - see io.Reader
func (file *openFile) Read(p []byte) (n int, err error) { func (file *openDocumentFile) Read(p []byte) (n int, err error) {
n, err = file.in.Read(p) n, err = file.in.Read(p)
file.bytes += int64(n) file.bytes += int64(n)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
@ -2197,7 +2226,7 @@ func (file *openFile) Read(p []byte) (n int, err error) {
} }
// Close the object and update bytes read // Close the object and update bytes read
func (file *openFile) Close() (err error) { func (file *openDocumentFile) Close() (err error) {
// If end of file, update bytes read // If end of file, update bytes read
if file.eof && !file.errored { if file.eof && !file.errored {
fs.Debugf(file.o, "Updating size of doc after download to %v", file.bytes) fs.Debugf(file.o, "Updating size of doc after download to %v", file.bytes)
@ -2207,7 +2236,7 @@ func (file *openFile) Close() (err error) {
} }
// Check it satisfies the interfaces // Check it satisfies the interfaces
var _ io.ReadCloser = &openFile{} var _ io.ReadCloser = (*openDocumentFile)(nil)
// Checks to see if err is a googleapi.Error with of type what // Checks to see if err is a googleapi.Error with of type what
func isGoogleError(err error, what string) bool { func isGoogleError(err error, what string) bool {
@ -2221,20 +2250,20 @@ func isGoogleError(err error, what string) bool {
return false return false
} }
// Open an object for read // open a url for reading
func (o *Object) Open(options ...fs.OpenOption) (in io.ReadCloser, err error) { func (o *baseObject) open(url string, options ...fs.OpenOption) (in io.ReadCloser, err error) {
_, res, err := o.httpResponse("GET", options) _, res, err := o.httpResponse(url, "GET", options)
if err != nil { if err != nil {
if isGoogleError(err, "cannotDownloadAbusiveFile") { if isGoogleError(err, "cannotDownloadAbusiveFile") {
if o.fs.opt.AcknowledgeAbuse { if o.fs.opt.AcknowledgeAbuse {
// Retry acknowledging abuse // Retry acknowledging abuse
if strings.ContainsRune(o.url, '?') { if strings.ContainsRune(url, '?') {
o.url += "&" url += "&"
} else { } else {
o.url += "?" url += "?"
} }
o.url += "acknowledgeAbuse=true" url += "acknowledgeAbuse=true"
_, res, err = o.httpResponse("GET", options) _, res, err = o.httpResponse(url, "GET", options)
} else { } else {
err = errors.Wrap(err, "Use the --drive-acknowledge-abuse flag to download this file") err = errors.Wrap(err, "Use the --drive-acknowledge-abuse flag to download this file")
} }
@ -2243,77 +2272,132 @@ func (o *Object) Open(options ...fs.OpenOption) (in io.ReadCloser, err error) {
return nil, errors.Wrap(err, "open file failed") return nil, errors.Wrap(err, "open file failed")
} }
} }
// If it is a document, update the size with what we are
// reading as it can change from the HEAD in the listing to
// this GET. This stops rclone marking the transfer as
// corrupted.
if o.documentMimeType != "" {
return &openFile{o: o, in: res.Body}, nil
}
return res.Body, nil return res.Body, nil
} }
// Open an object for read
func (o *Object) Open(options ...fs.OpenOption) (in io.ReadCloser, err error) {
if o.v2Download {
var v2File *drive_v2.File
err = o.fs.pacer.Call(func() (bool, error) {
v2File, err = o.fs.v2Svc.Files.Get(o.id).
Fields("downloadUrl").
SupportsTeamDrives(o.fs.isTeamDrive).
Do()
return shouldRetry(err)
})
if err == nil {
fs.Debugf(o, "Using v2 download: %v", v2File.DownloadUrl)
o.url = v2File.DownloadUrl
o.v2Download = false
}
}
return o.baseObject.open(o.url, options...)
}
func (o *documentObject) Open(options ...fs.OpenOption) (in io.ReadCloser, err error) {
// Update the size with what we are reading as it can change from
// the HEAD in the listing to this GET. This stops rclone marking
// the transfer as corrupted.
for _, o := range options {
// https://developers.google.com/drive/v3/web/manage-downloads#partial_download
if _, ok := o.(*fs.RangeOption); ok {
return nil, errors.New("partial downloads are not supported while exporting Google Documents")
}
}
in, err = o.baseObject.open(o.url, options...)
if in != nil {
in = &openDocumentFile{o: o, in: in}
}
return
}
func (o *baseObject) update(updateInfo *drive.File, uploadMimeType string, in io.Reader,
src fs.ObjectInfo) (info *drive.File, err error) {
// Make the API request to upload metadata and file data.
size := src.Size()
if size == 0 || size < int64(o.fs.opt.UploadCutoff) {
// Don't retry, return a retry error instead
err = o.fs.pacer.CallNoRetry(func() (bool, error) {
info, err = o.fs.svc.Files.Update(o.id, updateInfo).
Media(in, googleapi.ContentType(uploadMimeType)).
Fields(googleapi.Field(partialFields)).
SupportsTeamDrives(o.fs.isTeamDrive).
KeepRevisionForever(o.fs.opt.KeepRevisionForever).
Do()
return shouldRetry(err)
})
return
}
// Upload the file in chunks
return o.fs.Upload(in, size, uploadMimeType, o.id, updateInfo, o.remote)
}
// Update the already existing object // Update the already existing object
// //
// Copy the reader into the object updating modTime and size // Copy the reader into the object updating modTime and size
// //
// The new object may have been created if an error is returned // The new object may have been created if an error is returned
func (o *Object) Update(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error { func (o *Object) Update(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error {
size := src.Size() srcMimeType := fs.MimeType(src)
modTime := src.ModTime() updateInfo := &drive.File{
MimeType: srcMimeType,
ModifiedTime: src.ModTime().Format(timeFormatOut),
}
info, err := o.baseObject.update(updateInfo, srcMimeType, in, src)
if err != nil {
return err
}
newO, err := o.fs.newObjectWithInfo(src.Remote(), info)
switch newO := newO.(type) {
case *Object:
*o = *newO
default:
return errors.New("object type changed by update")
}
return nil
}
func (o *documentObject) Update(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error {
srcMimeType := fs.MimeType(src) srcMimeType := fs.MimeType(src)
importMimeType := "" importMimeType := ""
updateInfo := &drive.File{ updateInfo := &drive.File{
MimeType: srcMimeType, MimeType: srcMimeType,
ModifiedTime: modTime.Format(timeFormatOut), ModifiedTime: src.ModTime().Format(timeFormatOut),
} }
if o.fs.importMimeTypes != nil && !o.fs.opt.SkipGdocs { if o.fs.importMimeTypes == nil || o.fs.opt.SkipGdocs {
return errors.Errorf("can't update google document type without --drive-import-formats")
}
importMimeType = o.fs.findImportFormat(updateInfo.MimeType) importMimeType = o.fs.findImportFormat(updateInfo.MimeType)
if importMimeType != "" { if importMimeType == "" {
// FIXME: check importMimeType against original object MIME type return errors.Errorf("no import format found for %q", srcMimeType)
// if importMimeType != o.mimeType {
// return errors.Errorf("can't change google document type (o: %q, src: %q, import: %q)", o.mimeType, srcMimeType, importMimeType)
// }
updateInfo.MimeType = importMimeType
} }
if importMimeType != o.documentMimeType {
return errors.Errorf("can't change google document type (o: %q, src: %q, import: %q)", o.documentMimeType, srcMimeType, importMimeType)
}
updateInfo.MimeType = importMimeType
info, err := o.baseObject.update(updateInfo, srcMimeType, in, src)
if err != nil {
return err
} }
// Make the API request to upload metadata and file data. remote := src.Remote()
var err error remote = remote[:len(remote)-o.extLen]
var info *drive.File
if size == 0 || size < int64(o.fs.opt.UploadCutoff) { newO, err := o.fs.newObjectWithInfo(remote, info)
// Don't retry, return a retry error instead switch newO := newO.(type) {
err = o.fs.pacer.CallNoRetry(func() (bool, error) { case *documentObject:
info, err = o.fs.svc.Files.Update(o.id, updateInfo). *o = *newO
Media(in, googleapi.ContentType(srcMimeType)). default:
Fields(googleapi.Field(partialFields)). return errors.New("object type changed by update")
SupportsTeamDrives(o.fs.isTeamDrive).
KeepRevisionForever(o.fs.opt.KeepRevisionForever).
Do()
return shouldRetry(err)
})
if err != nil {
return err
}
} else {
// Upload the file in chunks
info, err = o.fs.Upload(in, size, srcMimeType, o.id, updateInfo, o.remote)
if err != nil {
return err
}
}
o.setMetaData(info)
if importMimeType != "" {
extension, exportMimeType, _ := o.fs.findExportFormatByMimeType(importMimeType)
o.setGdocsMetaData(info, extension, exportMimeType)
} }
return nil return nil
} }
// Remove an object // Remove an object
func (o *Object) Remove() error { func (o *baseObject) Remove() error {
var err error var err error
err = o.fs.pacer.Call(func() (bool, error) { err = o.fs.pacer.Call(func() (bool, error) {
if o.fs.opt.UseTrash { if o.fs.opt.UseTrash {
@ -2336,17 +2420,12 @@ func (o *Object) Remove() error {
} }
// MimeType of an Object if known, "" otherwise // MimeType of an Object if known, "" otherwise
func (o *Object) MimeType() string { func (o *baseObject) MimeType() string {
err := o.readMetaData()
if err != nil {
fs.Debugf(o, "Failed to read metadata: %v", err)
return ""
}
return o.mimeType return o.mimeType
} }
// ID returns the ID of the Object if known, or "" if not // ID returns the ID of the Object if known, or "" if not
func (o *Object) ID() string { func (o *baseObject) ID() string {
return o.id return o.id
} }
@ -2369,4 +2448,7 @@ var (
_ fs.Object = (*Object)(nil) _ fs.Object = (*Object)(nil)
_ fs.MimeTyper = (*Object)(nil) _ fs.MimeTyper = (*Object)(nil)
_ fs.IDer = (*Object)(nil) _ fs.IDer = (*Object)(nil)
_ fs.Object = (*documentObject)(nil)
_ fs.MimeTyper = (*documentObject)(nil)
_ fs.IDer = (*documentObject)(nil)
) )