%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/list.go

// Copyright 2015 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"
	"strings"

	"github.com/odeke-em/log"
)

type attribute struct {
	minimal       bool
	mask          int
	parent        string
	diskUsageOnly bool
}

type traversalSt struct {
	file             *File
	headPath         string
	depth            int
	mask             int
	inTrash          bool
	explicitNoPrompt bool
	sorters          []string
	matchQuery       *matchQuery
}

func sorters(opts *Options) []string {
	if opts == nil || opts.Meta == nil {
		return nil
	}

	meta := *(opts.Meta)
	retr, ok := meta[SortKey]
	if !ok {
		return nil
	}

	// Keys sent it via meta need to be comma split
	// first, space trimmed then added.
	// See Issue https://github.com/odeke-em/drive/issues/714.
	var sortKeys []string
	for _, attr := range retr {
		splits := strings.Split(attr, ",")
		for _, split := range splits {
			trimmedAttr := strings.TrimSpace(split)
			sortKeys = append(sortKeys, trimmedAttr)
		}
	}

	return sortKeys
}

func (g *Commands) ListMatches() error {

	inTrash := trashed(g.opts.TypeMask)

	mq := g.createMatchQuery(false)

	mq.titleSearches = append(mq.titleSearches, fuzzyStringsValuePair{
		fuzzyLevel: Like, values: g.opts.Sources, inTrash: inTrash, joiner: Or,
	})

	pagePair := g.rem.FindMatches(mq)

	spin := g.playabler()
	spin.play()
	defer spin.stop()

	traversalCount := 0

	matches := pagePair.filesChan
	errsChan := pagePair.errsChan

	working := true
	for working {
		select {
		case err := <-errsChan:
			if err != nil {
				return err
			}
		case match, stillHasContent := <-matches:
			if !stillHasContent {
				working = false
				break
			}
			if match == nil {
				continue
			}

			travSt := traversalSt{
				depth:    g.opts.Depth,
				file:     match,
				headPath: g.opts.Path,
				inTrash:  g.opts.InTrash,
				mask:     g.opts.TypeMask,
				sorters:  sorters(g.opts),
			}

			traversalCount += 1

			if !g.breadthFirst(travSt, spin) {
				break
			}
		}
	}

	if traversalCount < 1 {
		g.log.LogErrln("no matches found!")
	}

	return nil
}

func (g *Commands) createMatchQuery(exactMatch bool) *matchQuery {

	mimeQuerySearches := []fuzzyStringsValuePair{}
	titleSearches := []fuzzyStringsValuePair{}
	ownerSearches := []fuzzyStringsValuePair{}

	if g.opts.Meta != nil {
		meta := *(g.opts.Meta)
		skipMimes, sOk := meta[SkipMimeKeyKey]
		if sOk {
			mimeQuerySearches = append(mimeQuerySearches, fuzzyStringsValuePair{
				fuzzyLevel: Not, values: skipMimes, inTrash: g.opts.InTrash, joiner: And,
			})
		}

		matchMimes, mOk := meta[MatchMimeKeyKey]
		if mOk {
			mimeQuerySearches = append(mimeQuerySearches, fuzzyStringsValuePair{
				fuzzyLevel: Is, values: matchMimes, inTrash: g.opts.InTrash, joiner: Or,
			})
		}

		exactTitles, etOk := meta[ExactTitleKey]
		if etOk {
			titleSearches = append(titleSearches, fuzzyStringsValuePair{
				fuzzyLevel: Is, values: exactTitles, inTrash: g.opts.InTrash, joiner: Or,
			})
		}

		exactOwners, eoOk := meta[ExactOwnerKey]
		if eoOk {
			ownerSearches = append(ownerSearches, fuzzyStringsValuePair{
				fuzzyLevel: Is, values: exactOwners, joiner: Or,
			})
		}

		matchOwners, moOk := meta[MatchOwnerKey]
		if moOk {
			ownerSearches = append(ownerSearches, fuzzyStringsValuePair{
				fuzzyLevel: Like, values: matchOwners, joiner: Or,
			})
		}

		notOwner, soOk := meta[NotOwnerKey]
		if soOk {
			ownerSearches = append(ownerSearches, fuzzyStringsValuePair{
				fuzzyLevel: NotIn, values: notOwner, joiner: And,
			})
		}
	}

	mq := matchQuery{
		dirPath:           g.opts.Path,
		inTrash:           g.opts.InTrash,
		mimeQuerySearches: mimeQuerySearches,
		titleSearches:     titleSearches,
		ownerSearches:     ownerSearches,
	}

	return &mq
}

