%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/misc.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 (
	"bufio"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"reflect"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"

	"google.golang.org/api/googleapi"

	expirableCache "github.com/odeke-em/cache"
	spinner "github.com/odeke-em/cli-spinner"
	"github.com/odeke-em/drive/config"
)

var (
	// ErrRejectedTerms is empty "" because messages might be too
	// verbose to affirm a rejection that a user has already seen
	ErrRejectedTerms = errors.New("")
)

const (
	MimeTypeJoiner      = "-"
	RemoteDriveRootPath = "My Drive"
	RemoteSeparator     = "/"

	FmtTimeString           = "2006-01-02T15:04:05.000Z"
	MsgClashesFixedNowRetry = "Clashes were fixed, please retry the operation"
	MsgErrFileNotMutable    = "File not mutable"

	DriveIgnoreSuffix                 = ".driveignore"
	DriveIgnoreNegativeLookAheadToken = "!"
)

const (
	MaxFailedRetryCount = 20 // Arbitrary value
)

var DefaultMaxProcs = runtime.NumCPU()

var BytesPerKB = float64(1024)

type desktopEntry struct {
	name string
	url  string
	icon string
}

type playable struct {
	play  func()
	pause func()
	reset func()
	stop  func()
}

func noop() {
}

type tuple struct {
	first  interface{}
	second interface{}
	last   interface{}
}

type jobSt struct {
	id uint64
	do func() (interface{}, error)
}

func (js jobSt) Id() interface{} {
	return js.id
}

func (js jobSt) Do() (interface{}, error) {
	return js.do()
}

type changeJobSt struct {
	change   *Change
	verb     string
	throttle <-chan time.Time
	fn       func(*Change) error
}

func (cjs *changeJobSt) changeJober(g *Commands) func() (interface{}, error) {
	return func() (interface{}, error) {
		ch := cjs.change
		verb := cjs.verb

		canPrintSteps := g.opts.Verbose && g.opts.canPreview()
		if canPrintSteps {
			g.log.Logf("\033[01m%s::Started %s\033[00m\n", verb, ch.Path)
		}

		err := cjs.fn(ch)

		if canPrintSteps {
			g.log.Logf("\033[04m%s::Done %s\033[00m\n", verb, ch.Path)
		}

		<-cjs.throttle
		return ch.Path, err
	}
}

func retryableErrorCheck(v interface{}) (ok, retryable bool) {
	pr, pOk := v.(*tuple)
	if pr == nil || !pOk {
		retryable = true
		return
	}

	if pr.last == nil {
		ok = true
		return
	}

	err, assertOk := pr.last.(*googleapi.Error)
	// In relation to https://github.com/google/google-api-go-client/issues/93
	// where not every error is of googleapi.Error instance e.g io timeout errors
	// etc, let's assume that non-nil errors are retryable

	if !assertOk {
		retryable = true
		return
	}

	if err == nil {
		ok = true
		return
	}

	if strings.EqualFold(err.Message, MsgErrFileNotMutable) {
		retryable = false
		return
	}

	statusCode := err.Code
	if statusCode >= 500 && statusCode <= 599 {
		retryable = true
		return
	}

	switch statusCode {
	case 401, 403:
		retryable = true

		// TODO: Add other errors
	}

	return
}

func noopPlayable() *playable {
	return &playable{
		play:  noop,
		pause: noop,
		reset: noop,
		stop:  noop,
	}
}

func parseTime(ts string, round bool) (t time.Time) {
	t, _ = time.Parse(FmtTimeString, ts)
	if !round {
		return
	}
	return t.Round(time.Second)
}

func parseTimeAndRound(ts string) (t time.Time) {
	return parseTime(ts, true)
}

func internalIgnores() (ignores []string) {
	if runtime.GOOS == OSLinuxKey {
		ignores = append(ignores, "\\.\\s*desktop$")
	}
	return ignores
}

func newPlayable(freq int64) *playable {
	spin := spinner.New(freq)

	play := func() {
		spin.Start()
	}

	return &playable{
		play:  play,
		stop:  spin.Stop,
		reset: spin.Reset,
		pause: spin.Stop,
	}
}

func (g *Commands) playabler() *playable {
	if !g.opts.canPrompt() {
		return noopPlayable()
	}
	return newPlayable(10)
}

func rootLike(p string) bool {
	return p == "/" || p == "" || p == "root"
}

