Make ContentType be preserved for cloud -> cloud copies - fixes #733

This commit is contained in:
Nick Craig-Wood 2016-09-21 22:13:24 +01:00
parent 6c9a258d82
commit 945f49ab5e
28 changed files with 265 additions and 96 deletions

View File

@ -846,6 +846,14 @@ func (o *Object) Remove() error {
return err return err
} }
// MimeType of an Object if known, "" otherwise
func (o *Object) MimeType() string {
if o.info.ContentProperties.ContentType != nil {
return *o.info.ContentProperties.ContentType
}
return ""
}
// Check the interfaces are satisfied // Check the interfaces are satisfied
var ( var (
_ fs.Fs = (*Fs)(nil) _ fs.Fs = (*Fs)(nil)
@ -853,5 +861,6 @@ var (
// _ fs.Copier = (*Fs)(nil) // _ fs.Copier = (*Fs)(nil)
// _ fs.Mover = (*Fs)(nil) // _ fs.Mover = (*Fs)(nil)
// _ fs.DirMover = (*Fs)(nil) // _ fs.DirMover = (*Fs)(nil)
_ fs.Object = (*Object)(nil) _ fs.Object = (*Object)(nil)
_ fs.MimeTyper = &Object{}
) )

View File

@ -46,6 +46,7 @@ func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) } func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) } func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) }
func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) } func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) }
func TestObjectMimeType(t *testing.T) { fstests.TestObjectMimeType(t) }
func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) } func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) }
func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) } func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) }
func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) } func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) }

View File

@ -98,12 +98,13 @@ type Fs struct {
// Object describes a b2 object // Object describes a b2 object
type Object 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 // b2 id of the file id string // b2 id of the file
modTime time.Time // The modified time of the object if known modTime time.Time // The modified time of the object if known
sha1 string // SHA-1 hash if known sha1 string // SHA-1 hash if known
size int64 // Size of the object size int64 // Size of the object
mimeType string // Content-Type of the object
} }
// ------------------------------------------------------------ // ------------------------------------------------------------
@ -896,9 +897,10 @@ func (o *Object) Size() int64 {
// o.modTime // o.modTime
// o.size // o.size
// o.sha1 // o.sha1
func (o *Object) decodeMetaDataRaw(ID, SHA1 string, Size int64, UploadTimestamp api.Timestamp, Info map[string]string) (err error) { func (o *Object) decodeMetaDataRaw(ID, SHA1 string, Size int64, UploadTimestamp api.Timestamp, Info map[string]string, mimeType string) (err error) {
o.id = ID o.id = ID
o.sha1 = SHA1 o.sha1 = SHA1
o.mimeType = mimeType
// Read SHA1 from metadata if it exists and isn't set // Read SHA1 from metadata if it exists and isn't set
if o.sha1 == "" || o.sha1 == "none" { if o.sha1 == "" || o.sha1 == "none" {
o.sha1 = Info[sha1Key] o.sha1 = Info[sha1Key]
@ -917,7 +919,7 @@ func (o *Object) decodeMetaDataRaw(ID, SHA1 string, Size int64, UploadTimestamp
// o.size // o.size
// o.sha1 // o.sha1
func (o *Object) decodeMetaData(info *api.File) (err error) { func (o *Object) decodeMetaData(info *api.File) (err error) {
return o.decodeMetaDataRaw(info.ID, info.SHA1, info.Size, info.UploadTimestamp, info.Info) return o.decodeMetaDataRaw(info.ID, info.SHA1, info.Size, info.UploadTimestamp, info.Info, info.ContentType)
} }
// decodeMetaDataFileInfo sets the metadata in the object from an api.FileInfo // decodeMetaDataFileInfo sets the metadata in the object from an api.FileInfo
@ -928,7 +930,7 @@ func (o *Object) decodeMetaData(info *api.File) (err error) {
// o.size // o.size
// o.sha1 // o.sha1
func (o *Object) decodeMetaDataFileInfo(info *api.FileInfo) (err error) { func (o *Object) decodeMetaDataFileInfo(info *api.FileInfo) (err error) {
return o.decodeMetaDataRaw(info.ID, info.SHA1, info.Size, info.UploadTimestamp, info.Info) return o.decodeMetaDataRaw(info.ID, info.SHA1, info.Size, info.UploadTimestamp, info.Info, info.ContentType)
} }
// readMetaData gets the metadata if it hasn't already been fetched // readMetaData gets the metadata if it hasn't already been fetched
@ -1285,7 +1287,7 @@ func (o *Object) Update(in io.Reader, src fs.ObjectInfo) (err error) {
ExtraHeaders: map[string]string{ ExtraHeaders: map[string]string{
"Authorization": upload.AuthorizationToken, "Authorization": upload.AuthorizationToken,
"X-Bz-File-Name": urlEncode(o.fs.root + o.remote), "X-Bz-File-Name": urlEncode(o.fs.root + o.remote),
"Content-Type": fs.MimeType(o), "Content-Type": fs.MimeType(src),
sha1Header: calculatedSha1, sha1Header: calculatedSha1,
timeHeader: timeString(modTime), timeHeader: timeString(modTime),
}, },
@ -1337,10 +1339,16 @@ func (o *Object) Remove() error {
return nil return nil
} }
// MimeType of an Object if known, "" otherwise
func (o *Object) MimeType() string {
return o.mimeType
}
// Check the interfaces are satisfied // Check the interfaces are satisfied
var ( var (
_ fs.Fs = &Fs{} _ fs.Fs = &Fs{}
_ fs.Purger = &Fs{} _ fs.Purger = &Fs{}
_ fs.CleanUpper = &Fs{} _ fs.CleanUpper = &Fs{}
_ fs.Object = &Object{} _ fs.Object = &Object{}
_ fs.MimeTyper = &Object{}
) )

View File

@ -46,6 +46,7 @@ func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) } func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) } func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) }
func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) } func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) }
func TestObjectMimeType(t *testing.T) { fstests.TestObjectMimeType(t) }
func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) } func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) }
func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) } func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) }
func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) } func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) }