func (g *Commands) List(byId bool) error {
	var kvList []*keyValue

	resolver := g.rem.FindByPath
	if byId {
		resolver = g.rem.FindById
	}

	mq := g.createMatchQuery(true)

	for i, relPath := range g.opts.Sources {
		r, rErr := resolver(relPath)
		g.DebugPrintf("[Commands.List] #%d %q\n", i, relPath)
		if rErr != nil && rErr != ErrPathNotExists {
			return illogicalStateErr(fmt.Errorf("%v: '%s'", rErr, relPath))
		}

		if r == nil {
			g.log.LogErrf("%s cannot be found remotely\n", customQuote(relPath))
			continue
		}

		parentPath := ""
		if !byId {
			parentPath = g.parentPather(relPath)
		} else {
			parentPath = r.Id
		}

		if remoteRootLike(parentPath) {
			parentPath = ""
		}
		if remoteRootLike(r.Name) {
			r.Name = ""
		}
		if rootLike(parentPath) {
			parentPath = ""
		}

		kvList = append(kvList, &keyValue{key: parentPath, value: r})
	}

	spin := g.playabler()
	spin.play()
	for _, kv := range kvList {
		if kv == nil || kv.value == nil {
			continue
		}

		travSt := traversalSt{
			depth:      g.opts.Depth,
			file:       kv.value.(*File),
			headPath:   kv.key,
			inTrash:    g.opts.InTrash,
			mask:       g.opts.TypeMask,
			sorters:    sorters(g.opts),
			matchQuery: mq,
		}

		if !g.breadthFirst(travSt, spin) {
			break
		}
	}
	spin.stop()

	return nil
}

func (g *Commands) listSharedPerPath(relToRootPath string) ([]*keyValue, error) {
	pagePair := g.rem.FindByPathShared(relToRootPath)
	errsChan := pagePair.errsChan
	sharedRemotes := pagePair.filesChan

	var kvList []*keyValue

	working := true
	for working {
		select {
		case err := <-errsChan:
			if err != nil {
				g.log.LogErrf("%v: '%s'\n", err, relToRootPath)
				return kvList, err
			}
		case s, stillHasContent := <-sharedRemotes:
			if !stillHasContent {
				working = false
				break
			}

			parentPath := g.parentPather(relToRootPath)

			if remoteRootLike(parentPath) {
				parentPath = ""
			}

			if rootLike(parentPath) {
				parentPath = ""
			}

			if s == nil {
				continue
			}

			if remoteRootLike(s.Name) {
				s.Name = ""
			}

			kvList = append(kvList, &keyValue{key: parentPath, value: s})
		}
	}

	return kvList, nil
}

func (g *Commands) ListShared() (err error) {
	spin := g.playabler()
	spin.play()
	defer spin.stop()

	var kvList []*keyValue

	for _, relPath := range g.opts.Sources {
		childKvList, err := g.listSharedPerPath(relPath)
		if err != nil {
			return err
		}
		kvList = append(kvList, childKvList...)
	}

	for _, kv := range kvList {
		if kv == nil || kv.value == nil {
			continue
		}

		travSt := traversalSt{
			depth:    g.opts.Depth,
			file:     kv.value.(*File),
			headPath: kv.key,
			inTrash:  g.opts.InTrash,
			mask:     g.opts.TypeMask,
		}

		if !g.breadthFirst(travSt, spin) {
			break
		}
	}
	spin.stop()
	return
}

func (f *File) pretty(logy *log.Logger, opt attribute) {
	fmtdPath := sepJoin("/", opt.parent, f.Name)

	if opt.diskUsageOnly {
		logy.Logf("%-12v %s\n", f.Size, fmtdPath)
		return
	}

	if opt.minimal {
		logy.Logf("%s", fmtdPath)
	} else {
		if f.IsDir {
			logy.Logf("d")
		} else {
			logy.Logf("-")
		}
		if f.Shared {
			logy.Logf("s")
		} else {
			logy.Logf("-")
		}

		if f.UserPermission != nil {
			logy.Logf(" %-10s ", f.UserPermission.Role)
		}
	}

	if owners(opt.mask) && len(f.OwnerNames) >= 1 {
		logy.Logf(" %s ", strings.Join(f.OwnerNames, " & "))
	}

	if version(opt.mask) {
		logy.Logf(" v%d", f.Version)
	}

	if !opt.minimal {
		logy.Logf(" %-10s\t%-10s\t\t%-20s\t%-s\n", prettyBytes(f.Size), f.Id, f.ModTime, fmtdPath)
	} else {
		logy.Logln()
	}
}

