mirror of
https://github.com/gilbertchen/duplicacy
synced 2025-12-26 21:23:16 +00:00
243 lines
7.7 KiB
Go
243 lines
7.7 KiB
Go
// Copyright (c) Acrosync LLC. All rights reserved.
|
|
// Licensed under the Fair Source License 0.9 (https://fair.io/)
|
|
// User Limitation: 5 users
|
|
|
|
package duplicacy
|
|
|
|
import (
|
|
"fmt"
|
|
"path"
|
|
"strings"
|
|
)
|
|
|
|
type OneDriveStorage struct {
|
|
RateLimitedStorage
|
|
|
|
client *OneDriveClient
|
|
storageDir string
|
|
numberOfThread int
|
|
}
|
|
|
|
// CreateOneDriveStorage creates an OneDrive storage object.
|
|
func CreateOneDriveStorage(tokenFile string, storagePath string, threads int) (storage *OneDriveStorage, err error) {
|
|
|
|
for len(storagePath) > 0 && storagePath[len(storagePath) - 1] == '/' {
|
|
storagePath = storagePath[:len(storagePath) - 1]
|
|
}
|
|
|
|
client, err := NewOneDriveClient(tokenFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fileID, isDir, _, err := client.GetFileInfo(storagePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if fileID == "" {
|
|
return nil, fmt.Errorf("Path '%s' doesn't exist", storagePath)
|
|
}
|
|
|
|
if !isDir {
|
|
return nil, fmt.Errorf("Path '%s' is not a directory", storagePath)
|
|
}
|
|
|
|
storage = &OneDriveStorage {
|
|
client: client,
|
|
storageDir: storagePath,
|
|
numberOfThread: threads,
|
|
}
|
|
|
|
for _, path := range []string { "chunks", "fossils", "snapshots" } {
|
|
dir := storagePath + "/" + path
|
|
dirID, isDir, _, err := client.GetFileInfo(dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if dirID == "" {
|
|
err = client.CreateDirectory(storagePath, path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else if !isDir {
|
|
return nil, fmt.Errorf("%s is not a directory", dir)
|
|
}
|
|
}
|
|
|
|
return storage, nil
|
|
|
|
}
|
|
|
|
// ListFiles return the list of files and subdirectories under 'dir' (non-recursively)
|
|
func (storage *OneDriveStorage) ListFiles(threadIndex int, dir string) ([]string, []int64, error) {
|
|
for len(dir) > 0 && dir[len(dir) - 1] == '/' {
|
|
dir = dir[:len(dir) - 1]
|
|
}
|
|
|
|
if dir == "snapshots" {
|
|
entries, err := storage.client.ListEntries(storage.storageDir + "/" + dir)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
subDirs := []string{}
|
|
for _, entry := range entries {
|
|
if len(entry.Folder) > 0 {
|
|
subDirs = append(subDirs, entry.Name + "/")
|
|
}
|
|
}
|
|
return subDirs, nil, nil
|
|
} else if strings.HasPrefix(dir, "snapshots/") {
|
|
entries, err := storage.client.ListEntries(storage.storageDir + "/" + dir)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
files := []string{}
|
|
|
|
for _, entry := range entries {
|
|
if len(entry.Folder) == 0 {
|
|
files = append(files, entry.Name)
|
|
}
|
|
}
|
|
return files, nil, nil
|
|
} else {
|
|
files := []string{}
|
|
sizes := []int64{}
|
|
for _, parent := range []string {"chunks", "fossils" } {
|
|
entries, err := storage.client.ListEntries(storage.storageDir + "/" + parent)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
name := entry.Name
|
|
if parent == "fossils" {
|
|
name += ".fsl"
|
|
}
|
|
files = append(files, name)
|
|
sizes = append(sizes, entry.Size)
|
|
}
|
|
}
|
|
return files, sizes, nil
|
|
}
|
|
|
|
}
|
|
|
|
// DeleteFile deletes the file or directory at 'filePath'.
|
|
func (storage *OneDriveStorage) DeleteFile(threadIndex int, filePath string) (err error) {
|
|
if strings.HasSuffix(filePath, ".fsl") && strings.HasPrefix(filePath, "chunks/") {
|
|
filePath = "fossils/" + filePath[len("chunks/"):len(filePath) - len(".fsl")]
|
|
}
|
|
|
|
err = storage.client.DeleteFile(storage.storageDir + "/" + filePath)
|
|
if e, ok := err.(OneDriveError); ok && e.Status == 404 {
|
|
LOG_DEBUG("ONEDRIVE_DELETE", "Ignore 404 error")
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
// MoveFile renames the file.
|
|
func (storage *OneDriveStorage) MoveFile(threadIndex int, from string, to string) (err error) {
|
|
fromPath := storage.storageDir + "/" + from
|
|
toParent := storage.storageDir + "/fossils"
|
|
if strings.HasSuffix(from, ".fsl") {
|
|
fromPath = storage.storageDir + "/fossils/" + from[len("chunks/"):len(from) - len(".fsl")]
|
|
toParent = storage.storageDir + "/chunks"
|
|
}
|
|
|
|
err = storage.client.MoveFile(fromPath, toParent)
|
|
if err != nil {
|
|
if e, ok := err.(OneDriveError); ok && e.Status == 409 {
|
|
LOG_DEBUG("ONEDRIVE_MOVE", "Ignore 409 conflict error")
|
|
} else {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CreateDirectory creates a new directory.
|
|
func (storage *OneDriveStorage) CreateDirectory(threadIndex int, dir string) (err error) {
|
|
for len(dir) > 0 && dir[len(dir) - 1] == '/' {
|
|
dir = dir[:len(dir) - 1]
|
|
}
|
|
|
|
parent := path.Dir(dir)
|
|
|
|
if parent == "." {
|
|
return storage.client.CreateDirectory(storage.storageDir, dir)
|
|
} else {
|
|
return storage.client.CreateDirectory(storage.storageDir + "/" + parent, path.Base(dir))
|
|
}
|
|
}
|
|
|
|
// GetFileInfo returns the information about the file or directory at 'filePath'.
|
|
func (storage *OneDriveStorage) GetFileInfo(threadIndex int, filePath string) (exist bool, isDir bool, size int64, err error) {
|
|
|
|
for len(filePath) > 0 && filePath[len(filePath) - 1] == '/' {
|
|
filePath = filePath[:len(filePath) - 1]
|
|
}
|
|
fileID, isDir, size, err := storage.client.GetFileInfo(storage.storageDir + "/" + filePath)
|
|
return fileID != "", isDir, size, err
|
|
}
|
|
|
|
// FindChunk finds the chunk with the specified id. If 'isFossil' is true, it will search for chunk files with
|
|
// the suffix '.fsl'.
|
|
func (storage *OneDriveStorage) FindChunk(threadIndex int, chunkID string, isFossil bool) (filePath string, exist bool, size int64, err error) {
|
|
filePath = "chunks/" + chunkID
|
|
realPath := storage.storageDir + "/" + filePath
|
|
if isFossil {
|
|
filePath += ".fsl"
|
|
realPath = storage.storageDir + "/fossils/" + chunkID
|
|
}
|
|
|
|
fileID, _, size, err := storage.client.GetFileInfo(realPath)
|
|
return filePath, fileID != "", size, err
|
|
}
|
|
|
|
// DownloadFile reads the file at 'filePath' into the chunk.
|
|
func (storage *OneDriveStorage) DownloadFile(threadIndex int, filePath string, chunk *Chunk) (err error) {
|
|
readCloser, _, err := storage.client.DownloadFile(storage.storageDir + "/" + filePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer readCloser.Close()
|
|
|
|
_, err = RateLimitedCopy(chunk, readCloser, storage.DownloadRateLimit / storage.numberOfThread)
|
|
return err
|
|
}
|
|
|
|
// UploadFile writes 'content' to the file at 'filePath'.
|
|
func (storage *OneDriveStorage) UploadFile(threadIndex int, filePath string, content []byte) (err error) {
|
|
err = storage.client.UploadFile(storage.storageDir + "/" + filePath, content, storage.UploadRateLimit / storage.numberOfThread)
|
|
|
|
if e, ok := err.(OneDriveError); ok && e.Status == 409 {
|
|
LOG_TRACE("ONEDRIVE_UPLOAD", "File %s already exists", filePath)
|
|
return nil
|
|
} else {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// If a local snapshot cache is needed for the storage to avoid downloading/uploading chunks too often when
|
|
// managing snapshots.
|
|
func (storage *OneDriveStorage) IsCacheNeeded() (bool) { return true }
|
|
|
|
// If the 'MoveFile' method is implemented.
|
|
func (storage *OneDriveStorage) IsMoveFileImplemented() (bool) { return true }
|
|
|
|
// If the storage can guarantee strong consistency.
|
|
func (storage *OneDriveStorage) IsStrongConsistent() (bool) { return false }
|
|
|
|
// If the storage supports fast listing of files names.
|
|
func (storage *OneDriveStorage) IsFastListing() (bool) { return true }
|
|
|
|
// Enable the test mode.
|
|
func (storage *OneDriveStorage) EnableTestMode() {
|
|
storage.client.TestMode = true
|
|
}
|