%PDF- %PDF-
| Direktori : /home/waritko/go/src/github.com/odeke-em/drive/src/ |
| Current File : //home/waritko/go/src/github.com/odeke-em/drive/src/clashes.go |
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package drive
import (
"fmt"
"path/filepath"
"strings"
)
type FixClashesMode uint8
const (
FixClashesRename FixClashesMode = 1 + iota
FixClashesTrash
)
func (g *Commands) ListClashes(byId bool) error {
spin := g.playabler()
spin.play()
clashes, err := listClashes(g, g.opts.Sources, byId)
spin.stop()
if len(clashes) < 1 {
if err == nil {
return fmt.Errorf("no clashes exist!")
} else {
return err
}
}
if err != nil && err != ErrClashesDetected {
return err
}
warnClashesPersist(g.log, clashes)
return nil
}
func (g *Commands) FixClashes(byId bool) error {
spin := g.playabler()
spin.play()
clashes, err := listClashes(g, g.opts.Sources, byId)
spin.stop()
if len(clashes) < 1 {
return err
}
if err != nil && err != ErrClashesDetected {
return err
}
fn := g.opts.clashesHandler()
return fn(g, clashes)
}
type clashesHandler func(g *Commands, clashes []*Change) error
// clashesHandler returns the appropriate clashes handler depending
// on the FixMode ie whether renaming or trashing is to be done.
func (opts *Options) clashesHandler() clashesHandler {
fn := autoRenameClashes
if opts.FixClashesMode == FixClashesTrash {
fn = autoTrashClashes
}
return fn
}
func findClashesForChildren(g *Commands, parentId, relToRootPath string, depth int) (clashes []*Change, err error) {
if depth == 0 {
return
}
memoized := map[string][]*File{}
pagePair := g.rem.FindByParentId(parentId, g.opts.Hidden)
decrementedDepth := decrementTraversalDepth(depth)
children := pagePair.filesChan
discoveryOrder := []string{}
for child := range children {
if child == nil {
continue
}
cluster, alreadyDiscovered := memoized[child.Name]
if !alreadyDiscovered {
discoveryOrder = append(discoveryOrder, child.Name)
}
cluster = append(cluster, child)
memoized[child.Name] = cluster
}
// Now ensure that no error ensued during pagination/retrieval
for rErr := range pagePair.errsChan {
if rErr != nil {
err = rErr
return clashes, err
}
}
separatorPrefix := relToRootPath
if rootLike(separatorPrefix) {
// Avoid a situation where you have Join("/", "/", "a") -> "//a"
separatorPrefix = ""
}
// To preserve the discovery order
for _, commonKey := range discoveryOrder {
cluster, _ := memoized[commonKey]
fullRelToRootPath := sepJoin(RemoteSeparator, separatorPrefix, commonKey)
nameClashesPresent := len(cluster) > 1
for _, rem := range cluster {
if nameClashesPresent {
change := &Change{
Src: rem, g: g,
Path: fullRelToRootPath,
Parent: g.opts.Path,
}
clashes = append(clashes, change)
}
ccl, cErr := findClashesForChildren(g, rem.Id, fullRelToRootPath, decrementedDepth)
clashes = append(clashes, ccl...)
if cErr != nil {
err = combineErrors(err, cErr)
}
}
}
return
}
func findClashesByPath(g *Commands, relToRootPath string, depth int) (clashes []*Change, err error) {
if depth == 0 {
return
}
iterCount := uint64(0)
clashThresholdCount := uint64(1)
cl := []*Change{}
pagePair := g.rem.FindByPathM(relToRootPath)
working := true
for working {
select {
case err := <-pagePair.errsChan:
if err != nil {
return clashes, err
}
case rem, stillHasContent := <-pagePair.filesChan:
if !stillHasContent {
working = false
break
}
if rem == nil {
continue
}
iterCount++
change := &Change{
Src: rem, g: g,
Path: relToRootPath,
Parent: g.opts.Path,
}
cl = append(cl, change)
}
}
if iterCount > clashThresholdCount {
clashes = append(clashes, cl...)
}
// Check for any errors that were
// encountered during remote file retrieval
for pageErr := range pagePair.errsChan {
if pageErr == nil {
return clashes, pageErr
}
}
decrementedDepth := decrementTraversalDepth(depth)
if decrementedDepth == 0 {
return
}
for _, change := range cl {
ccl, cErr := findClashesForChildren(g, change.Src.Id, change.Path, decrementedDepth)
if cErr != nil {
err = combineErrors(err, cErr)
}
clashes = append(clashes, ccl...)
}
return
}
func listClashes(g *Commands, sources []string, byId bool) (clashes []*Change, err error) {
for _, relToRootPath := range g.opts.Sources {
cclashes, cErr := findClashesByPath(g, relToRootPath, g.opts.Depth)
clashes = append(clashes, cclashes...)
if cErr != nil {
err = combineErrors(err, cErr)
}
}
return
}
func autoRenameClashes(g *Commands, clashes []*Change) error {
clashesMap := map[string][]*Change{}
for _, clash := range clashes {
group := clashesMap[clash.Path]
if clash.Src == nil {
continue
}
clashesMap[clash.Path] = append(group, clash)
}
renames := make([]renameOp, 0, len(clashesMap)) // we will have at least len(clashesMap) renames
for commonPath, group := range clashesMap {
ext := filepath.Ext(commonPath)
name := strings.TrimSuffix(commonPath, ext)
nextIndex := 0
for i, n := 1, len(group); i < n; i++ { // we can leave first item with original name
var newName string
for {
newName = fmt.Sprintf("%v_%d%v", name, nextIndex, ext)
nextIndex++
dupCheck, err := g.rem.FindByPath(newName)
if err != nil && err != ErrPathNotExists {
return err
}
if dupCheck == nil {
newName = filepath.Base(newName)
break
}
}
r := renameOp{newName: newName, change: group[i], originalPath: commonPath}
renames = append(renames, r)
}
}
if g.opts.canPrompt() {
g.log.Logln("Some clashes found, we can fix them by following renames:")
for _, r := range renames {
g.log.Logf("%v %v -> %v\n", r.originalPath, r.change.Src.Id, r.newName)
}
status := promptForChanges("Proceed with the changes [Y/N] ? ")
if !accepted(status) {
return ErrClashFixingAborted
}
}
var composedError error
quot := customQuote
for _, r := range renames {
message := fmt.Sprintf("Renaming %s %s -> %s\n", quot(r.originalPath), quot(r.change.Src.Id), quot(r.newName))
_, err := g.rem.rename(r.change.Src.Id, r.newName)
if err == nil {
g.log.Log(message)
continue
}
composedError = reComposeError(composedError, fmt.Sprintf("%s err: %v", message, err))
}
return composedError
}
func autoTrashClashes(g *Commands, clashes []*Change) error {
for _, c := range clashes {
// Let's coerce this change to a deletion
c.Dest = c.Src
c.Src = nil
}
if g.opts.canPrompt() {
g.log.Logln("Some clashes found, trash them all?")
for _, c := range clashes {
g.log.Logln(c.Symbol(), c.Path, c.Dest.Id)
}
if status := promptForChanges(); !accepted(status) {
return status.Error()
}
}
opt := trashOpt{
toTrash: true,
permanent: false,
}
return g.playTrashChangeList(clashes, &opt)
}