-
Notifications
You must be signed in to change notification settings - Fork 340
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The url format is storj://satellite/bucket/path. You can get the satellite along with the api access key when requesting an Access Grant of type API Access.
- Loading branch information
1 parent
cde660e
commit 6009f64
Showing
3 changed files
with
208 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
// Copyright (c) Acrosync LLC. All rights reserved. | ||
// Free for personal use and commercial trial | ||
// Commercial use requires per-user licenses available from https://duplicacy.com | ||
|
||
package duplicacy | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"context" | ||
|
||
"storj.io/uplink" | ||
) | ||
|
||
// StorjStorage is a storage backend for Storj. | ||
type StorjStorage struct { | ||
StorageBase | ||
|
||
project *uplink.Project | ||
bucket string | ||
storageDir string | ||
numberOfThreads int | ||
} | ||
|
||
// CreateStorjStorage creates a Storj storage. | ||
func CreateStorjStorage(satellite string, apiKey string, passphrase string, | ||
bucket string, storageDir string, threads int) (storage *StorjStorage, err error) { | ||
|
||
ctx := context.Background() | ||
access, err := uplink.RequestAccessWithPassphrase(ctx, satellite, apiKey, passphrase) | ||
if err != nil { | ||
return nil, fmt.Errorf("cannot request the access grant: %v", err) | ||
} | ||
|
||
project, err := uplink.OpenProject(ctx, access) | ||
if err != nil { | ||
return nil, fmt.Errorf("cannot open the project: %v", err) | ||
} | ||
|
||
_, err = project.StatBucket(ctx, bucket) | ||
if err != nil { | ||
return nil, fmt.Errorf("cannot found the bucket: %v", err) | ||
} | ||
|
||
if storageDir != "" && storageDir[len(storageDir) - 1] != '/' { | ||
storageDir += "/" | ||
} | ||
|
||
storage = &StorjStorage { | ||
project: project, | ||
bucket: bucket, | ||
storageDir: storageDir, | ||
numberOfThreads: threads, | ||
} | ||
|
||
storage.DerivedStorage = storage | ||
storage.SetDefaultNestingLevels([]int{2, 3}, 2) | ||
return storage, nil | ||
} | ||
|
||
// ListFiles return the list of files and subdirectories under 'dir' (non-recursively). | ||
func (storage *StorjStorage) ListFiles(threadIndex int, dir string) ( | ||
files []string, sizes []int64, err error) { | ||
|
||
fullPath := storage.storageDir + dir | ||
if fullPath != "" && fullPath[len(fullPath) - 1] != '/' { | ||
fullPath += "/" | ||
} | ||
|
||
options := uplink.ListObjectsOptions { | ||
Prefix: fullPath, | ||
System: true, // request SystemMetadata which includes ContentLength | ||
} | ||
objects := storage.project.ListObjects(context.Background(), storage.bucket, &options) | ||
for objects.Next() { | ||
if objects.Err() != nil { | ||
return nil, nil, objects.Err() | ||
} | ||
item := objects.Item() | ||
name := item.Key[len(fullPath):] | ||
size := item.System.ContentLength | ||
if item.IsPrefix { | ||
if name != "" && name[len(name) - 1] != '/' { | ||
name += "/" | ||
size = 0 | ||
} | ||
} | ||
files = append(files, name) | ||
sizes = append(sizes, size) | ||
} | ||
|
||
return files, sizes, nil | ||
} | ||
|
||
// DeleteFile deletes the file or directory at 'filePath'. | ||
func (storage *StorjStorage) DeleteFile(threadIndex int, filePath string) (err error) { | ||
|
||
_, err = storage.project.DeleteObject(context.Background(), storage.bucket, | ||
storage.storageDir + filePath) | ||
return err | ||
} | ||
|
||
// MoveFile renames the file. | ||
func (storage *StorjStorage) MoveFile(threadIndex int, from string, to string) (err error) { | ||
err = storage.project.MoveObject(context.Background(), storage.bucket, | ||
storage.storageDir + from, storage.bucket, storage.storageDir + to, nil) | ||
|
||
return err | ||
} | ||
|
||
// CreateDirectory creates a new directory. | ||
func (storage *StorjStorage) CreateDirectory(threadIndex int, dir string) (err error) { | ||
return nil | ||
} | ||
|
||
// GetFileInfo returns the information about the file or directory at 'filePath'. | ||
func (storage *StorjStorage) GetFileInfo(threadIndex int, filePath string) ( | ||
exist bool, isDir bool, size int64, err error) { | ||
info, err := storage.project.StatObject(context.Background(), storage.bucket, | ||
storage.storageDir + filePath) | ||
|
||
if info == nil { | ||
return false, false, 0, nil | ||
} else if err != nil { | ||
return false, false, 0, err | ||
} else { | ||
return true, info.IsPrefix, info.System.ContentLength, nil | ||
} | ||
} | ||
|
||
// DownloadFile reads the file at 'filePath' into the chunk. | ||
func (storage *StorjStorage) DownloadFile(threadIndex int, filePath string, chunk *Chunk) (err error) { | ||
file, err := storage.project.DownloadObject(context.Background(), storage.bucket, | ||
storage.storageDir + filePath, nil) | ||
if err != nil { | ||
return err | ||
} | ||
defer file.Close() | ||
|
||
if _, err = RateLimitedCopy(chunk, file, storage.DownloadRateLimit/storage.numberOfThreads); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// UploadFile writes 'content' to the file at 'filePath' | ||
func (storage *StorjStorage) UploadFile(threadIndex int, filePath string, content []byte) (err error) { | ||
|
||
file, err := storage.project.UploadObject(context.Background(), storage.bucket, | ||
storage.storageDir + filePath, nil) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
reader := CreateRateLimitedReader(content, storage.UploadRateLimit/storage.numberOfThreads) | ||
_, err = io.Copy(file, reader) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = file.Commit() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// If a local snapshot cache is needed for the storage to avoid downloading/uploading chunks too often when | ||
// managing snapshots. | ||
func (storage *StorjStorage) IsCacheNeeded() bool { return true } | ||
|
||
// If the 'MoveFile' method is implemented. | ||
func (storage *StorjStorage) IsMoveFileImplemented() bool { return true } | ||
|
||
// If the storage can guarantee strong consistency. | ||
func (storage *StorjStorage) IsStrongConsistent() bool { return false } | ||
|
||
// If the storage supports fast listing of files names. | ||
func (storage *StorjStorage) IsFastListing() bool { return true } | ||
|
||
// Enable the test mode. | ||
func (storage *StorjStorage) EnableTestMode() {} |
6009f64
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This commit has been mentioned on Duplicacy Forum. There might be relevant details there:
https://forum.duplicacy.com/t/storj-storage-backend-added/6738/1
6009f64
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This commit has been mentioned on Duplicacy Forum. There might be relevant details there:
https://forum.duplicacy.com/t/here-is-an-article-i-wrote-for-using-the-cli-to-backup-to-storj-cloud-beta-re-writing-sections/6896/18