View File

@ -47,6 +47,7 @@ func TestObjectFs2(t *testing.T) { fstests.TestObjectFs(t) }
func TestObjectRemote2(t *testing.T) { fstests.TestObjectRemote(t) } func TestObjectRemote2(t *testing.T) { fstests.TestObjectRemote(t) }
func TestObjectHashes2(t *testing.T) { fstests.TestObjectHashes(t) } func TestObjectHashes2(t *testing.T) { fstests.TestObjectHashes(t) }
func TestObjectModTime2(t *testing.T) { fstests.TestObjectModTime(t) } func TestObjectModTime2(t *testing.T) { fstests.TestObjectModTime(t) }
func TestObjectMimeType2(t *testing.T) { fstests.TestObjectMimeType(t) }
func TestObjectSetModTime2(t *testing.T) { fstests.TestObjectSetModTime(t) } func TestObjectSetModTime2(t *testing.T) { fstests.TestObjectSetModTime(t) }
func TestObjectSize2(t *testing.T) { fstests.TestObjectSize(t) } func TestObjectSize2(t *testing.T) { fstests.TestObjectSize(t) }
func TestObjectOpen2(t *testing.T) { fstests.TestObjectOpen(t) } func TestObjectOpen2(t *testing.T) { fstests.TestObjectOpen(t) }

View File

@ -47,6 +47,7 @@ func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) } func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) } func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) }
func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) } func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) }
func TestObjectMimeType(t *testing.T) { fstests.TestObjectMimeType(t) }
func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) } func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) }
func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) } func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) }
func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) } func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) }

View File

@ -15,19 +15,19 @@ show through.
Here is an overview of the major features of each cloud storage system. Here is an overview of the major features of each cloud storage system.
| Name | Hash | ModTime | Case Insensitive | Duplicate Files | | Name | Hash | ModTime | Case Insensitive | Duplicate Files | MIME Type |
| ---------------------- |:-------:|:-------:|:----------------:|:---------------:| | ---------------------- |:-------:|:-------:|:----------------:|:---------------:|:---------:|
| Google Drive | MD5 | Yes | No | Yes | | Google Drive | MD5 | Yes | No | Yes | R/W |
| Amazon S3 | MD5 | Yes | No | No | | Amazon S3 | MD5 | Yes | No | No | R/W |
| Openstack Swift | MD5 | Yes | No | No | | Openstack Swift | MD5 | Yes | No | No | R/W |
| Dropbox | - | No | Yes | No | | Dropbox | - | No | Yes | No | R |
| Google Cloud Storage | MD5 | Yes | No | No | | Google Cloud Storage | MD5 | Yes | No | No | R/W |
| Amazon Drive | MD5 | No | Yes | No | | Amazon Drive | MD5 | No | Yes | No | R |
| Microsoft One Drive | SHA1 | Yes | Yes | No | | Microsoft One Drive | SHA1 | Yes | Yes | No | R |
| Hubic | MD5 | Yes | No | No | | Hubic | MD5 | Yes | No | No | R/W |
| Backblaze B2 | SHA1 | Yes | No | No | | Backblaze B2 | SHA1 | Yes | No | No | R/W |
| Yandex Disk | MD5 | Yes | No | No | | Yandex Disk | MD5 | Yes | No | No | R/W |
| The local filesystem | All | Yes | Depends | No | | The local filesystem | All | Yes | Depends | No | - |
### Hash ### ### Hash ###
@ -78,6 +78,23 @@ objects with the same name.
This confuses rclone greatly when syncing - use the `rclone dedupe` This confuses rclone greatly when syncing - use the `rclone dedupe`
command to rename or remove duplicates. command to rename or remove duplicates.
### MIME Type ###
MIME types (also known as media types) classify types of documents
using a simple text classification, eg `text/html` or
`application/pdf`.
Some cloud storage systems support reading (`R`) the MIME type of
objects and some support writing (`W`) the MIME type of objects.
The MIME type can be important if you are serving files directly to
HTTP from the storage system.
If you are copying from a remote which supports reading (`R`) to a
remote which supports writing (`W`) then rclone will preserve the MIME
types. Otherwise they will be guessed from the extension, or the
remote itself may assign the MIME type.
## Optional Features ## ## Optional Features ##
All the remotes support a basic set of features, but there are some All the remotes support a basic set of features, but there are some