func remoteRootLike(p string) bool {
	return p == RemoteDriveRootPath
}

type byteDescription func(b int64) string

func memoizeBytes() byteDescription {
	cache := map[int64]string{}
	suffixes := []string{"B", "KB", "MB", "GB", "TB", "PB"}
	maxLen := len(suffixes) - 1

	var cacheMu sync.Mutex

	return func(b int64) string {
		cacheMu.Lock()
		defer cacheMu.Unlock()

		description, ok := cache[b]
		if ok {
			return description
		}

		bf := float64(b)
		i := 0
		description = ""
		for {
			if bf/BytesPerKB < 1 || i >= maxLen {
				description = fmt.Sprintf("%.2f%s", bf, suffixes[i])
				break
			}
			bf /= BytesPerKB
			i += 1
		}
		cache[b] = description
		return description
	}
}

var prettyBytes = memoizeBytes()

func sepJoin(sep string, args ...string) string {
	return strings.Join(args, sep)
}

func sepJoinNonEmpty(sep string, args ...string) string {
	nonEmpties := NonEmptyStrings(args...)
	return sepJoin(sep, nonEmpties...)
}

func _centricPathJoin(sep string, segments ...string) string {
	// Always ensure that the first segment is the separator
	segments = append([]string{sep}, segments...)
	joins := sepJoinNonEmpty(sep, segments...)
	return path.Clean(joins)
}

func localPathJoin(segments ...string) string {
	return _centricPathJoin(UnescapedPathSep, segments...)
}

func remotePathJoin(segments ...string) string {
	return _centricPathJoin(RemoteSeparator, segments...)
}

func isHidden(p string, ignore bool) bool {
	if strings.HasPrefix(p, ".") {
		return !ignore
	}
	return false
}

func prompt(r *os.File, w *os.File, promptText ...interface{}) (input string) {

	fmt.Fprint(w, promptText...)

	flushTTYin()

	fmt.Fscanln(r, &input)
	return
}

func nextPage() bool {
	input := prompt(os.Stdin, os.Stdout, "---More---")
	if len(input) >= 1 && strings.ToLower(input[:1]) == QuitShortKey {
		return false
	}
	return true
}

func promptForChanges(args ...interface{}) Agreement {
	argv := []interface{}{
		"Proceed with the changes? [Y/n]: ",
	}
	if len(args) >= 1 {
		argv = args
	}

	input := prompt(os.Stdin, os.Stdout, argv...)

	if input == "" {
		input = YesShortKey
	}

	if strings.ToUpper(input) == YesShortKey {
		return Accepted
	}

	return Rejected
}

func (f *File) toDesktopEntry(urlMExt *urlMimeTypeExt) *desktopEntry {
	name := f.Name
	if urlMExt.ext != "" {
		name = sepJoin("-", f.Name, urlMExt.ext)
	}
	return &desktopEntry{
		name: name,
		url:  urlMExt.url,
		icon: urlMExt.mimeType,
	}
}

func (f *File) serializeAsDesktopEntry(destPath string, urlMExt *urlMimeTypeExt) (int, error) {
	deskEnt := f.toDesktopEntry(urlMExt)
	handle, err := os.Create(destPath)
	if err != nil {
		return 0, err
	}

	defer func() {
		handle.Close()
		chmodErr := os.Chmod(destPath, 0755)

		if chmodErr != nil {
			fmt.Fprintf(os.Stderr, "%s: [desktopEntry]::chmod %v\n", destPath, chmodErr)
		}

		chTimeErr := os.Chtimes(destPath, f.ModTime, f.ModTime)
		if chTimeErr != nil {
			fmt.Fprintf(os.Stderr, "%s: [desktopEntry]::chtime %v\n", destPath, chTimeErr)
		}
	}()

	icon := strings.Replace(deskEnt.icon, UnescapedPathSep, MimeTypeJoiner, -1)

	return fmt.Fprintf(handle, "[Desktop Entry]\nIcon=%s\nName=%s\nType=%s\nURL=%s\n",
		icon, deskEnt.name, LinkKey, deskEnt.url)
}

func remotePathSplit(p string) (dir, base string) {
	// Avoiding use of filepath.Split because of bug with trailing "/" not being stripped
	sp := strings.Split(p, "/")
	spl := len(sp)
	dirL, baseL := sp[:spl-1], sp[spl-1:]
	dir = strings.Join(dirL, "/")
	base = strings.Join(baseL, "/")
	return
}

