%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/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 }