1
0
mirror of https://github.com/rclone/rclone.git synced 2025-01-29 21:04:23 +02:00

Fix windows build - fixes #628

Try to make clearer the distinction between OS paths and rclone paths
(remotes) so it is harder to muddle them up.
This commit is contained in:
Nick Craig-Wood 2016-08-18 23:16:47 +01:00
parent 84eb7031bb
commit 6089f443b9
4 changed files with 41 additions and 57 deletions

View File

@ -15,6 +15,8 @@ import (
"time" "time"
"unicode/utf8" "unicode/utf8"
"golang.org/x/text/unicode/norm"
"github.com/ncw/rclone/fs" "github.com/ncw/rclone/fs"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -41,7 +43,7 @@ func init() {
// Fs represents a local filesystem rooted at root // Fs represents a local filesystem rooted at root
type Fs struct { type Fs struct {
name string // the name of the remote name string // the name of the remote
root string // The root directory root string // The root directory (OS path)
precisionOk sync.Once // Whether we need to read the precision precisionOk sync.Once // Whether we need to read the precision
precision time.Duration // precision of local filesystem precision time.Duration // precision of local filesystem
wmu sync.Mutex // used for locking access to 'warned'. wmu sync.Mutex // used for locking access to 'warned'.
@ -70,7 +72,7 @@ func NewFs(name, root string) (fs.Fs, error) {
warned: make(map[string]struct{}), warned: make(map[string]struct{}),
nounc: nounc == "true", nounc: nounc == "true",
} }
f.root = f.filterPath(root) f.root = f.cleanPath(root)
// Check to see if this points to a file // Check to see if this points to a file
fi, err := os.Lstat(f.root) fi, err := os.Lstat(f.root)
@ -100,9 +102,8 @@ func (f *Fs) String() string {
// newObject makes a half completed Object // newObject makes a half completed Object
func (f *Fs) newObject(remote string) *Object { func (f *Fs) newObject(remote string) *Object {
remote = normString(remote) dstPath := f.cleanPath(filepath.Join(f.root, remote))
dstPath := f.filterPath(filepath.Join(f.root, remote)) remote = f.cleanRemote(remote)
remote = filepath.ToSlash(f.cleanUtf8(remote))
return &Object{ return &Object{
fs: f, fs: f,
remote: remote, remote: remote,
@ -175,7 +176,7 @@ func (f *Fs) list(out fs.ListOpts, remote string, dirpath string, level int) (su
if fi.IsDir() { if fi.IsDir() {
if out.IncludeDirectory(newRemote) { if out.IncludeDirectory(newRemote) {
dir := &fs.Dir{ dir := &fs.Dir{
Name: normString(f.cleanUtf8(newRemote)), Name: f.cleanRemote(newRemote),
When: fi.ModTime(), When: fi.ModTime(),
Bytes: 0, Bytes: 0,
Count: 0, Count: 0,
@ -207,8 +208,8 @@ func (f *Fs) list(out fs.ListOpts, remote string, dirpath string, level int) (su
// Ignores everything which isn't Storable, eg links etc // Ignores everything which isn't Storable, eg links etc
func (f *Fs) List(out fs.ListOpts, dir string) { func (f *Fs) List(out fs.ListOpts, dir string) {
defer out.Finished() defer out.Finished()
dir = filterFragment(f.cleanUtf8(dir)) root := f.cleanPath(filepath.Join(f.root, dir))
root := filepath.Join(f.root, dir) dir = f.cleanRemote(dir)
_, err := os.Stat(root) _, err := os.Stat(root)
if err != nil { if err != nil {
out.SetError(fs.ErrorDirNotFound) out.SetError(fs.ErrorDirNotFound)
@ -252,10 +253,11 @@ func (f *Fs) List(out fs.ListOpts, dir string) {
wg.Wait() wg.Wait()
} }
// CleanUtf8 makes string a valid UTF-8 string // cleanRemote makes string a valid UTF-8 string for remote strings.
// //
// Any invalid UTF-8 characters will be replaced with utf8.RuneError // Any invalid UTF-8 characters will be replaced with utf8.RuneError
func (f *Fs) cleanUtf8(name string) string { // It also normalises the UTF-8 and converts the slashes if necessary.
func (f *Fs) cleanRemote(name string) string {
if !utf8.ValidString(name) { if !utf8.ValidString(name) {
f.wmu.Lock() f.wmu.Lock()
if _, ok := f.warned[name]; !ok { if _, ok := f.warned[name]; !ok {
@ -265,9 +267,8 @@ func (f *Fs) cleanUtf8(name string) string {
f.wmu.Unlock() f.wmu.Unlock()
name = string([]rune(name)) name = string([]rune(name))
} }
if runtime.GOOS == "windows" { name = norm.NFC.String(name)
name = cleanWindowsName(f, name) name = filepath.ToSlash(name)
}
return name return name
} }
@ -647,8 +648,8 @@ func (o *Object) Remove() error {
return os.Remove(o.path) return os.Remove(o.path)
} }
// Return the current directory and file from a path // Return the directory and file from an OS path. Assumes
// Assumes os.PathSeparator is used. // os.PathSeparator is used.
func getDirFile(s string) (string, string) { func getDirFile(s string) (string, string) {
i := strings.LastIndex(s, string(os.PathSeparator)) i := strings.LastIndex(s, string(os.PathSeparator))
dir, file := s[:i], s[i+1:] dir, file := s[:i], s[i+1:]
@ -658,9 +659,9 @@ func getDirFile(s string) (string, string) {
return dir, file return dir, file
} }
// filterFragment cleans a path fragment which is part of a bigger // cleanPathFragment cleans an OS path fragment which is part of a
// path and not necessarily absolute // bigger path and not necessarily absolute
func filterFragment(s string) string { func cleanPathFragment(s string) string {
if s == "" { if s == "" {
return s return s
} }
@ -671,11 +672,16 @@ func filterFragment(s string) string {
return s return s
} }
// filterPath cleans and makes absolute the path passed in. // cleanPath cleans and makes absolute the path passed in and returns
// an OS path.
// //
// On windows it makes the path UNC also. // The input might be in OS form or rclone form or a mixture, but the
func (f *Fs) filterPath(s string) string { // output is in OS form.
s = filterFragment(s) //
// On windows it makes the path UNC also and replaces any characters
// Windows can't deal with with their replacements.
func (f *Fs) cleanPath(s string) string {
s = cleanPathFragment(s)
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
if !filepath.IsAbs(s) && !strings.HasPrefix(s, "\\") { if !filepath.IsAbs(s) && !strings.HasPrefix(s, "\\") {
s2, err := filepath.Abs(s) s2, err := filepath.Abs(s)
@ -683,21 +689,19 @@ func (f *Fs) filterPath(s string) string {
s = s2 s = s2
} }
} }
if !f.nounc {
if f.nounc { // Convert to UNC
return s s = uncPath(s)
} }
// Convert to UNC s = cleanWindowsName(f, s)
return uncPath(s) } else {
} if !filepath.IsAbs(s) {
s2, err := filepath.Abs(s)
if !filepath.IsAbs(s) { if err == nil {
s2, err := filepath.Abs(s) s = s2
if err == nil { }
s = s2
} }
} }
return s return s
} }
@ -726,7 +730,7 @@ func uncPath(s string) string {
return s return s
} }
// cleanWindowsName will clean invalid Windows characters // cleanWindowsName will clean invalid Windows characters replacing them with _
func cleanWindowsName(f *Fs, name string) string { func cleanWindowsName(f *Fs, name string) string {
original := name original := name
var name2 string var name2 string

View File

@ -1,12 +0,0 @@
// +build darwin
package local
import (
"golang.org/x/text/unicode/norm"
)
// normString normalises the remote name as some OS X denormalises UTF-8 when storing it to disk
func normString(remote string) string {
return norm.NFC.String(remote)
}

View File

@ -1,8 +0,0 @@
// +build !darwin
package local
// normString normalises the remote name if necessary
func normString(remote string) string {
return remote
}

View File

@ -57,11 +57,11 @@ var utf8Tests = [][2]string{
{string([]byte{'a', 0x80, 'b'}), "a�b"}, {string([]byte{'a', 0x80, 'b'}), "a�b"},
} }
func TestCleanUtf8(t *testing.T) { func TestCleanRemote(t *testing.T) {
f := &Fs{} f := &Fs{}
f.warned = make(map[string]struct{}) f.warned = make(map[string]struct{})
for _, test := range utf8Tests { for _, test := range utf8Tests {
got := f.cleanUtf8(test[0]) got := f.cleanRemote(test[0])
expect := test[1] expect := test[1]
if got != expect { if got != expect {
t.Fatalf("got %q, expected %q", got, expect) t.Fatalf("got %q, expected %q", got, expect)