func commonPrefix(values ...string) string {
	vLen := len(values)
	if vLen < 1 {
		return ""
	}
	minIndex := 0
	min := values[0]
	minLen := len(min)

	for i := 1; i < vLen; i += 1 {
		st := values[i]
		if st == "" {
			return ""
		}
		lst := len(st)
		if lst < minLen {
			min = st
			minLen = lst
			minIndex = i + 0
		}
	}

	prefix := make([]byte, minLen)
	matchOn := true
	for i := 0; i < minLen; i += 1 {
		for j, other := range values {
			if minIndex == j {
				continue
			}
			if other[i] != min[i] {
				matchOn = false
				break
			}
		}
		if !matchOn {
			break
		}
		prefix[i] = min[i]
	}
	return string(prefix)
}

func ReadFullFile(p string) (clauses []string, err error) {
	return readFile_(p, nil)
}

func fReadFile_(f io.Reader, ignorer func(string) bool) (clauses []string, err error) {
	scanner := bufio.NewScanner(f)

	for scanner.Scan() {
		line := scanner.Text()
		line = strings.Trim(line, " ")
		line = strings.Trim(line, "\n")
		if ignorer != nil && ignorer(line) {
			continue
		}
		clauses = append(clauses, line)
	}

	return
}

func readFileFromStdin(ignorer func(string) bool) (clauses []string, err error) {
	return fReadFile_(os.Stdin, ignorer)
}

func readFile_(p string, ignorer func(string) bool) (clauses []string, err error) {
	f, fErr := os.Open(p)
	if fErr != nil || f == nil {
		err = fErr
		return
	}

	defer f.Close()

	clauses, err = fReadFile_(f, ignorer)

	return
}

func readCommentedFile(p, comment string) (clauses []string, err error) {
	ignorer := func(line string) bool {
		return strings.HasPrefix(line, comment) || len(line) < 1
	}

	return readFile_(p, ignorer)
}

func chunkInt64(v int64) chan int {
	var maxInt int
	maxInt = 1<<31 - 1
	maxIntCast := int64(maxInt)

	chunks := make(chan int)

	go func() {
		q, r := v/maxIntCast, v%maxIntCast
		for i := int64(0); i < q; i += 1 {
			chunks <- maxInt
		}

		if r > 0 {
			chunks <- int(r)
		}

		close(chunks)
	}()

	return chunks
}

func nonEmptyStrings(fn func(string) string, v ...string) (splits []string) {
	for _, elem := range v {
		if fn != nil {
			elem = fn(elem)
		}
		if elem != "" {
			splits = append(splits, elem)
		}
	}
	return
}

func NonEmptyStrings(v ...string) (splits []string) {
	return nonEmptyStrings(nil, v...)
}

func NonEmptyTrimmedStrings(v ...string) (splits []string) {
	return nonEmptyStrings(strings.TrimSpace, v...)
}

var regExtStrMap = map[string]string{
	"csv":   "text/csv",
	"html?": "text/html",
	"te?xt": "text/plain",
	"xml":   "text/xml",

	"gif":   "image/gif",
	"png":   "image/png",
	"svg":   "image/svg+xml",
	"jpe?g": "image/jpeg",

	"odt": "application/vnd.oasis.opendocument.text",
	"odm": "application/vnd.oasis.opendocument.text-master",
	"ott": "application/vnd.oasis.opendocument.text-template",
	"ods": "application/vnd.oasis.opendocument.spreadsheet",
	"ots": "application/vnd.oasis.opendocument.spreadsheet-template",
	"odg": "application/vnd.oasis.opendocument.graphics",
	"otg": "application/vnd.oasis.opendocument.graphics-template",
	"oth": "application/vnd.oasis.opendocument.text-web",
	"odp": "application/vnd.oasis.opendocument.presentation",
	"otp": "application/vnd.oasis.opendocument.presentation-template",
	"odi": "application/vnd.oasis.opendocument.image",
	"odb": "application/vnd.oasis.opendocument.database",
	"oxt": "application/vnd.openofficeorg.extension",

	"rtf": "application/rtf",
	"pdf": "application/pdf",

	"json": "application/json",
	"js":   "application/x-javascript",

	"apk":   "application/vnd.android.package-archive",
	"bin":   "application/octet-stream",
	"tiff?": "image/tiff",
	"tgz":   "application/x-compressed",
	"zip":   "application/zip",

	"mp3": "audio/mpeg",

	// docs and docx should not collide if "docx?" is used so terminate with "$"
	"docx?$": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
	"ppt$":   "application/vnd.ms-powerpoint",
	"pptx$":  "application/vnd.openxmlformats-officedocument.presentationml.presentation",
	"tsv":    "text/tab-separated-values",
	"xlsx?":  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
}

