github.com/fawick/restic@v0.1.1-0.20171126184616-c02923fbfc79/internal/cache/cache.go (about)

     1  package cache
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  	"strconv"
     9  
    10  	"github.com/pkg/errors"
    11  	"github.com/restic/restic/internal/debug"
    12  	"github.com/restic/restic/internal/fs"
    13  	"github.com/restic/restic/internal/restic"
    14  )
    15  
    16  // Cache manages a local cache.
    17  type Cache struct {
    18  	Path             string
    19  	Base             string
    20  	PerformReadahead func(restic.Handle) bool
    21  }
    22  
    23  const dirMode = 0700
    24  const fileMode = 0600
    25  
    26  func readVersion(dir string) (v uint, err error) {
    27  	buf, err := ioutil.ReadFile(filepath.Join(dir, "version"))
    28  	if os.IsNotExist(err) {
    29  		return 0, nil
    30  	}
    31  
    32  	if err != nil {
    33  		return 0, errors.Wrap(err, "ReadFile")
    34  	}
    35  
    36  	ver, err := strconv.ParseUint(string(buf), 10, 32)
    37  	if err != nil {
    38  		return 0, errors.Wrap(err, "ParseUint")
    39  	}
    40  
    41  	return uint(ver), nil
    42  }
    43  
    44  const cacheVersion = 1
    45  
    46  // ensure Cache implements restic.Cache
    47  var _ restic.Cache = &Cache{}
    48  
    49  var cacheLayoutPaths = map[restic.FileType]string{
    50  	restic.DataFile:     "data",
    51  	restic.SnapshotFile: "snapshots",
    52  	restic.IndexFile:    "index",
    53  }
    54  
    55  const cachedirTagSignature = "Signature: 8a477f597d28d172789f06886806bc55\n"
    56  
    57  func writeCachedirTag(dir string) error {
    58  	if err := fs.MkdirAll(dir, dirMode); err != nil {
    59  		return err
    60  	}
    61  
    62  	f, err := fs.OpenFile(filepath.Join(dir, "CACHEDIR.TAG"), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
    63  	if err != nil {
    64  		if os.IsExist(errors.Cause(err)) {
    65  			return nil
    66  		}
    67  
    68  		return errors.Wrap(err, "OpenFile")
    69  	}
    70  
    71  	debug.Log("Create CACHEDIR.TAG at %v", dir)
    72  	if _, err := f.Write([]byte(cachedirTagSignature)); err != nil {
    73  		_ = f.Close()
    74  		return errors.Wrap(err, "Write")
    75  	}
    76  
    77  	return f.Close()
    78  }
    79  
    80  // New returns a new cache for the repo ID at basedir. If basedir is the empty
    81  // string, the default cache location (according to the XDG standard) is used.
    82  //
    83  // For partial files, the complete file is loaded and stored in the cache when
    84  // performReadahead returns true.
    85  func New(id string, basedir string) (c *Cache, err error) {
    86  	if basedir == "" {
    87  		basedir, err = defaultCacheDir()
    88  		if err != nil {
    89  			return nil, err
    90  		}
    91  	}
    92  
    93  	// create base dir and tag it as a cache directory
    94  	if err = writeCachedirTag(basedir); err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	cachedir := filepath.Join(basedir, id)
    99  	debug.Log("using cache dir %v", cachedir)
   100  
   101  	v, err := readVersion(cachedir)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  
   106  	if v > cacheVersion {
   107  		return nil, errors.New("cache version is newer")
   108  	}
   109  
   110  	// create the repo cache dir if it does not exist yet
   111  	if err = fs.MkdirAll(cachedir, dirMode); err != nil {
   112  		return nil, err
   113  	}
   114  
   115  	if v < cacheVersion {
   116  		err = ioutil.WriteFile(filepath.Join(cachedir, "version"), []byte(fmt.Sprintf("%d", cacheVersion)), 0644)
   117  		if err != nil {
   118  			return nil, errors.Wrap(err, "WriteFile")
   119  		}
   120  	}
   121  
   122  	for _, p := range cacheLayoutPaths {
   123  		if err = fs.MkdirAll(filepath.Join(cachedir, p), dirMode); err != nil {
   124  			return nil, err
   125  		}
   126  	}
   127  
   128  	c = &Cache{
   129  		Path: cachedir,
   130  		Base: basedir,
   131  		PerformReadahead: func(restic.Handle) bool {
   132  			// do not perform readahead by default
   133  			return false
   134  		},
   135  	}
   136  
   137  	return c, nil
   138  }
   139  
   140  // errNoSuchFile is returned when a file is not cached.
   141  type errNoSuchFile struct {
   142  	Type string
   143  	Name string
   144  }
   145  
   146  func (e errNoSuchFile) Error() string {
   147  	return fmt.Sprintf("file %v (%v) is not cached", e.Name, e.Type)
   148  }
   149  
   150  // IsNotExist returns true if the error was caused by a non-existing file.
   151  func (c *Cache) IsNotExist(err error) bool {
   152  	_, ok := errors.Cause(err).(errNoSuchFile)
   153  	return ok
   154  }
   155  
   156  // Wrap returns a backend with a cache.
   157  func (c *Cache) Wrap(be restic.Backend) restic.Backend {
   158  	return newBackend(be, c)
   159  }
   160  
   161  // BaseDir returns the base directory.
   162  func (c *Cache) BaseDir() string {
   163  	return c.Base
   164  }