View File

@ -137,6 +137,7 @@ type Object struct {
bytes int64 // size of the object bytes int64 // size of the object
modifiedDate string // RFC3339 time it was last modified modifiedDate string // RFC3339 time it was last modified
isDocument bool // if set this is a Google doc isDocument bool // if set this is a Google doc
mimeType string
} }
// ------------------------------------------------------------ // ------------------------------------------------------------
@ -533,7 +534,7 @@ func (f *Fs) createFileInfo(remote string, modTime time.Time, size int64) (*Obje
Title: leaf, Title: leaf,
Description: leaf, Description: leaf,
Parents: []*drive.ParentReference{{Id: directoryID}}, Parents: []*drive.ParentReference{{Id: directoryID}},
MimeType: fs.MimeType(o), MimeType: fs.MimeTypeFromName(remote),
ModifiedDate: modTime.Format(timeFormatOut), ModifiedDate: modTime.Format(timeFormatOut),
} }
return o, createInfo, nil return o, createInfo, nil
@ -845,6 +846,7 @@ func (o *Object) setMetaData(info *drive.File) {
o.md5sum = strings.ToLower(info.Md5Checksum) o.md5sum = strings.ToLower(info.Md5Checksum)
o.bytes = info.FileSize o.bytes = info.FileSize
o.modifiedDate = info.ModifiedDate o.modifiedDate = info.ModifiedDate
o.mimeType = info.MimeType
} }
// readMetaData gets the info if it hasn't already been fetched // readMetaData gets the info if it hasn't already been fetched
@ -1009,7 +1011,7 @@ func (o *Object) Update(in io.Reader, src fs.ObjectInfo) error {
} }
updateInfo := &drive.File{ updateInfo := &drive.File{
Id: o.id, Id: o.id,
MimeType: fs.MimeType(o), MimeType: fs.MimeType(src),
ModifiedDate: modTime.Format(timeFormatOut), ModifiedDate: modTime.Format(timeFormatOut),
} }
@ -1027,7 +1029,7 @@ func (o *Object) Update(in io.Reader, src fs.ObjectInfo) error {
} }
} else { } else {
// Upload the file in chunks // Upload the file in chunks
info, err = o.fs.Upload(in, size, fs.MimeType(o), updateInfo, o.remote) info, err = o.fs.Upload(in, size, updateInfo.MimeType, updateInfo, o.remote)
if err != nil { if err != nil {
return err return err
} }
@ -1053,6 +1055,16 @@ func (o *Object) Remove() error {
return err return err
} }
// MimeType of an Object if known, "" otherwise
func (o *Object) MimeType() string {
err := o.readMetaData()
if err != nil {
fs.Log(o, "Failed to read metadata: %v", err)
return ""
}
return o.mimeType
}
// Check the interfaces are satisfied // Check the interfaces are satisfied
var ( var (
_ fs.Fs = (*Fs)(nil) _ fs.Fs = (*Fs)(nil)
@ -1062,4 +1074,5 @@ var (
_ fs.DirMover = (*Fs)(nil) _ fs.DirMover = (*Fs)(nil)
_ fs.PutUncheckeder = (*Fs)(nil) _ fs.PutUncheckeder = (*Fs)(nil)
_ fs.Object = (*Object)(nil) _ fs.Object = (*Object)(nil)
_ fs.MimeTyper = &Object{}
) )

View File

@ -46,6 +46,7 @@ func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) } func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) } func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) }
func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) } func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) }
func TestObjectMimeType(t *testing.T) { fstests.TestObjectMimeType(t) }
func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) } func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) }
func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) } func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) }
func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) } func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) }

View File