func regMapper(srcMaps ...map[string]string) map[*regexp.Regexp]string {
	regMap := make(map[*regexp.Regexp]string)
	for _, srcMap := range srcMaps {
		for regStr, resolve := range srcMap {
			regExComp, err := regexp.Compile(regStr)
			if err == nil {
				regMap[regExComp] = resolve
			}
		}
	}
	return regMap
}

func cacher(regMap map[*regexp.Regexp]string) func(string) string {
	var cache = make(map[string]string)
	var cacheMu sync.Mutex

	return func(ext string) string {
		cacheMu.Lock()
		defer cacheMu.Unlock()

		memoized, ok := cache[ext]
		if ok {
			return memoized
		}

		bExt := []byte(ext)
		for regEx, mimeType := range regMap {
			if regEx != nil && regEx.Match(bExt) {
				memoized = mimeType
				break
			}
		}

		cache[ext] = memoized
		return memoized
	}
}

func anyMatch(ignore func(string) bool, args ...string) bool {
	if ignore == nil {
		return false
	}

	for _, arg := range args {
		if ignore(arg) {
			return true
		}
	}
	return false
}

func siftExcludes(clauses []string) (excludes, includes []string) {
	alreadySeenExclude := map[string]bool{}
	alreadySeenInclude := map[string]bool{}

	for _, clause := range clauses {
		memoizerPtr := &alreadySeenExclude
		ptr := &excludes
		// Because Go lacks the negative lookahead ?! capability
		// it is necessary to avoid
		if strings.HasPrefix(clause, DriveIgnoreNegativeLookAheadToken) {
			ptr = &includes
			rest := strings.Split(clause, DriveIgnoreNegativeLookAheadToken)
			if len(rest) > 1 {
				memoizerPtr = &alreadySeenInclude
				clause = sepJoin("", rest[1:]...)
			}
		}

		memoizer := *memoizerPtr
		if _, alreadySeen := memoizer[clause]; alreadySeen {
			continue
		}

		*ptr = append(*ptr, clause)
		memoizer[clause] = true
	}
	return
}

func ignorerByClause(clauses ...string) (ignorer func(string) bool, err error) {
	if len(clauses) < 1 {
		return nil, nil
	}

	excludes, includes := siftExcludes(clauses)

	var excludesRegComp, includesComp *regexp.Regexp
	if len(excludes) >= 1 {
		excRegComp, excRegErr := regexp.Compile(strings.Join(excludes, "|"))
		if excRegErr != nil {
			err = combineErrors(err, makeErrorWithStatus("excludeIgnoreRegErr", excRegErr, StatusIllogicalState))
			return
		}
		excludesRegComp = excRegComp
	}

	if len(includes) >= 1 {
		incRegComp, incRegErr := regexp.Compile(strings.Join(includes, "|"))
		if incRegErr != nil {
			err = combineErrors(err, makeErrorWithStatus("includeIgnoreRegErr", incRegErr, StatusIllogicalState))
			return
		}
		includesComp = incRegComp
	}

	ignorer = func(s string) bool {
		sb := []byte(s)
		if excludesRegComp != nil && excludesRegComp.Match(sb) {
			if includesComp != nil && includesComp.Match(sb) {
				return false
			}
			return true
		}
		return false
	}

	return ignorer, nil
}

func combineIgnores(ignoresPath string) (ignorer func(string) bool, err error) {
	clauses, err := readCommentedFile(ignoresPath, "#")
	if err != nil && !os.IsNotExist(err) {
		return nil, err
	}

	// TODO: Should internalIgnores only be added only
	// after all the exclusion and exclusion steps.
	clauses = append(clauses, internalIgnores()...)

	return ignorerByClause(clauses...)
}

