mirror of
https://github.com/rclone/rclone.git
synced 2024-11-25 09:41:44 +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/)
|
||||
* Rackspace Cloud Files [:page_facing_up:](https://rclone.org/swift/)
|
||||
* 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/)
|
||||
* StackPath [:page_facing_up:](https://rclone.org/s3/#stackpath)
|
||||
* SugarSync [:page_facing_up:](https://rclone.org/sugarsync/)
|
||||
|
|
|
@ -31,6 +31,7 @@ import (
|
|||
_ "github.com/rclone/rclone/backend/putio"
|
||||
_ "github.com/rclone/rclone/backend/qingstor"
|
||||
_ "github.com/rclone/rclone/backend/s3"
|
||||
_ "github.com/rclone/rclone/backend/seafile"
|
||||
_ "github.com/rclone/rclone/backend/sftp"
|
||||
_ "github.com/rclone/rclone/backend/sharefile"
|
||||
_ "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",
|
||||
"premiumizeme.md",
|
||||
"putio.md",
|
||||
"seafile.md",
|
||||
"sftp.md",
|
||||
"sugarsync.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="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="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="StackPath" home="https://www.stackpath.com/products/object-storage/" config="/s3/#stackpath" >}}
|
||||
* {{< provider name="SugarSync" home="https://sugarsync.com/" config="/sugarsync/" >}}
|
||||
|
|
|
@ -50,6 +50,7 @@ See the following for detailed instructions for
|
|||
* [premiumize.me](/premiumizeme/)
|
||||
* [put.io](/putio/)
|
||||
* [QingStor](/qingstor/)
|
||||
* [Seafile](/seafile/)
|
||||
* [SFTP](/sftp/)
|
||||
* [SugarSync](/sugarsync/)
|
||||
* [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 |
|
||||
| put.io | CRC-32 | Yes | No | Yes | R |
|
||||
| QingStor | MD5 | No | No | No | R/W |
|
||||
| Seafile | - | No | No | No | - |
|
||||
| SFTP | MD5, SHA1 ‡ | Yes | Depends | No | - |
|
||||
| SugarSync | - | No | No | 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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
|
|
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="/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="/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="/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>
|
||||
|
|
|
@ -264,3 +264,14 @@ backends:
|
|||
remote: "TestMailru:"
|
||||
subdir: 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