github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/backend/cache/storage_persistent.go (about)

     1  // +build !plan9
     2  
     3  package cache
     4  
     5  import (
     6  	"bytes"
     7  	"context"
     8  	"encoding/binary"
     9  	"encoding/json"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"os"
    13  	"path"
    14  	"strconv"
    15  	"strings"
    16  	"sync"
    17  	"time"
    18  
    19  	bolt "github.com/etcd-io/bbolt"
    20  	"github.com/pkg/errors"
    21  	"github.com/rclone/rclone/fs"
    22  	"github.com/rclone/rclone/fs/walk"
    23  )
    24  
    25  // Constants
    26  const (
    27  	RootBucket   = "root"
    28  	RootTsBucket = "rootTs"
    29  	DataTsBucket = "dataTs"
    30  	tempBucket   = "pending"
    31  )
    32  
    33  // Features flags for this storage type
    34  type Features struct {
    35  	PurgeDb    bool          // purge the db before starting
    36  	DbWaitTime time.Duration // time to wait for DB to be available
    37  }
    38  
    39  var boltMap = make(map[string]*Persistent)
    40  var boltMapMx sync.Mutex
    41  
    42  // GetPersistent returns a single instance for the specific store
    43  func GetPersistent(dbPath, chunkPath string, f *Features) (*Persistent, error) {
    44  	// write lock to create one
    45  	boltMapMx.Lock()
    46  	defer boltMapMx.Unlock()
    47  	if b, ok := boltMap[dbPath]; ok {
    48  		if !b.open {
    49  			err := b.connect()
    50  			if err != nil {
    51  				return nil, err
    52  			}
    53  		}
    54  		return b, nil
    55  	}
    56  
    57  	bb, err := newPersistent(dbPath, chunkPath, f)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	boltMap[dbPath] = bb
    62  	return boltMap[dbPath], nil
    63  }
    64  
    65  type chunkInfo struct {
    66  	Path   string
    67  	Offset int64
    68  	Size   int64
    69  }
    70  
    71  type tempUploadInfo struct {
    72  	DestPath string
    73  	AddedOn  time.Time
    74  	Started  bool
    75  }
    76  
    77  // String representation of a tempUploadInfo
    78  func (t *tempUploadInfo) String() string {
    79  	return fmt.Sprintf("%v - %v (%v)", t.DestPath, t.Started, t.AddedOn)
    80  }
    81  
    82  // Persistent is a wrapper of persistent storage for a bolt.DB file
    83  type Persistent struct {
    84  	dbPath       string
    85  	dataPath     string
    86  	open         bool
    87  	db           *bolt.DB
    88  	cleanupMux   sync.Mutex
    89  	tempQueueMux sync.Mutex
    90  	features     *Features
    91  }
    92  
    93  // newPersistent builds a new wrapper and connects to the bolt.DB file
    94  func newPersistent(dbPath, chunkPath string, f *Features) (*Persistent, error) {
    95  	b := &Persistent{
    96  		dbPath:   dbPath,
    97  		dataPath: chunkPath,
    98  		features: f,
    99  	}
   100  
   101  	err := b.connect()
   102  	if err != nil {
   103  		fs.Errorf(dbPath, "Error opening storage cache. Is there another rclone running on the same remote? %v", err)
   104  		return nil, err
   105  	}
   106  
   107  	return b, nil
   108  }
   109  
   110  // String will return a human friendly string for this DB (currently the dbPath)
   111  func (b *Persistent) String() string {
   112  	return "<Cache DB> " + b.dbPath
   113  }
   114  
   115  // connect creates a connection to the configured file
   116  // refreshDb will delete the file before to create an empty DB if it's set to true
   117  func (b *Persistent) connect() error {
   118  	var err error
   119  
   120  	err = os.MkdirAll(b.dataPath, os.ModePerm)
   121  	if err != nil {
   122  		return errors.Wrapf(err, "failed to create a data directory %q", b.dataPath)
   123  	}
   124  	b.db, err = bolt.Open(b.dbPath, 0644, &bolt.Options{Timeout: b.features.DbWaitTime})
   125  	if err != nil {
   126  		return errors.Wrapf(err, "failed to open a cache connection to %q", b.dbPath)
   127  	}
   128  	if b.features.PurgeDb {
   129  		b.Purge()
   130  	}
   131  	_ = b.db.Update(func(tx *bolt.Tx) error {
   132  		_, _ = tx.CreateBucketIfNotExists([]byte(RootBucket))
   133  		_, _ = tx.CreateBucketIfNotExists([]byte(RootTsBucket))
   134  		_, _ = tx.CreateBucketIfNotExists([]byte(DataTsBucket))
   135  		_, _ = tx.CreateBucketIfNotExists([]byte(tempBucket))
   136  
   137  		return nil
   138  	})
   139  
   140  	b.open = true
   141  	return nil
   142  }
   143  
   144  // getBucket prepares and cleans a specific path of the form: /var/tmp and will iterate through each path component
   145  // to get to the nested bucket of the final part (in this example: tmp)
   146  func (b *Persistent) getBucket(dir string, createIfMissing bool, tx *bolt.Tx) *bolt.Bucket {
   147  	cleanPath(dir)
   148  
   149  	entries := strings.FieldsFunc(dir, func(c rune) bool {
   150  		// cover Windows where rclone still uses '/' as path separator
   151  		// this should be safe as '/' is not a valid Windows character
   152  		return (os.PathSeparator == c || c == rune('/'))
   153  	})
   154  	bucket := tx.Bucket([]byte(RootBucket))
   155  
   156  	for _, entry := range entries {
   157  		if createIfMissing {
   158  			bucket, _ = bucket.CreateBucketIfNotExists([]byte(entry))
   159  		} else {
   160  			bucket = bucket.Bucket([]byte(entry))
   161  		}
   162  
   163  		if bucket == nil {
   164  			return nil
   165  		}
   166  	}
   167  
   168  	return bucket
   169  }
   170  
   171  // GetDir will retrieve data of a cached directory
   172  func (b *Persistent) GetDir(remote string) (*Directory, error) {
   173  	cd := &Directory{}
   174  
   175  	err := b.db.View(func(tx *bolt.Tx) error {
   176  		bucket := b.getBucket(remote, false, tx)
   177  		if bucket == nil {
   178  			return errors.Errorf("couldn't open bucket (%v)", remote)
   179  		}
   180  
   181  		data := bucket.Get([]byte("."))
   182  		if data != nil {
   183  			return json.Unmarshal(data, cd)
   184  		}
   185  
   186  		return errors.Errorf("%v not found", remote)
   187  	})
   188  
   189  	return cd, err
   190  }
   191  
   192  // AddDir will update a CachedDirectory metadata and all its entries
   193  func (b *Persistent) AddDir(cachedDir *Directory) error {
   194  	return b.AddBatchDir([]*Directory{cachedDir})
   195  }
   196  
   197  // AddBatchDir will update a list of CachedDirectory metadata and all their entries
   198  func (b *Persistent) AddBatchDir(cachedDirs []*Directory) error {
   199  	if len(cachedDirs) == 0 {
   200  		return nil
   201  	}
   202  
   203  	return b.db.Update(func(tx *bolt.Tx) error {
   204  		var bucket *bolt.Bucket
   205  		if cachedDirs[0].Dir == "" {
   206  			bucket = tx.Bucket([]byte(RootBucket))
   207  		} else {
   208  			bucket = b.getBucket(cachedDirs[0].Dir, true, tx)
   209  		}
   210  		if bucket == nil {
   211  			return errors.Errorf("couldn't open bucket (%v)", cachedDirs[0].Dir)
   212  		}
   213  
   214  		for _, cachedDir := range cachedDirs {
   215  			var b *bolt.Bucket
   216  			var err error
   217  			if cachedDir.Name == "" {
   218  				b = bucket
   219  			} else {
   220  				b, err = bucket.CreateBucketIfNotExists([]byte(cachedDir.Name))
   221  			}
   222  			if err != nil {
   223  				return err
   224  			}
   225  
   226  			encoded, err := json.Marshal(cachedDir)
   227  			if err != nil {
   228  				return errors.Errorf("couldn't marshal object (%v): %v", cachedDir, err)
   229  			}
   230  			err = b.Put([]byte("."), encoded)
   231  			if err != nil {
   232  				return err
   233  			}
   234  		}
   235  		return nil
   236  	})
   237  }
   238  
   239  // GetDirEntries will return a CachedDirectory, its list of dir entries and/or an error if it encountered issues
   240  func (b *Persistent) GetDirEntries(cachedDir *Directory) (fs.DirEntries, error) {
   241  	var dirEntries fs.DirEntries
   242  
   243  	err := b.db.View(func(tx *bolt.Tx) error {
   244  		bucket := b.getBucket(cachedDir.abs(), false, tx)
   245  		if bucket == nil {
   246  			return errors.Errorf("couldn't open bucket (%v)", cachedDir.abs())
   247  		}
   248  
   249  		val := bucket.Get([]byte("."))
   250  		if val != nil {
   251  			err := json.Unmarshal(val, cachedDir)
   252  			if err != nil {
   253  				return errors.Errorf("error during unmarshalling obj: %v", err)
   254  			}
   255  		} else {
   256  			return errors.Errorf("missing cached dir: %v", cachedDir)
   257  		}
   258  
   259  		c := bucket.Cursor()
   260  		for k, v := c.First(); k != nil; k, v = c.Next() {
   261  			// ignore metadata key: .
   262  			if bytes.Equal(k, []byte(".")) {
   263  				continue
   264  			}
   265  			entryPath := path.Join(cachedDir.Remote(), string(k))
   266  
   267  			if v == nil { // directory
   268  				// we try to find a cached meta for the dir
   269  				currentBucket := c.Bucket().Bucket(k)
   270  				if currentBucket == nil {
   271  					return errors.Errorf("couldn't open bucket (%v)", string(k))
   272  				}
   273  
   274  				metaKey := currentBucket.Get([]byte("."))
   275  				d := NewDirectory(cachedDir.CacheFs, entryPath)
   276  				if metaKey != nil { //if we don't find it, we create an empty dir
   277  					err := json.Unmarshal(metaKey, d)
   278  					if err != nil { // if even this fails, we fallback to an empty dir
   279  						fs.Debugf(string(k), "error during unmarshalling obj: %v", err)
   280  					}
   281  				}
   282  
   283  				dirEntries = append(dirEntries, d)
   284  			} else { // object
   285  				o := NewObject(cachedDir.CacheFs, entryPath)
   286  				err := json.Unmarshal(v, o)
   287  				if err != nil {
   288  					fs.Debugf(string(k), "error during unmarshalling obj: %v", err)
   289  					continue
   290  				}
   291  
   292  				dirEntries = append(dirEntries, o)
   293  			}
   294  		}
   295  
   296  		return nil
   297  	})
   298  
   299  	return dirEntries, err
   300  }
   301  
   302  // RemoveDir will delete a CachedDirectory, all its objects and all the chunks stored for it
   303  func (b *Persistent) RemoveDir(fp string) error {
   304  	var err error
   305  	parentDir, dirName := path.Split(fp)
   306  	if fp == "" {
   307  		err = b.db.Update(func(tx *bolt.Tx) error {
   308  			err := tx.DeleteBucket([]byte(RootBucket))
   309  			if err != nil {
   310  				fs.Debugf(fp, "couldn't delete from cache: %v", err)
   311  				return err
   312  			}
   313  			_, _ = tx.CreateBucketIfNotExists([]byte(RootBucket))
   314  			return nil
   315  		})
   316  	} else {
   317  		err = b.db.Update(func(tx *bolt.Tx) error {
   318  			bucket := b.getBucket(cleanPath(parentDir), false, tx)
   319  			if bucket == nil {
   320  				return errors.Errorf("couldn't open bucket (%v)", fp)
   321  			}
   322  			// delete the cached dir
   323  			err := bucket.DeleteBucket([]byte(cleanPath(dirName)))
   324  			if err != nil {
   325  				fs.Debugf(fp, "couldn't delete from cache: %v", err)
   326  			}
   327  			return nil
   328  		})
   329  	}
   330  
   331  	// delete chunks on disk
   332  	// safe to ignore as the files might not have been open
   333  	if err == nil {
   334  		_ = os.RemoveAll(path.Join(b.dataPath, fp))
   335  		_ = os.MkdirAll(b.dataPath, os.ModePerm)
   336  	}
   337  
   338  	return err
   339  }
   340  
   341  // ExpireDir will flush a CachedDirectory and all its objects from the objects
   342  // chunks will remain as they are
   343  func (b *Persistent) ExpireDir(cd *Directory) error {
   344  	t := time.Now().Add(time.Duration(-cd.CacheFs.opt.InfoAge))
   345  	cd.CacheTs = &t
   346  
   347  	// expire all parents
   348  	return b.db.Update(func(tx *bolt.Tx) error {
   349  		// expire all the parents
   350  		currentDir := cd.abs()
   351  		for { // until we get to the root
   352  			bucket := b.getBucket(currentDir, false, tx)
   353  			if bucket != nil {
   354  				val := bucket.Get([]byte("."))
   355  				if val != nil {
   356  					cd2 := &Directory{CacheFs: cd.CacheFs}
   357  					err := json.Unmarshal(val, cd2)
   358  					if err == nil {
   359  						fs.Debugf(cd, "cache: expired %v", currentDir)
   360  						cd2.CacheTs = &t
   361  						enc2, _ := json.Marshal(cd2)
   362  						_ = bucket.Put([]byte("."), enc2)
   363  					}
   364  				}
   365  			}
   366  			if currentDir == "" {
   367  				break
   368  			}
   369  			currentDir = cleanPath(path.Dir(currentDir))
   370  		}
   371  		return nil
   372  	})
   373  }
   374  
   375  // GetObject will return a CachedObject from its parent directory or an error if it doesn't find it
   376  func (b *Persistent) GetObject(cachedObject *Object) (err error) {
   377  	return b.db.View(func(tx *bolt.Tx) error {
   378  		bucket := b.getBucket(cachedObject.Dir, false, tx)
   379  		if bucket == nil {
   380  			return errors.Errorf("couldn't open parent bucket for %v", cachedObject.Dir)
   381  		}
   382  		val := bucket.Get([]byte(cachedObject.Name))
   383  		if val != nil {
   384  			return json.Unmarshal(val, cachedObject)
   385  		}
   386  		return errors.Errorf("couldn't find object (%v)", cachedObject.Name)
   387  	})
   388  }
   389  
   390  // AddObject will create a cached object in its parent directory
   391  func (b *Persistent) AddObject(cachedObject *Object) error {
   392  	return b.db.Update(func(tx *bolt.Tx) error {
   393  		bucket := b.getBucket(cachedObject.Dir, true, tx)
   394  		if bucket == nil {
   395  			return errors.Errorf("couldn't open parent bucket for %v", cachedObject)
   396  		}
   397  		// cache Object Info
   398  		encoded, err := json.Marshal(cachedObject)
   399  		if err != nil {
   400  			return errors.Errorf("couldn't marshal object (%v) info: %v", cachedObject, err)
   401  		}
   402  		err = bucket.Put([]byte(cachedObject.Name), encoded)
   403  		if err != nil {
   404  			return errors.Errorf("couldn't cache object (%v) info: %v", cachedObject, err)
   405  		}
   406  		return nil
   407  	})
   408  }
   409  
   410  // RemoveObject will delete a single cached object and all the chunks which belong to it
   411  func (b *Persistent) RemoveObject(fp string) error {
   412  	parentDir, objName := path.Split(fp)
   413  	return b.db.Update(func(tx *bolt.Tx) error {
   414  		bucket := b.getBucket(cleanPath(parentDir), false, tx)
   415  		if bucket == nil {
   416  			return errors.Errorf("couldn't open parent bucket for %v", cleanPath(parentDir))
   417  		}
   418  		err := bucket.Delete([]byte(cleanPath(objName)))
   419  		if err != nil {
   420  			fs.Debugf(fp, "couldn't delete obj from storage: %v", err)
   421  		}
   422  		// delete chunks on disk
   423  		// safe to ignore as the file might not have been open
   424  		_ = os.RemoveAll(path.Join(b.dataPath, fp))
   425  		return nil
   426  	})
   427  }
   428  
   429  // ExpireObject will flush an Object and all its data if desired
   430  func (b *Persistent) ExpireObject(co *Object, withData bool) error {
   431  	co.CacheTs = time.Now().Add(time.Duration(-co.CacheFs.opt.InfoAge))
   432  	err := b.AddObject(co)
   433  	if withData {
   434  		_ = os.RemoveAll(path.Join(b.dataPath, co.abs()))
   435  	}
   436  	return err
   437  }
   438  
   439  // HasEntry confirms the existence of a single entry (dir or object)
   440  func (b *Persistent) HasEntry(remote string) bool {
   441  	dir, name := path.Split(remote)
   442  	dir = cleanPath(dir)
   443  	name = cleanPath(name)
   444  
   445  	err := b.db.View(func(tx *bolt.Tx) error {
   446  		bucket := b.getBucket(dir, false, tx)
   447  		if bucket == nil {
   448  			return errors.Errorf("couldn't open parent bucket for %v", remote)
   449  		}
   450  		if f := bucket.Bucket([]byte(name)); f != nil {
   451  			return nil
   452  		}
   453  		if f := bucket.Get([]byte(name)); f != nil {
   454  			return nil
   455  		}
   456  
   457  		return errors.Errorf("couldn't find object (%v)", remote)
   458  	})
   459  	if err == nil {
   460  		return true
   461  	}
   462  	return false
   463  }
   464  
   465  // HasChunk confirms the existence of a single chunk of an object
   466  func (b *Persistent) HasChunk(cachedObject *Object, offset int64) bool {
   467  	fp := path.Join(b.dataPath, cachedObject.abs(), strconv.FormatInt(offset, 10))
   468  	if _, err := os.Stat(fp); !os.IsNotExist(err) {
   469  		return true
   470  	}
   471  	return false
   472  }
   473  
   474  // GetChunk will retrieve a single chunk which belongs to a cached object or an error if it doesn't find it
   475  func (b *Persistent) GetChunk(cachedObject *Object, offset int64) ([]byte, error) {
   476  	var data []byte
   477  
   478  	fp := path.Join(b.dataPath, cachedObject.abs(), strconv.FormatInt(offset, 10))
   479  	data, err := ioutil.ReadFile(fp)
   480  	if err != nil {
   481  		return nil, err
   482  	}
   483  
   484  	return data, err
   485  }
   486  
   487  // AddChunk adds a new chunk of a cached object
   488  func (b *Persistent) AddChunk(fp string, data []byte, offset int64) error {
   489  	_ = os.MkdirAll(path.Join(b.dataPath, fp), os.ModePerm)
   490  
   491  	filePath := path.Join(b.dataPath, fp, strconv.FormatInt(offset, 10))
   492  	err := ioutil.WriteFile(filePath, data, os.ModePerm)
   493  	if err != nil {
   494  		return err
   495  	}
   496  
   497  	return b.db.Update(func(tx *bolt.Tx) error {
   498  		tsBucket := tx.Bucket([]byte(DataTsBucket))
   499  		ts := time.Now()
   500  		found := false
   501  
   502  		// delete (older) timestamps for the same object
   503  		c := tsBucket.Cursor()
   504  		for k, v := c.First(); k != nil; k, v = c.Next() {
   505  			var ci chunkInfo
   506  			err = json.Unmarshal(v, &ci)
   507  			if err != nil {
   508  				continue
   509  			}
   510  			if ci.Path == fp && ci.Offset == offset {
   511  				if tsInCache := time.Unix(0, btoi(k)); tsInCache.After(ts) && !found {
   512  					found = true
   513  					continue
   514  				}
   515  				err := c.Delete()
   516  				if err != nil {
   517  					fs.Debugf(fp, "failed to clean chunk: %v", err)
   518  				}
   519  			}
   520  		}
   521  		// don't overwrite if a newer one is already there
   522  		if found {
   523  			return nil
   524  		}
   525  		enc, err := json.Marshal(chunkInfo{Path: fp, Offset: offset, Size: int64(len(data))})
   526  		if err != nil {
   527  			fs.Debugf(fp, "failed to timestamp chunk: %v", err)
   528  		}
   529  		err = tsBucket.Put(itob(ts.UnixNano()), enc)
   530  		if err != nil {
   531  			fs.Debugf(fp, "failed to timestamp chunk: %v", err)
   532  		}
   533  		return nil
   534  	})
   535  }
   536  
   537  // CleanChunksByAge will cleanup on a cron basis
   538  func (b *Persistent) CleanChunksByAge(chunkAge time.Duration) {
   539  	// NOOP
   540  }
   541  
   542  // CleanChunksByNeed is a noop for this implementation
   543  func (b *Persistent) CleanChunksByNeed(offset int64) {
   544  	// noop: we want to clean a Bolt DB by time only
   545  }
   546  
   547  // CleanChunksBySize will cleanup chunks after the total size passes a certain point
   548  func (b *Persistent) CleanChunksBySize(maxSize int64) {
   549  	b.cleanupMux.Lock()
   550  	defer b.cleanupMux.Unlock()
   551  	var cntChunks int
   552  	var roughlyCleaned fs.SizeSuffix
   553  
   554  	err := b.db.Update(func(tx *bolt.Tx) error {
   555  		dataTsBucket := tx.Bucket([]byte(DataTsBucket))
   556  		if dataTsBucket == nil {
   557  			return errors.Errorf("Couldn't open (%v) bucket", DataTsBucket)
   558  		}
   559  		// iterate through ts
   560  		c := dataTsBucket.Cursor()
   561  		totalSize := int64(0)
   562  		for k, v := c.First(); k != nil; k, v = c.Next() {
   563  			var ci chunkInfo
   564  			err := json.Unmarshal(v, &ci)
   565  			if err != nil {
   566  				continue
   567  			}
   568  
   569  			totalSize += ci.Size
   570  		}
   571  
   572  		if totalSize > maxSize {
   573  			needToClean := totalSize - maxSize
   574  			roughlyCleaned = fs.SizeSuffix(needToClean)
   575  			for k, v := c.First(); k != nil; k, v = c.Next() {
   576  				var ci chunkInfo
   577  				err := json.Unmarshal(v, &ci)
   578  				if err != nil {
   579  					continue
   580  				}
   581  				// delete this ts entry
   582  				err = c.Delete()
   583  				if err != nil {
   584  					fs.Errorf(ci.Path, "failed deleting chunk ts during cleanup (%v): %v", ci.Offset, err)
   585  					continue
   586  				}
   587  				err = os.Remove(path.Join(b.dataPath, ci.Path, strconv.FormatInt(ci.Offset, 10)))
   588  				if err == nil {
   589  					cntChunks++
   590  					needToClean -= ci.Size
   591  					if needToClean <= 0 {
   592  						break
   593  					}
   594  				}
   595  			}
   596  		}
   597  		if cntChunks > 0 {
   598  			fs.Infof("cache-cleanup", "chunks %v, est. size: %v", cntChunks, roughlyCleaned.String())
   599  
   600  		}
   601  		return nil
   602  	})
   603  
   604  	if err != nil {
   605  		if err == bolt.ErrDatabaseNotOpen {
   606  			// we're likely a late janitor and we need to end quietly as there's no guarantee of what exists anymore
   607  			return
   608  		}
   609  		fs.Errorf("cache", "cleanup failed: %v", err)
   610  	}
   611  }
   612  
   613  // Stats returns a go map with the stats key values
   614  func (b *Persistent) Stats() (map[string]map[string]interface{}, error) {
   615  	r := make(map[string]map[string]interface{})
   616  	r["data"] = make(map[string]interface{})
   617  	r["data"]["oldest-ts"] = time.Now()
   618  	r["data"]["oldest-file"] = ""
   619  	r["data"]["newest-ts"] = time.Now()
   620  	r["data"]["newest-file"] = ""
   621  	r["data"]["total-chunks"] = 0
   622  	r["data"]["total-size"] = int64(0)
   623  	r["files"] = make(map[string]interface{})
   624  	r["files"]["oldest-ts"] = time.Now()
   625  	r["files"]["oldest-name"] = ""
   626  	r["files"]["newest-ts"] = time.Now()
   627  	r["files"]["newest-name"] = ""
   628  	r["files"]["total-files"] = 0
   629  
   630  	_ = b.db.View(func(tx *bolt.Tx) error {
   631  		dataTsBucket := tx.Bucket([]byte(DataTsBucket))
   632  		rootTsBucket := tx.Bucket([]byte(RootTsBucket))
   633  
   634  		var totalDirs int
   635  		var totalFiles int
   636  		_ = b.iterateBuckets(tx.Bucket([]byte(RootBucket)), func(name string) {
   637  			totalDirs++
   638  		}, func(key string, val []byte) {
   639  			totalFiles++
   640  		})
   641  		r["files"]["total-dir"] = totalDirs
   642  		r["files"]["total-files"] = totalFiles
   643  
   644  		c := dataTsBucket.Cursor()
   645  
   646  		totalChunks := 0
   647  		totalSize := int64(0)
   648  		for k, v := c.First(); k != nil; k, v = c.Next() {
   649  			var ci chunkInfo
   650  			err := json.Unmarshal(v, &ci)
   651  			if err != nil {
   652  				continue
   653  			}
   654  			totalChunks++
   655  			totalSize += ci.Size
   656  		}
   657  		r["data"]["total-chunks"] = totalChunks
   658  		r["data"]["total-size"] = totalSize
   659  
   660  		if k, v := c.First(); k != nil {
   661  			var ci chunkInfo
   662  			_ = json.Unmarshal(v, &ci)
   663  			r["data"]["oldest-ts"] = time.Unix(0, btoi(k))
   664  			r["data"]["oldest-file"] = ci.Path
   665  		}
   666  		if k, v := c.Last(); k != nil {
   667  			var ci chunkInfo
   668  			_ = json.Unmarshal(v, &ci)
   669  			r["data"]["newest-ts"] = time.Unix(0, btoi(k))
   670  			r["data"]["newest-file"] = ci.Path
   671  		}
   672  
   673  		c = rootTsBucket.Cursor()
   674  		if k, v := c.First(); k != nil {
   675  			// split to get (abs path - offset)
   676  			r["files"]["oldest-ts"] = time.Unix(0, btoi(k))
   677  			r["files"]["oldest-name"] = string(v)
   678  		}
   679  		if k, v := c.Last(); k != nil {
   680  			r["files"]["newest-ts"] = time.Unix(0, btoi(k))
   681  			r["files"]["newest-name"] = string(v)
   682  		}
   683  
   684  		return nil
   685  	})
   686  
   687  	return r, nil
   688  }
   689  
   690  // Purge will flush the entire cache
   691  func (b *Persistent) Purge() {
   692  	b.cleanupMux.Lock()
   693  	defer b.cleanupMux.Unlock()
   694  
   695  	_ = b.db.Update(func(tx *bolt.Tx) error {
   696  		_ = tx.DeleteBucket([]byte(RootBucket))
   697  		_ = tx.DeleteBucket([]byte(RootTsBucket))
   698  		_ = tx.DeleteBucket([]byte(DataTsBucket))
   699  
   700  		_, _ = tx.CreateBucketIfNotExists([]byte(RootBucket))
   701  		_, _ = tx.CreateBucketIfNotExists([]byte(RootTsBucket))
   702  		_, _ = tx.CreateBucketIfNotExists([]byte(DataTsBucket))
   703  
   704  		return nil
   705  	})
   706  
   707  	err := os.RemoveAll(b.dataPath)
   708  	if err != nil {
   709  		fs.Errorf(b, "issue removing data folder: %v", err)
   710  	}
   711  	err = os.MkdirAll(b.dataPath, os.ModePerm)
   712  	if err != nil {
   713  		fs.Errorf(b, "issue removing data folder: %v", err)
   714  	}
   715  }
   716  
   717  // GetChunkTs retrieves the current timestamp of this chunk
   718  func (b *Persistent) GetChunkTs(path string, offset int64) (time.Time, error) {
   719  	var t time.Time
   720  
   721  	err := b.db.View(func(tx *bolt.Tx) error {
   722  		tsBucket := tx.Bucket([]byte(DataTsBucket))
   723  		c := tsBucket.Cursor()
   724  		for k, v := c.First(); k != nil; k, v = c.Next() {
   725  			var ci chunkInfo
   726  			err := json.Unmarshal(v, &ci)
   727  			if err != nil {
   728  				continue
   729  			}
   730  			if ci.Path == path && ci.Offset == offset {
   731  				t = time.Unix(0, btoi(k))
   732  				return nil
   733  			}
   734  		}
   735  		return errors.Errorf("not found %v-%v", path, offset)
   736  	})
   737  
   738  	return t, err
   739  }
   740  
   741  func (b *Persistent) iterateBuckets(buk *bolt.Bucket, bucketFn func(name string), kvFn func(key string, val []byte)) error {
   742  	err := b.db.View(func(tx *bolt.Tx) error {
   743  		var c *bolt.Cursor
   744  		if buk == nil {
   745  			c = tx.Cursor()
   746  		} else {
   747  			c = buk.Cursor()
   748  		}
   749  		for k, v := c.First(); k != nil; k, v = c.Next() {
   750  			if v == nil {
   751  				var buk2 *bolt.Bucket
   752  				if buk == nil {
   753  					buk2 = tx.Bucket(k)
   754  				} else {
   755  					buk2 = buk.Bucket(k)
   756  				}
   757  
   758  				bucketFn(string(k))
   759  				_ = b.iterateBuckets(buk2, bucketFn, kvFn)
   760  			} else {
   761  				kvFn(string(k), v)
   762  			}
   763  		}
   764  		return nil
   765  	})
   766  
   767  	return err
   768  }
   769  
   770  func (b *Persistent) dumpRoot() string {
   771  	var itBuckets func(buk *bolt.Bucket) map[string]interface{}
   772  
   773  	itBuckets = func(buk *bolt.Bucket) map[string]interface{} {
   774  		m := make(map[string]interface{})
   775  		c := buk.Cursor()
   776  		for k, v := c.First(); k != nil; k, v = c.Next() {
   777  			if v == nil {
   778  				buk2 := buk.Bucket(k)
   779  				m[string(k)] = itBuckets(buk2)
   780  			} else {
   781  				m[string(k)] = "-"
   782  			}
   783  		}
   784  		return m
   785  	}
   786  	var mm map[string]interface{}
   787  	_ = b.db.View(func(tx *bolt.Tx) error {
   788  		mm = itBuckets(tx.Bucket([]byte(RootBucket)))
   789  		return nil
   790  	})
   791  	raw, _ := json.MarshalIndent(mm, "", "  ")
   792  	return string(raw)
   793  }
   794  
   795  // addPendingUpload adds a new file to the pending queue of uploads
   796  func (b *Persistent) addPendingUpload(destPath string, started bool) error {
   797  	return b.db.Update(func(tx *bolt.Tx) error {
   798  		bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket))
   799  		if err != nil {
   800  			return errors.Errorf("couldn't bucket for %v", tempBucket)
   801  		}
   802  		tempObj := &tempUploadInfo{
   803  			DestPath: destPath,
   804  			AddedOn:  time.Now(),
   805  			Started:  started,
   806  		}
   807  
   808  		// cache Object Info
   809  		encoded, err := json.Marshal(tempObj)
   810  		if err != nil {
   811  			return errors.Errorf("couldn't marshal object (%v) info: %v", destPath, err)
   812  		}
   813  		err = bucket.Put([]byte(destPath), encoded)
   814  		if err != nil {
   815  			return errors.Errorf("couldn't cache object (%v) info: %v", destPath, err)
   816  		}
   817  
   818  		return nil
   819  	})
   820  }
   821  
   822  // getPendingUpload returns the next file from the pending queue of uploads
   823  func (b *Persistent) getPendingUpload(inRoot string, waitTime time.Duration) (destPath string, err error) {
   824  	b.tempQueueMux.Lock()
   825  	defer b.tempQueueMux.Unlock()
   826  
   827  	err = b.db.Update(func(tx *bolt.Tx) error {
   828  		bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket))
   829  		if err != nil {
   830  			return errors.Errorf("couldn't bucket for %v", tempBucket)
   831  		}
   832  
   833  		c := bucket.Cursor()
   834  		for k, v := c.Seek([]byte(inRoot)); k != nil && bytes.HasPrefix(k, []byte(inRoot)); k, v = c.Next() {
   835  			//for k, v := c.First(); k != nil; k, v = c.Next() {
   836  			var tempObj = &tempUploadInfo{}
   837  			err = json.Unmarshal(v, tempObj)
   838  			if err != nil {
   839  				fs.Errorf(b, "failed to read pending upload: %v", err)
   840  				continue
   841  			}
   842  			// skip over started uploads
   843  			if tempObj.Started || time.Now().Before(tempObj.AddedOn.Add(waitTime)) {
   844  				continue
   845  			}
   846  
   847  			tempObj.Started = true
   848  			v2, err := json.Marshal(tempObj)
   849  			if err != nil {
   850  				fs.Errorf(b, "failed to update pending upload: %v", err)
   851  				continue
   852  			}
   853  			err = bucket.Put(k, v2)
   854  			if err != nil {
   855  				fs.Errorf(b, "failed to update pending upload: %v", err)
   856  				continue
   857  			}
   858  
   859  			destPath = tempObj.DestPath
   860  			return nil
   861  		}
   862  
   863  		return errors.Errorf("no pending upload found")
   864  	})
   865  
   866  	return destPath, err
   867  }
   868  
   869  // SearchPendingUpload returns the file info from the pending queue of uploads
   870  func (b *Persistent) SearchPendingUpload(remote string) (started bool, err error) {
   871  	err = b.db.View(func(tx *bolt.Tx) error {
   872  		bucket := tx.Bucket([]byte(tempBucket))
   873  		if bucket == nil {
   874  			return errors.Errorf("couldn't bucket for %v", tempBucket)
   875  		}
   876  
   877  		var tempObj = &tempUploadInfo{}
   878  		v := bucket.Get([]byte(remote))
   879  		err = json.Unmarshal(v, tempObj)
   880  		if err != nil {
   881  			return errors.Errorf("pending upload (%v) not found %v", remote, err)
   882  		}
   883  
   884  		started = tempObj.Started
   885  		return nil
   886  	})
   887  
   888  	return started, err
   889  }
   890  
   891  // searchPendingUploadFromDir files currently pending upload from a single dir
   892  func (b *Persistent) searchPendingUploadFromDir(dir string) (remotes []string, err error) {
   893  	err = b.db.View(func(tx *bolt.Tx) error {
   894  		bucket := tx.Bucket([]byte(tempBucket))
   895  		if bucket == nil {
   896  			return errors.Errorf("couldn't bucket for %v", tempBucket)
   897  		}
   898  
   899  		c := bucket.Cursor()
   900  		for k, v := c.First(); k != nil; k, v = c.Next() {
   901  			var tempObj = &tempUploadInfo{}
   902  			err = json.Unmarshal(v, tempObj)
   903  			if err != nil {
   904  				fs.Errorf(b, "failed to read pending upload: %v", err)
   905  				continue
   906  			}
   907  			parentDir := cleanPath(path.Dir(tempObj.DestPath))
   908  			if dir == parentDir {
   909  				remotes = append(remotes, tempObj.DestPath)
   910  			}
   911  		}
   912  
   913  		return nil
   914  	})
   915  
   916  	return remotes, err
   917  }
   918  
   919  func (b *Persistent) rollbackPendingUpload(remote string) error {
   920  	b.tempQueueMux.Lock()
   921  	defer b.tempQueueMux.Unlock()
   922  
   923  	return b.db.Update(func(tx *bolt.Tx) error {
   924  		bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket))
   925  		if err != nil {
   926  			return errors.Errorf("couldn't bucket for %v", tempBucket)
   927  		}
   928  		var tempObj = &tempUploadInfo{}
   929  		v := bucket.Get([]byte(remote))
   930  		err = json.Unmarshal(v, tempObj)
   931  		if err != nil {
   932  			return errors.Errorf("pending upload (%v) not found %v", remote, err)
   933  		}
   934  		tempObj.Started = false
   935  		v2, err := json.Marshal(tempObj)
   936  		if err != nil {
   937  			return errors.Errorf("pending upload not updated %v", err)
   938  		}
   939  		err = bucket.Put([]byte(tempObj.DestPath), v2)
   940  		if err != nil {
   941  			return errors.Errorf("pending upload not updated %v", err)
   942  		}
   943  		return nil
   944  	})
   945  }
   946  
   947  func (b *Persistent) removePendingUpload(remote string) error {
   948  	b.tempQueueMux.Lock()
   949  	defer b.tempQueueMux.Unlock()
   950  
   951  	return b.db.Update(func(tx *bolt.Tx) error {
   952  		bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket))
   953  		if err != nil {
   954  			return errors.Errorf("couldn't bucket for %v", tempBucket)
   955  		}
   956  		return bucket.Delete([]byte(remote))
   957  	})
   958  }
   959  
   960  // updatePendingUpload allows to update an existing item in the queue while checking if it's not started in the same
   961  // transaction. If it is started, it will not allow the update
   962  func (b *Persistent) updatePendingUpload(remote string, fn func(item *tempUploadInfo) error) error {
   963  	b.tempQueueMux.Lock()
   964  	defer b.tempQueueMux.Unlock()
   965  
   966  	return b.db.Update(func(tx *bolt.Tx) error {
   967  		bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket))
   968  		if err != nil {
   969  			return errors.Errorf("couldn't bucket for %v", tempBucket)
   970  		}
   971  
   972  		var tempObj = &tempUploadInfo{}
   973  		v := bucket.Get([]byte(remote))
   974  		err = json.Unmarshal(v, tempObj)
   975  		if err != nil {
   976  			return errors.Errorf("pending upload (%v) not found %v", remote, err)
   977  		}
   978  		if tempObj.Started {
   979  			return errors.Errorf("pending upload already started %v", remote)
   980  		}
   981  		err = fn(tempObj)
   982  		if err != nil {
   983  			return err
   984  		}
   985  		if remote != tempObj.DestPath {
   986  			err := bucket.Delete([]byte(remote))
   987  			if err != nil {
   988  				return err
   989  			}
   990  			// if this is removed then the entry can be removed too
   991  			if tempObj.DestPath == "" {
   992  				return nil
   993  			}
   994  		}
   995  		v2, err := json.Marshal(tempObj)
   996  		if err != nil {
   997  			return errors.Errorf("pending upload not updated %v", err)
   998  		}
   999  		err = bucket.Put([]byte(tempObj.DestPath), v2)
  1000  		if err != nil {
  1001  			return errors.Errorf("pending upload not updated %v", err)
  1002  		}
  1003  
  1004  		return nil
  1005  	})
  1006  }
  1007  
  1008  // SetPendingUploadToStarted is a way to mark an entry as started (even if it's not already)
  1009  // TO BE USED IN TESTING ONLY
  1010  func (b *Persistent) SetPendingUploadToStarted(remote string) error {
  1011  	return b.updatePendingUpload(remote, func(item *tempUploadInfo) error {
  1012  		item.Started = true
  1013  		return nil
  1014  	})
  1015  }
  1016  
  1017  // ReconcileTempUploads will recursively look for all the files in the temp directory and add them to the queue
  1018  func (b *Persistent) ReconcileTempUploads(ctx context.Context, cacheFs *Fs) error {
  1019  	return b.db.Update(func(tx *bolt.Tx) error {
  1020  		_ = tx.DeleteBucket([]byte(tempBucket))
  1021  		bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket))
  1022  		if err != nil {
  1023  			return err
  1024  		}
  1025  
  1026  		var queuedEntries []fs.Object
  1027  		err = walk.ListR(ctx, cacheFs.tempFs, "", true, -1, walk.ListObjects, func(entries fs.DirEntries) error {
  1028  			for _, o := range entries {
  1029  				if oo, ok := o.(fs.Object); ok {
  1030  					queuedEntries = append(queuedEntries, oo)
  1031  				}
  1032  			}
  1033  			return nil
  1034  		})
  1035  		if err != nil {
  1036  			return err
  1037  		}
  1038  
  1039  		fs.Debugf(cacheFs, "reconciling temporary uploads")
  1040  		for _, queuedEntry := range queuedEntries {
  1041  			destPath := path.Join(cacheFs.Root(), queuedEntry.Remote())
  1042  			tempObj := &tempUploadInfo{
  1043  				DestPath: destPath,
  1044  				AddedOn:  time.Now(),
  1045  				Started:  false,
  1046  			}
  1047  
  1048  			// cache Object Info
  1049  			encoded, err := json.Marshal(tempObj)
  1050  			if err != nil {
  1051  				return errors.Errorf("couldn't marshal object (%v) info: %v", queuedEntry, err)
  1052  			}
  1053  			err = bucket.Put([]byte(destPath), encoded)
  1054  			if err != nil {
  1055  				return errors.Errorf("couldn't cache object (%v) info: %v", destPath, err)
  1056  			}
  1057  			fs.Debugf(cacheFs, "reconciled temporary upload: %v", destPath)
  1058  		}
  1059  
  1060  		return nil
  1061  	})
  1062  }
  1063  
  1064  // PurgeTempUploads will remove all the pending uploads from the queue
  1065  // TO BE USED IN TESTING ONLY
  1066  func (b *Persistent) PurgeTempUploads() {
  1067  	b.tempQueueMux.Lock()
  1068  	defer b.tempQueueMux.Unlock()
  1069  
  1070  	_ = b.db.Update(func(tx *bolt.Tx) error {
  1071  		_ = tx.DeleteBucket([]byte(tempBucket))
  1072  		_, _ = tx.CreateBucketIfNotExists([]byte(tempBucket))
  1073  		return nil
  1074  	})
  1075  }
  1076  
  1077  // Close should be called when the program ends gracefully
  1078  func (b *Persistent) Close() {
  1079  	b.cleanupMux.Lock()
  1080  	defer b.cleanupMux.Unlock()
  1081  
  1082  	err := b.db.Close()
  1083  	if err != nil {
  1084  		fs.Errorf(b, "closing handle: %v", err)
  1085  	}
  1086  	b.open = false
  1087  }
  1088  
  1089  // itob returns an 8-byte big endian representation of v.
  1090  func itob(v int64) []byte {
  1091  	b := make([]byte, 8)
  1092  	binary.BigEndian.PutUint64(b, uint64(v))
  1093  	return b
  1094  }
  1095  
  1096  func btoi(d []byte) int64 {
  1097  	return int64(binary.BigEndian.Uint64(d))
  1098  }