func (g *Commands) breadthFirst(travSt traversalSt, spin *playable) bool {

	opt := attribute{
		minimal:       isMinimal(g.opts.TypeMask),
		diskUsageOnly: diskUsageOnly(g.opts.TypeMask),
		mask:          travSt.mask,
	}

	opt.parent = ""
	if travSt.headPath != "/" {
		opt.parent = travSt.headPath
	}

	f := travSt.file
	if !f.IsDir {
		f.pretty(g.log, opt)
		return true
	}

	// New head path
	if !(rootLike(opt.parent) && rootLike(f.Name)) {
		opt.parent = sepJoin("/", opt.parent, f.Name)
	}

	// A depth of < 0 means traverse as deep as you can
	if travSt.depth == 0 {
		// At the end of the line, this was successful.
		return true
	} else if travSt.depth > 0 {
		travSt.depth -= 1
	}

	expr := buildExpression(f.Id, travSt.mask, travSt.inTrash)

	if travSt.matchQuery != nil {
		exprExtra := travSt.matchQuery.Stringer()
		expr = sepJoinNonEmpty(" and ", fmt.Sprintf("(%s)", expr), exprExtra)
	}

	req := g.rem.service.Files.List()
	req.Q(expr)
	req.MaxResults(g.opts.PageSize)

	spin.pause()

	canPrompt := !travSt.explicitNoPrompt
	if canPrompt {
		canPrompt = g.opts.canPrompt()
	}

	spin.play()

	onlyFiles := nonFolderExplicitly(g.opts.TypeMask)

	iterCount := uint64(0)

	var collector []*File

	// We shouldn't prompt in between the same page otherwise we get
	// spurious prompts. See Issue https://github.com/odeke-em/drive/issues/724.
	// We'll only make the prompts in between children.
	pagePair := reqDoPage(req, g.opts.Hidden, false)
	errsChan := pagePair.errsChan
	filesChan := pagePair.filesChan

	working := true
	for working {
		select {
		case err := <-errsChan:
			if err != nil {
				g.log.LogErrf("%v", err)
				return false
			}
		case file, stillHasContent := <-filesChan:
			if !stillHasContent {
				working = false
				break
			}
			if file == nil {
				return false
			}

			if !isHidden(file.Name, g.opts.Hidden) {
				collector = append(collector, file)
			}

		}
	}

	if len(travSt.sorters) >= 1 {
		collector = g.sort(collector, travSt.sorters...)
	}

	var children []*File
	for _, file := range collector {
		if file.IsDir {
			children = append(children, file)
		}

		// The case in which only directories wanted is covered by the buildExpression clause
		// reason being that only folder are allowed to be roots, including the only files clause
		// would result in incorrect traversal since non-folders don't have children.
		// Just don't print it, however, the folder will still be explored.
		if onlyFiles && file.IsDir {
			continue
		}
		file.pretty(g.log, opt)
		iterCount += 1
	}

	if !travSt.inTrash && !g.opts.InTrash {
		// We'll only prompt when traversing children to avoid
		// spurious prompts that result from asynchronous paging
		// before children have been retrieved, sorted and printed.
		// See Issue https://github.com/odeke-em/drive/issues/724.
		canPage := travSt.depth != 0 && len(children) > 0
		if canPage && canPrompt && !nextPage() {
			return false
		}

		for _, file := range children {
			childSt := traversalSt{
				depth:            travSt.depth,
				file:             file,
				headPath:         opt.parent,
				inTrash:          travSt.inTrash,
				mask:             g.opts.TypeMask,
				explicitNoPrompt: travSt.explicitNoPrompt,
				sorters:          travSt.sorters,
				matchQuery:       travSt.matchQuery,
			}

			if !g.breadthFirst(childSt, spin) {
				return false
			}
		}
		return true
	}

	return iterCount >= 1
}

func diskUsageOnly(mask int) bool {
	return (mask & DiskUsageOnly) != 0
}

func isMinimal(mask int) bool {
	return (mask & Minimal) != 0
}

func owners(mask int) bool {
	return (mask & Owners) != 0
}

func version(mask int) bool {
	return (mask & CurrentVersion) != 0
}

func shared(mask int) bool {
	return (mask & Shared) != 0
}

func trashed(mask int) bool {
	return (mask & InTrash) != 0
}

func starred(mask int) bool {
	return (mask & Starred) != 0
}

Zerion Mini Shell 1.0