2015-05-24 02:56:48 +08:00
|
|
|
package dropbox
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"github.com/ncw/rclone/fs"
|
|
|
|
"github.com/stacktic/dropbox"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
type NameTreeNode struct {
|
|
|
|
// Map from lowercase directory name to tree node
|
|
|
|
Directories map[string]*NameTreeNode
|
|
|
|
|
|
|
|
// Map from file name (case sensitive) to dropbox entry
|
|
|
|
Files map[string]*dropbox.Entry
|
|
|
|
|
|
|
|
// Empty string if exact case is unknown or root node
|
|
|
|
CaseCorrectName string
|
|
|
|
}
|
|
|
|
|
|
|
|
// ------------------------------------------------------------
|
|
|
|
|
|
|
|
func newNameTreeNode(caseCorrectName string) *NameTreeNode {
|
|
|
|
return &NameTreeNode{
|
|
|
|
CaseCorrectName: caseCorrectName,
|
|
|
|
Directories: make(map[string]*NameTreeNode),
|
|
|
|
Files: make(map[string]*dropbox.Entry),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewNameTree() *NameTreeNode {
|
|
|
|
return newNameTreeNode("")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tree *NameTreeNode) String() string {
|
|
|
|
if len(tree.CaseCorrectName) == 0 {
|
|
|
|
return "NameTreeNode/<root>"
|
|
|
|
} else {
|
|
|
|
return fmt.Sprintf("NameTreeNode/%q", tree.CaseCorrectName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tree *NameTreeNode) getTreeNode(path string) *NameTreeNode {
|
|
|
|
if len(path) == 0 {
|
|
|
|
// no lookup required, just return root
|
|
|
|
return tree
|
|
|
|
}
|
|
|
|
|
|
|
|
current := tree
|
|
|
|
for _, component := range strings.Split(path, "/") {
|
|
|
|
if len(component) == 0 {
|
|
|
|
fs.Stats.Error()
|
2015-08-09 03:10:31 +08:00
|
|
|
fs.ErrorLog(tree, "getTreeNode: path component is empty (full path %q)", path)
|
2015-05-24 02:56:48 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
lowercase := strings.ToLower(component)
|
|
|
|
|
|
|
|
lookup := current.Directories[lowercase]
|
|
|
|
if lookup == nil {
|
|
|
|
lookup = newNameTreeNode("")
|
|
|
|
current.Directories[lowercase] = lookup
|
|
|
|
}
|
|
|
|
|
|
|
|
current = lookup
|
|
|
|
}
|
|
|
|
|
|
|
|
return current
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tree *NameTreeNode) PutCaseCorrectDirectoryName(parentPath string, caseCorrectDirectoryName string) {
|
|
|
|
if len(caseCorrectDirectoryName) == 0 {
|
|
|
|
fs.Stats.Error()
|
2015-08-09 03:10:31 +08:00
|
|
|
fs.ErrorLog(tree, "PutCaseCorrectDirectoryName: empty caseCorrectDirectoryName is not allowed (parentPath: %q)", parentPath)
|
2015-05-24 02:56:48 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
node := tree.getTreeNode(parentPath)
|
|
|
|
if node == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
lowerCaseDirectoryName := strings.ToLower(caseCorrectDirectoryName)
|
|
|
|
directory := node.Directories[lowerCaseDirectoryName]
|
|
|
|
if directory == nil {
|
|
|
|
directory = newNameTreeNode(caseCorrectDirectoryName)
|
|
|
|
node.Directories[lowerCaseDirectoryName] = directory
|
|
|
|
} else {
|
|
|
|
if len(directory.CaseCorrectName) > 0 {
|
|
|
|
fs.Stats.Error()
|
2015-08-09 03:10:31 +08:00
|
|
|
fs.ErrorLog(tree, "PutCaseCorrectDirectoryName: directory %q is already exists under parent path %q", caseCorrectDirectoryName, parentPath)
|
2015-05-24 02:56:48 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
directory.CaseCorrectName = caseCorrectDirectoryName
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tree *NameTreeNode) PutFile(parentPath string, caseCorrectFileName string, dropboxEntry *dropbox.Entry) {
|
|
|
|
node := tree.getTreeNode(parentPath)
|
|
|
|
if node == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if node.Files[caseCorrectFileName] != nil {
|
|
|
|
fs.Stats.Error()
|
2015-08-09 03:10:31 +08:00
|
|
|
fs.ErrorLog(tree, "PutFile: file %q is already exists at %q", caseCorrectFileName, parentPath)
|
2015-05-24 02:56:48 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
node.Files[caseCorrectFileName] = dropboxEntry
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tree *NameTreeNode) GetPathWithCorrectCase(path string) *string {
|
|
|
|
if path == "" {
|
|
|
|
empty := ""
|
|
|
|
return &empty
|
|
|
|
}
|
|
|
|
|
|
|
|
var result bytes.Buffer
|
|
|
|
|
|
|
|
current := tree
|
|
|
|
for _, component := range strings.Split(path, "/") {
|
|
|
|
if component == "" {
|
|
|
|
fs.Stats.Error()
|
2015-08-09 03:10:31 +08:00
|
|
|
fs.ErrorLog(tree, "GetPathWithCorrectCase: path component is empty (full path %q)", path)
|
2015-05-24 02:56:48 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
lowercase := strings.ToLower(component)
|
|
|
|
|
|
|
|
current = current.Directories[lowercase]
|
|
|
|
if current == nil || current.CaseCorrectName == "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
result.WriteString("/")
|
|
|
|
result.WriteString(current.CaseCorrectName)
|
|
|
|
}
|
|
|
|
|
|
|
|
resultString := result.String()
|
|
|
|
return &resultString
|
|
|
|
}
|
|
|
|
|
|
|
|
type NameTreeFileWalkFunc func(caseCorrectFilePath string, entry *dropbox.Entry)
|
|
|
|
|
|
|
|
func (tree *NameTreeNode) walkFilesRec(currentPath string, walkFunc NameTreeFileWalkFunc) {
|
|
|
|
var prefix string
|
|
|
|
if currentPath == "" {
|
|
|
|
prefix = ""
|
|
|
|
} else {
|
|
|
|
prefix = currentPath + "/"
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, entry := range tree.Files {
|
|
|
|
walkFunc(prefix+name, entry)
|
|
|
|
}
|
|
|
|
|
|
|
|
for lowerCaseName, directory := range tree.Directories {
|
|
|
|
caseCorrectName := directory.CaseCorrectName
|
|
|
|
if caseCorrectName == "" {
|
|
|
|
fs.Stats.Error()
|
2015-08-09 03:10:31 +08:00
|
|
|
fs.ErrorLog(tree, "WalkFiles: exact name of the directory %q is unknown (parent path: %q)", lowerCaseName, currentPath)
|
2015-05-24 02:56:48 +08:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
directory.walkFilesRec(prefix+caseCorrectName, walkFunc)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tree *NameTreeNode) WalkFiles(rootPath string, walkFunc NameTreeFileWalkFunc) {
|
|
|
|
node := tree.getTreeNode(rootPath)
|
|
|
|
if node == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
node.walkFilesRec(rootPath, walkFunc)
|
|
|
|
}
|