@ -110,6 +110,7 @@ type Object struct {
bytes int64 // size of the object bytes int64 // size of the object
modTime time.Time // time it was last modified modTime time.Time // time it was last modified
hasMetadata bool // metadata is valid hasMetadata bool // metadata is valid
mimeType string // content type according to the server
} }
// ------------------------------------------------------------ // ------------------------------------------------------------
@ -622,6 +623,7 @@ func (o *Object) Size() int64 {
func (o *Object) setMetadataFromEntry(info *dropbox.Entry) { func (o *Object) setMetadataFromEntry(info *dropbox.Entry) {
o.bytes = info.Bytes o.bytes = info.Bytes
o.modTime = time.Time(info.ClientMtime) o.modTime = time.Time(info.ClientMtime)
o.mimeType = info.MimeType
o.hasMetadata = true o.hasMetadata = true
} }
@ -745,12 +747,23 @@ func (o *Object) Remove() error {
return err return err
} }
// MimeType of an Object if known, "" otherwise
func (o *Object) MimeType() string {
err := o.readMetaData()
if err != nil {
fs.Log(o, "Failed to read metadata: %v", err)
return ""
}
return o.mimeType
}
// Check the interfaces are satisfied // Check the interfaces are satisfied
var ( var (
_ fs.Fs = (*Fs)(nil) _ fs.Fs = (*Fs)(nil)
_ fs.Copier = (*Fs)(nil) _ fs.Copier = (*Fs)(nil)
_ fs.Purger = (*Fs)(nil) _ fs.Purger = (*Fs)(nil)
_ fs.Mover = (*Fs)(nil) _ fs.Mover = (*Fs)(nil)
_ fs.DirMover = (*Fs)(nil) _ fs.DirMover = (*Fs)(nil)
_ fs.Object = (*Object)(nil) _ fs.Object = (*Object)(nil)
_ fs.MimeTyper = (*Object)(nil)
) )

View File

@ -46,6 +46,7 @@ func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) } func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) } func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) }
func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) } func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) }
func TestObjectMimeType(t *testing.T) { fstests.TestObjectMimeType(t) }
func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) } func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) }
func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) } func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) }
func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) } func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) }

View File

@ -210,6 +210,13 @@ type BasicInfo interface {
Size() int64 Size() int64
} }
// MimeTyper is an optional interface for Object
type MimeTyper interface {
// MimeType returns the content type of the Object if
// known, or "" if not
MimeType() string
}
// Purger is an optional interfaces for Fs // Purger is an optional interfaces for Fs
type Purger interface { type Purger interface {
// Purge all files in the root and the root directory // Purge all files in the root and the root directory

View File

@ -169,15 +169,29 @@ func Equal(src, dst Object) bool {
return true return true
} }
// MimeType returns a guess at the mime type from the extension // MimeTypeFromName returns a guess at the mime type from the name
func MimeType(o ObjectInfo) string { func MimeTypeFromName(remote string) (mimeType string) {
mimeType := mime.TypeByExtension(path.Ext(o.Remote())) mimeType = mime.TypeByExtension(path.Ext(remote))
if !strings.ContainsRune(mimeType, '/') { if !strings.ContainsRune(mimeType, '/') {
mimeType = "application/octet-stream" mimeType = "application/octet-stream"
} }
return mimeType return mimeType
} }
// MimeType returns the MimeType from the object, either by calling
// the MimeTyper interface or using MimeTypeFromName
func MimeType(o ObjectInfo) (mimeType string) {
// Read the MimeType from the optional interface if available
if do, ok := o.(MimeTyper); ok {
mimeType = do.MimeType()
Debug(o, "Read MimeType as %q", mimeType)
if mimeType != "" {
return mimeType
}
}
return MimeTypeFromName(o.Remote())
}
// Used to remove a failed copy // Used to remove a failed copy
// //
// Returns whether the file was succesfully removed or not // Returns whether the file was succesfully removed or not

View File

@ -501,6 +501,22 @@ func TestObjectModTime(t *testing.T) {
file1.CheckModTime(t, obj, obj.ModTime(), remote.Precision()) file1.CheckModTime(t, obj, obj.ModTime(), remote.Precision())
} }
// TestObjectMimeType tests the MimeType of the object is correct
func TestObjectMimeType(t *testing.T) {
skipIfNotOk(t)
obj := findObject(t, file1.Path)
do, ok := obj.(fs.MimeTyper)
if !ok {
t.Skip("MimeType method not supported")
}
mimeType := do.MimeType()
if strings.ContainsRune(mimeType, ';') {
assert.Equal(t, "text/plain; charset=utf-8", mimeType)
} else {
assert.Equal(t, "text/plain", mimeType)
}
}
// TestObjectSetModTime tests that SetModTime works // TestObjectSetModTime tests that SetModTime works
func TestObjectSetModTime(t *testing.T) { func TestObjectSetModTime(t *testing.T) {
skipIfNotOk(t) skipIfNotOk(t)

View File

@ -143,12 +143,13 @@ type Fs struct {
// //
// Will definitely have info but maybe not meta // Will definitely have info but maybe not meta
type Object 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
url string // download path url string // download path
md5sum string // The MD5Sum of the object md5sum string // The MD5Sum of the object
bytes int64 // Bytes in the object bytes int64 // Bytes in the object
modTime time.Time // Modified time of the object modTime time.Time // Modified time of the object
mimeType string
} }
// ------------------------------------------------------------ // ------------------------------------------------------------
@ -558,6 +559,7 @@ func (o *Object) Size() int64 {
func (o *Object) setMetaData(info *storage.Object) { func (o *Object) setMetaData(info *storage.Object) {
o.url = info.MediaLink o.url = info.MediaLink
o.bytes = int64(info.Size) o.bytes = int64(info.Size)
o.mimeType = info.ContentType
// Read md5sum // Read md5sum
md5sumData, err := base64.StdEncoding.DecodeString(info.Md5Hash) md5sumData, err := base64.StdEncoding.DecodeString(info.Md5Hash)
@ -675,7 +677,7 @@ func (o *Object) Update(in io.Reader, src fs.ObjectInfo) error {
object := storage.Object{ object := storage.Object{
Bucket: o.fs.bucket, Bucket: o.fs.bucket,
Name: o.fs.root + o.remote, Name: o.fs.root + o.remote,
ContentType: fs.MimeType(o), ContentType: fs.MimeType(src),
Size: uint64(size), Size: uint64(size),
Updated: modTime.Format(timeFormatOut), // Doesn't get set Updated: modTime.Format(timeFormatOut), // Doesn't get set
Metadata: metadataFromModTime(modTime), Metadata: metadataFromModTime(modTime),
@ -694,9 +696,15 @@ func (o *Object) Remove() error {
return o.fs.svc.Objects.Delete(o.fs.bucket, o.fs.root+o.remote).Do() return o.fs.svc.Objects.Delete(o.fs.bucket, o.fs.root+o.remote).Do()
} }
// MimeType of an Object if known, "" otherwise
func (o *Object) MimeType() string {
return o.mimeType
}
// Check the interfaces are satisfied // Check the interfaces are satisfied
var ( var (
_ fs.Fs = &Fs{} _ fs.Fs = &Fs{}
_ fs.Copier = &Fs{} _ fs.Copier = &Fs{}
_ fs.Object = &Object{} _ fs.Object = &Object{}
_ fs.MimeTyper = &Object{}
) )

View File

@ -46,6 +46,7 @@ func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) } func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) } func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) }
func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) } func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) }
func TestObjectMimeType(t *testing.T) { fstests.TestObjectMimeType(t) }
func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) } func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) }
func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) } func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) }
func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) } func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) }

