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  }