rclone/fs/fs.go

279 lines
6.9 KiB
Go
Raw Normal View History

// File system interface
2013-06-27 21:13:07 +02:00
package fs
import (
"fmt"
"io"
"log"
2013-06-27 21:13:07 +02:00
"regexp"
"time"
)
2013-06-27 21:13:07 +02:00
// Globals
var (
// Global config
2013-06-28 09:57:32 +02:00
Config = &ConfigInfo{}
2013-06-27 21:13:07 +02:00
// Filesystem registry
2013-06-28 09:57:32 +02:00
fsRegistry []registryItem
2013-06-27 21:13:07 +02:00
)
// Filesystem config options
2013-06-28 09:57:32 +02:00
type ConfigInfo struct {
Verbose bool
Quiet bool
2013-06-27 21:13:07 +02:00
ModifyWindow time.Duration
2013-06-28 09:57:32 +02:00
Checkers int
Transfers int
2013-06-27 21:13:07 +02:00
}
// Filesystem registry item
2013-06-28 09:57:32 +02:00
type registryItem struct {
2013-06-27 21:13:07 +02:00
match *regexp.Regexp // if this matches then can call newFs
newFs func(string) (Fs, error) // create a new file system
}
// Register a filesystem
//
// If a path matches with match then can call newFs on it
//
2013-06-29 13:15:55 +02:00
// Pass with match nil goes last and matches everything (used by local fs)
//
2013-06-27 21:13:07 +02:00
// Fs modules should use this in an init() function
func Register(match *regexp.Regexp, newFs func(string) (Fs, error)) {
2013-06-28 09:57:32 +02:00
fsRegistry = append(fsRegistry, registryItem{match: match, newFs: newFs})
2013-06-29 13:15:55 +02:00
// Keep one nil match at the end
last := len(fsRegistry) - 1
if last >= 1 && fsRegistry[last-1].match == nil {
fsRegistry[last], fsRegistry[last-1] = fsRegistry[last-1], fsRegistry[last]
}
2013-06-27 21:13:07 +02:00
}
// A Filesystem, describes the local filesystem and the remote object store
type Fs interface {
2013-01-18 19:54:19 +01:00
// String returns a description of the FS
String() string
2013-01-18 19:54:19 +01:00
// List the Fs into a channel
2013-06-28 09:57:32 +02:00
List() ObjectsChan
2013-01-18 19:54:19 +01:00
2013-01-23 23:43:20 +01:00
// List the Fs directories/buckets/containers into a channel
2013-06-28 09:57:32 +02:00
ListDir() DirChan
2013-01-23 23:43:20 +01:00
2013-06-28 09:57:32 +02:00
// Find the Object at remote. Returns nil if can't be found
NewFsObject(remote string) Object
2013-01-18 19:54:19 +01: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-06-28 09:57:32 +02:00
Put(in io.Reader, remote string, modTime time.Time, size int64) (Object, error)
2013-01-18 19:54:19 +01:00
// Make the directory (container, bucket)
Mkdir() error
2013-01-18 19:54:19 +01:00
// Remove the directory (container, bucket) if empty
Rmdir() error
// Precision of the ModTimes in this Fs
Precision() time.Duration
}
// FIXME make f.Debugf...
// A filesystem like object which can either be a remote object or a
// local file/directory
2013-06-28 09:57:32 +02:00
type Object interface {
2013-01-18 19:54:19 +01:00
// Remote returns the remote path
Remote() string
2013-01-18 19:54:19 +01:00
// Md5sum returns the md5 checksum of the file
Md5sum() (string, error)
2013-01-18 19:54:19 +01:00
// ModTime returns the modification date of the file
ModTime() time.Time
2013-01-18 19:54:19 +01:00
// SetModTime sets the metadata on the object to set the modification date
SetModTime(time.Time)
2013-01-18 19:54:19 +01:00
// Size returns the size of the file
Size() int64
2013-01-18 19:54:19 +01:00
// Open opens the file for read. Call Close() on the returned io.ReadCloser
Open() (io.ReadCloser, error)
2013-01-18 19:54:19 +01:00
// Storable says whether this object can be stored
Storable() bool
2013-01-18 19:54:19 +01:00
// Removes this object
Remove() error
}
// 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-06-28 09:57:32 +02:00
// A channel of Objects
type ObjectsChan chan Object
2013-06-28 09:57:32 +02:00
// A slice of Objects
type Objects []Object
2013-01-23 23:43:20 +01:00
// A structure of directory/container/bucket lists
2013-06-28 09:57:32 +02:00
type Dir struct {
2013-01-23 23:43:20 +01:00
Name string // name of the directory
When time.Time // modification or creation time - IsZero for unknown
Bytes int64 // size of directory and contents -1 for unknown
Count int64 // number of objects -1 for unknown
}
2013-06-28 09:57:32 +02:00
// A channel of Dir objects
type DirChan chan *Dir
2013-01-23 23:43:20 +01:00
// NewFs makes a new Fs object from the path
//
2013-01-18 19:54:19 +01:00
// FIXME make more generic
func NewFs(path string) (Fs, error) {
2013-06-27 21:13:07 +02:00
for _, item := range fsRegistry {
2013-06-29 13:15:55 +02:00
if item.match == nil || item.match.MatchString(path) {
2013-06-27 21:13:07 +02:00
return item.newFs(path)
}
}
2013-06-27 21:13:07 +02:00
panic("Not found") // FIXME
}
2013-06-28 09:57:32 +02:00
// Write debuging output for this Object
func Debug(fs Object, text string, args ...interface{}) {
2013-06-27 21:13:07 +02:00
if Config.Verbose {
out := fmt.Sprintf(text, args...)
log.Printf("%s: %s", fs.Remote(), out)
}
}
2013-06-28 09:57:32 +02:00
// Write log output for this Object
func Log(fs Object, text string, args ...interface{}) {
2013-06-27 21:13:07 +02:00
if !Config.Quiet {
out := fmt.Sprintf(text, args...)
log.Printf("%s: %s", fs.Remote(), out)
}
}
// 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
}
}
2012-12-31 18:31:19 +01: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
2013-06-28 09:57:32 +02:00
func CheckMd5sums(src, dst Object) (bool, error) {
2012-12-31 18:31:19 +01:00
srcMd5, err := src.Md5sum()
if err != nil {
2013-06-27 21:13:07 +02:00
Stats.Error()
2013-06-28 09:57:32 +02:00
Log(src, "Failed to calculate src md5: %s", err)
2012-12-31 18:31:19 +01:00
return false, err
}
dstMd5, err := dst.Md5sum()
if err != nil {
2013-06-27 21:13:07 +02:00
Stats.Error()
2013-06-28 09:57:32 +02:00
Log(dst, "Failed to calculate dst md5: %s", err)
2012-12-31 18:31:19 +01:00
return false, err
}
2013-06-28 09:57:32 +02:00
// Debug("Src MD5 %s", srcMd5)
// Debug("Dst MD5 %s", obj.Hash)
2012-12-31 18:31:19 +01:00
return srcMd5 == dstMd5, nil
}
// 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.
2013-06-28 09:57:32 +02:00
func Equal(src, dst Object) bool {
if src.Size() != dst.Size() {
2013-06-28 09:57:32 +02:00
Debug(src, "Sizes differ")
return false
}
// Size the same so check the mtime
srcModTime := src.ModTime()
dstModTime := dst.ModTime()
dt := dstModTime.Sub(srcModTime)
2013-06-27 21:13:07 +02:00
ModifyWindow := Config.ModifyWindow
if dt >= ModifyWindow || dt <= -ModifyWindow {
2013-06-28 09:57:32 +02:00
Debug(src, "Modification times differ by %s: %v, %v", dt, srcModTime, dstModTime)
} else {
2013-06-28 09:57:32 +02:00
Debug(src, "Size and modification time differ by %s (within %s)", dt, ModifyWindow)
return true
}
// mtime is unreadable or different but size is the same so
// check the MD5SUM
same, _ := CheckMd5sums(src, dst)
2012-12-31 18:31:19 +01:00
if !same {
2013-06-28 09:57:32 +02:00
Debug(src, "Md5sums differ")
return false
}
// Size and MD5 the same but mtime different so update the
// mtime of the dst object here
dst.SetModTime(srcModTime)
2013-06-28 09:57:32 +02:00
Debug(src, "Size and MD5SUM of src and dst objects identical")
return true
}
// Copy src object to f
2013-06-28 09:57:32 +02:00
func Copy(f Fs, src Object) {
in0, err := src.Open()
if err != nil {
2013-06-27 21:13:07 +02:00
Stats.Error()
2013-06-28 09:57:32 +02:00
Log(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 {
2013-06-27 21:13:07 +02:00
Stats.Error()
2013-06-28 09:57:32 +02:00
Log(src, "Failed to copy: %s", err)
2013-01-18 19:54:19 +01:00
if dst != nil {
2013-06-28 09:57:32 +02:00
Debug(dst, "Removing failed copy")
2013-01-18 19:54:19 +01:00
removeErr := dst.Remove()
if removeErr != nil {
2013-06-27 21:13:07 +02:00
Stats.Error()
2013-06-28 09:57:32 +02:00
Log(dst, "Failed to remove failed copy: %s", removeErr)
2013-01-18 19:54:19 +01:00
}
}
return
}
2013-06-28 09:57:32 +02:00
Debug(src, "Copied")
}