mirror of
https://github.com/rclone/rclone.git
synced 2025-12-18 01:03:14 +00:00
Compare commits
7 Commits
fix-9031-b
...
drime
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c919fad933 | ||
|
|
f7f32d1a7c | ||
|
|
672d9469eb | ||
|
|
8e94a154ed | ||
|
|
4d858210b3 | ||
|
|
aa6973b89e | ||
|
|
42373c1cff |
@@ -38,6 +38,7 @@ directories to and from different cloud storage providers.
|
|||||||
- DigitalOcean Spaces [:page_facing_up:](https://rclone.org/s3/#digitalocean-spaces)
|
- DigitalOcean Spaces [:page_facing_up:](https://rclone.org/s3/#digitalocean-spaces)
|
||||||
- Digi Storage [:page_facing_up:](https://rclone.org/koofr/#digi-storage)
|
- Digi Storage [:page_facing_up:](https://rclone.org/koofr/#digi-storage)
|
||||||
- Dreamhost [:page_facing_up:](https://rclone.org/s3/#dreamhost)
|
- Dreamhost [:page_facing_up:](https://rclone.org/s3/#dreamhost)
|
||||||
|
- Drime [:page_facing_up:](https://rclone.org/s3/#drime)
|
||||||
- Dropbox [:page_facing_up:](https://rclone.org/dropbox/)
|
- Dropbox [:page_facing_up:](https://rclone.org/dropbox/)
|
||||||
- Enterprise File Fabric [:page_facing_up:](https://rclone.org/filefabric/)
|
- Enterprise File Fabric [:page_facing_up:](https://rclone.org/filefabric/)
|
||||||
- Exaba [:page_facing_up:](https://rclone.org/s3/#exaba)
|
- Exaba [:page_facing_up:](https://rclone.org/s3/#exaba)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
_ "github.com/rclone/rclone/backend/compress"
|
_ "github.com/rclone/rclone/backend/compress"
|
||||||
_ "github.com/rclone/rclone/backend/crypt"
|
_ "github.com/rclone/rclone/backend/crypt"
|
||||||
_ "github.com/rclone/rclone/backend/doi"
|
_ "github.com/rclone/rclone/backend/doi"
|
||||||
|
_ "github.com/rclone/rclone/backend/drime"
|
||||||
_ "github.com/rclone/rclone/backend/drive"
|
_ "github.com/rclone/rclone/backend/drive"
|
||||||
_ "github.com/rclone/rclone/backend/dropbox"
|
_ "github.com/rclone/rclone/backend/dropbox"
|
||||||
_ "github.com/rclone/rclone/backend/fichier"
|
_ "github.com/rclone/rclone/backend/fichier"
|
||||||
|
|||||||
231
backend/drime/api/types.go
Normal file
231
backend/drime/api/types.go
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
// Package api has type definitions for drime
|
||||||
|
//
|
||||||
|
// Converted from the API docs with help from https://mholt.github.io/json-to-go/
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Types of things in Item
|
||||||
|
const (
|
||||||
|
ItemTypeFolder = "folder"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
ID json.Number `json:"id"`
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
ModelType string `json:"model_type"`
|
||||||
|
OwnsEntry bool `json:"owns_entry"`
|
||||||
|
EntryPermissions []any `json:"entry_permissions"`
|
||||||
|
DisplayName string `json:"display_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Permissions struct {
|
||||||
|
FilesUpdate bool `json:"files.update"`
|
||||||
|
FilesCreate bool `json:"files.create"`
|
||||||
|
FilesDownload bool `json:"files.download"`
|
||||||
|
FilesDelete bool `json:"files.delete"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item describes a folder or a file as returned by /drive/file-entries
|
||||||
|
type Item struct {
|
||||||
|
ID json.Number `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description any `json:"description"`
|
||||||
|
FileName string `json:"file_name"`
|
||||||
|
Mime string `json:"mime"`
|
||||||
|
Color any `json:"color"`
|
||||||
|
Backup bool `json:"backup"`
|
||||||
|
Tracked int `json:"tracked"`
|
||||||
|
FileSize int64 `json:"file_size"`
|
||||||
|
UserID json.Number `json:"user_id"`
|
||||||
|
ParentID json.Number `json:"parent_id"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
DeletedAt any `json:"deleted_at"`
|
||||||
|
IsDeleted int `json:"is_deleted"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
DiskPrefix any `json:"disk_prefix"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Extension any `json:"extension"`
|
||||||
|
FileHash any `json:"file_hash"`
|
||||||
|
Public bool `json:"public"`
|
||||||
|
Thumbnail bool `json:"thumbnail"`
|
||||||
|
MuxStatus any `json:"mux_status"`
|
||||||
|
ThumbnailURL any `json:"thumbnail_url"`
|
||||||
|
WorkspaceID int `json:"workspace_id"`
|
||||||
|
IsEncrypted int `json:"is_encrypted"`
|
||||||
|
Iv any `json:"iv"`
|
||||||
|
VaultID any `json:"vault_id"`
|
||||||
|
OwnerID int `json:"owner_id"`
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Users []User `json:"users"`
|
||||||
|
Tags []any `json:"tags"`
|
||||||
|
Permissions Permissions `json:"permissions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Listing struct {
|
||||||
|
CurrentPage int `json:"current_page"`
|
||||||
|
Data []Item `json:"data"`
|
||||||
|
From int `json:"from"`
|
||||||
|
LastPage int `json:"last_page"`
|
||||||
|
NextPage int `json:"next_page"`
|
||||||
|
PerPage int `json:"per_page"`
|
||||||
|
PrevPage int `json:"prev_page"`
|
||||||
|
To int `json:"to"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
FileEntry Item `json:"fileEntry"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateFolderRequest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
ParentID json.Number `json:"parentId,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateFolderResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Folder Item `json:"folder"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error is returned from drime when things go wrong
|
||||||
|
type Error struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns a string for the error and satisfies the error interface
|
||||||
|
func (e Error) Error() string {
|
||||||
|
out := fmt.Sprintf("Error %q", e.Message)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Error satisfies the error interface
|
||||||
|
var _ error = (*Error)(nil)
|
||||||
|
|
||||||
|
// DeleteRequest is the input to DELETE /file-entries
|
||||||
|
type DeleteRequest struct {
|
||||||
|
EntryIds []string `json:"entryIds"`
|
||||||
|
DeleteForever bool `json:"deleteForever"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteResponse is the input to DELETE /file-entries
|
||||||
|
type DeleteResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Errors map[string]string `json:"errors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateItemRequest describes the updates to be done to an item for PUT /file-entries/{id}/
|
||||||
|
type UpdateItemRequest struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateItemResponse is returned by PUT /file-entries/{id}/
|
||||||
|
type UpdateItemResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
FileEntry Item `json:"fileEntry"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveRequest is the input to /file-entries/move
|
||||||
|
type MoveRequest struct {
|
||||||
|
EntryIds []string `json:"entryIds"`
|
||||||
|
DestinationID string `json:"destinationId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveResponse is returned by POST /file-entries/move
|
||||||
|
type MoveResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Entries []Item `json:"entries"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyRequest is the input to /file-entries/duplicate
|
||||||
|
type CopyRequest struct {
|
||||||
|
EntryIds []string `json:"entryIds"`
|
||||||
|
DestinationID string `json:"destinationId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyResponse is returned by POST /file-entries/duplicate
|
||||||
|
type CopyResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Entries []Item `json:"entries"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiPartCreateRequest is the input of POST /s3/multipart/create
|
||||||
|
type MultiPartCreateRequest struct {
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
Mime string `json:"mime"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
Extension string `json:"extension"`
|
||||||
|
ParentID json.Number `json:"parent_id"`
|
||||||
|
RelativePath string `json:"relativePath"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiPartCreateResponse is returned by POST /s3/multipart/create
|
||||||
|
type MultiPartCreateResponse struct {
|
||||||
|
UploadID string `json:"uploadId"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompletedPart Type for completed parts when making a multipart upload.
|
||||||
|
type CompletedPart struct {
|
||||||
|
ETag string `json:"ETag"`
|
||||||
|
PartNumber int32 `json:"PartNumber"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiPartGetURLsRequest is the input of POST /s3/multipart/batch-sign-part-urls
|
||||||
|
type MultiPartGetURLsRequest struct {
|
||||||
|
UploadID string `json:"uploadId"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
PartNumbers []int `json:"partNumbers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiPartGetURLsResponse is the result of POST /s3/multipart/batch-sign-part-urls
|
||||||
|
type MultiPartGetURLsResponse struct {
|
||||||
|
URLs []struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
PartNumber int32 `json:"partNumber"`
|
||||||
|
} `json:"urls"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiPartCompleteRequest is the input to POST /s3/multipart/complete
|
||||||
|
type MultiPartCompleteRequest struct {
|
||||||
|
UploadID string `json:"uploadId"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
Parts []CompletedPart `json:"parts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiPartCompleteResponse is the result of POST /s3/multipart/complete
|
||||||
|
type MultiPartCompleteResponse struct {
|
||||||
|
Location string `json:"location"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiPartEntriesRequest is the input to POST /s3/entries
|
||||||
|
type MultiPartEntriesRequest struct {
|
||||||
|
ClientMime string `json:"clientMime"`
|
||||||
|
ClientName string `json:"clientName"`
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
ClientExtension string `json:"clientExtension"`
|
||||||
|
ParentID json.Number `json:"parent_id"`
|
||||||
|
RelativePath string `json:"relativePath"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiPartEntriesResponse is the result of POST /s3/entries
|
||||||
|
type MultiPartEntriesResponse struct {
|
||||||
|
FileEntry Item `json:"fileEntry"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiPartAbort is the input of POST /s3/multipart/abort
|
||||||
|
type MultiPartAbort struct {
|
||||||
|
UploadID string `json:"uploadId"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
}
|
||||||
1546
backend/drime/drime.go
Normal file
1546
backend/drime/drime.go
Normal file
File diff suppressed because it is too large
Load Diff
33
backend/drime/drime_test.go
Normal file
33
backend/drime/drime_test.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// Drime filesystem interface
|
||||||
|
package drime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/rclone/rclone/fs"
|
||||||
|
"github.com/rclone/rclone/fstest/fstests"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestIntegration runs integration tests against the remote
|
||||||
|
func TestIntegration(t *testing.T) {
|
||||||
|
fstests.Run(t, &fstests.Opt{
|
||||||
|
RemoteName: "TestDrime:",
|
||||||
|
NilObject: (*Object)(nil),
|
||||||
|
ChunkedUpload: fstests.ChunkedUploadConfig{
|
||||||
|
MinChunkSize: minChunkSize,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fs) SetUploadChunkSize(cs fs.SizeSuffix) (fs.SizeSuffix, error) {
|
||||||
|
return f.setUploadChunkSize(cs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fs) SetUploadCutoff(cs fs.SizeSuffix) (fs.SizeSuffix, error) {
|
||||||
|
return f.setUploadCutoff(cs)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ fstests.SetUploadChunkSizer = (*Fs)(nil)
|
||||||
|
_ fstests.SetUploadCutoffer = (*Fs)(nil)
|
||||||
|
)
|
||||||
@@ -72,7 +72,7 @@ func (ik *ImageKit) Upload(ctx context.Context, file io.Reader, param UploadPara
|
|||||||
|
|
||||||
response := &UploadResult{}
|
response := &UploadResult{}
|
||||||
|
|
||||||
formReader, contentType, _, err := rest.MultipartUpload(ctx, file, formParams, "file", param.FileName)
|
formReader, contentType, _, err := rest.MultipartUpload(ctx, file, formParams, "file", param.FileName, "application/octet-stream")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to make multipart upload: %w", err)
|
return nil, nil, fmt.Errorf("failed to make multipart upload: %w", err)
|
||||||
|
|||||||
@@ -1327,7 +1327,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
|
|||||||
// opts.Body=0), so upload it as a multipart form POST with
|
// opts.Body=0), so upload it as a multipart form POST with
|
||||||
// Content-Length set.
|
// Content-Length set.
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
formReader, contentType, overhead, err := rest.MultipartUpload(ctx, in, opts.Parameters, "content", leaf)
|
formReader, contentType, overhead, err := rest.MultipartUpload(ctx, in, opts.Parameters, "content", leaf, opts.ContentType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to make multipart upload for 0 length file: %w", err)
|
return fmt.Errorf("failed to make multipart upload for 0 length file: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1384,7 +1384,7 @@ func (f *Fs) uploadByForm(ctx context.Context, in io.Reader, name string, size i
|
|||||||
for i := range iVal.NumField() {
|
for i := range iVal.NumField() {
|
||||||
params.Set(iTyp.Field(i).Tag.Get("json"), iVal.Field(i).String())
|
params.Set(iTyp.Field(i).Tag.Get("json"), iVal.Field(i).String())
|
||||||
}
|
}
|
||||||
formReader, contentType, overhead, err := rest.MultipartUpload(ctx, in, params, "file", name)
|
formReader, contentType, overhead, err := rest.MultipartUpload(ctx, in, params, "file", name, "application/octet-stream")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to make multipart upload: %w", err)
|
return fmt.Errorf("failed to make multipart upload: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -688,7 +688,7 @@ func (f *Fs) upload(ctx context.Context, in io.Reader, uploadLink, filePath stri
|
|||||||
"need_idx_progress": {"true"},
|
"need_idx_progress": {"true"},
|
||||||
"replace": {"1"},
|
"replace": {"1"},
|
||||||
}
|
}
|
||||||
formReader, contentType, _, err := rest.MultipartUpload(ctx, in, parameters, "file", f.opt.Enc.FromStandardName(filename))
|
formReader, contentType, _, err := rest.MultipartUpload(ctx, in, parameters, "file", f.opt.Enc.FromStandardName(filename), "application/octet-stream")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to make multipart upload: %w", err)
|
return nil, fmt.Errorf("failed to make multipart upload: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -817,7 +817,7 @@ func (f *Fs) upload(ctx context.Context, name string, parent string, size int64,
|
|||||||
params.Set("filename", url.QueryEscape(name))
|
params.Set("filename", url.QueryEscape(name))
|
||||||
params.Set("parent_id", parent)
|
params.Set("parent_id", parent)
|
||||||
params.Set("override-name-exist", strconv.FormatBool(true))
|
params.Set("override-name-exist", strconv.FormatBool(true))
|
||||||
formReader, contentType, overhead, err := rest.MultipartUpload(ctx, in, nil, "content", name)
|
formReader, contentType, overhead, err := rest.MultipartUpload(ctx, in, nil, "content", name, "application/octet-stream")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to make multipart upload: %w", err)
|
return nil, fmt.Errorf("failed to make multipart upload: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ docs = [
|
|||||||
"compress.md",
|
"compress.md",
|
||||||
"combine.md",
|
"combine.md",
|
||||||
"doi.md",
|
"doi.md",
|
||||||
|
"drime.md"
|
||||||
"dropbox.md",
|
"dropbox.md",
|
||||||
"filefabric.md",
|
"filefabric.md",
|
||||||
"filelu.md",
|
"filelu.md",
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ WebDAV or S3, that work out of the box.)
|
|||||||
{{< provider name="DigitalOcean Spaces" home="https://www.digitalocean.com/products/object-storage/" config="/s3/#digitalocean-spaces" >}}
|
{{< provider name="DigitalOcean Spaces" home="https://www.digitalocean.com/products/object-storage/" config="/s3/#digitalocean-spaces" >}}
|
||||||
{{< provider name="Digi Storage" home="https://storage.rcs-rds.ro/" config="/koofr/#digi-storage" >}}
|
{{< provider name="Digi Storage" home="https://storage.rcs-rds.ro/" config="/koofr/#digi-storage" >}}
|
||||||
{{< provider name="Dreamhost" home="https://www.dreamhost.com/cloud/storage/" config="/s3/#dreamhost" >}}
|
{{< provider name="Dreamhost" home="https://www.dreamhost.com/cloud/storage/" config="/s3/#dreamhost" >}}
|
||||||
|
{{< provider name="Drime" home="https://www.drime.cloud/" config="/drime/" >}}
|
||||||
{{< provider name="Dropbox" home="https://www.dropbox.com/" config="/dropbox/" >}}
|
{{< provider name="Dropbox" home="https://www.dropbox.com/" config="/dropbox/" >}}
|
||||||
{{< provider name="Enterprise File Fabric" home="https://storagemadeeasy.com/about/" config="/filefabric/" >}}
|
{{< provider name="Enterprise File Fabric" home="https://storagemadeeasy.com/about/" config="/filefabric/" >}}
|
||||||
{{< provider name="Exaba" home="https://exaba.com/" config="/s3/#exaba" >}}
|
{{< provider name="Exaba" home="https://exaba.com/" config="/s3/#exaba" >}}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ See the following for detailed instructions for
|
|||||||
- [Crypt](/crypt/) - to encrypt other remotes
|
- [Crypt](/crypt/) - to encrypt other remotes
|
||||||
- [DigitalOcean Spaces](/s3/#digitalocean-spaces)
|
- [DigitalOcean Spaces](/s3/#digitalocean-spaces)
|
||||||
- [Digi Storage](/koofr/#digi-storage)
|
- [Digi Storage](/koofr/#digi-storage)
|
||||||
|
- [Drime](/drime/)
|
||||||
- [Dropbox](/dropbox/)
|
- [Dropbox](/dropbox/)
|
||||||
- [Enterprise File Fabric](/filefabric/)
|
- [Enterprise File Fabric](/filefabric/)
|
||||||
- [FileLu Cloud Storage](/filelu/)
|
- [FileLu Cloud Storage](/filelu/)
|
||||||
|
|||||||
236
docs/content/drime.md
Normal file
236
docs/content/drime.md
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
---
|
||||||
|
title: "Drime"
|
||||||
|
description: "Rclone docs for Drime"
|
||||||
|
versionIntroduced: "v1.73"
|
||||||
|
---
|
||||||
|
|
||||||
|
# {{< icon "fa fa-cloud" >}} Drime
|
||||||
|
|
||||||
|
[Drime](https://drime.cloud/) is a cloud storage and transfer service focused
|
||||||
|
on fast, resilient file delivery. It offers both free and paid tiers with
|
||||||
|
emphasis on high-speed uploads and link sharing.
|
||||||
|
|
||||||
|
The setup Drime you need to log in, go to Settings, Developer, and create a
|
||||||
|
token to use as an API access key. Give it a sensible name and copy the token
|
||||||
|
for use in the config.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Here is a run through of `rclone config` to make a remote called `Drime`.
|
||||||
|
|
||||||
|
Firstly run:
|
||||||
|
|
||||||
|
|
||||||
|
```console
|
||||||
|
rclone config
|
||||||
|
```
|
||||||
|
|
||||||
|
Then follow through the interactive setup:
|
||||||
|
|
||||||
|
|
||||||
|
```text
|
||||||
|
No remotes found, make a new one?
|
||||||
|
n) New remote
|
||||||
|
s) Set configuration password
|
||||||
|
q) Quit config
|
||||||
|
n/s/q> n
|
||||||
|
|
||||||
|
Enter name for new remote.
|
||||||
|
name> Drime
|
||||||
|
|
||||||
|
Option Storage.
|
||||||
|
Type of storage to configure.
|
||||||
|
Choose a number from below, or type in your own value.
|
||||||
|
XX / Drime
|
||||||
|
\ (drime)
|
||||||
|
Storage> drime
|
||||||
|
|
||||||
|
Option access_token.
|
||||||
|
API Access token
|
||||||
|
You can get this from the web control panel.
|
||||||
|
Enter a value. Press Enter to leave empty.
|
||||||
|
access_token> YOUR_API_ACCESS_TOKEN
|
||||||
|
|
||||||
|
Edit advanced config?
|
||||||
|
y) Yes
|
||||||
|
n) No (default)
|
||||||
|
y/n> n
|
||||||
|
|
||||||
|
Configuration complete.
|
||||||
|
Options:
|
||||||
|
- type: drime
|
||||||
|
- access_token: YOUR_API_ACCESS_TOKEN
|
||||||
|
Keep this "remote" remote?
|
||||||
|
y) Yes this is OK (default)
|
||||||
|
e) Edit this remote
|
||||||
|
d) Delete this remote
|
||||||
|
y/e/d> y
|
||||||
|
```
|
||||||
|
|
||||||
|
Once configured you can then use `rclone` like this (replace `remote` with the
|
||||||
|
name you gave your remote):
|
||||||
|
|
||||||
|
List directories and files in the top level of your Drime
|
||||||
|
|
||||||
|
```console
|
||||||
|
rclone lsf remote:
|
||||||
|
```
|
||||||
|
|
||||||
|
To copy a local directory to a Drime directory called backup
|
||||||
|
|
||||||
|
```console
|
||||||
|
rclone copy /home/source remote:backup
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Modification times and hashes
|
||||||
|
|
||||||
|
Drime does not support modification times or hashes.
|
||||||
|
|
||||||
|
|
||||||
|
### Restricted filename characters
|
||||||
|
|
||||||
|
In addition to the [default restricted characters set](/overview/#restricted-characters)
|
||||||
|
the following characters are also replaced:
|
||||||
|
|
||||||
|
| Character | Value | Replacement |
|
||||||
|
| --------- |:-----:|:-----------:|
|
||||||
|
| \ | 0x5C | \ |
|
||||||
|
|
||||||
|
File names can also not start or end with the following characters.
|
||||||
|
These only get replaced if they are the last character in the name:
|
||||||
|
|
||||||
|
| Character | Value | Replacement |
|
||||||
|
| --------- |:-----:|:-----------:|
|
||||||
|
| SP | 0x20 | ␠ |
|
||||||
|
|
||||||
|
Invalid UTF-8 bytes will also be [replaced](/overview/#invalid-utf8),
|
||||||
|
as they can't be used in JSON strings.
|
||||||
|
|
||||||
|
### Root folder ID
|
||||||
|
|
||||||
|
You can set the `root_folder_id` for rclone. This is the directory
|
||||||
|
(identified by its `Folder ID`) that rclone considers to be the root
|
||||||
|
of your Drime drive.
|
||||||
|
|
||||||
|
Normally you will leave this blank and rclone will determine the
|
||||||
|
correct root to use itself and fill in the value in the config file.
|
||||||
|
|
||||||
|
However you can set this to restrict rclone to a specific folder
|
||||||
|
hierarchy.
|
||||||
|
|
||||||
|
In order to do this you will have to find the `Folder ID` of the
|
||||||
|
directory you wish rclone to display.
|
||||||
|
|
||||||
|
You can do this with rclone
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ rclone lsf -Fip --dirs-only remote:
|
||||||
|
d6341f53-ee65-4f29-9f59-d11e8070b2a0;Files/
|
||||||
|
f4f5c9b8-6ece-478b-b03e-4538edfe5a1c;Photos/
|
||||||
|
d50e356c-29ca-4b27-a3a7-494d91026e04;Videos/
|
||||||
|
```
|
||||||
|
|
||||||
|
The ID to use is the part before the `;` so you could set
|
||||||
|
|
||||||
|
```text
|
||||||
|
root_folder_id = d6341f53-ee65-4f29-9f59-d11e8070b2a0
|
||||||
|
```
|
||||||
|
|
||||||
|
To restrict rclone to the `Files` directory.
|
||||||
|
|
||||||
|
<!-- autogenerated options start - DO NOT EDIT - instead edit fs.RegInfo in backend/drime/drime.go and run make backenddocs to verify --> <!-- markdownlint-disable-line line-length -->
|
||||||
|
### Standard options
|
||||||
|
|
||||||
|
Here are the Standard options specific to drime (Drime).
|
||||||
|
|
||||||
|
#### --drime-access-token
|
||||||
|
|
||||||
|
API Access token
|
||||||
|
|
||||||
|
You can get this from the web control panel.
|
||||||
|
|
||||||
|
Properties:
|
||||||
|
|
||||||
|
- Config: access_token
|
||||||
|
- Env Var: RCLONE_DRIME_ACCESS_TOKEN
|
||||||
|
- Type: string
|
||||||
|
- Required: false
|
||||||
|
|
||||||
|
### Advanced options
|
||||||
|
|
||||||
|
Here are the Advanced options specific to drime (Drime).
|
||||||
|
|
||||||
|
#### --drime-root-folder-id
|
||||||
|
|
||||||
|
ID of the root folder
|
||||||
|
|
||||||
|
Leave this blank normally, rclone will fill it in automatically.
|
||||||
|
|
||||||
|
If you want rclone to be restricted to a particular folder you can
|
||||||
|
fill it in - see the docs for more info.
|
||||||
|
|
||||||
|
|
||||||
|
Properties:
|
||||||
|
|
||||||
|
- Config: root_folder_id
|
||||||
|
- Env Var: RCLONE_DRIME_ROOT_FOLDER_ID
|
||||||
|
- Type: string
|
||||||
|
- Required: false
|
||||||
|
|
||||||
|
#### --drime-workspace-id
|
||||||
|
|
||||||
|
Account ID
|
||||||
|
|
||||||
|
Leave this blank normally, rclone will fill it in automatically.
|
||||||
|
|
||||||
|
|
||||||
|
Properties:
|
||||||
|
|
||||||
|
- Config: workspace_id
|
||||||
|
- Env Var: RCLONE_DRIME_WORKSPACE_ID
|
||||||
|
- Type: string
|
||||||
|
- Required: false
|
||||||
|
|
||||||
|
#### --drime-list-chunk
|
||||||
|
|
||||||
|
Number of items to list in each call
|
||||||
|
|
||||||
|
Properties:
|
||||||
|
|
||||||
|
- Config: list_chunk
|
||||||
|
- Env Var: RCLONE_DRIME_LIST_CHUNK
|
||||||
|
- Type: int
|
||||||
|
- Default: 1000
|
||||||
|
|
||||||
|
#### --drime-encoding
|
||||||
|
|
||||||
|
The encoding for the backend.
|
||||||
|
|
||||||
|
See the [encoding section in the overview](/overview/#encoding) for more info.
|
||||||
|
|
||||||
|
Properties:
|
||||||
|
|
||||||
|
- Config: encoding
|
||||||
|
- Env Var: RCLONE_DRIME_ENCODING
|
||||||
|
- Type: Encoding
|
||||||
|
- Default: Slash,BackSlash,Del,Ctl,LeftSpace,RightSpace,InvalidUtf8,Dot
|
||||||
|
|
||||||
|
#### --drime-description
|
||||||
|
|
||||||
|
Description of the remote.
|
||||||
|
|
||||||
|
Properties:
|
||||||
|
|
||||||
|
- Config: description
|
||||||
|
- Env Var: RCLONE_DRIME_DESCRIPTION
|
||||||
|
- Type: string
|
||||||
|
- Required: false
|
||||||
|
|
||||||
|
<!-- autogenerated options stop -->
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
Drime only supports filenames up to 255 characters in length, where a
|
||||||
|
character is a UTF8-byte character.
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@ Here is an overview of the major features of each cloud storage system.
|
|||||||
| Box | SHA1 | R/W | Yes | No | - | - |
|
| Box | SHA1 | R/W | Yes | No | - | - |
|
||||||
| Citrix ShareFile | MD5 | R/W | Yes | No | - | - |
|
| Citrix ShareFile | MD5 | R/W | Yes | No | - | - |
|
||||||
| Cloudinary | MD5 | R | No | Yes | - | - |
|
| Cloudinary | MD5 | R | No | Yes | - | - |
|
||||||
|
| Drime | - | - | No | No | R/W | - |
|
||||||
| Dropbox | DBHASH ¹ | R | Yes | No | - | - |
|
| Dropbox | DBHASH ¹ | R | Yes | No | - | - |
|
||||||
| Enterprise File Fabric | - | R/W | Yes | No | R/W | - |
|
| Enterprise File Fabric | - | R/W | Yes | No | R/W | - |
|
||||||
| FileLu Cloud Storage | MD5 | R/W | No | Yes | R | - |
|
| FileLu Cloud Storage | MD5 | R/W | No | Yes | R | - |
|
||||||
@@ -515,6 +516,7 @@ upon backend-specific capabilities.
|
|||||||
| Backblaze B2 | No | Yes | No | No | Yes | Yes | Yes | Yes | Yes | No | No |
|
| Backblaze B2 | No | Yes | No | No | Yes | Yes | Yes | Yes | Yes | No | No |
|
||||||
| Box | Yes | Yes | Yes | Yes | Yes | No | Yes | No | Yes | Yes | Yes |
|
| Box | Yes | Yes | Yes | Yes | Yes | No | Yes | No | Yes | Yes | Yes |
|
||||||
| Citrix ShareFile | Yes | Yes | Yes | Yes | No | No | No | No | No | No | Yes |
|
| Citrix ShareFile | Yes | Yes | Yes | Yes | No | No | No | No | No | No | Yes |
|
||||||
|
| Drime | Yes | Yes | Yes | Yes | No | No | Yes | No | No | No | Yes |
|
||||||
| Dropbox | Yes | Yes | Yes | Yes | No | No | Yes | No | Yes | Yes | Yes |
|
| Dropbox | Yes | Yes | Yes | Yes | No | No | Yes | No | Yes | Yes | Yes |
|
||||||
| Cloudinary | No | No | No | No | No | No | Yes | No | No | No | No |
|
| Cloudinary | No | No | No | No | No | No | Yes | No | No | No | No |
|
||||||
| Enterprise File Fabric | Yes | Yes | Yes | Yes | Yes | No | No | No | No | No | Yes |
|
| Enterprise File Fabric | Yes | Yes | Yes | Yes | Yes | No | No | No | No | No | Yes |
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
<a class="dropdown-item" href="/sharefile/"><i class="fas fa-share-square fa-fw"></i> Citrix ShareFile</a>
|
<a class="dropdown-item" href="/sharefile/"><i class="fas fa-share-square fa-fw"></i> Citrix ShareFile</a>
|
||||||
<a class="dropdown-item" href="/crypt/"><i class="fa fa-lock fa-fw"></i> Crypt (encrypts the others)</a>
|
<a class="dropdown-item" href="/crypt/"><i class="fa fa-lock fa-fw"></i> Crypt (encrypts the others)</a>
|
||||||
<a class="dropdown-item" href="/koofr/#digi-storage"><i class="fa fa-cloud fa-fw"></i> Digi Storage</a>
|
<a class="dropdown-item" href="/koofr/#digi-storage"><i class="fa fa-cloud fa-fw"></i> Digi Storage</a>
|
||||||
|
<a class="dropdown-item" href="/drime/"><i class="fab fa-cloud fa-fw"></i> Drime</a>
|
||||||
<a class="dropdown-item" href="/dropbox/"><i class="fab fa-dropbox fa-fw"></i> Dropbox</a>
|
<a class="dropdown-item" href="/dropbox/"><i class="fab fa-dropbox fa-fw"></i> Dropbox</a>
|
||||||
<a class="dropdown-item" href="/filefabric/"><i class="fa fa-cloud fa-fw"></i> Enterprise File Fabric</a>
|
<a class="dropdown-item" href="/filefabric/"><i class="fa fa-cloud fa-fw"></i> Enterprise File Fabric</a>
|
||||||
<a class="dropdown-item" href="/filelu/"><i class="fa fa-folder fa-fw"></i> FileLu Cloud Storage</a>
|
<a class="dropdown-item" href="/filelu/"><i class="fa fa-folder fa-fw"></i> FileLu Cloud Storage</a>
|
||||||
|
|||||||
@@ -561,7 +561,7 @@ func TestUploadFile(t *testing.T) {
|
|||||||
assert.NoError(t, currentFile.Close())
|
assert.NoError(t, currentFile.Close())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
formReader, contentType, _, err := rest.MultipartUpload(ctx, currentFile, url.Values{}, "file", testFileName)
|
formReader, contentType, _, err := rest.MultipartUpload(ctx, currentFile, url.Values{}, "file", testFileName, "application/octet-stream")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
httpReq := httptest.NewRequest("POST", "/", formReader)
|
httpReq := httptest.NewRequest("POST", "/", formReader)
|
||||||
@@ -587,7 +587,7 @@ func TestUploadFile(t *testing.T) {
|
|||||||
assert.NoError(t, currentFile2.Close())
|
assert.NoError(t, currentFile2.Close())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
formReader, contentType, _, err = rest.MultipartUpload(ctx, currentFile2, url.Values{}, "file", testFileName)
|
formReader, contentType, _, err = rest.MultipartUpload(ctx, currentFile2, url.Values{}, "file", testFileName, "application/octet-stream")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
httpReq = httptest.NewRequest("POST", "/", formReader)
|
httpReq = httptest.NewRequest("POST", "/", formReader)
|
||||||
|
|||||||
@@ -677,3 +677,8 @@ backends:
|
|||||||
# with the parent backend having a different precision.
|
# with the parent backend having a different precision.
|
||||||
- TestServerSideCopyOverSelf
|
- TestServerSideCopyOverSelf
|
||||||
- TestServerSideMoveOverSelf
|
- TestServerSideMoveOverSelf
|
||||||
|
- backend: "drime"
|
||||||
|
remote: "TestDrime:"
|
||||||
|
ignoretests:
|
||||||
|
- TestBisyncRemoteLocal/check_access_filters
|
||||||
|
fastlist: false
|
||||||
|
|||||||
@@ -361,9 +361,6 @@ func (dc *DirCache) RootParentID(ctx context.Context, create bool) (ID string, e
|
|||||||
} else if dc.rootID == dc.trueRootID {
|
} else if dc.rootID == dc.trueRootID {
|
||||||
return "", errors.New("is root directory")
|
return "", errors.New("is root directory")
|
||||||
}
|
}
|
||||||
if dc.rootParentID == "" {
|
|
||||||
return "", errors.New("internal error: didn't find rootParentID")
|
|
||||||
}
|
|
||||||
return dc.rootParentID, nil
|
return dc.rootParentID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"maps"
|
"maps"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/textproto"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -145,6 +146,7 @@ type Opts struct {
|
|||||||
MultipartMetadataName string // ..this is used for the name of the metadata form part if set
|
MultipartMetadataName string // ..this is used for the name of the metadata form part if set
|
||||||
MultipartContentName string // ..name of the parameter which is the attached file
|
MultipartContentName string // ..name of the parameter which is the attached file
|
||||||
MultipartFileName string // ..name of the file for the attached file
|
MultipartFileName string // ..name of the file for the attached file
|
||||||
|
MultipartContentType string // ..content type of the attached file
|
||||||
Parameters url.Values // any parameters for the final URL
|
Parameters url.Values // any parameters for the final URL
|
||||||
TransferEncoding []string // transfer encoding, set to "identity" to disable chunked encoding
|
TransferEncoding []string // transfer encoding, set to "identity" to disable chunked encoding
|
||||||
Trailer *http.Header // set the request trailer
|
Trailer *http.Header // set the request trailer
|
||||||
@@ -371,6 +373,17 @@ func (api *Client) Call(ctx context.Context, opts *Opts) (resp *http.Response, e
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateFormFile is a convenience wrapper around [Writer.CreatePart]. It creates
|
||||||
|
// a new form-data header with the provided field name and file name.
|
||||||
|
func CreateFormFile(w *multipart.Writer, fieldname, filename, contentType string) (io.Writer, error) {
|
||||||
|
h := make(textproto.MIMEHeader)
|
||||||
|
h.Set("Content-Disposition", multipart.FileContentDisposition(fieldname, filename))
|
||||||
|
if contentType != "" {
|
||||||
|
h.Set("Content-Type", contentType)
|
||||||
|
}
|
||||||
|
return w.CreatePart(h)
|
||||||
|
}
|
||||||
|
|
||||||
// MultipartUpload creates an io.Reader which produces an encoded a
|
// MultipartUpload creates an io.Reader which produces an encoded a
|
||||||
// multipart form upload from the params passed in and the passed in
|
// multipart form upload from the params passed in and the passed in
|
||||||
//
|
//
|
||||||
@@ -382,10 +395,10 @@ func (api *Client) Call(ctx context.Context, opts *Opts) (resp *http.Response, e
|
|||||||
// the int64 returned is the overhead in addition to the file contents, in case Content-Length is required
|
// the int64 returned is the overhead in addition to the file contents, in case Content-Length is required
|
||||||
//
|
//
|
||||||
// NB This doesn't allow setting the content type of the attachment
|
// NB This doesn't allow setting the content type of the attachment
|
||||||
func MultipartUpload(ctx context.Context, in io.Reader, params url.Values, contentName, fileName string) (io.ReadCloser, string, int64, error) {
|
func MultipartUpload(ctx context.Context, in io.Reader, params url.Values, contentName, fileName string, contentType string) (io.ReadCloser, string, int64, error) {
|
||||||
bodyReader, bodyWriter := io.Pipe()
|
bodyReader, bodyWriter := io.Pipe()
|
||||||
writer := multipart.NewWriter(bodyWriter)
|
writer := multipart.NewWriter(bodyWriter)
|
||||||
contentType := writer.FormDataContentType()
|
formContentType := writer.FormDataContentType()
|
||||||
|
|
||||||
// Create a Multipart Writer as base for calculating the Content-Length
|
// Create a Multipart Writer as base for calculating the Content-Length
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
@@ -404,7 +417,7 @@ func MultipartUpload(ctx context.Context, in io.Reader, params url.Values, conte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if in != nil {
|
if in != nil {
|
||||||
_, err = dummyMultipartWriter.CreateFormFile(contentName, fileName)
|
_, err = CreateFormFile(dummyMultipartWriter, contentName, fileName, contentType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", 0, err
|
return nil, "", 0, err
|
||||||
}
|
}
|
||||||
@@ -445,7 +458,7 @@ func MultipartUpload(ctx context.Context, in io.Reader, params url.Values, conte
|
|||||||
}
|
}
|
||||||
|
|
||||||
if in != nil {
|
if in != nil {
|
||||||
part, err := writer.CreateFormFile(contentName, fileName)
|
part, err := CreateFormFile(writer, contentName, fileName, contentType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = bodyWriter.CloseWithError(fmt.Errorf("failed to create form file: %w", err))
|
_ = bodyWriter.CloseWithError(fmt.Errorf("failed to create form file: %w", err))
|
||||||
return
|
return
|
||||||
@@ -467,7 +480,7 @@ func MultipartUpload(ctx context.Context, in io.Reader, params url.Values, conte
|
|||||||
_ = bodyWriter.Close()
|
_ = bodyWriter.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return bodyReader, contentType, multipartLength, nil
|
return bodyReader, formContentType, multipartLength, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CallJSON runs Call and decodes the body as a JSON object into response (if not nil)
|
// CallJSON runs Call and decodes the body as a JSON object into response (if not nil)
|
||||||
@@ -539,7 +552,7 @@ func (api *Client) callCodec(ctx context.Context, opts *Opts, request any, respo
|
|||||||
opts = opts.Copy()
|
opts = opts.Copy()
|
||||||
|
|
||||||
var overhead int64
|
var overhead int64
|
||||||
opts.Body, opts.ContentType, overhead, err = MultipartUpload(ctx, opts.Body, params, opts.MultipartContentName, opts.MultipartFileName)
|
opts.Body, opts.ContentType, overhead, err = MultipartUpload(ctx, opts.Body, params, opts.MultipartContentName, opts.MultipartFileName, opts.MultipartContentType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user