var mimeTypeFromQuery = cacher(regMapper(regExtStrMap, map[string]string{
	"docs":   "application/vnd.google-apps.document",
	"folder": DriveFolderMimeType,
	"form":   "application/vnd.google-apps.form",
	"mp4":    "video/mp4",
	"slides?|presentation": "application/vnd.google-apps.presentation",
	"sheet":                "application/vnd.google-apps.spreadsheet",
	"script":               "application/vnd.google-apps.script",
}))

var mimeTypeFromExt = cacher(regMapper(regExtStrMap))

func guessMimeType(p string) string {
	resolvedMimeType := mimeTypeFromExt(p)
	return resolvedMimeType
}

func CrudAtoi(ops ...string) CrudValue {
	opValue := None

	for _, op := range ops {
		if len(op) < 1 {
			continue
		}

		first := op[0]
		if first == 'c' || first == 'C' {
			opValue |= Create
		} else if first == 'r' || first == 'R' {
			opValue |= Read
		} else if first == 'u' || first == 'U' {
			opValue |= Update
		} else if first == 'd' || first == 'D' {
			opValue |= Delete
		}
	}

	return opValue
}

func httpOk(statusCode int) bool {
	return statusCode >= 200 && statusCode <= 299
}

func hasAnyPrefix(value string, prefixes ...string) bool {
	return _hasAnyAtExtreme(value, strings.HasPrefix, prefixes)
}

func hasAnySuffix(value string, prefixes ...string) bool {
	return _hasAnyAtExtreme(value, strings.HasSuffix, prefixes)
}

func _hasAnyAtExtreme(value string, fn func(string, string) bool, queries []string) bool {
	for _, query := range queries {
		if fn(value, query) {
			return true
		}
	}
	return false
}

func maxProcs() int {
	maxProcs, err := strconv.ParseInt(os.Getenv(DriveGoMaxProcsKey), 10, 0)
	if err != nil {
		return DefaultMaxProcs
	}

	maxProcsInt := int(maxProcs)
	if maxProcsInt < 1 {
		return DefaultMaxProcs
	}

	return maxProcsInt
}

func customQuote(s string) string {
	/*
	   See
	      + https://github.com/golang/go/issues/11511
	      + https://github.com/odeke-em/drive/issues/250
	*/
	return "\"" + strings.Replace(strings.Replace(s, "\\", "\\\\", -1), "\"", "\\\"", -1) + "\""
}

func newExpirableCacheValue(v interface{}) *expirableCache.ExpirableValue {
	return expirableCache.NewExpirableValueWithOffset(v, uint64(time.Hour))
}

func combineErrors(prevErr, supplementaryErr error) error {
	if prevErr == nil && supplementaryErr == nil {
		return nil
	}

	if supplementaryErr == nil {
		return prevErr
	}

	newErr := reComposeError(prevErr, supplementaryErr.Error())
	if codedErr, ok := supplementaryErr.(*Error); ok {
		return makeError(newErr, ErrorStatus(codedErr.Code()))
	}

	return newErr
}

// copyErrStatus copies the error status code from fromErr
// to toErr only if toErr and fromErr are both non-nil.
func copyErrStatusCode(toErr, fromErr error) error {
	if toErr == nil || fromErr == nil {
		return toErr
	}
	codedErr, hasCode := fromErr.(*Error)
	if hasCode {
		toErr = makeError(toErr, ErrorStatus(codedErr.Code()))
	}
	return toErr
}

func reComposeError(prevErr error, messages ...string) error {
	if len(messages) < 1 {
		return prevErr
	}

	joinedMessage := messages[0]
	for i, n := 1, len(messages); i < n; i++ {
		joinedMessage = fmt.Sprintf("%s\n%s", joinedMessage, messages[i])
	}

	if prevErr == nil {
		if len(joinedMessage) < 1 {
			return nil
		}
	} else {
		joinedMessage = fmt.Sprintf("%v\n%s", prevErr, joinedMessage)
	}

	err := errors.New(joinedMessage)
	codedErr, hasCode := prevErr.(*Error)
	if !hasCode {
		return err
	}

	return makeError(err, ErrorStatus(codedErr.Code()))
}

