2012-12-26 20:23:58 +08:00
|
|
|
// File system interface
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2013-01-01 00:40:34 +08:00
|
|
|
"fmt"
|
2012-12-26 20:23:58 +08:00
|
|
|
"io"
|
2013-01-01 00:40:34 +08:00
|
|
|
"log"
|
2012-12-26 20:23:58 +08:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
// A Filesystem, describes the local filesystem and the remote object store
|
|
|
|
type Fs interface {
|
2013-01-19 02:54:19 +08:00
|
|
|
// String returns a description of the FS
|
2013-01-01 00:40:34 +08:00
|
|
|
String() string
|
2013-01-19 02:54:19 +08:00
|
|
|
|
|
|
|
// List the Fs into a channel
|
2012-12-26 20:23:58 +08:00
|
|
|
List() FsObjectsChan
|
2013-01-19 02:54:19 +08:00
|
|
|
|
|
|
|
// Find the FsObject at remote. Returns nil if can't be found
|
2012-12-26 20:23:58 +08:00
|
|
|
NewFsObject(remote string) FsObject
|
2013-01-19 02:54:19 +08:00
|
|
|
|
|
|
|
// Put in to the remote path with the modTime given of the given size
|
|
|
|
//
|
|
|
|
// May create the object even if it returns an error - if so
|
|
|
|
// will return the object and the error, otherwise will return
|
|
|
|
// nil and the error
|
2013-01-11 05:58:46 +08:00
|
|
|
Put(in io.Reader, remote string, modTime time.Time, size int64) (FsObject, error)
|
2013-01-19 02:54:19 +08:00
|
|
|
|
|
|
|
// Make the directory (container, bucket)
|
2012-12-26 20:23:58 +08:00
|
|
|
Mkdir() error
|
2013-01-19 02:54:19 +08:00
|
|
|
|
|
|
|
// Remove the directory (container, bucket) if empty
|
2012-12-26 20:23:58 +08:00
|
|
|
Rmdir() error
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME make f.Debugf...
|
|
|
|
|
|
|
|
// A filesystem like object which can either be a remote object or a
|
|
|
|
// local file/directory
|
|
|
|
type FsObject interface {
|
2013-01-19 02:54:19 +08:00
|
|
|
// Remote returns the remote path
|
2012-12-26 20:23:58 +08:00
|
|
|
Remote() string
|
2013-01-19 02:54:19 +08:00
|
|
|
|
|
|
|
// Md5sum returns the md5 checksum of the file
|
2012-12-26 20:23:58 +08:00
|
|
|
Md5sum() (string, error)
|
2013-01-19 02:54:19 +08:00
|
|
|
|
|
|
|
// ModTime returns the modification date of the file
|
2013-01-02 23:21:55 +08:00
|
|
|
ModTime() time.Time
|
2013-01-19 02:54:19 +08:00
|
|
|
|
|
|
|
// SetModTime sets the metadata on the object to set the modification date
|
2012-12-26 20:23:58 +08:00
|
|
|
SetModTime(time.Time)
|
2013-01-19 02:54:19 +08:00
|
|
|
|
|
|
|
// Size returns the size of the file
|
2012-12-26 20:23:58 +08:00
|
|
|
Size() int64
|
2013-01-19 02:54:19 +08:00
|
|
|
|
|
|
|
// Open opens the file for read. Call Close() on the returned io.ReadCloser
|
2012-12-26 20:23:58 +08:00
|
|
|
Open() (io.ReadCloser, error)
|
2013-01-19 02:54:19 +08:00
|
|
|
|
|
|
|
// Storable says whether this object can be stored
|
2012-12-26 20:23:58 +08:00
|
|
|
Storable() bool
|
2013-01-19 02:54:19 +08:00
|
|
|
|
|
|
|
// Removes this object
|
2012-12-26 20:23:58 +08:00
|
|
|
Remove() error
|
|
|
|
}
|
|
|
|
|
2013-01-19 01:01:47 +08:00
|
|
|
// Optional interfaces
|
|
|
|
type Purger interface {
|
|
|
|
// Purge all files in the root and the root directory
|
|
|
|
//
|
|
|
|
// Implement this if you have a way of deleting all the files
|
|
|
|
// quicker than just running Remove() on the result of List()
|
|
|
|
Purge() error
|
|
|
|
}
|
|
|
|
|
2013-01-19 02:54:19 +08:00
|
|
|
// A channel of FsObjects
|
2012-12-26 20:23:58 +08:00
|
|
|
type FsObjectsChan chan FsObject
|
|
|
|
|
2013-01-19 02:54:19 +08:00
|
|
|
// A slice of FsObjects
|
2012-12-26 20:23:58 +08:00
|
|
|
type FsObjects []FsObject
|
|
|
|
|
2012-12-29 19:35:41 +08:00
|
|
|
// NewFs makes a new Fs object from the path
|
|
|
|
//
|
2013-01-19 02:54:19 +08:00
|
|
|
// FIXME make more generic
|
2012-12-29 19:35:41 +08:00
|
|
|
func NewFs(path string) (Fs, error) {
|
|
|
|
if swiftMatch.MatchString(path) {
|
|
|
|
return NewFsSwift(path)
|
|
|
|
}
|
2013-01-09 02:53:35 +08:00
|
|
|
if s3Match.MatchString(path) {
|
|
|
|
return NewFsS3(path)
|
|
|
|
}
|
2013-01-15 07:38:18 +08:00
|
|
|
if driveMatch.MatchString(path) {
|
|
|
|
return NewFsDrive(path)
|
|
|
|
}
|
2012-12-29 19:35:41 +08:00
|
|
|
return NewFsLocal(path)
|
|
|
|
}
|
|
|
|
|
2013-01-01 00:40:34 +08:00
|
|
|
// Write debuging output for this FsObject
|
|
|
|
func FsDebug(fs FsObject, text string, args ...interface{}) {
|
|
|
|
if *verbose {
|
|
|
|
out := fmt.Sprintf(text, args...)
|
|
|
|
log.Printf("%s: %s", fs.Remote(), out)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write log output for this FsObject
|
|
|
|
func FsLog(fs FsObject, text string, args ...interface{}) {
|
|
|
|
if !*quiet {
|
|
|
|
out := fmt.Sprintf(text, args...)
|
|
|
|
log.Printf("%s: %s", fs.Remote(), out)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-26 20:23:58 +08:00
|
|
|
// checkClose is a utility function used to check the return from
|
|
|
|
// Close in a defer statement.
|
|
|
|
func checkClose(c io.Closer, err *error) {
|
|
|
|
cerr := c.Close()
|
|
|
|
if *err == nil {
|
|
|
|
*err = cerr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-01 01:31:19 +08:00
|
|
|
// Check the two files to see if the MD5sums are the same
|
|
|
|
//
|
|
|
|
// May return an error which will already have been logged
|
|
|
|
//
|
|
|
|
// If an error is returned it will return false
|
|
|
|
func CheckMd5sums(src, dst FsObject) (bool, error) {
|
|
|
|
srcMd5, err := src.Md5sum()
|
|
|
|
if err != nil {
|
2013-01-04 06:50:00 +08:00
|
|
|
stats.Error()
|
2013-01-01 01:31:19 +08:00
|
|
|
FsLog(src, "Failed to calculate src md5: %s", err)
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
dstMd5, err := dst.Md5sum()
|
|
|
|
if err != nil {
|
2013-01-04 06:50:00 +08:00
|
|
|
stats.Error()
|
2013-01-01 01:31:19 +08:00
|
|
|
FsLog(dst, "Failed to calculate dst md5: %s", err)
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
// FsDebug("Src MD5 %s", srcMd5)
|
|
|
|
// FsDebug("Dst MD5 %s", obj.Hash)
|
|
|
|
return srcMd5 == dstMd5, nil
|
|
|
|
}
|
|
|
|
|
2012-12-26 20:23:58 +08:00
|
|
|
// Checks to see if the src and dst objects are equal by looking at
|
|
|
|
// size, mtime and MD5SUM
|
|
|
|
//
|
|
|
|
// If the src and dst size are different then it is considered to be
|
|
|
|
// not equal.
|
|
|
|
//
|
|
|
|
// If the size is the same and the mtime is the same then it is
|
|
|
|
// considered to be equal. This is the heuristic rsync uses when
|
|
|
|
// not using --checksum.
|
|
|
|
//
|
|
|
|
// If the size is the same and and mtime is different or unreadable
|
|
|
|
// and the MD5SUM is the same then the file is considered to be equal.
|
|
|
|
// In this case the mtime on the dst is updated.
|
|
|
|
//
|
|
|
|
// Otherwise the file is considered to be not equal including if there
|
|
|
|
// were errors reading info.
|
|
|
|
func Equal(src, dst FsObject) bool {
|
|
|
|
if src.Size() != dst.Size() {
|
2013-01-01 00:40:34 +08:00
|
|
|
FsDebug(src, "Sizes differ")
|
2012-12-26 20:23:58 +08:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Size the same so check the mtime
|
2013-01-02 23:21:55 +08:00
|
|
|
srcModTime := src.ModTime()
|
|
|
|
dstModTime := dst.ModTime()
|
|
|
|
if !dstModTime.Equal(srcModTime) {
|
2013-01-15 07:38:18 +08:00
|
|
|
FsDebug(src, "Modification times differ: %v, %v", srcModTime, dstModTime)
|
2012-12-26 20:23:58 +08:00
|
|
|
} else {
|
2013-01-02 23:21:55 +08:00
|
|
|
FsDebug(src, "Size and modification time the same")
|
|
|
|
return true
|
2012-12-26 20:23:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// mtime is unreadable or different but size is the same so
|
|
|
|
// check the MD5SUM
|
2013-01-02 23:21:55 +08:00
|
|
|
same, _ := CheckMd5sums(src, dst)
|
2013-01-01 01:31:19 +08:00
|
|
|
if !same {
|
2013-01-01 00:40:34 +08:00
|
|
|
FsDebug(src, "Md5sums differ")
|
2012-12-26 20:23:58 +08:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Size and MD5 the same but mtime different so update the
|
|
|
|
// mtime of the dst object here
|
|
|
|
dst.SetModTime(srcModTime)
|
|
|
|
|
2013-01-01 00:40:34 +08:00
|
|
|
FsDebug(src, "Size and MD5SUM of src and dst objects identical")
|
2012-12-26 20:23:58 +08:00
|
|
|
return true
|
|
|
|
}
|
2013-01-11 05:58:46 +08:00
|
|
|
|
|
|
|
// Copy src object to f
|
|
|
|
func Copy(f Fs, src FsObject) {
|
|
|
|
in0, err := src.Open()
|
|
|
|
if err != nil {
|
|
|
|
stats.Error()
|
|
|
|
FsLog(src, "Failed to open: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
in := NewAccount(in0) // account the transfer
|
|
|
|
|
|
|
|
dst, err := f.Put(in, src.Remote(), src.ModTime(), src.Size())
|
|
|
|
inErr := in.Close()
|
|
|
|
if err == nil {
|
|
|
|
err = inErr
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
stats.Error()
|
2013-01-19 02:54:19 +08:00
|
|
|
FsLog(src, "Failed to copy: %s", err)
|
|
|
|
if dst != nil {
|
|
|
|
FsDebug(dst, "Removing failed copy")
|
|
|
|
removeErr := dst.Remove()
|
|
|
|
if removeErr != nil {
|
|
|
|
stats.Error()
|
|
|
|
FsLog(dst, "Failed to remove failed copy: %s", removeErr)
|
|
|
|
}
|
2013-01-11 05:58:46 +08:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
FsDebug(src, "Copied")
|
|
|
|
}
|