github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/lib/dircache/dircache.go (about) 1 // Package dircache provides a simple cache for caching directory to path lookups 2 package dircache 3 4 // _methods are called without the lock 5 6 import ( 7 "context" 8 "log" 9 "strings" 10 "sync" 11 12 "github.com/ncw/rclone/fs" 13 "github.com/pkg/errors" 14 ) 15 16 // DirCache caches paths to directory IDs and vice versa 17 type DirCache struct { 18 cacheMu sync.RWMutex 19 cache map[string]string 20 invCache map[string]string 21 mu sync.Mutex 22 fs DirCacher // Interface to find and make stuff 23 trueRootID string // ID of the absolute root 24 root string // the path we are working on 25 rootID string // ID of the root directory 26 rootParentID string // ID of the root's parent directory 27 foundRoot bool // Whether we have found the root or not 28 } 29 30 // DirCacher describes an interface for doing the low level directory work 31 type DirCacher interface { 32 FindLeaf(ctx context.Context, pathID, leaf string) (pathIDOut string, found bool, err error) 33 CreateDir(ctx context.Context, pathID, leaf string) (newID string, err error) 34 } 35 36 // New makes a DirCache 37 // 38 // The cache is safe for concurrent use 39 func New(root string, trueRootID string, fs DirCacher) *DirCache { 40 d := &DirCache{ 41 trueRootID: trueRootID, 42 root: root, 43 fs: fs, 44 } 45 d.Flush() 46 d.ResetRoot() 47 return d 48 } 49 50 // Get an ID given a path 51 func (dc *DirCache) Get(path string) (id string, ok bool) { 52 dc.cacheMu.RLock() 53 id, ok = dc.cache[path] 54 dc.cacheMu.RUnlock() 55 return 56 } 57 58 // GetInv gets a path given an ID 59 func (dc *DirCache) GetInv(id string) (path string, ok bool) { 60 dc.cacheMu.RLock() 61 path, ok = dc.invCache[id] 62 dc.cacheMu.RUnlock() 63 return 64 } 65 66 // Put a path, id into the map 67 func (dc *DirCache) Put(path, id string) { 68 dc.cacheMu.Lock() 69 dc.cache[path] = id 70 dc.invCache[id] = path 71 dc.cacheMu.Unlock() 72 } 73 74 // Flush the map of all data 75 func (dc *DirCache) Flush() { 76 dc.cacheMu.Lock() 77 dc.cache = make(map[string]string) 78 dc.invCache = make(map[string]string) 79 dc.cacheMu.Unlock() 80 } 81 82 // FlushDir flushes the map of all data starting with dir 83 // 84 // If dir is empty then this is equivalent to calling ResetRoot 85 func (dc *DirCache) FlushDir(dir string) { 86 if dir == "" { 87 dc.ResetRoot() 88 return 89 } 90 dc.cacheMu.Lock() 91 92 // Delete the root dir 93 ID, ok := dc.cache[dir] 94 if ok { 95 delete(dc.cache, dir) 96 delete(dc.invCache, ID) 97 } 98 99 // And any sub directories 100 dir += "/" 101 for key, ID := range dc.cache { 102 if strings.HasPrefix(key, dir) { 103 delete(dc.cache, key) 104 delete(dc.invCache, ID) 105 } 106 } 107 108 dc.cacheMu.Unlock() 109 } 110 111 // SplitPath splits a path into directory, leaf 112 // 113 // Path shouldn't start or end with a / 114 // 115 // If there are no slashes then directory will be "" and leaf = path 116 func SplitPath(path string) (directory, leaf string) { 117 lastSlash := strings.LastIndex(path, "/") 118 if lastSlash >= 0 { 119 directory = path[:lastSlash] 120 leaf = path[lastSlash+1:] 121 } else { 122 directory = "" 123 leaf = path 124 } 125 return 126 } 127 128 // FindDir finds the directory passed in returning the directory ID 129 // starting from pathID 130 // 131 // Path shouldn't start or end with a / 132 // 133 // If create is set it will make the directory if not found 134 // 135 // Algorithm: 136 // Look in the cache for the path, if found return the pathID 137 // If not found strip the last path off the path and recurse 138 // Now have a parent directory id, so look in the parent for self and return it 139 func (dc *DirCache) FindDir(ctx context.Context, path string, create bool) (pathID string, err error) { 140 dc.mu.Lock() 141 defer dc.mu.Unlock() 142 return dc._findDir(ctx, path, create) 143 } 144 145 // Look for the root and in the cache - safe to call without the mu 146 func (dc *DirCache) _findDirInCache(path string) string { 147 // fmt.Println("Finding",path,"create",create,"cache",cache) 148 // If it is the root, then return it 149 if path == "" { 150 // fmt.Println("Root") 151 return dc.rootID 152 } 153 154 // If it is in the cache then return it 155 pathID, ok := dc.Get(path) 156 if ok { 157 // fmt.Println("Cache hit on", path) 158 return pathID 159 } 160 161 return "" 162 } 163 164 // Unlocked findDir - must have mu 165 func (dc *DirCache) _findDir(ctx context.Context, path string, create bool) (pathID string, err error) { 166 pathID = dc._findDirInCache(path) 167 if pathID != "" { 168 return pathID, nil 169 } 170 171 // Split the path into directory, leaf 172 directory, leaf := SplitPath(path) 173 174 // Recurse and find pathID for parent directory 175 parentPathID, err := dc._findDir(ctx, directory, create) 176 if err != nil { 177 return "", err 178 179 } 180 181 // Find the leaf in parentPathID 182 pathID, found, err := dc.fs.FindLeaf(ctx, parentPathID, leaf) 183 if err != nil { 184 return "", err 185 } 186 187 // If not found create the directory if required or return an error 188 if !found { 189 if create { 190 pathID, err = dc.fs.CreateDir(ctx, parentPathID, leaf) 191 if err != nil { 192 return "", errors.Wrap(err, "failed to make directory") 193 } 194 } else { 195 return "", fs.ErrorDirNotFound 196 } 197 } 198 199 // Store the leaf directory in the cache 200 dc.Put(path, pathID) 201 202 // fmt.Println("Dir", path, "is", pathID) 203 return pathID, nil 204 } 205 206 // FindPath finds the leaf and directoryID from a path 207 // 208 // Do not call FindPath with the root directory - it will return an error 209 // 210 // If create is set parent directories will be created if they don't exist 211 func (dc *DirCache) FindPath(ctx context.Context, path string, create bool) (leaf, directoryID string, err error) { 212 if path == "" { 213 err = errors.New("internal error: can't call FindPath with root directory") 214 return 215 } 216 dc.mu.Lock() 217 defer dc.mu.Unlock() 218 directory, leaf := SplitPath(path) 219 directoryID, err = dc._findDir(ctx, directory, create) 220 return 221 } 222 223 // FindRoot finds the root directory if not already found 224 // 225 // Resets the root directory 226 // 227 // If create is set it will make the directory if not found 228 func (dc *DirCache) FindRoot(ctx context.Context, create bool) error { 229 dc.mu.Lock() 230 defer dc.mu.Unlock() 231 if dc.foundRoot { 232 return nil 233 } 234 rootID, err := dc._findDir(ctx, dc.root, create) 235 if err != nil { 236 return err 237 } 238 dc.foundRoot = true 239 dc.rootID = rootID 240 241 // Find the parent of the root while we still have the root 242 // directory tree cached 243 rootParentPath, _ := SplitPath(dc.root) 244 dc.rootParentID, _ = dc.Get(rootParentPath) 245 246 // Reset the tree based on dc.root 247 dc.Flush() 248 // Put the root directory in 249 dc.Put("", dc.rootID) 250 return nil 251 } 252 253 // FindRootAndPath finds the root first if not found then finds leaf and directoryID from a path 254 // 255 // If create is set parent directories will be created if they don't exist 256 func (dc *DirCache) FindRootAndPath(ctx context.Context, path string, create bool) (leaf, directoryID string, err error) { 257 err = dc.FindRoot(ctx, create) 258 if err != nil { 259 return 260 } 261 return dc.FindPath(ctx, path, create) 262 } 263 264 // FoundRoot returns whether the root directory has been found yet 265 // 266 // Call this from FindLeaf or CreateDir only 267 func (dc *DirCache) FoundRoot() bool { 268 return dc.foundRoot 269 } 270 271 // RootID returns the ID of the root directory 272 // 273 // This should be called after FindRoot 274 func (dc *DirCache) RootID() string { 275 dc.mu.Lock() 276 defer dc.mu.Unlock() 277 if !dc.foundRoot { 278 log.Fatalf("Internal Error: RootID() called before FindRoot") 279 } 280 return dc.rootID 281 } 282 283 // RootParentID returns the ID of the parent of the root directory 284 // 285 // This should be called after FindRoot 286 func (dc *DirCache) RootParentID() (string, error) { 287 dc.mu.Lock() 288 defer dc.mu.Unlock() 289 if !dc.foundRoot { 290 return "", errors.New("internal error: RootID() called before FindRoot") 291 } 292 if dc.rootParentID == "" { 293 return "", errors.New("internal error: didn't find rootParentID") 294 } 295 if dc.rootID == dc.trueRootID { 296 return "", errors.New("is root directory") 297 } 298 return dc.rootParentID, nil 299 } 300 301 // ResetRoot resets the root directory to the absolute root and clears 302 // the DirCache 303 func (dc *DirCache) ResetRoot() { 304 dc.mu.Lock() 305 defer dc.mu.Unlock() 306 dc.foundRoot = false 307 dc.Flush() 308 309 // Put the true root in 310 dc.rootID = dc.trueRootID 311 312 // Put the root directory in 313 dc.Put("", dc.rootID) 314 }