forked from TrueCloudLab/rclone
opendrive: use lib/encoder
This commit is contained in:
parent
dac20093c5
commit
4b9fdb8475
4 changed files with 50 additions and 114 deletions
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/rclone/rclone/fs/config/configmap"
|
"github.com/rclone/rclone/fs/config/configmap"
|
||||||
"github.com/rclone/rclone/fs/config/configstruct"
|
"github.com/rclone/rclone/fs/config/configstruct"
|
||||||
"github.com/rclone/rclone/fs/config/obscure"
|
"github.com/rclone/rclone/fs/config/obscure"
|
||||||
|
"github.com/rclone/rclone/fs/encodings"
|
||||||
"github.com/rclone/rclone/fs/fserrors"
|
"github.com/rclone/rclone/fs/fserrors"
|
||||||
"github.com/rclone/rclone/fs/fshttp"
|
"github.com/rclone/rclone/fs/fshttp"
|
||||||
"github.com/rclone/rclone/fs/hash"
|
"github.com/rclone/rclone/fs/hash"
|
||||||
|
@ -25,6 +26,8 @@ import (
|
||||||
"github.com/rclone/rclone/lib/rest"
|
"github.com/rclone/rclone/lib/rest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const enc = encodings.OpenDrive
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultEndpoint = "https://dev.opendrive.com/api/v1"
|
defaultEndpoint = "https://dev.opendrive.com/api/v1"
|
||||||
minSleep = 10 * time.Millisecond
|
minSleep = 10 * time.Millisecond
|
||||||
|
@ -585,7 +588,7 @@ func (f *Fs) createObject(ctx context.Context, remote string, modTime time.Time,
|
||||||
fs: f,
|
fs: f,
|
||||||
remote: remote,
|
remote: remote,
|
||||||
}
|
}
|
||||||
return o, leaf, directoryID, nil
|
return o, enc.FromStandardName(leaf), directoryID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// readMetaDataForPath reads the metadata from the path
|
// readMetaDataForPath reads the metadata from the path
|
||||||
|
@ -636,7 +639,11 @@ func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options .
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
response := createFileResponse{}
|
response := createFileResponse{}
|
||||||
err := o.fs.pacer.Call(func() (bool, error) {
|
err := o.fs.pacer.Call(func() (bool, error) {
|
||||||
createFileData := createFile{SessionID: o.fs.session.SessionID, FolderID: directoryID, Name: replaceReservedChars(leaf)}
|
createFileData := createFile{
|
||||||
|
SessionID: o.fs.session.SessionID,
|
||||||
|
FolderID: directoryID,
|
||||||
|
Name: leaf,
|
||||||
|
}
|
||||||
opts := rest.Opts{
|
opts := rest.Opts{
|
||||||
Method: "POST",
|
Method: "POST",
|
||||||
Path: "/upload/create_file.json",
|
Path: "/upload/create_file.json",
|
||||||
|
@ -683,7 +690,7 @@ func (f *Fs) CreateDir(ctx context.Context, pathID, leaf string) (newID string,
|
||||||
err = f.pacer.Call(func() (bool, error) {
|
err = f.pacer.Call(func() (bool, error) {
|
||||||
createDirData := createFolder{
|
createDirData := createFolder{
|
||||||
SessionID: f.session.SessionID,
|
SessionID: f.session.SessionID,
|
||||||
FolderName: replaceReservedChars(leaf),
|
FolderName: enc.FromStandardName(leaf),
|
||||||
FolderSubParent: pathID,
|
FolderSubParent: pathID,
|
||||||
FolderIsPublic: 0,
|
FolderIsPublic: 0,
|
||||||
FolderPublicUpl: 0,
|
FolderPublicUpl: 0,
|
||||||
|
@ -729,8 +736,8 @@ func (f *Fs) FindLeaf(ctx context.Context, pathID, leaf string) (pathIDOut strin
|
||||||
return "", false, errors.Wrap(err, "failed to get folder list")
|
return "", false, errors.Wrap(err, "failed to get folder list")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
leaf = enc.FromStandardName(leaf)
|
||||||
for _, folder := range folderList.Folders {
|
for _, folder := range folderList.Folders {
|
||||||
folder.Name = restoreReservedChars(folder.Name)
|
|
||||||
// fs.Debugf(nil, "Folder: %s (%s)", folder.Name, folder.FolderID)
|
// fs.Debugf(nil, "Folder: %s (%s)", folder.Name, folder.FolderID)
|
||||||
|
|
||||||
if leaf == folder.Name {
|
if leaf == folder.Name {
|
||||||
|
@ -777,7 +784,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, folder := range folderList.Folders {
|
for _, folder := range folderList.Folders {
|
||||||
folder.Name = restoreReservedChars(folder.Name)
|
folder.Name = enc.ToStandardName(folder.Name)
|
||||||
// fs.Debugf(nil, "Folder: %s (%s)", folder.Name, folder.FolderID)
|
// fs.Debugf(nil, "Folder: %s (%s)", folder.Name, folder.FolderID)
|
||||||
remote := path.Join(dir, folder.Name)
|
remote := path.Join(dir, folder.Name)
|
||||||
// cache the directory ID for later lookups
|
// cache the directory ID for later lookups
|
||||||
|
@ -788,7 +795,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range folderList.Files {
|
for _, file := range folderList.Files {
|
||||||
file.Name = restoreReservedChars(file.Name)
|
file.Name = enc.ToStandardName(file.Name)
|
||||||
// fs.Debugf(nil, "File: %s (%s)", file.Name, file.FileID)
|
// fs.Debugf(nil, "File: %s (%s)", file.Name, file.FileID)
|
||||||
remote := path.Join(dir, file.Name)
|
remote := path.Join(dir, file.Name)
|
||||||
o, err := f.newObjectWithInfo(ctx, remote, &file)
|
o, err := f.newObjectWithInfo(ctx, remote, &file)
|
||||||
|
@ -851,7 +858,11 @@ func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
|
||||||
NoResponse: true,
|
NoResponse: true,
|
||||||
Path: "/file/filesettings.json",
|
Path: "/file/filesettings.json",
|
||||||
}
|
}
|
||||||
update := modTimeFile{SessionID: o.fs.session.SessionID, FileID: o.id, FileModificationTime: strconv.FormatInt(modTime.Unix(), 10)}
|
update := modTimeFile{
|
||||||
|
SessionID: o.fs.session.SessionID,
|
||||||
|
FileID: o.id,
|
||||||
|
FileModificationTime: strconv.FormatInt(modTime.Unix(), 10),
|
||||||
|
}
|
||||||
err := o.fs.pacer.Call(func() (bool, error) {
|
err := o.fs.pacer.Call(func() (bool, error) {
|
||||||
resp, err := o.fs.srv.CallJSON(ctx, &opts, &update, nil)
|
resp, err := o.fs.srv.CallJSON(ctx, &opts, &update, nil)
|
||||||
return o.fs.shouldRetry(resp, err)
|
return o.fs.shouldRetry(resp, err)
|
||||||
|
@ -1038,7 +1049,8 @@ func (o *Object) readMetaData(ctx context.Context) (err error) {
|
||||||
err = o.fs.pacer.Call(func() (bool, error) {
|
err = o.fs.pacer.Call(func() (bool, error) {
|
||||||
opts := rest.Opts{
|
opts := rest.Opts{
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
Path: "/folder/itembyname.json/" + o.fs.session.SessionID + "/" + directoryID + "?name=" + url.QueryEscape(replaceReservedChars(leaf)),
|
Path: fmt.Sprintf("/folder/itembyname.json/%s/%s?name=%s",
|
||||||
|
o.fs.session.SessionID, directoryID, url.QueryEscape(enc.FromStandardName(leaf))),
|
||||||
}
|
}
|
||||||
resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &folderList)
|
resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &folderList)
|
||||||
return o.fs.shouldRetry(resp, err)
|
return o.fs.shouldRetry(resp, err)
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
/*
|
|
||||||
Translate file names for OpenDrive
|
|
||||||
|
|
||||||
OpenDrive reserved characters
|
|
||||||
|
|
||||||
The following characters are OpenDrive reserved characters, and can't
|
|
||||||
be used in OpenDrive folder and file names.
|
|
||||||
|
|
||||||
\ / : * ? " < > |
|
|
||||||
|
|
||||||
OpenDrive files and folders can't have leading or trailing spaces also.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
package opendrive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// charMap holds replacements for characters
|
|
||||||
//
|
|
||||||
// OpenDrive has a restricted set of characters compared to other cloud
|
|
||||||
// storage systems, so we to map these to the FULLWIDTH unicode
|
|
||||||
// equivalents
|
|
||||||
//
|
|
||||||
// http://unicode-search.net/unicode-namesearch.pl?term=SOLIDUS
|
|
||||||
var (
|
|
||||||
charMap = map[rune]rune{
|
|
||||||
'\\': '\', // FULLWIDTH REVERSE SOLIDUS
|
|
||||||
':': ':', // FULLWIDTH COLON
|
|
||||||
'*': '*', // FULLWIDTH ASTERISK
|
|
||||||
'?': '?', // FULLWIDTH QUESTION MARK
|
|
||||||
'"': '"', // FULLWIDTH QUOTATION MARK
|
|
||||||
'<': '<', // FULLWIDTH LESS-THAN SIGN
|
|
||||||
'>': '>', // FULLWIDTH GREATER-THAN SIGN
|
|
||||||
'|': '|', // FULLWIDTH VERTICAL LINE
|
|
||||||
' ': '␠', // SYMBOL FOR SPACE
|
|
||||||
}
|
|
||||||
fixStartingWithSpace = regexp.MustCompile(`(/|^) `)
|
|
||||||
fixEndingWithSpace = regexp.MustCompile(` (/|$)`)
|
|
||||||
invCharMap map[rune]rune
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Create inverse charMap
|
|
||||||
invCharMap = make(map[rune]rune, len(charMap))
|
|
||||||
for k, v := range charMap {
|
|
||||||
invCharMap[v] = k
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// replaceReservedChars takes a path and substitutes any reserved
|
|
||||||
// characters in it
|
|
||||||
func replaceReservedChars(in string) string {
|
|
||||||
// Filenames can't start with space
|
|
||||||
in = fixStartingWithSpace.ReplaceAllString(in, "$1"+string(charMap[' ']))
|
|
||||||
// Filenames can't end with space
|
|
||||||
in = fixEndingWithSpace.ReplaceAllString(in, string(charMap[' '])+"$1")
|
|
||||||
return strings.Map(func(c rune) rune {
|
|
||||||
if replacement, ok := charMap[c]; ok && c != ' ' {
|
|
||||||
return replacement
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}, in)
|
|
||||||
}
|
|
||||||
|
|
||||||
// restoreReservedChars takes a path and undoes any substitutions
|
|
||||||
// made by replaceReservedChars
|
|
||||||
func restoreReservedChars(in string) string {
|
|
||||||
return strings.Map(func(c rune) rune {
|
|
||||||
if replacement, ok := invCharMap[c]; ok {
|
|
||||||
return replacement
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}, in)
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
package opendrive
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestReplace(t *testing.T) {
|
|
||||||
for _, test := range []struct {
|
|
||||||
in string
|
|
||||||
out string
|
|
||||||
}{
|
|
||||||
{"", ""},
|
|
||||||
{"abc 123", "abc 123"},
|
|
||||||
{`\*<>?:|#%".~`, `\*<>?:|#%".~`},
|
|
||||||
{`\*<>?:|#%".~/\*<>?:|#%".~`, `\*<>?:|#%".~/\*<>?:|#%".~`},
|
|
||||||
{" leading space", "␠leading space"},
|
|
||||||
{" path/ leading spaces", "␠path/␠ leading spaces"},
|
|
||||||
{"trailing space ", "trailing space␠"},
|
|
||||||
{"trailing spaces /path ", "trailing spaces ␠/path␠"},
|
|
||||||
} {
|
|
||||||
got := replaceReservedChars(test.in)
|
|
||||||
if got != test.out {
|
|
||||||
t.Errorf("replaceReservedChars(%q) want %q got %q", test.in, test.out, got)
|
|
||||||
}
|
|
||||||
got2 := restoreReservedChars(got)
|
|
||||||
if got2 != test.in {
|
|
||||||
t.Errorf("restoreReservedChars(%q) want %q got %q", got, test.in, got2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -69,6 +69,36 @@ OpenDrive allows modification times to be set on objects accurate to 1
|
||||||
second. These will be used to detect whether objects need syncing or
|
second. These will be used to detect whether objects need syncing or
|
||||||
not.
|
not.
|
||||||
|
|
||||||
|
#### Restricted filename characters
|
||||||
|
|
||||||
|
| Character | Value | Replacement |
|
||||||
|
| --------- |:-----:|:-----------:|
|
||||||
|
| NUL | 0x00 | ␀ |
|
||||||
|
| / | 0x2F | / |
|
||||||
|
| " | 0x22 | " |
|
||||||
|
| * | 0x2A | * |
|
||||||
|
| : | 0x3A | : |
|
||||||
|
| < | 0x3C | < |
|
||||||
|
| > | 0x3E | > |
|
||||||
|
| ? | 0x3F | ? |
|
||||||
|
| \ | 0x5C | \ |
|
||||||
|
| \| | 0x7C | | |
|
||||||
|
|
||||||
|
File names can also not begin or end with the following characters.
|
||||||
|
These only get replaced if they are the first or last character in the name:
|
||||||
|
|
||||||
|
| Character | Value | Replacement |
|
||||||
|
| --------- |:-----:|:-----------:|
|
||||||
|
| SP | 0x20 | ␠ |
|
||||||
|
| HT | 0x09 | ␉ |
|
||||||
|
| LF | 0x0A | ␊ |
|
||||||
|
| VT | 0x0B | ␋ |
|
||||||
|
| CR | 0x0D | ␍ |
|
||||||
|
|
||||||
|
|
||||||
|
Invalid UTF-8 bytes will also be [replaced](/overview/#invalid-utf8),
|
||||||
|
as they can't be used in JSON strings.
|
||||||
|
|
||||||
<!--- autogenerated options start - DO NOT EDIT, instead edit fs.RegInfo in backend/opendrive/opendrive.go then run make backenddocs -->
|
<!--- autogenerated options start - DO NOT EDIT, instead edit fs.RegInfo in backend/opendrive/opendrive.go then run make backenddocs -->
|
||||||
### Standard Options
|
### Standard Options
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue