mirror of
https://github.com/rclone/rclone.git
synced 2024-11-23 00:43:49 +01:00
drive: restructure Object type
This commit is contained in:
parent
171e39b230
commit
0b2fc621fc
@ -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)
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user