%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/fetch.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"
"strconv"
"sync"
"time"
"github.com/odeke-em/drive/config"
)
const (
FetchById = iota
FetchMatches
Fetch
)
func setIndexingOnlyOption(g *Commands) {
g.opts.indexingOnly = true
}
func (g *Commands) Prune() (err error) {
setIndexingOnlyOption(g)
deletions, delErr := g.pruneStaleIndices()
if delErr != nil {
return delErr
}
for staleFileId := range deletions {
delErr := g.context.PopIndicesKey(staleFileId)
if delErr != nil {
g.log.LogErrf("fetch removing index for: \"%s\" %v\n", staleFileId, delErr)
}
}
return nil
}
func (g *Commands) Fetch() (err error) {
return g.fetch(Fetch)
}
func (g *Commands) FetchById() (err error) {
return g.fetch(FetchById)
}
func (g *Commands) FetchMatches() (err error) {
return g.fetch(FetchMatches)
}
func (g *Commands) fetch(fetchOp int) (err error) {
setIndexingOnlyOption(g)
err = g.context.CreateIndicesBucket()
if err != nil {
return err
}
var cl []*Change
switch fetchOp {
case FetchById:
cl, _, err = pullLikeResolve(g, TypeById)
case FetchMatches:
cl, _, err = pullLikeResolve(g, TypeMatches)
default:
cl, _, err = pullLikeResolve(g, TypeAll)
}
if err != nil {
return err
}
clArg := changeListArg{
logy: g.log,
changes: cl,
noPrompt: !g.opts.canPrompt(),
noClobber: g.opts.NoClobber,
}
status, opMap := printFetchChangeList(&clArg)
if !accepted(status) {
return status.Error()
}
return g.playFetchChanges(cl, opMap)
}
func loneCountRegister(wg *sync.WaitGroup, progress chan int) {
progress <- 1
wg.Done()
return
}
func (g *Commands) playFetchChanges(cl []*Change, opMap *map[Operation]sizeCounter) (err error) {
changeCount := len(cl)
g.taskStart(int64(changeCount))
progressDone := make(chan bool, 1)
var wg sync.WaitGroup
wg.Add(changeCount)
go func() {
for n := range g.rem.progressChan {
g.taskAdd(int64(n))
}
progressDone <- true
}()
ticker := time.Tick(1e9 / 10)
for _, c := range cl {
switch c.Op() {
case OpDelete:
go g.removeIndex(&wg, c.Dest)
case OpNone:
loneCountRegister(&wg, g.rem.progressChan)
default:
go g.addIndex(&wg, c.Src)
}
<-ticker
}
wg.Wait()
close(g.rem.progressChan)
<-progressDone
g.taskFinish()
return nil
}
func (g *Commands) addIndex(wg *sync.WaitGroup, f *File) error {
defer loneCountRegister(wg, g.rem.progressChan)
indexErr := g.createIndex(f)
// TODO: Should indexing errors be reported?
if indexErr != nil {
g.log.LogErrf("addIndex %s: %v\n", f.Name, indexErr)
}
return indexErr
}
func (g *Commands) removeIndex(wg *sync.WaitGroup, f *File) error {
if err := g.context.CreateIndicesBucket(); err != nil {
return err
}
defer loneCountRegister(wg, g.rem.progressChan)
if f.Id == "" {
return nil
}
index := f.ToIndex()
rmErr := g.context.RemoveIndex(index, g.context.AbsPathOf(""))
// TODO: Should indexing errors be reported?
if rmErr != nil {
g.log.LogErrf("removeIndex %s: %v\n", f.Name, rmErr)
}
return rmErr
}
func queryIdify(ids map[string]*File) string {
idified := []string{}
for id, _ := range ids {
idified = append(idified, fmt.Sprintf("(id=%s)", strconv.Quote(id)))
}
return sepJoin(" or ", idified...)
}
func mapifyFiles(g *Commands, ids map[string]bool) (map[string]*File, error) {
delivery := make(chan *File)
mapping := make(map[string]*File)
for id, _ := range ids {
go func(qId string) {
f, err := g.rem.FindById(qId)
if false && err != nil && err != ErrPathNotExists {
g.log.LogErrln(id, err, "x")
}
delivery <- f
}(id)
}
doneCount := len(ids)
for i := 0; i < doneCount; i += 1 {
f := <-delivery
if f == nil {
continue
}
mapping[f.Id] = f
}
return mapping, nil
}
func (g *Commands) listIndicesKeys() (chan string, error) {
return g.context.ListKeys(g.context.AbsPathOf(""), config.IndicesKey)
}
func (g *Commands) pruneStaleIndices() (deletions chan string, err error) {
var listing chan string
listing, err = g.listIndicesKeys()
deletions = make(chan string)
if err != nil {
close(deletions)
return
}
go func() {
spin := g.playabler()
defer func() {
spin.stop()
close(deletions)
}()
queriesPerRequest := 10
tick := time.Tick(time.Duration(1e9 / queriesPerRequest))
iterating := true
doneCount := uint64(0)
totalDeletions := uint64(0)
for iterating {
spin.play()
localIds := map[string]bool{}
i := 0
for i < queriesPerRequest {
i += 1
fileId, ok := <-listing
if !ok {
iterating = false
break
}
localIds[fileId] = true
}
if len(localIds) < 1 {
break
}
mapping, mErr := mapifyFiles(g, localIds)
if mErr != nil {
g.log.LogErrf("syncIndices: %v\n", mErr)
continue
}
delCount := 0
for localId, _ := range localIds {
_, ok := mapping[localId]
if !ok {
delCount += 1
deletions <- localId
}
}
doneCount += uint64(i)
totalDeletions += uint64(delCount)
spin.pause()
deletionsReport := ""
if delCount >= 1 {
deletionsReport = fmt.Sprintf("%v/%v to be deleted", delCount, i)
}
if totalDeletions >= 1 {
deletionsReport = fmt.Sprintf("%s(%v deletions so far)", deletionsReport, totalDeletions)
}
g.log.LogErrf("\rprune: %s %v index items processed so far\r", deletionsReport, doneCount)
<-tick
}
}()
return
}
func (g *Commands) createIndex(f *File) error {
if f == nil {
return config.ErrDerefNilIndex
}
index := f.ToIndex()
return g.context.SerializeIndex(index)
}
func printFetchChangeList(clArg *changeListArg) (Agreement, *map[Operation]sizeCounter) {
if len(clArg.changes) == 0 {
clArg.logy.Logln("Everything is up-to-date.")
return NotApplicable, nil
}
if clArg.noPrompt {
return AcceptedImplicitly, nil
}
logy := clArg.logy
changes := clArg.changes
op := OpIndexAddition
_, description := op.description()
for _, c := range changes {
op := c.Op()
if op != OpNone {
logy.Logln(c.Symbol(), c.Path)
}
}
logy.Logf("%s %d\n", description, len(changes))
return promptForChanges(), nil
}