Repository: rclone/rclone
File: fs/cache/cache.go (Go)

Code before:
		if canonicalName != canonicalFsString {
			// Note that if err == fs.ErrorIsFile at this moment
			// then we can't rename the remote as it will have the
			// wrong error status, we need to add a new one.
			if err == nil {
				fs.Debugf(nil, "fs cache: renaming cache item %q to be canonical %q", canonicalFsString, canonicalName)
				value, found := c.Rename(canonicalFsString, canonicalName)
				if found {
					f = value.(fs.Fs)
				}
				addMapping(canonicalFsString, canonicalName)
			} else {
				fs.Debugf(nil, "fs cache: adding new entry for parent of %q, %q", canonicalFsString, canonicalName)
				Put(canonicalName, f)
			}
		}
	}
	return f, err
}

// Pin f into the cache until Unpin is called
func Pin(f fs.Fs) {
	createOnFirstUse()
	configString := fs.ConfigString(f)
	c.Pin(configString)
	c.PinPrefix(strings.TrimSuffix(configString, "/")) // also pin its aliases, if any
}

// PinUntilFinalized pins f into the cache until x is garbage collected
//
// This calls runtime.SetFinalizer on x so it shouldn't have a
// finalizer already.
func PinUntilFinalized(f fs.Fs, x interface{}) {
	Pin(f)
	runtime.SetFinalizer(x, func(_ interface{}) {
		Unpin(f)
	})
}

// Unpin f from the cache
func Unpin(f fs.Fs) {
	createOnFirstUse()
	configString := fs.ConfigString(f)
	c.Unpin(configString)
	c.UnpinPrefix(strings.TrimSuffix(configString, "/")) // also unpin its aliases, if any
}

// To avoid circular dependencies these are filled in by fs/rc/jobs/job.go
var (
	// JobGetJobID for internal use only
	JobGetJobID func(context.Context) (int64, bool)

Review comment (suggestion):
I'm worried that c.PinPrefix would pin things it shouldn't here, so if you made `remote:path/dir` then made `remote/path/file.txt` then the latter would pin the former.

Code after:
	mu.Unlock()
	return found
}

// ensures that we return fs.ErrorIsFile when necessary
func getError(fsString string, err error) error {
	if err != nil && err != fs.ErrorIsFile {
		return err
	}
	if isChild(fsString) {
		return fs.ErrorIsFile
	}
	return nil
}

// GetFn gets an fs.Fs named fsString either from the cache or creates
// it afresh with the create function
func GetFn(ctx context.Context, fsString string, create func(ctx context.Context, fsString string) (fs.Fs, error)) (f fs.Fs, err error) {
	createOnFirstUse()
	canonicalFsString := Canonicalize(fsString)
	created := false
	value, err := c.Get(canonicalFsString, func(canonicalFsString string) (f interface{}, ok bool, err error) {
		f, err = create(ctx, fsString) // always create the backend with the original non-canonicalised string
		ok = err == nil || err == fs.ErrorIsFile
		created = ok
		return f, ok, err
	})
	f, ok := value.(fs.Fs)
	if err != nil && err != fs.ErrorIsFile {
		if ok {
			return f, err // for possible future uses of PutErr
		}
		return nil, err
	}
	// Check we stored the Fs at the canonical name
	if created {
		canonicalName := fs.ConfigString(f)
		if canonicalName != canonicalFsString {
			if err == nil { // it's a dir
				fs.Debugf(nil, "fs cache: renaming cache item %q to be canonical %q", canonicalFsString, canonicalName)
				value, found := c.Rename(canonicalFsString, canonicalName)
				if found {
					f = value.(fs.Fs)
				}
				addMapping(canonicalFsString, canonicalName)
			} else { // it's a file
				// the fs we cache is always the file's parent, never the file,
				// but we use the childParentMap to return the correct error status based on the fsString passed in.
				fs.Debugf(nil, "fs cache: renaming child cache item %q to be canonical for parent %q", canonicalFsString, canonicalName)
				value, found := c.Rename(canonicalFsString, canonicalName) // rename the file entry to parent
				if found {