%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /home/waritko/go/src/github.com/odeke-em/drive/src/
Upload File :
Create Path :
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)
}

Zerion Mini Shell 1.0