View File

@ -46,6 +46,7 @@ func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) } func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) } func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) }
func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) } func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) }
func TestObjectMimeType(t *testing.T) { fstests.TestObjectMimeType(t) }
func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) } func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) }
func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) } func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) }
func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) } func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) }

View File

@ -46,6 +46,7 @@ func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) } func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) } func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) }
func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) } func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) }
func TestObjectMimeType(t *testing.T) { fstests.TestObjectMimeType(t) }
func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) } func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) }
func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) } func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) }
func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) } func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) }

View File

@ -98,6 +98,7 @@ type Object struct {
modTime time.Time // modification time of the object modTime time.Time // modification time of the object
id string // ID of the object id string // ID of the object
sha1 string // SHA-1 of the object content sha1 string // SHA-1 of the object content
mimeType string // Content-Type of object from server (may not be as uploaded)
} }
// ------------------------------------------------------------ // ------------------------------------------------------------
@ -686,8 +687,11 @@ func (o *Object) setMetaData(info *api.Item) {
// fact uppercase hex strings. // fact uppercase hex strings.
// //
// In OneDrive for Business, SHA1 and CRC32 hash values are not returned for files. // In OneDrive for Business, SHA1 and CRC32 hash values are not returned for files.
if info.File != nil && info.File.Hashes.Sha1Hash != "" { if info.File != nil {
o.sha1 = strings.ToLower(info.File.Hashes.Sha1Hash) o.mimeType = info.File.MimeType
if info.File.Hashes.Sha1Hash != "" {
o.sha1 = strings.ToLower(info.File.Hashes.Sha1Hash)
}
} }
if info.FileSystemInfo != nil { if info.FileSystemInfo != nil {
o.modTime = time.Time(info.FileSystemInfo.LastModifiedDateTime) o.modTime = time.Time(info.FileSystemInfo.LastModifiedDateTime)
@ -935,6 +939,11 @@ func (o *Object) Remove() error {
return o.fs.deleteObject(o.id) return o.fs.deleteObject(o.id)
} }
// MimeType of an Object if known, "" otherwise
func (o *Object) MimeType() string {
return o.mimeType
}
// Check the interfaces are satisfied // Check the interfaces are satisfied
var ( var (
_ fs.Fs = (*Fs)(nil) _ fs.Fs = (*Fs)(nil)
@ -942,5 +951,6 @@ var (
_ fs.Copier = (*Fs)(nil) _ fs.Copier = (*Fs)(nil)
// _ fs.Mover = (*Fs)(nil) // _ fs.Mover = (*Fs)(nil)
// _ fs.DirMover = (*Fs)(nil) // _ fs.DirMover = (*Fs)(nil)
_ fs.Object = (*Object)(nil) _ fs.Object = (*Object)(nil)
_ fs.MimeTyper = &Object{}
) )

View File

@ -46,6 +46,7 @@ func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) } func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) } func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) }
func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) } func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) }
func TestObjectMimeType(t *testing.T) { fstests.TestObjectMimeType(t) }
func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) } func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) }
func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) } func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) }
func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) } func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) }

