github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/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  	"github.com/pkg/errors"
    20  	"github.com/rclone/rclone/fs"
    21  	"github.com/rclone/rclone/fs/walk"
    22  	bolt "go.etcd.io/bbolt"
    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  // addPendingUpload adds a new file to the pending queue of uploads
   771  func (b *Persistent) addPendingUpload(destPath string, started bool) error {
   772  	return b.db.Update(func(tx *bolt.Tx) error {
   773  		bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket))
   774  		if err != nil {
   775  			return errors.Errorf("couldn't bucket for %v", tempBucket)
   776  		}
   777  		tempObj := &tempUploadInfo{
   778  			DestPath: destPath,
   779  			AddedOn:  time.Now(),
   780  			Started:  started,
   781  		}
   782  
   783  		// cache Object Info
   784  		encoded, err := json.Marshal(tempObj)
   785  		if err != nil {
   786  			return errors.Errorf("couldn't marshal object (%v) info: %v", destPath, err)
   787  		}
   788  		err = bucket.Put([]byte(destPath), encoded)
   789  		if err != nil {
   790  			return errors.Errorf("couldn't cache object (%v) info: %v", destPath, err)
   791  		}
   792  
   793  		return nil
   794  	})
   795  }
   796  
   797  // getPendingUpload returns the next file from the pending queue of uploads
   798  func (b *Persistent) getPendingUpload(inRoot string, waitTime time.Duration) (destPath string, err error) {
   799  	b.tempQueueMux.Lock()
   800  	defer b.tempQueueMux.Unlock()
   801  
   802  	err = b.db.Update(func(tx *bolt.Tx) error {
   803  		bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket))
   804  		if err != nil {
   805  			return errors.Errorf("couldn't bucket for %v", tempBucket)
   806  		}
   807  
   808  		c := bucket.Cursor()
   809  		for k, v := c.Seek([]byte(inRoot)); k != nil && bytes.HasPrefix(k, []byte(inRoot)); k, v = c.Next() {
   810  			//for k, v := c.First(); k != nil; k, v = c.Next() {
   811  			var tempObj = &tempUploadInfo{}
   812  			err = json.Unmarshal(v, tempObj)
   813  			if err != nil {
   814  				fs.Errorf(b, "failed to read pending upload: %v", err)
   815  				continue
   816  			}
   817  			// skip over started uploads
   818  			if tempObj.Started || time.Now().Before(tempObj.AddedOn.Add(waitTime)) {
   819  				continue
   820  			}
   821  
   822  			tempObj.Started = true
   823  			v2, err := json.Marshal(tempObj)
   824  			if err != nil {
   825  				fs.Errorf(b, "failed to update pending upload: %v", err)
   826  				continue
   827  			}
   828  			err = bucket.Put(k, v2)
   829  			if err != nil {
   830  				fs.Errorf(b, "failed to update pending upload: %v", err)
   831  				continue
   832  			}
   833  
   834  			destPath = tempObj.DestPath
   835  			return nil
   836  		}
   837  
   838  		return errors.Errorf("no pending upload found")
   839  	})
   840  
   841  	return destPath, err
   842  }
   843  
   844  // SearchPendingUpload returns the file info from the pending queue of uploads
   845  func (b *Persistent) SearchPendingUpload(remote string) (started bool, err error) {
   846  	err = b.db.View(func(tx *bolt.Tx) error {
   847  		bucket := tx.Bucket([]byte(tempBucket))
   848  		if bucket == nil {
   849  			return errors.Errorf("couldn't bucket for %v", tempBucket)
   850  		}
   851  
   852  		var tempObj = &tempUploadInfo{}
   853  		v := bucket.Get([]byte(remote))
   854  		err = json.Unmarshal(v, tempObj)
   855  		if err != nil {
   856  			return errors.Errorf("pending upload (%v) not found %v", remote, err)
   857  		}
   858  
   859  		started = tempObj.Started
   860  		return nil
   861  	})
   862  
   863  	return started, err
   864  }
   865  
   866  // searchPendingUploadFromDir files currently pending upload from a single dir
   867  func (b *Persistent) searchPendingUploadFromDir(dir string) (remotes []string, err error) {
   868  	err = b.db.View(func(tx *bolt.Tx) error {
   869  		bucket := tx.Bucket([]byte(tempBucket))
   870  		if bucket == nil {
   871  			return errors.Errorf("couldn't bucket for %v", tempBucket)
   872  		}
   873  
   874  		c := bucket.Cursor()
   875  		for k, v := c.First(); k != nil; k, v = c.Next() {
   876  			var tempObj = &tempUploadInfo{}
   877  			err = json.Unmarshal(v, tempObj)
   878  			if err != nil {
   879  				fs.Errorf(b, "failed to read pending upload: %v", err)
   880  				continue
   881  			}
   882  			parentDir := cleanPath(path.Dir(tempObj.DestPath))
   883  			if dir == parentDir {
   884  				remotes = append(remotes, tempObj.DestPath)
   885  			}
   886  		}
   887  
   888  		return nil
   889  	})
   890  
   891  	return remotes, err
   892  }
   893  
   894  func (b *Persistent) rollbackPendingUpload(remote string) error {
   895  	b.tempQueueMux.Lock()
   896  	defer b.tempQueueMux.Unlock()
   897  
   898  	return b.db.Update(func(tx *bolt.Tx) error {
   899  		bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket))
   900  		if err != nil {
   901  			return errors.Errorf("couldn't bucket for %v", tempBucket)
   902  		}
   903  		var tempObj = &tempUploadInfo{}
   904  		v := bucket.Get([]byte(remote))
   905  		err = json.Unmarshal(v, tempObj)
   906  		if err != nil {
   907  			return errors.Errorf("pending upload (%v) not found %v", remote, err)
   908  		}
   909  		tempObj.Started = false
   910  		v2, err := json.Marshal(tempObj)
   911  		if err != nil {
   912  			return errors.Errorf("pending upload not updated %v", err)
   913  		}
   914  		err = bucket.Put([]byte(tempObj.DestPath), v2)
   915  		if err != nil {
   916  			return errors.Errorf("pending upload not updated %v", err)
   917  		}
   918  		return nil
   919  	})
   920  }
   921  
   922  func (b *Persistent) removePendingUpload(remote string) error {
   923  	b.tempQueueMux.Lock()
   924  	defer b.tempQueueMux.Unlock()
   925  
   926  	return b.db.Update(func(tx *bolt.Tx) error {
   927  		bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket))
   928  		if err != nil {
   929  			return errors.Errorf("couldn't bucket for %v", tempBucket)
   930  		}
   931  		return bucket.Delete([]byte(remote))
   932  	})
   933  }
   934  
   935  // updatePendingUpload allows to update an existing item in the queue while checking if it's not started in the same
   936  // transaction. If it is started, it will not allow the update
   937  func (b *Persistent) updatePendingUpload(remote string, fn func(item *tempUploadInfo) error) error {
   938  	b.tempQueueMux.Lock()
   939  	defer b.tempQueueMux.Unlock()
   940  
   941  	return b.db.Update(func(tx *bolt.Tx) error {
   942  		bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket))
   943  		if err != nil {
   944  			return errors.Errorf("couldn't bucket for %v", tempBucket)
   945  		}
   946  
   947  		var tempObj = &tempUploadInfo{}
   948  		v := bucket.Get([]byte(remote))
   949  		err = json.Unmarshal(v, tempObj)
   950  		if err != nil {
   951  			return errors.Errorf("pending upload (%v) not found %v", remote, err)
   952  		}
   953  		if tempObj.Started {
   954  			return errors.Errorf("pending upload already started %v", remote)
   955  		}
   956  		err = fn(tempObj)
   957  		if err != nil {
   958  			return err
   959  		}
   960  		if remote != tempObj.DestPath {
   961  			err := bucket.Delete([]byte(remote))
   962  			if err != nil {
   963  				return err
   964  			}
   965  			// if this is removed then the entry can be removed too
   966  			if tempObj.DestPath == "" {
   967  				return nil
   968  			}
   969  		}
   970  		v2, err := json.Marshal(tempObj)
   971  		if err != nil {
   972  			return errors.Errorf("pending upload not updated %v", err)
   973  		}
   974  		err = bucket.Put([]byte(tempObj.DestPath), v2)
   975  		if err != nil {
   976  			return errors.Errorf("pending upload not updated %v", err)
   977  		}
   978  
   979  		return nil
   980  	})
   981  }
   982  
   983  // ReconcileTempUploads will recursively look for all the files in the temp directory and add them to the queue
   984  func (b *Persistent) ReconcileTempUploads(ctx context.Context, cacheFs *Fs) error {
   985  	return b.db.Update(func(tx *bolt.Tx) error {
   986  		_ = tx.DeleteBucket([]byte(tempBucket))
   987  		bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket))
   988  		if err != nil {
   989  			return err
   990  		}
   991  
   992  		var queuedEntries []fs.Object
   993  		err = walk.ListR(ctx, cacheFs.tempFs, "", true, -1, walk.ListObjects, func(entries fs.DirEntries) error {
   994  			for _, o := range entries {
   995  				if oo, ok := o.(fs.Object); ok {
   996  					queuedEntries = append(queuedEntries, oo)
   997  				}
   998  			}
   999  			return nil
  1000  		})
  1001  		if err != nil {
  1002  			return err
  1003  		}
  1004  
  1005  		fs.Debugf(cacheFs, "reconciling temporary uploads")
  1006  		for _, queuedEntry := range queuedEntries {
  1007  			destPath := path.Join(cacheFs.Root(), queuedEntry.Remote())
  1008  			tempObj := &tempUploadInfo{
  1009  				DestPath: destPath,
  1010  				AddedOn:  time.Now(),
  1011  				Started:  false,
  1012  			}
  1013  
  1014  			// cache Object Info
  1015  			encoded, err := json.Marshal(tempObj)
  1016  			if err != nil {
  1017  				return errors.Errorf("couldn't marshal object (%v) info: %v", queuedEntry, err)
  1018  			}
  1019  			err = bucket.Put([]byte(destPath), encoded)
  1020  			if err != nil {
  1021  				return errors.Errorf("couldn't cache object (%v) info: %v", destPath, err)
  1022  			}
  1023  			fs.Debugf(cacheFs, "reconciled temporary upload: %v", destPath)
  1024  		}
  1025  
  1026  		return nil
  1027  	})
  1028  }
  1029  
  1030  // Close should be called when the program ends gracefully
  1031  func (b *Persistent) Close() {
  1032  	b.cleanupMux.Lock()
  1033  	defer b.cleanupMux.Unlock()
  1034  
  1035  	err := b.db.Close()
  1036  	if err != nil {
  1037  		fs.Errorf(b, "closing handle: %v", err)
  1038  	}
  1039  	b.open = false
  1040  }
  1041  
  1042  // itob returns an 8-byte big endian representation of v.
  1043  func itob(v int64) []byte {
  1044  	b := make([]byte, 8)
  1045  	binary.BigEndian.PutUint64(b, uint64(v))
  1046  	return b
  1047  }
  1048  
  1049  func btoi(d []byte) int64 {
  1050  	return int64(binary.BigEndian.Uint64(d))
  1051  }