Files
2022-12-21 00:25:37 +11:00

265 lines
5.8 KiB
Go

package plg_backend_storj
import (
"context"
. "github.com/mickael-kerjean/filestash/server/common"
"golang.org/x/sync/semaphore"
"io"
"os"
"path/filepath"
"storj.io/uplink"
"strings"
"sync"
"time"
)
const CONCURRENCY_LEVEL = 20
type Storj struct {
Context context.Context
Project *uplink.Project
}
func init() {
Backend.Register("storj", Storj{})
}
func (this Storj) Init(params map[string]string, app *App) (IBackend, error) {
access, err := uplink.ParseAccess(params["access-grant"])
if err != nil {
return nil, ErrAuthenticationFailed
}
project, err := uplink.OpenProject(app.Context, access)
if err != nil {
return nil, ErrAuthenticationFailed
}
return Storj{app.Context, project}, nil
}
func (this Storj) LoginForm() Form {
return Form{
Elmnts: []FormElement{
{
Name: "type",
Type: "hidden",
Value: "storj",
},
{
Name: "access-grant",
Type: "text",
Placeholder: "Access Grant",
},
},
}
}
func (this Storj) Meta(path string) Metadata {
defer this.Project.Close()
if path == "/" {
return Metadata{
CanCreateFile: NewBool(false),
CanRename: NewBool(false),
CanMove: NewBool(false),
CanUpload: NewBool(false),
}
}
return Metadata{}
}
func (this Storj) Ls(path string) ([]os.FileInfo, error) {
defer this.Project.Close()
files := make([]os.FileInfo, 0)
bucket, prefix := this.path(path)
if bucket == "" {
buckets := this.Project.ListBuckets(this.Context, nil)
for buckets.Next() {
item := buckets.Item()
files = append(files, File{
FName: item.Name,
FType: "directory",
FTime: item.Created.Unix(),
})
}
if err := buckets.Err(); err != nil {
return files, err
}
} else {
objects := this.Project.ListObjects(this.Context, bucket, &uplink.ListObjectsOptions{
Prefix: prefix,
Recursive: false,
})
for objects.Next() {
item := objects.Item()
fname := filepath.Base(item.Key)
if fname == ".file_placeholder" {
continue
}
files = append(files, File{
FName: fname,
FType: func() string {
if item.IsPrefix {
return "directory"
}
return "file"
}(),
FSize: -1,
FTime: -1,
})
}
if err := objects.Err(); err != nil {
return files, err
}
}
return files, nil
}
func (this Storj) Cat(path string) (io.ReadCloser, error) {
defer this.Project.Close()
bucket, prefix := this.path(path)
return this.Project.DownloadObject(this.Context, bucket, prefix, nil)
}
func (this Storj) Mkdir(path string) error {
if bucket, prefix := this.path(path); prefix == "" {
_, err := this.Project.CreateBucket(this.Context, bucket)
this.Project.Close()
return err
}
return this.Touch(path + ".file_placeholder")
}
func (this Storj) Rm(path string) error {
defer this.Project.Close()
bucket, prefix := this.path(path)
if prefix == "" { // remove an entire bucket with its content
_, err := this.Project.DeleteBucketWithObjects(this.Context, bucket)
return err
} else if strings.HasSuffix(path, "/") == false { // remove a file
_, err := this.Project.DeleteObject(this.Context, bucket, prefix)
return err
}
// remove a directory
objects := this.Project.ListObjects(this.Context, bucket, &uplink.ListObjectsOptions{
Prefix: prefix,
Recursive: true,
})
var (
err error
mu sync.Mutex
)
sem := semaphore.NewWeighted(CONCURRENCY_LEVEL)
for objects.Next() {
item := objects.Item()
if item.IsPrefix {
continue
}
mu.Lock()
if err != nil {
mu.Unlock()
break
}
mu.Unlock()
sem.Acquire(this.Context, 1)
go func() {
_, _err := this.Project.DeleteObject(this.Context, bucket, item.Key)
if _err != nil {
mu.Lock()
err = _err
mu.Unlock()
}
sem.Release(1)
}()
}
sem.Acquire(this.Context, CONCURRENCY_LEVEL) // wait
return err
}
func (this Storj) Mv(from, to string) error { // TODO
defer this.Project.Close()
fromBucket, fromPrefix := this.path(from)
toBucket, toPrefix := this.path(to)
if strings.HasSuffix(from, "/") == false && strings.HasSuffix(to, "/") == false {
// move single file
return this.Project.MoveObject(
this.Context,
fromBucket, fromPrefix,
toBucket, toPrefix, nil,
)
} else if strings.HasSuffix(from, "/") && strings.HasSuffix(to, "/") {
// move a directory
objects := this.Project.ListObjects(this.Context, fromBucket, &uplink.ListObjectsOptions{
Prefix: fromPrefix,
Recursive: true,
})
var (
err error
mu sync.Mutex
)
sem := semaphore.NewWeighted(CONCURRENCY_LEVEL)
for objects.Next() {
item := objects.Item()
if item.IsPrefix {
continue
}
mu.Lock()
if err != nil {
mu.Unlock()
break
}
mu.Unlock()
sem.Acquire(this.Context, 1)
go func() {
_err := this.Project.MoveObject(
this.Context,
fromBucket, item.Key,
toBucket, strings.Replace(item.Key, fromPrefix, toPrefix, 1),
nil,
)
if _err != nil {
mu.Lock()
err = _err
mu.Unlock()
}
sem.Release(1)
}()
}
sem.Acquire(this.Context, CONCURRENCY_LEVEL) // wait
return err
}
// attempt to move a directory onto a file or some other weird combination
return ErrNotValid
}
func (this Storj) Save(path string, content io.Reader) error {
defer this.Project.Close()
bucket, prefix := this.path(path)
upload, err := this.Project.UploadObject(this.Context, bucket, prefix, nil)
if err != nil {
return err
}
if _, err = io.Copy(upload, content); err != nil {
_ = upload.Abort()
return err
}
_ = upload.SetCustomMetadata(this.Context, map[string]string{
"creation_date": time.Now().Format(time.RFC3339),
"created_by": "Filestash",
})
return upload.Commit()
}
func (this Storj) Touch(path string) error {
return this.Save(path, strings.NewReader(""))
}
func (this Storj) path(path string) (string, string) {
path = strings.TrimPrefix(path, "/")
sp := strings.SplitN(path, "/", 2)
if len(sp) != 2 {
return "", ""
}
return sp[0], sp[1]
}