View File

@ -230,14 +230,15 @@ type Fs struct {
type Object struct { type Object struct {
// Will definitely have everything but meta which may be nil // Will definitely have everything but meta which may be nil
// //
// List will read everything but meta - to fill that in need to call // List will read everything but meta & mimeType - to fill
// readMetaData // that in you need to call readMetaData
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
etag string // md5sum of the object etag string // md5sum of the object
bytes int64 // size of the object bytes int64 // size of the object
lastModified time.Time // Last modified lastModified time.Time // Last modified
meta map[string]*string // The object metadata if known - may be nil meta map[string]*string // The object metadata if known - may be nil
mimeType string // MimeType of object - may be ""
} }
// ------------------------------------------------------------ // ------------------------------------------------------------
@ -777,6 +778,7 @@ func (o *Object) readMetaData() (err error) {
} else { } else {
o.lastModified = *resp.LastModified o.lastModified = *resp.LastModified
} }
o.mimeType = aws.StringValue(resp.ContentType)
return nil return nil
} }
@ -818,7 +820,7 @@ func (o *Object) SetModTime(modTime time.Time) error {
} }
// Guess the content type // Guess the content type
contentType := fs.MimeType(o) mimeType := fs.MimeType(o)
// Copy the object to itself to update the metadata // Copy the object to itself to update the metadata
key := o.fs.root + o.remote key := o.fs.root + o.remote
@ -828,7 +830,7 @@ func (o *Object) SetModTime(modTime time.Time) error {
Bucket: &o.fs.bucket, Bucket: &o.fs.bucket,
ACL: &o.fs.acl, ACL: &o.fs.acl,
Key: &key, Key: &key,
ContentType: &contentType, ContentType: &mimeType,
CopySource: aws.String(url.QueryEscape(sourceKey)), CopySource: aws.String(url.QueryEscape(sourceKey)),
Metadata: o.meta, Metadata: o.meta,
MetadataDirective: &directive, MetadataDirective: &directive,
@ -880,7 +882,7 @@ func (o *Object) Update(in io.Reader, src fs.ObjectInfo) error {
} }
// Guess the content type // Guess the content type
contentType := fs.MimeType(o) mimeType := fs.MimeType(src)
key := o.fs.root + o.remote key := o.fs.root + o.remote
req := s3manager.UploadInput{ req := s3manager.UploadInput{
@ -888,7 +890,7 @@ func (o *Object) Update(in io.Reader, src fs.ObjectInfo) error {
ACL: &o.fs.acl, ACL: &o.fs.acl,
Key: &key, Key: &key,
Body: in, Body: in,
ContentType: &contentType, ContentType: &mimeType,
Metadata: metadata, Metadata: metadata,
//ContentLength: &size, //ContentLength: &size,
} }
@ -920,9 +922,20 @@ func (o *Object) Remove() error {
return err return err
} }
// MimeType of an Object if known, "" otherwise
func (o *Object) MimeType() string {
err := o.readMetaData()
if err != nil {
fs.Log(o, "Failed to read metadata: %v", err)
return ""
}
return o.mimeType
}
// Check the interfaces are satisfied // Check the interfaces are satisfied
var ( var (
_ fs.Fs = &Fs{} _ fs.Fs = &Fs{}
_ fs.Copier = &Fs{} _ fs.Copier = &Fs{}
_ fs.Object = &Object{} _ fs.Object = &Object{}
_ fs.MimeTyper = &Object{}
) )

View File

@ -46,6 +46,7 @@ func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) } func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) } func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) }
func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) } func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) }
func TestObjectMimeType(t *testing.T) { fstests.TestObjectMimeType(t) }
func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) } func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) }
func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) } func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) }
func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) } func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) }

View File

