mirror of
https://github.com/rclone/rclone.git
synced 2024-11-22 10:54:48 +08:00
seafile: New backend for seafile server
This commit is contained in:
parent
62cfe3f384
commit
c754e89906
|
@ -59,6 +59,7 @@ Rclone *("rsync for cloud storage")* is a command line program to sync files and
|
||||||
* QingStor [:page_facing_up:](https://rclone.org/qingstor/)
|
* QingStor [:page_facing_up:](https://rclone.org/qingstor/)
|
||||||
* Rackspace Cloud Files [:page_facing_up:](https://rclone.org/swift/)
|
* Rackspace Cloud Files [:page_facing_up:](https://rclone.org/swift/)
|
||||||
* Scaleway [:page_facing_up:](https://rclone.org/s3/#scaleway)
|
* Scaleway [:page_facing_up:](https://rclone.org/s3/#scaleway)
|
||||||
|
* Seafile [:page_facing_up:](https://rclone.org/seafile/)
|
||||||
* SFTP [:page_facing_up:](https://rclone.org/sftp/)
|
* SFTP [:page_facing_up:](https://rclone.org/sftp/)
|
||||||
* StackPath [:page_facing_up:](https://rclone.org/s3/#stackpath)
|
* StackPath [:page_facing_up:](https://rclone.org/s3/#stackpath)
|
||||||
* SugarSync [:page_facing_up:](https://rclone.org/sugarsync/)
|
* SugarSync [:page_facing_up:](https://rclone.org/sugarsync/)
|
||||||
|
|
|
@ -31,6 +31,7 @@ import (
|
||||||
_ "github.com/rclone/rclone/backend/putio"
|
_ "github.com/rclone/rclone/backend/putio"
|
||||||
_ "github.com/rclone/rclone/backend/qingstor"
|
_ "github.com/rclone/rclone/backend/qingstor"
|
||||||
_ "github.com/rclone/rclone/backend/s3"
|
_ "github.com/rclone/rclone/backend/s3"
|
||||||
|
_ "github.com/rclone/rclone/backend/seafile"
|
||||||
_ "github.com/rclone/rclone/backend/sftp"
|
_ "github.com/rclone/rclone/backend/sftp"
|
||||||
_ "github.com/rclone/rclone/backend/sharefile"
|
_ "github.com/rclone/rclone/backend/sharefile"
|
||||||
_ "github.com/rclone/rclone/backend/sugarsync"
|
_ "github.com/rclone/rclone/backend/sugarsync"
|
||||||
|
|
153
backend/seafile/api/types.go
Normal file
153
backend/seafile/api/types.go
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
// Some api objects are duplicated with only small differences,
|
||||||
|
// it's because the returned JSON objects are very inconsistent between api calls
|
||||||
|
|
||||||
|
// AuthenticationRequest contains user credentials
|
||||||
|
type AuthenticationRequest struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthenticationResult is returned by a call to the authentication api
|
||||||
|
type AuthenticationResult struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
Errors []string `json:"non_field_errors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountInfo contains simple user properties
|
||||||
|
type AccountInfo struct {
|
||||||
|
Usage int64 `json:"usage"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerInfo contains server information
|
||||||
|
type ServerInfo struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultLibrary when none specified
|
||||||
|
type DefaultLibrary struct {
|
||||||
|
ID string `json:"repo_id"`
|
||||||
|
Exists bool `json:"exists"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateLibraryRequest contains the information needed to create a library
|
||||||
|
type CreateLibraryRequest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"desc"`
|
||||||
|
Password string `json:"passwd"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Library properties. Please note not all properties are going to be useful for rclone
|
||||||
|
type Library struct {
|
||||||
|
Encrypted bool `json:"encrypted"`
|
||||||
|
Owner string `json:"owner"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Modified int64 `json:"mtime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateLibrary properties. Seafile is not consistent and returns different types for different API calls
|
||||||
|
type CreateLibrary struct {
|
||||||
|
ID string `json:"repo_id"`
|
||||||
|
Name string `json:"repo_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileType is either "dir" or "file"
|
||||||
|
type FileType string
|
||||||
|
|
||||||
|
// File types
|
||||||
|
var (
|
||||||
|
FileTypeDir FileType = "dir"
|
||||||
|
FileTypeFile FileType = "file"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileDetail contains file properties (for older api v2.0)
|
||||||
|
type FileDetail struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type FileType `json:"type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
Parent string `json:"parent_dir"`
|
||||||
|
Modified string `json:"last_modified"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DirEntries contains a list of DirEntry
|
||||||
|
type DirEntries struct {
|
||||||
|
Entries []DirEntry `json:"dirent_list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DirEntry contains a directory entry
|
||||||
|
type DirEntry struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type FileType `json:"type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
Path string `json:"parent_dir"`
|
||||||
|
Modified int64 `json:"mtime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Operation is move, copy or rename
|
||||||
|
type Operation string
|
||||||
|
|
||||||
|
// Operations
|
||||||
|
var (
|
||||||
|
CopyFileOperation Operation = "copy"
|
||||||
|
MoveFileOperation Operation = "move"
|
||||||
|
RenameFileOperation Operation = "rename"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileOperationRequest is sent to the api to copy, move or rename a file
|
||||||
|
type FileOperationRequest struct {
|
||||||
|
Operation Operation `json:"operation"`
|
||||||
|
DestinationLibraryID string `json:"dst_repo"` // For copy/move operation
|
||||||
|
DestinationPath string `json:"dst_dir"` // For copy/move operation
|
||||||
|
NewName string `json:"newname"` // Only to be used by the rename operation
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileInfo is returned by a server file copy/move/rename (new api v2.1)
|
||||||
|
type FileInfo struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
LibraryID string `json:"repo_id"`
|
||||||
|
Path string `json:"parent_dir"`
|
||||||
|
Name string `json:"obj_name"`
|
||||||
|
ID string `json:"obj_id"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDirRequest only contain an operation field
|
||||||
|
type CreateDirRequest struct {
|
||||||
|
Operation string `json:"operation"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DirectoryDetail contains the directory details specific to the getDirectoryDetails call
|
||||||
|
type DirectoryDetail struct {
|
||||||
|
ID string `json:"repo_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShareLinkRequest contains the information needed to create or list shared links
|
||||||
|
type ShareLinkRequest struct {
|
||||||
|
LibraryID string `json:"repo_id"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SharedLink contains the information returned by a call to shared link creation
|
||||||
|
type SharedLink struct {
|
||||||
|
Link string `json:"link"`
|
||||||
|
IsExpired bool `json:"is_expired"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchSourceDestRequest contains JSON parameters for sending a batch copy or move operation
|
||||||
|
type BatchSourceDestRequest struct {
|
||||||
|
SrcLibraryID string `json:"src_repo_id"`
|
||||||
|
SrcParentDir string `json:"src_parent_dir"`
|
||||||
|
SrcItems []string `json:"src_dirents"`
|
||||||
|
DstLibraryID string `json:"dst_repo_id"`
|
||||||
|
DstParentDir string `json:"dst_parent_dir"`
|
||||||
|
}
|
127
backend/seafile/object.go
Normal file
127
backend/seafile/object.go
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
package seafile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rclone/rclone/fs"
|
||||||
|
"github.com/rclone/rclone/fs/hash"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Object describes a seafile object (also commonly called a file)
|
||||||
|
type Object struct {
|
||||||
|
fs *Fs // what this object is part of
|
||||||
|
id string // internal ID of object
|
||||||
|
remote string // The remote path (full path containing library name if target at root)
|
||||||
|
pathInLibrary string // Path of the object without the library name
|
||||||
|
size int64 // size of the object
|
||||||
|
modTime time.Time // modification time of the object
|
||||||
|
libraryID string // Needed to download the file
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Interface fs.DirEntry ====================
|
||||||
|
|
||||||
|
// Return a string version
|
||||||
|
func (o *Object) String() string {
|
||||||
|
if o == nil {
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
return o.remote
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remote returns the remote string
|
||||||
|
func (o *Object) Remote() string {
|
||||||
|
return o.remote
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModTime returns last modified time
|
||||||
|
func (o *Object) ModTime(context.Context) time.Time {
|
||||||
|
return o.modTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the size of an object in bytes
|
||||||
|
func (o *Object) Size() int64 {
|
||||||
|
return o.size
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Interface fs.ObjectInfo ====================
|
||||||
|
|
||||||
|
// Fs returns the parent Fs
|
||||||
|
func (o *Object) Fs() fs.Info {
|
||||||
|
return o.fs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash returns the selected checksum of the file
|
||||||
|
// If no checksum is available it returns ""
|
||||||
|
func (o *Object) Hash(ctx context.Context, ty hash.Type) (string, error) {
|
||||||
|
return "", hash.ErrUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storable says whether this object can be stored
|
||||||
|
func (o *Object) Storable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Interface fs.Object ====================
|
||||||
|
|
||||||
|
// SetModTime sets the metadata on the object to set the modification date
|
||||||
|
func (o *Object) SetModTime(ctx context.Context, t time.Time) error {
|
||||||
|
return fs.ErrorCantSetModTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open opens the file for read. Call Close() on the returned io.ReadCloser
|
||||||
|
func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (io.ReadCloser, error) {
|
||||||
|
downloadLink, err := o.fs.getDownloadLink(ctx, o.libraryID, o.pathInLibrary)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
reader, err := o.fs.download(ctx, downloadLink, o.Size(), options...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return reader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update in to the object with the modTime given of the given size
|
||||||
|
//
|
||||||
|
// When called from outside a Fs by rclone, src.Size() will always be >= 0.
|
||||||
|
// But for unknown-sized objects (indicated by src.Size() == -1), Upload should either
|
||||||
|
// return an error or update the object properly (rather than e.g. calling panic).
|
||||||
|
func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error {
|
||||||
|
// The upload sometimes return a temporary 500 error
|
||||||
|
// We cannot use the pacer to retry uploading the file as the upload link is single use only
|
||||||
|
for retry := 0; retry <= 3; retry++ {
|
||||||
|
uploadLink, err := o.fs.getUploadLink(ctx, o.libraryID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
uploaded, err := o.fs.upload(ctx, in, uploadLink, o.pathInLibrary)
|
||||||
|
if err == ErrorInternalDuringUpload {
|
||||||
|
// This is a temporary error, try again with a new upload link
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Set the properties from the upload back to the object
|
||||||
|
o.size = uploaded.Size
|
||||||
|
o.id = uploaded.ID
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ErrorInternalDuringUpload
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove this object
|
||||||
|
func (o *Object) Remove(ctx context.Context) error {
|
||||||
|
return o.fs.deleteFile(ctx, o.libraryID, o.pathInLibrary)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Optional Interface fs.IDer ====================
|
||||||
|
|
||||||
|
// ID returns the ID of the Object if known, or "" if not
|
||||||
|
func (o *Object) ID() string {
|
||||||
|
return o.id
|
||||||
|
}
|
67
backend/seafile/pacer.go
Normal file
67
backend/seafile/pacer.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package seafile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rclone/rclone/fs"
|
||||||
|
"github.com/rclone/rclone/lib/pacer"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
minSleep = 100 * time.Millisecond
|
||||||
|
maxSleep = 10 * time.Second
|
||||||
|
decayConstant = 2 // bigger for slower decay, exponential
|
||||||
|
)
|
||||||
|
|
||||||
|
// Use only one pacer per server URL
|
||||||
|
var (
|
||||||
|
pacers map[string]*fs.Pacer
|
||||||
|
pacerMutex sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
pacers = make(map[string]*fs.Pacer, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPacer returns the unique pacer for that remote URL
|
||||||
|
func getPacer(remote string) *fs.Pacer {
|
||||||
|
pacerMutex.Lock()
|
||||||
|
defer pacerMutex.Unlock()
|
||||||
|
|
||||||
|
remote = parseRemote(remote)
|
||||||
|
if existing, found := pacers[remote]; found {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
|
||||||
|
pacers[remote] = fs.NewPacer(
|
||||||
|
pacer.NewDefault(
|
||||||
|
pacer.MinSleep(minSleep),
|
||||||
|
pacer.MaxSleep(maxSleep),
|
||||||
|
pacer.DecayConstant(decayConstant),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return pacers[remote]
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseRemote formats a remote url into "hostname:port"
|
||||||
|
func parseRemote(remote string) string {
|
||||||
|
remoteURL, err := url.Parse(remote)
|
||||||
|
if err != nil {
|
||||||
|
// Return a default value in the very unlikely event we're not going to parse remote
|
||||||
|
fs.Infof(nil, "Cannot parse remote %s", remote)
|
||||||
|
return "default"
|
||||||
|
}
|
||||||
|
host := remoteURL.Hostname()
|
||||||
|
port := remoteURL.Port()
|
||||||
|
if port == "" {
|
||||||
|
if remoteURL.Scheme == "https" {
|
||||||
|
port = "443"
|
||||||
|
} else {
|
||||||
|
port = "80"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s:%s", host, port)
|
||||||
|
}
|
1247
backend/seafile/seafile.go
Normal file
1247
backend/seafile/seafile.go
Normal file
File diff suppressed because it is too large
Load Diff
123
backend/seafile/seafile_internal_test.go
Normal file
123
backend/seafile/seafile_internal_test.go
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
package seafile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pathData struct {
|
||||||
|
configLibrary string // Library specified in the config
|
||||||
|
configRoot string // Root directory specified in the config
|
||||||
|
argumentPath string // Path given as an argument in the command line
|
||||||
|
expectedLibrary string
|
||||||
|
expectedPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the method to split a library name and a path
|
||||||
|
// from a mix of configuration data and path command line argument
|
||||||
|
func TestSplitPath(t *testing.T) {
|
||||||
|
testData := []pathData{
|
||||||
|
pathData{
|
||||||
|
configLibrary: "",
|
||||||
|
configRoot: "",
|
||||||
|
argumentPath: "",
|
||||||
|
expectedLibrary: "",
|
||||||
|
expectedPath: "",
|
||||||
|
},
|
||||||
|
pathData{
|
||||||
|
configLibrary: "",
|
||||||
|
configRoot: "",
|
||||||
|
argumentPath: "Library",
|
||||||
|
expectedLibrary: "Library",
|
||||||
|
expectedPath: "",
|
||||||
|
},
|
||||||
|
pathData{
|
||||||
|
configLibrary: "",
|
||||||
|
configRoot: "",
|
||||||
|
argumentPath: path.Join("Library", "path", "to", "file"),
|
||||||
|
expectedLibrary: "Library",
|
||||||
|
expectedPath: path.Join("path", "to", "file"),
|
||||||
|
},
|
||||||
|
pathData{
|
||||||
|
configLibrary: "Library",
|
||||||
|
configRoot: "",
|
||||||
|
argumentPath: "",
|
||||||
|
expectedLibrary: "Library",
|
||||||
|
expectedPath: "",
|
||||||
|
},
|
||||||
|
pathData{
|
||||||
|
configLibrary: "Library",
|
||||||
|
configRoot: "",
|
||||||
|
argumentPath: "path",
|
||||||
|
expectedLibrary: "Library",
|
||||||
|
expectedPath: "path",
|
||||||
|
},
|
||||||
|
pathData{
|
||||||
|
configLibrary: "Library",
|
||||||
|
configRoot: "",
|
||||||
|
argumentPath: path.Join("path", "to", "file"),
|
||||||
|
expectedLibrary: "Library",
|
||||||
|
expectedPath: path.Join("path", "to", "file"),
|
||||||
|
},
|
||||||
|
pathData{
|
||||||
|
configLibrary: "Library",
|
||||||
|
configRoot: "root",
|
||||||
|
argumentPath: "",
|
||||||
|
expectedLibrary: "Library",
|
||||||
|
expectedPath: "root",
|
||||||
|
},
|
||||||
|
pathData{
|
||||||
|
configLibrary: "Library",
|
||||||
|
configRoot: path.Join("root", "path"),
|
||||||
|
argumentPath: "",
|
||||||
|
expectedLibrary: "Library",
|
||||||
|
expectedPath: path.Join("root", "path"),
|
||||||
|
},
|
||||||
|
pathData{
|
||||||
|
configLibrary: "Library",
|
||||||
|
configRoot: "root",
|
||||||
|
argumentPath: "path",
|
||||||
|
expectedLibrary: "Library",
|
||||||
|
expectedPath: path.Join("root", "path"),
|
||||||
|
},
|
||||||
|
pathData{
|
||||||
|
configLibrary: "Library",
|
||||||
|
configRoot: "root",
|
||||||
|
argumentPath: path.Join("path", "to", "file"),
|
||||||
|
expectedLibrary: "Library",
|
||||||
|
expectedPath: path.Join("root", "path", "to", "file"),
|
||||||
|
},
|
||||||
|
pathData{
|
||||||
|
configLibrary: "Library",
|
||||||
|
configRoot: path.Join("root", "path"),
|
||||||
|
argumentPath: path.Join("subpath", "to", "file"),
|
||||||
|
expectedLibrary: "Library",
|
||||||
|
expectedPath: path.Join("root", "path", "subpath", "to", "file"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range testData {
|
||||||
|
fs := &Fs{
|
||||||
|
libraryName: test.configLibrary,
|
||||||
|
rootDirectory: test.configRoot,
|
||||||
|
}
|
||||||
|
libraryName, path := fs.splitPath(test.argumentPath)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expectedLibrary, libraryName)
|
||||||
|
assert.Equal(t, test.expectedPath, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitPathIntoSlice(t *testing.T) {
|
||||||
|
testData := map[string][]string{
|
||||||
|
"1": {"1"},
|
||||||
|
"/1": {"1"},
|
||||||
|
"/1/": {"1"},
|
||||||
|
"1/2/3": {"1", "2", "3"},
|
||||||
|
}
|
||||||
|
for input, expected := range testData {
|
||||||
|
output := splitPath(input)
|
||||||
|
assert.Equal(t, expected, output)
|
||||||
|
}
|
||||||
|
}
|
17
backend/seafile/seafile_test.go
Normal file
17
backend/seafile/seafile_test.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// Test Seafile filesystem interface
|
||||||
|
package seafile_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/rclone/rclone/backend/seafile"
|
||||||
|
"github.com/rclone/rclone/fstest/fstests"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestIntegration runs integration tests against the remote
|
||||||
|
func TestIntegration(t *testing.T) {
|
||||||
|
fstests.Run(t, &fstests.Opt{
|
||||||
|
RemoteName: "TestSeafile:",
|
||||||
|
NilObject: (*seafile.Object)(nil),
|
||||||
|
})
|
||||||
|
}
|
1083
backend/seafile/webapi.go
Normal file
1083
backend/seafile/webapi.go
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -54,6 +54,7 @@ docs = [
|
||||||
"pcloud.md",
|
"pcloud.md",
|
||||||
"premiumizeme.md",
|
"premiumizeme.md",
|
||||||
"putio.md",
|
"putio.md",
|
||||||
|
"seafile.md",
|
||||||
"sftp.md",
|
"sftp.md",
|
||||||
"sugarsync.md",
|
"sugarsync.md",
|
||||||
"union.md",
|
"union.md",
|
||||||
|
|
|
@ -51,6 +51,7 @@ Rclone is a command line program to sync files and directories to and from:
|
||||||
* {{< provider name="Rackspace Cloud Files" home="https://www.rackspace.com/cloud/files" config="/swift/" >}}
|
* {{< provider name="Rackspace Cloud Files" home="https://www.rackspace.com/cloud/files" config="/swift/" >}}
|
||||||
* {{< provider name="rsync.net" home="https://rsync.net/products/rclone.html" config="/sftp/#rsync-net" >}}
|
* {{< provider name="rsync.net" home="https://rsync.net/products/rclone.html" config="/sftp/#rsync-net" >}}
|
||||||
* {{< provider name="Scaleway" home="https://www.scaleway.com/object-storage/" config="/s3/#scaleway" >}}
|
* {{< provider name="Scaleway" home="https://www.scaleway.com/object-storage/" config="/s3/#scaleway" >}}
|
||||||
|
* {{< provider name="Seafile" home="https://www.seafile.com/" config="/seafile/" >}}
|
||||||
* {{< provider name="SFTP" home="https://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol" config="/sftp/" >}}
|
* {{< provider name="SFTP" home="https://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol" config="/sftp/" >}}
|
||||||
* {{< provider name="StackPath" home="https://www.stackpath.com/products/object-storage/" config="/s3/#stackpath" >}}
|
* {{< provider name="StackPath" home="https://www.stackpath.com/products/object-storage/" config="/s3/#stackpath" >}}
|
||||||
* {{< provider name="SugarSync" home="https://sugarsync.com/" config="/sugarsync/" >}}
|
* {{< provider name="SugarSync" home="https://sugarsync.com/" config="/sugarsync/" >}}
|
||||||
|
|
|
@ -50,6 +50,7 @@ See the following for detailed instructions for
|
||||||
* [premiumize.me](/premiumizeme/)
|
* [premiumize.me](/premiumizeme/)
|
||||||
* [put.io](/putio/)
|
* [put.io](/putio/)
|
||||||
* [QingStor](/qingstor/)
|
* [QingStor](/qingstor/)
|
||||||
|
* [Seafile](/seafile/)
|
||||||
* [SFTP](/sftp/)
|
* [SFTP](/sftp/)
|
||||||
* [SugarSync](/sugarsync/)
|
* [SugarSync](/sugarsync/)
|
||||||
* [Union](/union/)
|
* [Union](/union/)
|
||||||
|
|
|
@ -43,6 +43,7 @@ Here is an overview of the major features of each cloud storage system.
|
||||||
| premiumize.me | - | No | Yes | No | R |
|
| premiumize.me | - | No | Yes | No | R |
|
||||||
| put.io | CRC-32 | Yes | No | Yes | R |
|
| put.io | CRC-32 | Yes | No | Yes | R |
|
||||||
| QingStor | MD5 | No | No | No | R/W |
|
| QingStor | MD5 | No | No | No | R/W |
|
||||||
|
| Seafile | - | No | No | No | - |
|
||||||
| SFTP | MD5, SHA1 ‡ | Yes | Depends | No | - |
|
| SFTP | MD5, SHA1 ‡ | Yes | Depends | No | - |
|
||||||
| SugarSync | - | No | No | No | - |
|
| SugarSync | - | No | No | No | - |
|
||||||
| WebDAV | MD5, SHA1 ††| Yes ††† | Depends | No | - |
|
| WebDAV | MD5, SHA1 ††| Yes ††† | Depends | No | - |
|
||||||
|
@ -342,6 +343,7 @@ operations more efficient.
|
||||||
| premiumize.me | Yes | No | Yes | Yes | No | No | No | Yes | Yes | Yes |
|
| premiumize.me | Yes | No | Yes | Yes | No | No | No | Yes | Yes | Yes |
|
||||||
| put.io | Yes | No | Yes | Yes | Yes | No | Yes | No [#2178](https://github.com/rclone/rclone/issues/2178) | Yes | Yes |
|
| put.io | Yes | No | Yes | Yes | Yes | No | Yes | No [#2178](https://github.com/rclone/rclone/issues/2178) | Yes | Yes |
|
||||||
| QingStor | No | Yes | No | No | Yes | Yes | No | No [#2178](https://github.com/rclone/rclone/issues/2178) | No | No |
|
| QingStor | No | Yes | No | No | Yes | Yes | No | No [#2178](https://github.com/rclone/rclone/issues/2178) | No | No |
|
||||||
|
| Seafile | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||||
| SFTP | No | No | Yes | Yes | No | No | Yes | No [#2178](https://github.com/rclone/rclone/issues/2178) | Yes | Yes |
|
| SFTP | No | No | Yes | Yes | No | No | Yes | No [#2178](https://github.com/rclone/rclone/issues/2178) | Yes | Yes |
|
||||||
| SugarSync | Yes | Yes | Yes | Yes | No | No | Yes | Yes | No | Yes |
|
| SugarSync | Yes | Yes | Yes | Yes | No | No | Yes | Yes | No | Yes |
|
||||||
| WebDAV | Yes | Yes | Yes | Yes | No | No | Yes ‡ | No [#2178](https://github.com/rclone/rclone/issues/2178) | Yes | Yes |
|
| WebDAV | Yes | Yes | Yes | Yes | No | No | Yes ‡ | No [#2178](https://github.com/rclone/rclone/issues/2178) | Yes | Yes |
|
||||||
|
|
251
docs/content/seafile.md
Normal file
251
docs/content/seafile.md
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
---
|
||||||
|
title: "Seafile"
|
||||||
|
description: "Seafile"
|
||||||
|
date: "2020-05-02"
|
||||||
|
---
|
||||||
|
|
||||||
|
<i class="fa fa-server"></i>Seafile
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
This is a backend for the [Seafile](https://www.seafile.com/) storage service.
|
||||||
|
It works with both the free community edition, or the professional edition.
|
||||||
|
Seafile versions 6.x and 7.x are all supported.
|
||||||
|
Encrypted libraries are also supported.
|
||||||
|
|
||||||
|
### Root mode vs Library mode ###
|
||||||
|
|
||||||
|
There are two distinct modes you can setup your remote:
|
||||||
|
- you point your remote to the **root of the server**, meaning you don't specify a library during the configuration:
|
||||||
|
Paths are specified as `remote:library`. You may put subdirectories in too, eg `remote:library/path/to/dir`.
|
||||||
|
- you point your remote to a specific library during the configuration:
|
||||||
|
Paths are specified as `remote:path/to/dir`. **This is the recommended mode when using encrypted libraries**.
|
||||||
|
|
||||||
|
### Configuration in root mode ###
|
||||||
|
|
||||||
|
Here is an example of making a seafile configuration. First run
|
||||||
|
|
||||||
|
rclone config
|
||||||
|
|
||||||
|
This will guide you through an interactive setup process. To authenticate
|
||||||
|
you will need the URL of your server, your email (or username) and your password.
|
||||||
|
|
||||||
|
```
|
||||||
|
No remotes found - make a new one
|
||||||
|
n) New remote
|
||||||
|
s) Set configuration password
|
||||||
|
q) Quit config
|
||||||
|
n/s/q> n
|
||||||
|
name> seafile
|
||||||
|
Type of storage to configure.
|
||||||
|
Enter a string value. Press Enter for the default ("").
|
||||||
|
Choose a number from below, or type in your own value
|
||||||
|
[snip]
|
||||||
|
XX / Seafile
|
||||||
|
\ "seafile"
|
||||||
|
[snip]
|
||||||
|
Storage> seafile
|
||||||
|
** See help for seafile backend at: https://rclone.org/seafile/ **
|
||||||
|
|
||||||
|
URL of seafile host to connect to
|
||||||
|
Enter a string value. Press Enter for the default ("").
|
||||||
|
Choose a number from below, or type in your own value
|
||||||
|
1 / Connect to cloud.seafile.com
|
||||||
|
\ "https://cloud.seafile.com/"
|
||||||
|
url> http://my.seafile.server/
|
||||||
|
User name
|
||||||
|
Enter a string value. Press Enter for the default ("").
|
||||||
|
user> me@example.com
|
||||||
|
Password
|
||||||
|
y) Yes type in my own password
|
||||||
|
g) Generate random password
|
||||||
|
y/g> y
|
||||||
|
Enter the password:
|
||||||
|
password:
|
||||||
|
Confirm the password:
|
||||||
|
password:
|
||||||
|
Name of the library. Leave blank to access all non-encrypted libraries.
|
||||||
|
Enter a string value. Press Enter for the default ("").
|
||||||
|
library>
|
||||||
|
Library password (for encrypted libraries only). Leave blank if you pass it through the command line.
|
||||||
|
y) Yes type in my own password
|
||||||
|
g) Generate random password
|
||||||
|
n) No leave this optional password blank (default)
|
||||||
|
y/g/n> n
|
||||||
|
Edit advanced config? (y/n)
|
||||||
|
y) Yes
|
||||||
|
n) No (default)
|
||||||
|
y/n> n
|
||||||
|
Remote config
|
||||||
|
--------------------
|
||||||
|
[seafile]
|
||||||
|
type = seafile
|
||||||
|
url = http://my.seafile.server/
|
||||||
|
user = me@example.com
|
||||||
|
password = *** ENCRYPTED ***
|
||||||
|
--------------------
|
||||||
|
y) Yes this is OK (default)
|
||||||
|
e) Edit this remote
|
||||||
|
d) Delete this remote
|
||||||
|
y/e/d> y
|
||||||
|
```
|
||||||
|
|
||||||
|
This remote is called `seafile`. It's pointing to the root of your seafile server and can now be used like this
|
||||||
|
|
||||||
|
See all libraries
|
||||||
|
|
||||||
|
rclone lsd seafile:
|
||||||
|
|
||||||
|
Create a new library
|
||||||
|
|
||||||
|
rclone mkdir seafile:library
|
||||||
|
|
||||||
|
List the contents of a library
|
||||||
|
|
||||||
|
rclone ls seafile:library
|
||||||
|
|
||||||
|
Sync `/home/local/directory` to the remote library, deleting any
|
||||||
|
excess files in the library.
|
||||||
|
|
||||||
|
rclone sync /home/local/directory seafile:library
|
||||||
|
|
||||||
|
### Configuration in library mode ###
|
||||||
|
|
||||||
|
```
|
||||||
|
No remotes found - make a new one
|
||||||
|
n) New remote
|
||||||
|
s) Set configuration password
|
||||||
|
q) Quit config
|
||||||
|
n/s/q> n
|
||||||
|
name> seafile
|
||||||
|
Type of storage to configure.
|
||||||
|
Enter a string value. Press Enter for the default ("").
|
||||||
|
Choose a number from below, or type in your own value
|
||||||
|
[snip]
|
||||||
|
XX / Seafile
|
||||||
|
\ "seafile"
|
||||||
|
[snip]
|
||||||
|
Storage> seafile
|
||||||
|
** See help for seafile backend at: https://rclone.org/seafile/ **
|
||||||
|
|
||||||
|
URL of seafile host to connect to
|
||||||
|
Enter a string value. Press Enter for the default ("").
|
||||||
|
Choose a number from below, or type in your own value
|
||||||
|
1 / Connect to cloud.seafile.com
|
||||||
|
\ "https://cloud.seafile.com/"
|
||||||
|
url> http://my.seafile.server/
|
||||||
|
User name
|
||||||
|
Enter a string value. Press Enter for the default ("").
|
||||||
|
user> me@example.com
|
||||||
|
Password
|
||||||
|
y) Yes type in my own password
|
||||||
|
g) Generate random password
|
||||||
|
y/g> y
|
||||||
|
Enter the password:
|
||||||
|
password:
|
||||||
|
Confirm the password:
|
||||||
|
password:
|
||||||
|
Name of the library. Leave blank to access all non-encrypted libraries.
|
||||||
|
Enter a string value. Press Enter for the default ("").
|
||||||
|
library> My Library
|
||||||
|
Library password (for encrypted libraries only). Leave blank if you pass it through the command line.
|
||||||
|
y) Yes type in my own password
|
||||||
|
g) Generate random password
|
||||||
|
n) No leave this optional password blank (default)
|
||||||
|
y/g/n> n
|
||||||
|
Edit advanced config? (y/n)
|
||||||
|
y) Yes
|
||||||
|
n) No (default)
|
||||||
|
y/n> n
|
||||||
|
Remote config
|
||||||
|
--------------------
|
||||||
|
[seafile]
|
||||||
|
type = seafile
|
||||||
|
url = http://my.seafile.server/
|
||||||
|
user = me@example.com
|
||||||
|
password = *** ENCRYPTED ***
|
||||||
|
library = My Library
|
||||||
|
--------------------
|
||||||
|
y) Yes this is OK (default)
|
||||||
|
e) Edit this remote
|
||||||
|
d) Delete this remote
|
||||||
|
y/e/d> y
|
||||||
|
```
|
||||||
|
|
||||||
|
You specified `My Library` during the configuration. The root of the remote is pointing at the
|
||||||
|
root of the library `My Library`:
|
||||||
|
|
||||||
|
See all files in the library:
|
||||||
|
|
||||||
|
rclone lsd seafile:
|
||||||
|
|
||||||
|
Create a new directory inside the library
|
||||||
|
|
||||||
|
rclone mkdir seafile:directory
|
||||||
|
|
||||||
|
List the contents of a directory
|
||||||
|
|
||||||
|
rclone ls seafile:directory
|
||||||
|
|
||||||
|
Sync `/home/local/directory` to the remote library, deleting any
|
||||||
|
excess files in the library.
|
||||||
|
|
||||||
|
rclone sync /home/local/directory seafile:
|
||||||
|
|
||||||
|
|
||||||
|
### --fast-list ###
|
||||||
|
|
||||||
|
Seafile version 7+ supports `--fast-list` which allows you to use fewer
|
||||||
|
transactions in exchange for more memory. See the [rclone
|
||||||
|
docs](/docs/#fast-list) for more details.
|
||||||
|
Please note this is not supported on seafile server version 6.x
|
||||||
|
|
||||||
|
|
||||||
|
#### Restricted filename characters
|
||||||
|
|
||||||
|
In addition to the [default restricted characters set](/overview/#restricted-characters)
|
||||||
|
the following characters are also replaced:
|
||||||
|
|
||||||
|
| Character | Value | Replacement |
|
||||||
|
| --------- |:-----:|:-----------:|
|
||||||
|
| / | 0x2F | / |
|
||||||
|
| " | 0x22 | " |
|
||||||
|
| \ | 0x5C | \ |
|
||||||
|
|
||||||
|
Invalid UTF-8 bytes will also be [replaced](/overview/#invalid-utf8),
|
||||||
|
as they can't be used in JSON strings.
|
||||||
|
|
||||||
|
### Seafile and rclone link ###
|
||||||
|
|
||||||
|
Rclone supports generating share links for non-encrypted libraries only.
|
||||||
|
They can either be for a file or a directory:
|
||||||
|
|
||||||
|
```
|
||||||
|
rclone link seafile:seafile-tutorial.doc
|
||||||
|
http://my.seafile.server/f/fdcd8a2f93f84b8b90f4/
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
or if run on a directory you will get:
|
||||||
|
|
||||||
|
```
|
||||||
|
rclone link seafile:dir
|
||||||
|
http://my.seafile.server/d/9ea2455f6f55478bbb0d/
|
||||||
|
```
|
||||||
|
|
||||||
|
Please note a share link is unique for each file or directory. If you run a link command on a file/dir
|
||||||
|
that has already been shared, you will get the exact same link.
|
||||||
|
|
||||||
|
### Compatibility ###
|
||||||
|
|
||||||
|
It has been actively tested using the [seafile docker image](https://github.com/haiwen/seafile-docker) of these versions:
|
||||||
|
- 6.3.4 community edition
|
||||||
|
- 7.0.5 community edition
|
||||||
|
- 7.1.3 community edition
|
||||||
|
|
||||||
|
Versions below 6.0 are not supported.
|
||||||
|
Versions between 6.0 and 6.3 haven't been tested and might not work properly.
|
||||||
|
|
||||||
|
<!--- autogenerated options start - DO NOT EDIT, instead edit fs.RegInfo in backend/seafile/seafile.go then run make backenddocs -->
|
||||||
|
|
||||||
|
<!--- autogenerated options stop -->
|
||||||
|
|
|
@ -86,6 +86,7 @@
|
||||||
<li><a href="/pcloud/"><i class="fa fa-cloud"></i> pCloud</a></li>
|
<li><a href="/pcloud/"><i class="fa fa-cloud"></i> pCloud</a></li>
|
||||||
<li><a href="/premiumizeme/"><i class="fa fa-user"></i> premiumize.me</a></li>
|
<li><a href="/premiumizeme/"><i class="fa fa-user"></i> premiumize.me</a></li>
|
||||||
<li><a href="/putio/"><i class="fas fa-parking"></i> put.io</a></li>
|
<li><a href="/putio/"><i class="fas fa-parking"></i> put.io</a></li>
|
||||||
|
<li><a href="/seafile/"><i class="fa fa-server"></i> Seafile</a></li>
|
||||||
<li><a href="/sftp/"><i class="fa fa-server"></i> SFTP</a></li>
|
<li><a href="/sftp/"><i class="fa fa-server"></i> SFTP</a></li>
|
||||||
<li><a href="/sugarsync/"><i class="fas fa-dove"></i> SugarSync</a></li>
|
<li><a href="/sugarsync/"><i class="fas fa-dove"></i> SugarSync</a></li>
|
||||||
<li><a href="/union/"><i class="fa fa-link"></i> Union (merge backends)</a></li>
|
<li><a href="/union/"><i class="fa fa-link"></i> Union (merge backends)</a></li>
|
||||||
|
|
|
@ -264,3 +264,14 @@ backends:
|
||||||
remote: "TestMailru:"
|
remote: "TestMailru:"
|
||||||
subdir: false
|
subdir: false
|
||||||
fastlist: false
|
fastlist: false
|
||||||
|
- backend: "seafile"
|
||||||
|
remote: "TestSeafileV6:"
|
||||||
|
fastlist: false
|
||||||
|
ignore:
|
||||||
|
- TestIntegration/FsMkdir/FsPutFiles/FsDirMove
|
||||||
|
- backend: "seafile"
|
||||||
|
remote: "TestSeafile:"
|
||||||
|
fastlist: true
|
||||||
|
- backend: "seafile"
|
||||||
|
remote: "TestSeafileEncrypted:"
|
||||||
|
fastlist: true
|
||||||
|
|
60
fstest/testserver/init.d/TestSeafile
Executable file
60
fstest/testserver/init.d/TestSeafile
Executable file
|
@ -0,0 +1,60 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# environment variables passed on docker-compose
|
||||||
|
export NAME=seafile7
|
||||||
|
export MYSQL_ROOT_PASSWORD=pixenij4zacoguq0kopamid6
|
||||||
|
export SEAFILE_ADMIN_EMAIL=seafile@rclone.org
|
||||||
|
export SEAFILE_ADMIN_PASSWORD=pixenij4zacoguq0kopamid6
|
||||||
|
export SEAFILE_IP=127.0.0.1
|
||||||
|
export SEAFILE_PORT=8087
|
||||||
|
export SEAFILE_TEST_DATA=${SEAFILE_TEST_DATA:-/tmp/seafile-test-data}
|
||||||
|
export SEAFILE_VERSION=latest
|
||||||
|
|
||||||
|
# make sure the data directory exists
|
||||||
|
mkdir -p ${SEAFILE_TEST_DATA}/${NAME}
|
||||||
|
|
||||||
|
# docker-compose project directory
|
||||||
|
COMPOSE_DIR=$(dirname "$0")/seafile
|
||||||
|
|
||||||
|
start() {
|
||||||
|
docker-compose --project-directory ${COMPOSE_DIR} --project-name ${NAME} --file ${COMPOSE_DIR}/docker-compose.yml up -d
|
||||||
|
|
||||||
|
# it takes some time for the database to be created
|
||||||
|
sleep 60
|
||||||
|
|
||||||
|
# authentication token answer should be like: {"token":"dbf58423f1632b5b679a13b0929f1d0751d9250c"}
|
||||||
|
TOKEN=`curl --silent \
|
||||||
|
--data-urlencode username=${SEAFILE_ADMIN_EMAIL} -d password=${SEAFILE_ADMIN_PASSWORD} \
|
||||||
|
http://${SEAFILE_IP}:${SEAFILE_PORT}/api2/auth-token/ \
|
||||||
|
| sed 's/^{"token":"\(.*\)"}$/\1/'`
|
||||||
|
|
||||||
|
# create default library
|
||||||
|
curl -X POST -H "Authorization: Token ${TOKEN}" "http://${SEAFILE_IP}:${SEAFILE_PORT}/api2/default-repo/"
|
||||||
|
|
||||||
|
echo _connect=${SEAFILE_IP}:${SEAFILE_PORT}
|
||||||
|
echo type=seafile
|
||||||
|
echo url=http://${SEAFILE_IP}:${SEAFILE_PORT}/
|
||||||
|
echo user=${SEAFILE_ADMIN_EMAIL}
|
||||||
|
echo pass=$(rclone obscure ${SEAFILE_ADMIN_PASSWORD})
|
||||||
|
echo library=My Library
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
if status ; then
|
||||||
|
docker-compose --project-directory ${COMPOSE_DIR} --project-name ${NAME} --file ${COMPOSE_DIR}/docker-compose.yml down
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
status() {
|
||||||
|
if docker ps --format "{{.Names}}" | grep ^${NAME}_seafile_1$ >/dev/null ; then
|
||||||
|
echo "$NAME running"
|
||||||
|
else
|
||||||
|
echo "$NAME not running"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
. $(dirname "$0")/run.bash
|
65
fstest/testserver/init.d/TestSeafileEncrypted
Executable file
65
fstest/testserver/init.d/TestSeafileEncrypted
Executable file
|
@ -0,0 +1,65 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# local variables
|
||||||
|
TEST_LIBRARY=Encrypted
|
||||||
|
TEST_LIBRARY_PASSWORD=SecretKey
|
||||||
|
|
||||||
|
# environment variables passed on docker-compose
|
||||||
|
export NAME=seafile7encrypted
|
||||||
|
export MYSQL_ROOT_PASSWORD=pixenij4zacoguq0kopamid6
|
||||||
|
export SEAFILE_ADMIN_EMAIL=seafile@rclone.org
|
||||||
|
export SEAFILE_ADMIN_PASSWORD=pixenij4zacoguq0kopamid6
|
||||||
|
export SEAFILE_IP=127.0.0.1
|
||||||
|
export SEAFILE_PORT=8088
|
||||||
|
export SEAFILE_TEST_DATA=${SEAFILE_TEST_DATA:-/tmp/seafile-test-data}
|
||||||
|
export SEAFILE_VERSION=latest
|
||||||
|
|
||||||
|
# make sure the data directory exists
|
||||||
|
mkdir -p ${SEAFILE_TEST_DATA}/${NAME}
|
||||||
|
|
||||||
|
# docker-compose project directory
|
||||||
|
COMPOSE_DIR=$(dirname "$0")/seafile
|
||||||
|
|
||||||
|
start() {
|
||||||
|
docker-compose --project-directory ${COMPOSE_DIR} --project-name ${NAME} --file ${COMPOSE_DIR}/docker-compose.yml up -d
|
||||||
|
|
||||||
|
# it takes some time for the database to be created
|
||||||
|
sleep 60
|
||||||
|
|
||||||
|
# authentication token answer should be like: {"token":"dbf58423f1632b5b679a13b0929f1d0751d9250c"}
|
||||||
|
TOKEN=`curl --silent \
|
||||||
|
--data-urlencode username=${SEAFILE_ADMIN_EMAIL} -d password=${SEAFILE_ADMIN_PASSWORD} \
|
||||||
|
http://${SEAFILE_IP}:${SEAFILE_PORT}/api2/auth-token/ \
|
||||||
|
| sed 's/^{"token":"\(.*\)"}$/\1/'`
|
||||||
|
|
||||||
|
# create encrypted library
|
||||||
|
curl -X POST -d "name=${TEST_LIBRARY}&passwd=${TEST_LIBRARY_PASSWORD}" -H "Authorization: Token ${TOKEN}" "http://${SEAFILE_IP}:${SEAFILE_PORT}/api2/repos/"
|
||||||
|
|
||||||
|
echo _connect=${SEAFILE_IP}:${SEAFILE_PORT}
|
||||||
|
echo type=seafile
|
||||||
|
echo url=http://${SEAFILE_IP}:${SEAFILE_PORT}/
|
||||||
|
echo user=${SEAFILE_ADMIN_EMAIL}
|
||||||
|
echo pass=$(rclone obscure ${SEAFILE_ADMIN_PASSWORD})
|
||||||
|
echo library=${TEST_LIBRARY}
|
||||||
|
echo library_key=$(rclone obscure ${TEST_LIBRARY_PASSWORD})
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
if status ; then
|
||||||
|
docker-compose --project-directory ${COMPOSE_DIR} --project-name ${NAME} --file ${COMPOSE_DIR}/docker-compose.yml down
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
status() {
|
||||||
|
if docker ps --format "{{.Names}}" | grep ^${NAME}_seafile_1$ >/dev/null ; then
|
||||||
|
echo "$NAME running"
|
||||||
|
else
|
||||||
|
echo "$NAME not running"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
. $(dirname "$0")/run.bash
|
48
fstest/testserver/init.d/TestSeafileV6
Executable file
48
fstest/testserver/init.d/TestSeafileV6
Executable file
|
@ -0,0 +1,48 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# local variables
|
||||||
|
NAME=seafile6
|
||||||
|
SEAFILE_IP=127.0.0.1
|
||||||
|
SEAFILE_PORT=8086
|
||||||
|
SEAFILE_ADMIN_EMAIL=seafile@rclone.org
|
||||||
|
SEAFILE_ADMIN_PASSWORD=qebiwob7wafixif8sojiboj4
|
||||||
|
SEAFILE_TEST_DATA=${SEAFILE_TEST_DATA:-/tmp/seafile-test-data}
|
||||||
|
SEAFILE_VERSION=latest
|
||||||
|
|
||||||
|
. $(dirname "$0")/docker.bash
|
||||||
|
|
||||||
|
start() {
|
||||||
|
# make sure the data directory exists
|
||||||
|
mkdir -p ${SEAFILE_TEST_DATA}/${NAME}
|
||||||
|
|
||||||
|
docker run --rm -d --name $NAME \
|
||||||
|
-e SEAFILE_SERVER_HOSTNAME=${SEAFILE_IP}:${SEAFILE_PORT} \
|
||||||
|
-e SEAFILE_ADMIN_EMAIL=${SEAFILE_ADMIN_EMAIL} \
|
||||||
|
-e SEAFILE_ADMIN_PASSWORD=${SEAFILE_ADMIN_PASSWORD} \
|
||||||
|
-v ${SEAFILE_TEST_DATA}/${NAME}:/shared \
|
||||||
|
-p ${SEAFILE_IP}:${SEAFILE_PORT}:80 \
|
||||||
|
seafileltd/seafile:${SEAFILE_VERSION}
|
||||||
|
|
||||||
|
# it takes some time for the database to be created
|
||||||
|
sleep 60
|
||||||
|
|
||||||
|
# authentication token answer should be like: {"token":"dbf58423f1632b5b679a13b0929f1d0751d9250c"}
|
||||||
|
TOKEN=`curl --silent \
|
||||||
|
--data-urlencode username=${SEAFILE_ADMIN_EMAIL} -d password=${SEAFILE_ADMIN_PASSWORD} \
|
||||||
|
http://${SEAFILE_IP}:${SEAFILE_PORT}/api2/auth-token/ \
|
||||||
|
| sed 's/^{"token":"\(.*\)"}$/\1/'`
|
||||||
|
|
||||||
|
# create default library
|
||||||
|
curl -X POST -H "Authorization: Token ${TOKEN}" "http://${SEAFILE_IP}:${SEAFILE_PORT}/api2/default-repo/"
|
||||||
|
|
||||||
|
echo _connect=${SEAFILE_IP}:${SEAFILE_PORT}
|
||||||
|
echo type=seafile
|
||||||
|
echo url=http://${SEAFILE_IP}:${SEAFILE_PORT}/
|
||||||
|
echo user=${SEAFILE_ADMIN_EMAIL}
|
||||||
|
echo pass=$(rclone obscure ${SEAFILE_ADMIN_PASSWORD})
|
||||||
|
echo library=My Library
|
||||||
|
}
|
||||||
|
|
||||||
|
. $(dirname "$0")/run.bash
|
31
fstest/testserver/init.d/seafile/docker-compose.yml
Normal file
31
fstest/testserver/init.d/seafile/docker-compose.yml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
version: '2.0'
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: mariadb:10.1
|
||||||
|
environment:
|
||||||
|
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
|
||||||
|
- MYSQL_LOG_CONSOLE=true
|
||||||
|
volumes:
|
||||||
|
- ${SEAFILE_TEST_DATA}/${NAME}/seafile-mysql/db:/var/lib/mysql
|
||||||
|
|
||||||
|
memcached:
|
||||||
|
image: memcached:1.5.6
|
||||||
|
entrypoint: memcached -m 256
|
||||||
|
|
||||||
|
seafile:
|
||||||
|
image: seafileltd/seafile-mc:${SEAFILE_VERSION}
|
||||||
|
ports:
|
||||||
|
- "${SEAFILE_IP}:${SEAFILE_PORT}:80"
|
||||||
|
volumes:
|
||||||
|
- ${SEAFILE_TEST_DATA}/${NAME}/seafile-data:/shared
|
||||||
|
environment:
|
||||||
|
- DB_HOST=db
|
||||||
|
- DB_ROOT_PASSWD=${MYSQL_ROOT_PASSWORD}
|
||||||
|
- TIME_ZONE=Etc/UTC
|
||||||
|
- SEAFILE_ADMIN_EMAIL=${SEAFILE_ADMIN_EMAIL}
|
||||||
|
- SEAFILE_ADMIN_PASSWORD=${SEAFILE_ADMIN_PASSWORD}
|
||||||
|
- SEAFILE_SERVER_LETSENCRYPT=false
|
||||||
|
- SEAFILE_SERVER_HOSTNAME=${SEAFILE_IP}:${SEAFILE_PORT}
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
- memcached
|
Loading…
Reference in New Issue
Block a user