github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/model/vfs/store.go (about) 1 package vfs 2 3 import ( 4 "context" 5 "encoding/hex" 6 "encoding/json" 7 "errors" 8 "sync" 9 "time" 10 11 "github.com/cozy/cozy-stack/pkg/config/config" 12 "github.com/cozy/cozy-stack/pkg/crypto" 13 "github.com/cozy/cozy-stack/pkg/prefixer" 14 "github.com/redis/go-redis/v9" 15 ) 16 17 // Store is essentially a place to store transient objects between two HTTP 18 // requests: it can be a secret for downloading a file, a list of files in an 19 // archive, or a metadata object for an upcoming upload. 20 type Store interface { 21 AddFile(db prefixer.Prefixer, filePath string) (string, error) 22 AddThumb(db prefixer.Prefixer, fileID string) (string, error) 23 AddThumbs(db prefixer.Prefixer, fileIDs []string) (map[string]string, error) 24 AddVersion(db prefixer.Prefixer, versionID string) (string, error) 25 AddArchive(db prefixer.Prefixer, archive *Archive) (string, error) 26 AddMetadata(db prefixer.Prefixer, metadata *Metadata) (string, error) 27 GetFile(db prefixer.Prefixer, key string) (string, error) 28 GetThumb(db prefixer.Prefixer, key string) (string, error) 29 GetVersion(db prefixer.Prefixer, key string) (string, error) 30 GetArchive(db prefixer.Prefixer, key string) (*Archive, error) 31 GetMetadata(db prefixer.Prefixer, key string) (*Metadata, error) 32 } 33 34 // storeTTL is time after which the data in the store will be considered stale. 35 var storeTTL = 10 * time.Minute 36 37 // storeCleanInterval is the time interval between each download cleanup. 38 var storeCleanInterval = 1 * time.Hour 39 40 var globalStoreMu sync.Mutex 41 var globalStore Store 42 43 // GetStore returns the global Store. 44 func GetStore() Store { 45 globalStoreMu.Lock() 46 defer globalStoreMu.Unlock() 47 if globalStore != nil { 48 return globalStore 49 } 50 cli := config.GetConfig().DownloadStorage 51 if cli == nil { 52 globalStore = newMemStore() 53 } else { 54 globalStore = newRedisStore(cli) 55 } 56 return globalStore 57 } 58 59 func newMemStore() Store { 60 store := &memStore{vals: make(map[string]*memRef)} 61 go store.cleaner() 62 return store 63 } 64 65 type memStore struct { 66 mu sync.Mutex 67 vals map[string]*memRef 68 } 69 70 type memRef struct { 71 val interface{} 72 exp time.Time 73 } 74 75 func (s *memStore) cleaner() { 76 for range time.Tick(storeCleanInterval) { 77 now := time.Now() 78 for k, v := range s.vals { 79 if now.After(v.exp) { 80 delete(s.vals, k) 81 } 82 } 83 } 84 } 85 86 func (s *memStore) AddFile(db prefixer.Prefixer, filePath string) (string, error) { 87 key := makeSecret() 88 s.mu.Lock() 89 defer s.mu.Unlock() 90 s.vals[db.DBPrefix()+":"+key] = &memRef{ 91 val: filePath, 92 exp: time.Now().Add(storeTTL), 93 } 94 return key, nil 95 } 96 97 func (s *memStore) AddThumb(db prefixer.Prefixer, fileID string) (string, error) { 98 key := makeSecret() 99 s.mu.Lock() 100 defer s.mu.Unlock() 101 s.vals[db.DBPrefix()+":"+key] = &memRef{ 102 val: fileID, 103 exp: time.Now().Add(storeTTL), 104 } 105 return key, nil 106 } 107 108 func (s *memStore) AddThumbs(db prefixer.Prefixer, fileIDs []string) (map[string]string, error) { 109 secrets := make(map[string]string) 110 for _, fileID := range fileIDs { 111 secret, err := s.AddThumb(db, fileID) 112 if err != nil { 113 return nil, err 114 } 115 secrets[fileID] = secret 116 } 117 return secrets, nil 118 } 119 120 func (s *memStore) AddVersion(db prefixer.Prefixer, versionID string) (string, error) { 121 key := makeSecret() 122 s.mu.Lock() 123 defer s.mu.Unlock() 124 s.vals[db.DBPrefix()+":"+key] = &memRef{ 125 val: versionID, 126 exp: time.Now().Add(storeTTL), 127 } 128 return key, nil 129 } 130 131 func (s *memStore) AddArchive(db prefixer.Prefixer, archive *Archive) (string, error) { 132 key := makeSecret() 133 s.mu.Lock() 134 defer s.mu.Unlock() 135 s.vals[db.DBPrefix()+":"+key] = &memRef{ 136 val: archive, 137 exp: time.Now().Add(storeTTL), 138 } 139 return key, nil 140 } 141 142 func (s *memStore) AddMetadata(db prefixer.Prefixer, metadata *Metadata) (string, error) { 143 key := makeSecret() 144 s.mu.Lock() 145 defer s.mu.Unlock() 146 s.vals[db.DBPrefix()+":"+key] = &memRef{ 147 val: metadata, 148 exp: time.Now().Add(storeTTL), 149 } 150 return key, nil 151 } 152 153 func (s *memStore) GetFile(db prefixer.Prefixer, key string) (string, error) { 154 s.mu.Lock() 155 defer s.mu.Unlock() 156 key = db.DBPrefix() + ":" + key 157 ref, ok := s.vals[key] 158 if !ok { 159 return "", ErrWrongToken 160 } 161 if time.Now().After(ref.exp) { 162 delete(s.vals, key) 163 return "", ErrWrongToken 164 } 165 f, ok := ref.val.(string) 166 if !ok { 167 return "", ErrWrongToken 168 } 169 return f, nil 170 } 171 172 func (s *memStore) GetThumb(db prefixer.Prefixer, key string) (string, error) { 173 s.mu.Lock() 174 defer s.mu.Unlock() 175 key = db.DBPrefix() + ":" + key 176 ref, ok := s.vals[key] 177 if !ok { 178 return "", ErrWrongToken 179 } 180 if time.Now().After(ref.exp) { 181 delete(s.vals, key) 182 return "", ErrWrongToken 183 } 184 f, ok := ref.val.(string) 185 if !ok { 186 return "", ErrWrongToken 187 } 188 return f, nil 189 } 190 191 func (s *memStore) GetVersion(db prefixer.Prefixer, key string) (string, error) { 192 s.mu.Lock() 193 defer s.mu.Unlock() 194 key = db.DBPrefix() + ":" + key 195 ref, ok := s.vals[key] 196 if !ok { 197 return "", ErrWrongToken 198 } 199 if time.Now().After(ref.exp) { 200 delete(s.vals, key) 201 return "", ErrWrongToken 202 } 203 f, ok := ref.val.(string) 204 if !ok { 205 return "", ErrWrongToken 206 } 207 return f, nil 208 } 209 210 func (s *memStore) GetArchive(db prefixer.Prefixer, key string) (*Archive, error) { 211 s.mu.Lock() 212 defer s.mu.Unlock() 213 key = db.DBPrefix() + ":" + key 214 ref, ok := s.vals[key] 215 if !ok { 216 return nil, ErrWrongToken 217 } 218 if time.Now().After(ref.exp) { 219 delete(s.vals, key) 220 return nil, ErrWrongToken 221 } 222 a, ok := ref.val.(*Archive) 223 if !ok { 224 return nil, ErrWrongToken 225 } 226 return a, nil 227 } 228 229 func (s *memStore) GetMetadata(db prefixer.Prefixer, key string) (*Metadata, error) { 230 s.mu.Lock() 231 defer s.mu.Unlock() 232 key = db.DBPrefix() + ":" + key 233 ref, ok := s.vals[key] 234 if !ok { 235 return nil, ErrInvalidMetadataID 236 } 237 if time.Now().After(ref.exp) { 238 delete(s.vals, key) 239 return nil, ErrInvalidMetadataID 240 } 241 m, ok := ref.val.(*Metadata) 242 if !ok { 243 return nil, ErrInvalidMetadataID 244 } 245 return m, nil 246 } 247 248 type redisStore struct { 249 c redis.UniversalClient 250 ctx context.Context 251 } 252 253 func newRedisStore(cli redis.UniversalClient) Store { 254 ctx := context.Background() 255 return &redisStore{cli, ctx} 256 } 257 258 func (s *redisStore) AddFile(db prefixer.Prefixer, filePath string) (string, error) { 259 key := makeSecret() 260 if err := s.c.Set(s.ctx, db.DBPrefix()+":"+key, filePath, storeTTL).Err(); err != nil { 261 return "", err 262 } 263 return key, nil 264 } 265 266 func (s *redisStore) AddThumb(db prefixer.Prefixer, fileID string) (string, error) { 267 key := makeSecret() 268 if err := s.c.Set(s.ctx, db.DBPrefix()+":"+key, fileID, storeTTL).Err(); err != nil { 269 return "", err 270 } 271 return key, nil 272 } 273 274 func (s *redisStore) AddThumbs(db prefixer.Prefixer, fileIDs []string) (map[string]string, error) { 275 secrets := make(map[string]string) 276 pipe := s.c.Pipeline() 277 for _, fileID := range fileIDs { 278 key := makeSecret() 279 secrets[fileID] = key 280 pipe.Set(s.ctx, db.DBPrefix()+":"+key, fileID, storeTTL) 281 } 282 if _, err := pipe.Exec(s.ctx); err != nil { 283 return nil, err 284 } 285 return secrets, nil 286 } 287 288 func (s *redisStore) AddVersion(db prefixer.Prefixer, versionID string) (string, error) { 289 key := makeSecret() 290 if err := s.c.Set(s.ctx, db.DBPrefix()+":"+key, versionID, storeTTL).Err(); err != nil { 291 return "", err 292 } 293 return key, nil 294 } 295 296 func (s *redisStore) AddArchive(db prefixer.Prefixer, archive *Archive) (string, error) { 297 v, err := json.Marshal(archive) 298 if err != nil { 299 return "", err 300 } 301 key := makeSecret() 302 if err = s.c.Set(s.ctx, db.DBPrefix()+":"+key, v, storeTTL).Err(); err != nil { 303 return "", err 304 } 305 return key, nil 306 } 307 308 func (s *redisStore) AddMetadata(db prefixer.Prefixer, metadata *Metadata) (string, error) { 309 v, err := json.Marshal(metadata) 310 if err != nil { 311 return "", err 312 } 313 key := makeSecret() 314 if err = s.c.Set(s.ctx, db.DBPrefix()+":"+key, v, storeTTL).Err(); err != nil { 315 return "", err 316 } 317 return key, nil 318 } 319 320 func (s *redisStore) GetFile(db prefixer.Prefixer, key string) (string, error) { 321 f, err := s.c.Get(s.ctx, db.DBPrefix()+":"+key).Result() 322 if errors.Is(err, redis.Nil) { 323 return "", ErrWrongToken 324 } 325 if err != nil { 326 return "", err 327 } 328 return f, nil 329 } 330 331 func (s *redisStore) GetThumb(db prefixer.Prefixer, key string) (string, error) { 332 f, err := s.c.Get(s.ctx, db.DBPrefix()+":"+key).Result() 333 if errors.Is(err, redis.Nil) { 334 return "", ErrWrongToken 335 } 336 if err != nil { 337 return "", err 338 } 339 return f, nil 340 } 341 342 func (s *redisStore) GetVersion(db prefixer.Prefixer, key string) (string, error) { 343 f, err := s.c.Get(s.ctx, db.DBPrefix()+":"+key).Result() 344 if errors.Is(err, redis.Nil) { 345 return "", ErrWrongToken 346 } 347 if err != nil { 348 return "", err 349 } 350 return f, nil 351 } 352 353 func (s *redisStore) GetArchive(db prefixer.Prefixer, key string) (*Archive, error) { 354 b, err := s.c.Get(s.ctx, db.DBPrefix()+":"+key).Bytes() 355 if errors.Is(err, redis.Nil) { 356 return nil, ErrWrongToken 357 } 358 if err != nil { 359 return nil, err 360 } 361 arch := &Archive{} 362 if err = json.Unmarshal(b, arch); err != nil { 363 return nil, err 364 } 365 return arch, nil 366 } 367 368 func (s *redisStore) GetMetadata(db prefixer.Prefixer, key string) (*Metadata, error) { 369 b, err := s.c.Get(s.ctx, db.DBPrefix()+":"+key).Bytes() 370 if errors.Is(err, redis.Nil) { 371 return nil, ErrInvalidMetadataID 372 } 373 if err != nil { 374 return nil, err 375 } 376 meta := &Metadata{} 377 if err = json.Unmarshal(b, meta); err != nil { 378 return nil, err 379 } 380 return meta, nil 381 } 382 383 func makeSecret() string { 384 return hex.EncodeToString(crypto.GenerateRandomBytes(8)) 385 }