@ -689,7 +689,7 @@ func urlEncode(str string) string {
// updateChunks updates the existing object using chunks to a separate // updateChunks updates the existing object using chunks to a separate
// container. It returns a string which prefixes current segments. // container. It returns a string which prefixes current segments.
func (o *Object) updateChunks(in io.Reader, headers swift.Headers, size int64) (string, error) { func (o *Object) updateChunks(in io.Reader, headers swift.Headers, size int64, contentType string) (string, error) {
// Create the segmentsContainer if it doesn't exist // Create the segmentsContainer if it doesn't exist
err := o.fs.c.ContainerCreate(o.fs.segmentsContainer, nil) err := o.fs.c.ContainerCreate(o.fs.segmentsContainer, nil)
if err != nil { if err != nil {
@ -718,7 +718,7 @@ func (o *Object) updateChunks(in io.Reader, headers swift.Headers, size int64) (
headers["Content-Length"] = "0" // set Content-Length as we know it headers["Content-Length"] = "0" // set Content-Length as we know it
emptyReader := bytes.NewReader(nil) emptyReader := bytes.NewReader(nil)
manifestName := o.fs.root + o.remote manifestName := o.fs.root + o.remote
_, err = o.fs.c.ObjectPut(o.fs.container, manifestName, emptyReader, true, "", "", headers) _, err = o.fs.c.ObjectPut(o.fs.container, manifestName, emptyReader, true, "", contentType, headers)
return uniquePrefix + "/", err return uniquePrefix + "/", err
} }
@ -738,16 +738,17 @@ func (o *Object) Update(in io.Reader, src fs.ObjectInfo) error {
// Set the mtime // Set the mtime
m := swift.Metadata{} m := swift.Metadata{}
m.SetModTime(modTime) m.SetModTime(modTime)
contentType := fs.MimeType(src)
headers := m.ObjectHeaders() headers := m.ObjectHeaders()
uniquePrefix := "" uniquePrefix := ""
if size > int64(chunkSize) { if size > int64(chunkSize) {
uniquePrefix, err = o.updateChunks(in, headers, size) uniquePrefix, err = o.updateChunks(in, headers, size, contentType)
if err != nil { if err != nil {
return err return err
} }
} else { } else {
headers["Content-Length"] = strconv.FormatInt(size, 10) // set Content-Length as we know it headers["Content-Length"] = strconv.FormatInt(size, 10) // set Content-Length as we know it
_, err := o.fs.c.ObjectPut(o.fs.container, o.fs.root+o.remote, in, true, "", "", headers) _, err := o.fs.c.ObjectPut(o.fs.container, o.fs.root+o.remote, in, true, "", contentType, headers)
if err != nil { if err != nil {
return err return err
} }
@ -787,10 +788,16 @@ func (o *Object) Remove() error {
return nil return nil
} }
// MimeType of an Object if known, "" otherwise
func (o *Object) MimeType() string {
return o.info.ContentType
}
// Check the interfaces are satisfied // Check the interfaces are satisfied
var ( var (
_ fs.Fs = &Fs{} _ fs.Fs = &Fs{}
_ fs.Purger = &Fs{} _ fs.Purger = &Fs{}
_ fs.Copier = &Fs{} _ fs.Copier = &Fs{}
_ fs.Object = &Object{} _ fs.Object = &Object{}
_ fs.MimeTyper = &Object{}
) )

View File

@ -46,6 +46,7 @@ func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) } func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) } func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) }
func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) } func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) }
func TestObjectMimeType(t *testing.T) { fstests.TestObjectMimeType(t) }
func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) } func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) }
func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) } func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) }
func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) } func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) }

View File