func CopyOptionsFromKeysIfNotSet(fromPtr, toPtr *Options, alreadySetKeys map[string]bool) {
	from := *fromPtr
	fromValue := reflect.ValueOf(from)
	toValue := reflect.ValueOf(toPtr).Elem()

	fromType := reflect.TypeOf(from)

	for i, n := 0, fromType.NumField(); i < n; i++ {
		fromFieldT := fromType.Field(i)
		fromTag := fromFieldT.Tag.Get("cli")

		_, alreadySet := alreadySetKeys[fromTag]
		if alreadySet {
			continue
		}

		fromFieldV := fromValue.Field(i)
		toFieldV := toValue.Field(i)

		if !toFieldV.CanSet() {
			continue
		}

		toFieldV.Set(fromFieldV)
	}
}

type CliSifter struct {
	From           interface{}
	Defaults       map[string]interface{}
	AlreadyDefined map[string]bool
}

func SiftCliTags(cs *CliSifter) string {
	from := cs.From
	defaults := cs.Defaults
	alreadyDefined := cs.AlreadyDefined

	fromValue := reflect.ValueOf(from)
	fromType := reflect.TypeOf(from)

	mapping := map[string]string{}

	for i, n := 0, fromType.NumField(); i < n; i++ {
		fromFieldT := fromType.Field(i)
		fromTag := fromFieldT.Tag.Get("json")

		if fromTag == "" {
			continue
		}

		fromFieldV := fromValue.Field(i)

		elem := fromFieldV.Elem()

		if _, defined := alreadyDefined[fromTag]; !defined {
			if retr, defaultSet := defaults[fromTag]; defaultSet {
				elem = reflect.ValueOf(retr)
			}
		}

		stringified := ""
		switch elem.Kind() {
		case reflect.String:
			stringified = fmt.Sprintf("%q", elem)
		case reflect.Invalid:
			continue
		default:
			stringified = fmt.Sprintf("%v", elem.Interface())
		}

		mapping[fromTag] = stringified
	}

	joined := []string{}

	for k, v := range mapping {
		joined = append(joined, fmt.Sprintf("%q:%v", k, v))
	}

	stringified := sepJoin(",", joined...)

	return fmt.Sprintf("{%v}", stringified)
}

func decrementTraversalDepth(d int) int {
	// Anything less than 0 is a request for infinite traversal
	// 0 is the minimum positive traversal
	if d <= 0 {
		return d
	}

	return d - 1
}

type fsListingArg struct {
	context *config.Context
	parent  string
	hidden  bool
	ignore  func(string) bool
	depth   int
}

func list(flArg *fsListingArg) (fileChan chan *File, err error) {
	context := flArg.context
	p := flArg.parent
	hidden := flArg.hidden
	ignore := flArg.ignore
	depth := flArg.depth

	absPath := context.AbsPathOf(p)
	var f []os.FileInfo
	f, err = ioutil.ReadDir(absPath)
	fileChan = make(chan *File)
	if err != nil {
		close(fileChan)
		return
	}

	go func() {
		defer close(fileChan)

		depth = decrementTraversalDepth(depth)
		if depth == 0 {
			return
		}

		for _, file := range f {
			fileName := file.Name()
			if fileName == config.GDDirSuffix {
				continue
			}
			if isHidden(fileName, hidden) {
				continue
			}

			resPath := path.Join(absPath, fileName)
			if anyMatch(ignore, fileName, resPath) {
				continue
			}

			// TODO: (@odeke-em) decide on how to deal with isFifo
			if namedPipe(file.Mode()) {
				fmt.Fprintf(os.Stderr, "%s (%s) is a named pipe, not reading from it\n", p, resPath)
				continue
			}

			if !symlink(file.Mode()) {
				fileChan <- NewLocalFile(resPath, file)
			} else {
				var symResolvPath string
				symResolvPath, err = filepath.EvalSymlinks(resPath)
				if err != nil {
					continue
				}

				if anyMatch(ignore, symResolvPath) {
					continue
				}

				var symInfo os.FileInfo
				symInfo, err = os.Stat(symResolvPath)
				if err != nil {
					continue
				}

				lf := NewLocalFile(symResolvPath, symInfo)
				// Retain the original name as appeared in
				// the manifest instead of the resolved one
				lf.Name = fileName
				fileChan <- lf
			}
		}
	}()
	return
}

func resolver(g *Commands, byId bool, sources []string, fileOp func(*File) interface{}) (kvChan chan *keyValue) {
	resolve := g.rem.FindByPath
	if byId {
		resolve = g.rem.FindById
	}

	kvChan = make(chan *keyValue)

	go func() {
		defer close(kvChan)

		for _, source := range sources {
			f, err := resolve(source)

			kv := keyValue{key: source, value: err}
			if err == nil {
				kv.value = fileOp(f)
			}

			kvChan <- &kv
		}
	}()

	return kvChan
}

