%PDF- %PDF-
Direktori : /home/waritko/go/src/github.com/odeke-em/drive/config/ |
Current File : //home/waritko/go/src/github.com/odeke-em/drive/config/config.go |
// Copyright 2013 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 config import ( "encoding/json" "errors" "fmt" "io/ioutil" "os" "path" "path/filepath" "strings" "golang.org/x/oauth2/jwt" "github.com/boltdb/bolt" ) var ( GDDirSuffix = ".gd" PathSeparator = fmt.Sprintf("%c", os.PathSeparator) ErrNoDriveContext = errors.New("no drive context found; run `drive init` or go into one of the directories (sub directories) that you performed `drive init`") ErrDerefNilIndex = errors.New("cannot dereference a nil index") ErrDerefNilDB = errors.New("cannot dereference a nil db") ErrEmptyFileIdForIndex = errors.New("fileId for index must be non-empty") ErrNoSuchDbKey = errors.New("no such db key exists") ErrNoSuchDbBucket = errors.New("no such bucket exists") ) const ( IndicesKey = "indices" DriveDb = "drivedb" ) const ( O_RWForAll = 0666 ) type Context struct { GSAJWTConfig *jwt.Config `json:"gsa_jwt_config,omitempty"` ClientId string `json:"client_id"` ClientSecret string `json:"client_secret"` RefreshToken string `json:"refresh_token"` AbsPath string `json:"-"` } type Index struct { FileId string `json:"id"` Etag string `json:"etag"` Md5Checksum string `json:"md5"` MimeType string `json:"mtype"` ModTime int64 `json:"mtime"` Version int64 `json:"version"` IndexTime int64 `json:"itime"` } type MountPoint struct { CanClean bool Name string AbsPath string MountPath string } type Mount struct { CreatedMountDir string ShortestMountRoot string Points []*MountPoint } func byteify(s string) []byte { return []byte(s) } func (mpt *MountPoint) mounted() bool { // TODO: Find proper scheme for resolving symlinks return mpt.CanClean } func (mpt *MountPoint) Unmount() error { if mpt.mounted() { return os.RemoveAll(mpt.MountPath) } return nil } func (c *Context) AbsPathOf(fileOrDirPath string) string { return path.Join(c.AbsPath, fileOrDirPath) } func (c *Context) Cwd() string { cwd, _ := os.Getwd() return cwd } func (c *Context) Read() error { data, err := ioutil.ReadFile(credentialsPath(c.AbsPath)) if err != nil { return err } return json.Unmarshal(data, c) } func (c *Context) DeserializeIndex(key string) (*Index, error) { if creationErr := c.CreateIndicesBucket(); creationErr != nil { return nil, creationErr } db, err := c.OpenDB() if err != nil { return nil, err } defer db.Close() var data []byte err = db.View(func(tx *bolt.Tx) error { bucket := tx.Bucket(byteify(IndicesKey)) if bucket == nil { return ErrNoSuchDbBucket } retr := bucket.Get(byteify(key)) if len(retr) < 1 { return ErrNoSuchDbKey } data = retr return nil }) if err != nil { return nil, err } index := Index{} err = json.Unmarshal(data, &index) return &index, err } func (c *Context) ListKeys(dir, bucketName string) (chan string, error) { keysChan := make(chan string) if err := c.CreateIndicesBucket(); err != nil { close(keysChan) return keysChan, err } db, err := c.OpenDB() if err != nil { close(keysChan) return keysChan, err } go func() { defer func() { db.Close() close(keysChan) }() db.View(func(tx *bolt.Tx) error { bucket := tx.Bucket(byteify(bucketName)) if bucket == nil { return ErrNoSuchDbBucket } cur := bucket.Cursor() for key, _ := cur.First(); key != nil; key, _ = cur.Next() { keysChan <- string(key) } return nil }) }() return keysChan, nil } func (c *Context) PopIndicesKey(key string) error { return c.popDbKey(IndicesKey, key) } func (c *Context) popDbKey(bucketName, key string) error { db, err := c.OpenDB() if err != nil { return err } defer db.Close() return db.Update(func(tx *bolt.Tx) error { bucket, err := tx.CreateBucketIfNotExists(byteify(IndicesKey)) if err != nil { return err } if bucket == nil { return ErrNoSuchDbBucket } return bucket.Delete(byteify(key)) }) } func (c *Context) RemoveIndex(index *Index, p string) error { if index == nil { return ErrDerefNilIndex } if empty(index.FileId) { return ErrEmptyFileIdForIndex } db, err := c.OpenDB() if err != nil { return err } defer db.Close() return db.Update(func(tx *bolt.Tx) error { bucket, err := tx.CreateBucketIfNotExists(byteify(IndicesKey)) if err != nil { return err } if bucket == nil { return ErrNoSuchDbBucket } return bucket.Delete(byteify(index.FileId)) }) } func (c *Context) CreateIndicesBucket() error { db, err := c.OpenDB() if err != nil { return err } defer db.Close() return db.Update(func(tx *bolt.Tx) error { bucket, err := tx.CreateBucketIfNotExists(byteify(IndicesKey)) if err != nil { return err } if bucket == nil { return ErrNoSuchDbBucket } return nil }) } func (c *Context) SerializeIndex(index *Index) error { data, err := json.Marshal(index) if err != nil { return err } db, err := c.OpenDB() if err != nil { return err } defer db.Close() return db.Update(func(tx *bolt.Tx) error { bucket, err := tx.CreateBucketIfNotExists(byteify(IndicesKey)) if err != nil { return err } if bucket == nil { return ErrNoSuchDbBucket } return bucket.Put(byteify(index.FileId), data) }) } func (c *Context) Write() error { data, err := json.Marshal(c) if err != nil { return err } return ioutil.WriteFile(credentialsPath(c.AbsPath), data, 0600) } func (c *Context) DeInitialize(prompter func(...interface{}) bool, returnOnAnyError bool) error { rootDir := c.AbsPathOf("") pathsToRemove := []string{ credentialsPath(rootDir), DbSuffixedPath(rootDir), } for _, p := range pathsToRemove { if !prompter("remove: ", p, ". This operation is permanent (Y/N) ") { continue } rmErr := os.RemoveAll(p) if rmErr != nil { if returnOnAnyError { return rmErr } fmt.Fprintf(os.Stderr, "deinit.removeAll: %s %v\n", p, rmErr) } } return nil } func (c *Context) OpenDB() (*bolt.DB, error) { dbPath := DbSuffixedPath(c.AbsPathOf("")) db, err := bolt.Open(dbPath, O_RWForAll, nil) if err != nil { return db, err } if db == nil { return db, ErrDerefNilDB } return db, nil } // Discovers the gd directory, if no gd directory or credentials // could be found for the path, returns ErrNoContext. func Discover(currentAbsPath string) (*Context, error) { p := currentAbsPath found := false for { info, e := os.Stat(gdPath(p)) if e == nil && info.IsDir() { found = true break } newPath := filepath.Join(p, "..") if p == newPath { break } p = newPath } if !found { return nil, ErrNoDriveContext } context := &Context{AbsPath: p} if err := context.Read(); err != nil { return nil, err } return context, nil } func Initialize(absPath string) (pathGD string, firstInit bool, c *Context, err error) { pathGD = gdPath(absPath) sInfo, sErr := os.Stat(pathGD) if sErr != nil { if os.IsNotExist(sErr) { firstInit = true } else { // An err not related to path existence err = sErr return } } if sInfo != nil && !sInfo.IsDir() { err = fmt.Errorf("%s is not a directory", pathGD) return } if err = os.MkdirAll(pathGD, 0755); err != nil { return } c = &Context{AbsPath: absPath} err = c.Write() return } func gdPath(absPath string) string { return path.Join(absPath, GDDirSuffix) } func credentialsPath(absPath string) string { return path.Join(gdPath(absPath), "credentials.json") } func DbSuffixedPath(dir string) string { return path.Join(gdPath(dir), DriveDb) } func LeastNonExistantRoot(contextAbsPath string) string { last := "" p := contextAbsPath for p != "" { fInfo, _ := os.Stat(p) if fInfo != nil { break } last = p p, _ = filepath.Split(strings.TrimRight(p, PathSeparator)) } return last } func empty(p string) bool { return p == "" } func MountPoints(contextPath, contextAbsPath string, paths []string, hidden bool) ( mount *Mount, sources []string) { createdMountDir := false shortestMountRoot := "" _, err := os.Stat(contextAbsPath) if err != nil { if !os.IsNotExist(err) { return } if sRoot := LeastNonExistantRoot(contextAbsPath); sRoot != "" { shortestMountRoot = sRoot // Link traversal is already implemented, no // need to append the shortestMountRoot if false { sources = append(sources, sRoot) } } if err := os.MkdirAll(contextAbsPath, os.ModeDir|0755); err != nil { fmt.Fprintf(os.Stderr, "mountpoint: %v\n", err) return } createdMountDir = true } var mtPoints []*MountPoint visitors := map[string]bool{} for _, path := range paths { _, visited := visitors[path] if visited { continue } visitors[path] = true localinfo, err := os.Stat(path) if err != nil || localinfo == nil { continue } base := filepath.Base(path) if !hidden && strings.HasPrefix(base, ".") { continue } canClean := true mountPath := filepath.Join(contextAbsPath, base) err = os.Symlink(path, mountPath) if err != nil { if !os.IsExist(err) { continue } // This is an old symlink probably due to a name clash. // TODO: Due to the name clash, find a good name for this symlink. canClean = false } var relPath = "" if contextPath == "" { relPath = strings.Join([]string{"", base}, "/") } else { relPath = strings.Join([]string{"", contextPath, base}, "/") } mtPoints = append(mtPoints, &MountPoint{ AbsPath: path, CanClean: canClean, MountPath: mountPath, Name: relPath, }) } if len(mtPoints) >= 1 { mount = &Mount{ Points: mtPoints, } if createdMountDir { mount.CreatedMountDir = contextAbsPath mount.ShortestMountRoot = shortestMountRoot } } return }