@ -11,11 +11,12 @@ import (
) )
// PerformUpload does the actual upload via unscoped PUT request. // PerformUpload does the actual upload via unscoped PUT request.
func (c *Client) PerformUpload(url string, data io.Reader) (err error) { func (c *Client) PerformUpload(url string, data io.Reader, contentType string) (err error) {
req, err := http.NewRequest("PUT", url, data) req, err := http.NewRequest("PUT", url, data)
if err != nil { if err != nil {
return err return err
} }
req.Header.Set("Content-Type", contentType)
//c.setRequestScope(req) //c.setRequestScope(req)

View File

@ -17,13 +17,13 @@ type UploadResponse struct {
} }
// Upload will put specified data to Yandex.Disk. // Upload will put specified data to Yandex.Disk.
func (c *Client) Upload(data io.Reader, remotePath string, overwrite bool) error { func (c *Client) Upload(data io.Reader, remotePath string, overwrite bool, contentType string) error {
ur, err := c.UploadRequest(remotePath, overwrite) ur, err := c.UploadRequest(remotePath, overwrite)
if err != nil { if err != nil {
return err return err
} }
if err := c.PerformUpload(ur.HRef, data); err != nil { if err := c.PerformUpload(ur.HRef, data, contentType); err != nil {
return err return err
} }

View File

@ -73,11 +73,12 @@ type Fs struct {
// Object describes a swift object // Object describes a swift object
type Object 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
md5sum string // The MD5Sum of the object md5sum string // The MD5Sum of the object
bytes uint64 // Bytes in the object bytes uint64 // Bytes in the object
modTime time.Time // Modified time of the object modTime time.Time // Modified time of the object
mimeType string // Content type according to the server
} }
// ------------------------------------------------------------ // ------------------------------------------------------------
@ -326,31 +327,33 @@ func (f *Fs) newObjectWithInfo(remote string, info *yandex.ResourceInfoResponse)
func (o *Object) setMetaData(info *yandex.ResourceInfoResponse) { func (o *Object) setMetaData(info *yandex.ResourceInfoResponse) {
o.bytes = info.Size o.bytes = info.Size
o.md5sum = info.Md5 o.md5sum = info.Md5
o.mimeType = info.MimeType
if info.CustomProperties["rclone_modified"] == nil { var modTimeString string
//read modTime from Modified property of object modTimeObj, ok := info.CustomProperties["rclone_modified"]
t, err := time.Parse(time.RFC3339Nano, info.Modified) if ok {
if err != nil { // read modTime from rclone_modified custom_property of object
return modTimeString, ok = modTimeObj.(string)
} }
o.modTime = t if !ok {
// read modTime from Modified property of object as a fallback
modTimeString = info.Modified
}
t, err := time.Parse(time.RFC3339Nano, modTimeString)
if err != nil {
fs.Log("Failed to parse modtime from %q: %v", modTimeString, err)
} else { } else {
// interface{} to string type assertion o.modTime = t
if modtimestr, ok := info.CustomProperties["rclone_modified"].(string); ok {
//read modTime from rclone_modified custom_property of object
t, err := time.Parse(time.RFC3339Nano, modtimestr)
if err != nil {
return
}
o.modTime = t
} else {
return //if it is not a string
}
} }
} }
// readMetaData gets the info if it hasn't already been fetched // readMetaData gets the info if it hasn't already been fetched
func (o *Object) readMetaData() (err error) { func (o *Object) readMetaData() (err error) {
// exit if already fetched
if !o.modTime.IsZero() {
return nil
}
//request meta info //request meta info
var opt2 yandex.ResourceInfoRequestOptions var opt2 yandex.ResourceInfoRequestOptions
ResourceInfoResponse, err := o.fs.yd.NewResourceInfoRequest(o.remotePath(), opt2).Exec() ResourceInfoResponse, err := o.fs.yd.NewResourceInfoRequest(o.remotePath(), opt2).Exec()
@ -498,8 +501,13 @@ func (o *Object) Remove() error {
// Commits the datastore // Commits the datastore
func (o *Object) SetModTime(modTime time.Time) error { func (o *Object) SetModTime(modTime time.Time) error {
remote := o.remotePath() remote := o.remotePath()
//set custom_property 'rclone_modified' of object to modTime // set custom_property 'rclone_modified' of object to modTime
return o.fs.yd.SetCustomProperty(remote, "rclone_modified", modTime.Format(time.RFC3339Nano)) err := o.fs.yd.SetCustomProperty(remote, "rclone_modified", modTime.Format(time.RFC3339Nano))
if err != nil {
return err
}
o.modTime = modTime
return nil
} }
// Storable returns whether this object is storable // Storable returns whether this object is storable
@ -529,7 +537,8 @@ func (o *Object) Update(in io.Reader, src fs.ObjectInfo) error {
} }
//upload file //upload file
overwrite := true //overwrite existing file overwrite := true //overwrite existing file
err := o.fs.yd.Upload(in, remote, overwrite) mimeType := fs.MimeType(src)
err := o.fs.yd.Upload(in, remote, overwrite, mimeType)
if err == nil { if err == nil {
//if file uploaded sucessfully then return metadata //if file uploaded sucessfully then return metadata
o.bytes = uint64(size) o.bytes = uint64(size)
@ -587,10 +596,21 @@ func mkDirFullPath(client *yandex.Client, path string) error {
return nil return nil
} }
// MimeType of an Object if known, "" otherwise
func (o *Object) MimeType() string {
err := o.readMetaData()
if err != nil {
fs.Log(o, "Failed to read metadata: %v", err)
return ""
}
return o.mimeType
}
// Check the interfaces are satisfied // Check the interfaces are satisfied
var ( var (
_ fs.Fs = (*Fs)(nil) _ fs.Fs = (*Fs)(nil)
_ fs.Purger = (*Fs)(nil) _ fs.Purger = (*Fs)(nil)
//_ fs.Copier = (*Fs)(nil) //_ fs.Copier = (*Fs)(nil)
_ fs.Object = (*Object)(nil) _ fs.Object = (*Object)(nil)
_ fs.MimeTyper = &Object{}
) )

View File

@ -46,6 +46,7 @@ func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) } func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) } func TestObjectHashes(t *testing.T) { fstests.TestObjectHashes(t) }
func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) } func TestObjectModTime(t *testing.T) { fstests.TestObjectModTime(t) }
func TestObjectMimeType(t *testing.T) { fstests.TestObjectMimeType(t) }
func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) } func TestObjectSetModTime(t *testing.T) { fstests.TestObjectSetModTime(t) }
func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) } func TestObjectSize(t *testing.T) { fstests.TestObjectSize(t) }
func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) } func TestObjectOpen(t *testing.T) { fstests.TestObjectOpen(t) }