github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/model/sharing/upload_store.go (about) 1 package sharing 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 // A UploadStore is essentially an object to store files metadata by key 18 type UploadStore interface { 19 Get(db prefixer.Prefixer, key string) (*FileDocWithRevisions, error) 20 Save(db prefixer.Prefixer, doc *FileDocWithRevisions) (string, error) 21 } 22 23 // uploadStoreTTL is the time an entry stay alive 24 var uploadStoreTTL = 5 * time.Minute 25 26 // uploadStoreCleanInterval is the time interval between each upload 27 // cleanup. 28 var uploadStoreCleanInterval = 1 * time.Hour 29 30 var mu sync.Mutex 31 var globalStore UploadStore 32 33 func getStore() UploadStore { 34 mu.Lock() 35 defer mu.Unlock() 36 if globalStore != nil { 37 return globalStore 38 } 39 cli := config.GetConfig().DownloadStorage 40 if cli == nil { 41 globalStore = newMemStore() 42 } else { 43 ctx := context.Background() 44 globalStore = &redisStore{cli, ctx} 45 } 46 return globalStore 47 } 48 49 type memRef struct { 50 val *FileDocWithRevisions 51 exp time.Time 52 } 53 54 func newMemStore() UploadStore { 55 store := &memStore{vals: make(map[string]*memRef)} 56 go store.cleaner() 57 return store 58 } 59 60 type memStore struct { 61 mu sync.Mutex 62 vals map[string]*memRef 63 } 64 65 func (s *memStore) cleaner() { 66 for range time.Tick(uploadStoreCleanInterval) { 67 now := time.Now() 68 for k, v := range s.vals { 69 if now.After(v.exp) { 70 delete(s.vals, k) 71 } 72 } 73 } 74 } 75 76 func (s *memStore) Get(db prefixer.Prefixer, key string) (*FileDocWithRevisions, error) { 77 s.mu.Lock() 78 defer s.mu.Unlock() 79 key = db.DBPrefix() + ":" + key 80 ref, ok := s.vals[key] 81 if !ok { 82 return nil, nil 83 } 84 if time.Now().After(ref.exp) { 85 delete(s.vals, key) 86 } 87 return ref.val, nil 88 } 89 90 func (s *memStore) Save(db prefixer.Prefixer, doc *FileDocWithRevisions) (string, error) { 91 key := makeSecret() 92 s.mu.Lock() 93 defer s.mu.Unlock() 94 s.vals[db.DBPrefix()+":"+key] = &memRef{ 95 val: doc, 96 exp: time.Now().Add(uploadStoreTTL), 97 } 98 return key, nil 99 } 100 101 type redisStore struct { 102 c redis.UniversalClient 103 ctx context.Context 104 } 105 106 func (s *redisStore) Get(db prefixer.Prefixer, key string) (*FileDocWithRevisions, error) { 107 b, err := s.c.Get(s.ctx, db.DBPrefix()+":"+key).Bytes() 108 if errors.Is(err, redis.Nil) { 109 return nil, nil 110 } 111 if err != nil { 112 return nil, err 113 } 114 var doc FileDocWithRevisions 115 if err = json.Unmarshal(b, &doc); err != nil { 116 return nil, err 117 } 118 return &doc, nil 119 } 120 121 func (s *redisStore) Save(db prefixer.Prefixer, doc *FileDocWithRevisions) (string, error) { 122 v, err := json.Marshal(doc) 123 if err != nil { 124 return "", err 125 } 126 key := makeSecret() 127 if err = s.c.Set(s.ctx, db.DBPrefix()+":"+key, v, uploadStoreTTL).Err(); err != nil { 128 return "", err 129 } 130 return key, nil 131 } 132 133 func makeSecret() string { 134 return hex.EncodeToString(crypto.GenerateRandomBytes(8)) 135 }