func NotExist(err error) bool {
	return os.IsNotExist(err) || err == ErrPathNotExists
}

func localOpToChangerTranslator(g *Commands, c *Change) func(*Change, []string) error {
	var fn func(*Change, []string) error = nil

	op := c.Op()
	switch op {
	case OpMod:
		fn = g.localMod
	case OpModConflict:
		fn = g.localMod
	case OpAdd:
		fn = g.localAdd
	case OpDelete:
		fn = g.localDelete
	case OpIndexAddition:
		fn = g.localAddIndex
	}
	return fn
}

func remoteOpToChangerTranslator(g *Commands, c *Change) func(*Change) error {
	var fn func(*Change) error = nil

	op := c.Op()
	switch op {
	case OpMod:
		fn = g.remoteMod
	case OpModConflict:
		fn = g.remoteMod
	case OpAdd:
		fn = g.remoteAdd
	case OpDelete:
		fn = g.remoteTrash
	}
	return fn
}

const (
	// Since we'll be sharing `TypeMask` with other bit flags
	// we'll need to avoid collisions with other args
	InTrash int = 1 << (31 - 1 - iota)
	Folder
	Shared
	Owners
	Minimal
	Starred
	NonFolder
	DiskUsageOnly
	CurrentVersion
)

func folderExplicitly(mask int) bool    { return (mask & Folder) == Folder }
func nonFolderExplicitly(mask int) bool { return (mask & NonFolder) == NonFolder }

type driveFileFilter func(*File) bool

func makeFileFilter(mask int) driveFileFilter {
	return func(f *File) bool {
		// TODO: Decide if nil files should be either a pass or fail?
		if f == nil {
			return true
		}
		truths := []bool{}
		if nonFolderExplicitly(mask) {
			truths = append(truths, !f.IsDir)
		}
		// Even though regular file & folder are mutually exclusive let's
		// compare them separately in case we want this logical inconsistency
		// instead of an if...else clause
		if folderExplicitly(mask) {
			truths = append(truths, f.IsDir)
		}

		return allTruthsHold(truths...)
	}
}

func allTruthsHold(truths ...bool) bool {
	for _, truth := range truths {
		if !truth {
			return false
		}
	}
	return true
}

func parseDate(dateStr string, fmtSpecifiers ...string) (*time.Time, error) {
	var err error

	for _, fmtSpecifier := range fmtSpecifiers {
		var t time.Time
		t, err = time.Parse(fmtSpecifier, dateStr)
		if err == nil {
			return &t, err
		}
	}

	return nil, err
}

func parseDurationOffsetFromNow(durationOffsetStr string) (*time.Time, error) {
	d, err := time.ParseDuration(durationOffsetStr)
	if err != nil {
		return nil, err
	}

	offsetFromNow := time.Now().Add(d)
	return &offsetFromNow, nil
}

// Debug returns true if DRIVE_DEBUG is set in the environment.
// Set it to anything non-empty, for example `DRIVE_DEBUG=true`.
func Debug() bool {
	return os.Getenv("DRIVE_DEBUG") != ""
}

func DebugPrintf(fmt_ string, args ...interface{}) {
	FDebugPrintf(os.Stdout, fmt_, args...)
}

// FDebugPrintf will only print output to the out writer if
// environment variable `DRIVE_DEBUG` is set. It prints out a header
// on a newline containing the introspection of the callsite,
// and then the formatted message you'd like,
// appending an obligatory newline at the end.
// The output will be of the form:
// [<FILE>:<FUNCTION>:<LINE_NUMBER>]
// <MSG>\n
func FDebugPrintf(f io.Writer, fmt_ string, args ...interface{}) {
	if !Debug() {
		return
	}
	if f == nil {
		f = os.Stdout
	}

	programCounter, file, line, _ := runtime.Caller(2)
	fn := runtime.FuncForPC(programCounter)
	prefix := fmt.Sprintf("[\033[32m%s:%s:\033[33m%d\033[00m]\n%s\n", file, fn.Name(), line, fmt_)
	fmt.Fprintf(f, prefix, args...)
}

Zerion Mini Shell 1.0