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 }