github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/cache/cache.go (about) 1 // Package cache implements the Fs cache 2 package cache 3 4 import ( 5 "context" 6 "runtime" 7 "sync" 8 9 "github.com/rclone/rclone/fs" 10 "github.com/rclone/rclone/fs/filter" 11 "github.com/rclone/rclone/lib/cache" 12 ) 13 14 var ( 15 once sync.Once // creation 16 c *cache.Cache 17 mu sync.Mutex // mutex to protect remap 18 remap = map[string]string{} // map user supplied names to canonical names 19 ) 20 21 // Create the cache just once 22 func createOnFirstUse() { 23 once.Do(func() { 24 ci := fs.GetConfig(context.Background()) 25 c = cache.New() 26 c.SetExpireDuration(ci.FsCacheExpireDuration) 27 c.SetExpireInterval(ci.FsCacheExpireInterval) 28 c.SetFinalizer(func(value interface{}) { 29 if s, ok := value.(fs.Shutdowner); ok { 30 _ = fs.CountError(s.Shutdown(context.Background())) 31 } 32 }) 33 }) 34 } 35 36 // Canonicalize looks up fsString in the mapping from user supplied 37 // names to canonical names and return the canonical form 38 func Canonicalize(fsString string) string { 39 createOnFirstUse() 40 mu.Lock() 41 canonicalName, ok := remap[fsString] 42 mu.Unlock() 43 if !ok { 44 return fsString 45 } 46 fs.Debugf(nil, "fs cache: switching user supplied name %q for canonical name %q", fsString, canonicalName) 47 return canonicalName 48 } 49 50 // Put in a mapping from fsString => canonicalName if they are different 51 func addMapping(fsString, canonicalName string) { 52 if canonicalName == fsString { 53 return 54 } 55 mu.Lock() 56 remap[fsString] = canonicalName 57 mu.Unlock() 58 } 59 60 // GetFn gets an fs.Fs named fsString either from the cache or creates 61 // it afresh with the create function 62 func GetFn(ctx context.Context, fsString string, create func(ctx context.Context, fsString string) (fs.Fs, error)) (f fs.Fs, err error) { 63 createOnFirstUse() 64 canonicalFsString := Canonicalize(fsString) 65 created := false 66 value, err := c.Get(canonicalFsString, func(canonicalFsString string) (f interface{}, ok bool, err error) { 67 f, err = create(ctx, fsString) // always create the backend with the original non-canonicalised string 68 ok = err == nil || err == fs.ErrorIsFile 69 created = ok 70 return f, ok, err 71 }) 72 if err != nil && err != fs.ErrorIsFile { 73 return nil, err 74 } 75 f = value.(fs.Fs) 76 // Check we stored the Fs at the canonical name 77 if created { 78 canonicalName := fs.ConfigString(f) 79 if canonicalName != canonicalFsString { 80 // Note that if err == fs.ErrorIsFile at this moment 81 // then we can't rename the remote as it will have the 82 // wrong error status, we need to add a new one. 83 if err == nil { 84 fs.Debugf(nil, "fs cache: renaming cache item %q to be canonical %q", canonicalFsString, canonicalName) 85 value, found := c.Rename(canonicalFsString, canonicalName) 86 if found { 87 f = value.(fs.Fs) 88 } 89 addMapping(canonicalFsString, canonicalName) 90 } else { 91 fs.Debugf(nil, "fs cache: adding new entry for parent of %q, %q", canonicalFsString, canonicalName) 92 Put(canonicalName, f) 93 } 94 } 95 } 96 return f, err 97 } 98 99 // Pin f into the cache until Unpin is called 100 func Pin(f fs.Fs) { 101 createOnFirstUse() 102 c.Pin(fs.ConfigString(f)) 103 } 104 105 // PinUntilFinalized pins f into the cache until x is garbage collected 106 // 107 // This calls runtime.SetFinalizer on x so it shouldn't have a 108 // finalizer already. 109 func PinUntilFinalized(f fs.Fs, x interface{}) { 110 Pin(f) 111 runtime.SetFinalizer(x, func(_ interface{}) { 112 Unpin(f) 113 }) 114 115 } 116 117 // Unpin f from the cache 118 func Unpin(f fs.Fs) { 119 createOnFirstUse() 120 c.Unpin(fs.ConfigString(f)) 121 } 122 123 // To avoid circular dependencies these are filled in by fs/rc/jobs/job.go 124 var ( 125 // JobGetJobID for internal use only 126 JobGetJobID func(context.Context) (int64, bool) 127 // JobOnFinish for internal use only 128 JobOnFinish func(int64, func()) (func(), error) 129 ) 130 131 // Get gets an fs.Fs named fsString either from the cache or creates it afresh 132 func Get(ctx context.Context, fsString string) (f fs.Fs, err error) { 133 // If we are making a long lived backend which lives longer 134 // than this request, we want to disconnect it from the 135 // current context and in particular any WithCancel contexts, 136 // but we want to preserve the config embedded in the context. 137 newCtx := context.Background() 138 newCtx = fs.CopyConfig(newCtx, ctx) 139 newCtx = filter.CopyConfig(newCtx, ctx) 140 f, err = GetFn(newCtx, fsString, fs.NewFs) 141 if f == nil || (err != nil && err != fs.ErrorIsFile) { 142 return f, err 143 } 144 // If this is part of an rc job then pin the backend until it finishes 145 if JobOnFinish != nil && JobGetJobID != nil { 146 if jobID, ok := JobGetJobID(ctx); ok { 147 // fs.Debugf(f, "Pin for job %d", jobID) 148 Pin(f) 149 _, _ = JobOnFinish(jobID, func() { 150 // fs.Debugf(f, "Unpin for job %d", jobID) 151 Unpin(f) 152 }) 153 } 154 } 155 return f, err 156 } 157 158 // GetArr gets []fs.Fs from []fsStrings either from the cache or creates it afresh 159 func GetArr(ctx context.Context, fsStrings []string) (f []fs.Fs, err error) { 160 var fArr []fs.Fs 161 for _, fsString := range fsStrings { 162 f1, err1 := GetFn(ctx, fsString, fs.NewFs) 163 if err1 != nil { 164 return fArr, err1 165 } 166 fArr = append(fArr, f1) 167 } 168 return fArr, nil 169 } 170 171 // PutErr puts an fs.Fs named fsString into the cache with err 172 func PutErr(fsString string, f fs.Fs, err error) { 173 createOnFirstUse() 174 canonicalName := fs.ConfigString(f) 175 c.PutErr(canonicalName, f, err) 176 addMapping(fsString, canonicalName) 177 } 178 179 // Put puts an fs.Fs named fsString into the cache 180 func Put(fsString string, f fs.Fs) { 181 PutErr(fsString, f, nil) 182 } 183 184 // ClearConfig deletes all entries which were based on the config name passed in 185 // 186 // Returns number of entries deleted 187 func ClearConfig(name string) (deleted int) { 188 createOnFirstUse() 189 return c.DeletePrefix(name + ":") 190 } 191 192 // Clear removes everything from the cache 193 func Clear() { 194 createOnFirstUse() 195 c.Clear() 196 } 197 198 // Entries returns the number of entries in the cache 199 func Entries() int { 200 createOnFirstUse() 201 return c.Entries() 202 }