github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/settings_db.go (about) 1 package libkbfs 2 3 import ( 4 "os" 5 "path" 6 "strconv" 7 "sync" 8 9 "github.com/keybase/client/go/kbfs/idutil" 10 "github.com/keybase/client/go/kbfs/ldbutils" 11 "github.com/keybase/client/go/libkb" 12 "github.com/keybase/client/go/logger" 13 "github.com/keybase/client/go/protocol/keybase1" 14 "github.com/pkg/errors" 15 "github.com/syndtr/goleveldb/leveldb" 16 "github.com/syndtr/goleveldb/leveldb/opt" 17 "github.com/syndtr/goleveldb/leveldb/storage" 18 "golang.org/x/net/context" 19 ) 20 21 const ( 22 // Where in config.StorageRoot() we store settings information. 23 settingsDBDir = "kbfs_settings" 24 settingsDBVersionString = "v1" 25 settingsDBName = "kbfsSettings.leveldb" 26 27 // Settings keys 28 spaceAvailableNotificationThresholdKey = "spaceAvailableNotificationThreshold" 29 30 sfmiBannerDismissedKey = "sfmiBannerDismissed" 31 syncOnCellularKey = "syncOnCellular" 32 ) 33 34 // ErrNoSettingsDB is returned when there is no settings DB potentially due to 35 // multiple concurrent KBFS instances. 36 var ErrNoSettingsDB = errors.New("no settings DB") 37 38 var errNoSession = errors.New("no session") 39 40 type currentSessionGetter interface { 41 CurrentSessionGetter() idutil.CurrentSessionGetter 42 } 43 44 // SettingsDB stores KBFS user settings for a given device. 45 type SettingsDB struct { 46 *ldbutils.LevelDb 47 sessionGetter currentSessionGetter 48 logger logger.Logger 49 vlogger *libkb.VDebugLog 50 51 lock sync.RWMutex 52 cache map[string][]byte 53 } 54 55 func openSettingsDBInternal(config Config) (*ldbutils.LevelDb, error) { 56 if config.IsTestMode() { 57 return ldbutils.OpenLevelDb(storage.NewMemStorage(), config.Mode()) 58 } 59 dbPath := path.Join(config.StorageRoot(), settingsDBDir, 60 settingsDBVersionString) 61 err := os.MkdirAll(dbPath, os.ModePerm) 62 if err != nil { 63 return nil, err 64 } 65 66 stor, err := storage.OpenFile(path.Join(dbPath, settingsDBName), false) 67 if err != nil { 68 return nil, err 69 } 70 71 return ldbutils.OpenLevelDb(stor, config.Mode()) 72 } 73 74 func openSettingsDB(config Config) *SettingsDB { 75 logger := config.MakeLogger("SDB") 76 vlogger := config.MakeVLogger(logger) 77 db, err := openSettingsDBInternal(config) 78 if err != nil { 79 logger.CWarningf(context.Background(), 80 "Could not open settings DB. "+ 81 "Perhaps multiple KBFS instances are being run concurrently"+ 82 "? Error: %+v", err) 83 if db != nil { 84 db.Close() 85 } 86 return nil 87 } 88 return &SettingsDB{ 89 LevelDb: db, 90 sessionGetter: config, 91 logger: logger, 92 vlogger: vlogger, 93 cache: make(map[string][]byte), 94 } 95 } 96 97 func (db *SettingsDB) getUID(ctx context.Context) keybase1.UID { 98 if db.sessionGetter == nil || db.sessionGetter.CurrentSessionGetter() == nil { 99 return keybase1.UID("") 100 } 101 si, err := db.sessionGetter.CurrentSessionGetter().GetCurrentSession(ctx) 102 if err != nil { 103 return keybase1.UID("") 104 } 105 return si.UID 106 } 107 108 func getSettingsDbKey(uid keybase1.UID, key string) []byte { 109 return append([]byte(uid), []byte(key)...) 110 } 111 112 func (db *SettingsDB) getFromCache(key string) (val []byte, isCached bool) { 113 db.lock.RLock() 114 defer db.lock.RUnlock() 115 val, isCached = db.cache[key] 116 return val, isCached 117 } 118 119 func (db *SettingsDB) updateCache(key string, val []byte) { 120 db.lock.Lock() 121 defer db.lock.Unlock() 122 if val == nil { 123 delete(db.cache, key) 124 } else { 125 db.cache[key] = val 126 } 127 } 128 129 // Get overrides (*LevelDb).Get to cache values in memory. 130 func (db *SettingsDB) Get(key []byte, ro *opt.ReadOptions) ([]byte, error) { 131 val, isCached := db.getFromCache(string(key)) 132 if isCached { 133 return val, nil 134 } 135 val, err := db.LevelDb.Get(key, ro) 136 if err == nil { 137 db.updateCache(string(key), val) 138 } 139 return val, err 140 } 141 142 // Put overrides (*LevelDb).Put to cache values in memory. 143 func (db *SettingsDB) Put(key []byte, val []byte, wo *opt.WriteOptions) error { 144 err := db.LevelDb.Put(key, val, wo) 145 if err != nil { 146 db.updateCache(string(key), nil) 147 } else { 148 db.updateCache(string(key), val) 149 } 150 return err 151 } 152 153 // Settings returns the logged-in user's settings as a keybase1.FSSettings. 154 func (db *SettingsDB) Settings(ctx context.Context) (keybase1.FSSettings, error) { 155 uid := db.getUID(ctx) 156 if uid == keybase1.UID("") { 157 return keybase1.FSSettings{}, errNoSession 158 } 159 160 var notificationThreshold int64 161 notificationThresholdBytes, err := 162 db.Get(getSettingsDbKey(uid, spaceAvailableNotificationThresholdKey), nil) 163 switch errors.Cause(err) { 164 case leveldb.ErrNotFound: 165 db.vlogger.CLogf(ctx, libkb.VLog1, 166 "notificationThreshold not set; using default value") 167 case nil: 168 notificationThreshold, err = 169 strconv.ParseInt(string(notificationThresholdBytes), 10, 64) 170 if err != nil { 171 return keybase1.FSSettings{}, err 172 } 173 default: 174 db.logger.CWarningf(ctx, 175 "reading notificationThreshold from leveldb error: %+v", err) 176 return keybase1.FSSettings{}, err 177 } 178 179 var sfmiBannerDismissed bool 180 sfmiBannerDismissedBytes, err := 181 db.Get(getSettingsDbKey(uid, sfmiBannerDismissedKey), nil) 182 switch errors.Cause(err) { 183 case leveldb.ErrNotFound: 184 db.vlogger.CLogf(ctx, libkb.VLog1, 185 "sfmiBannerDismissed not set; using default value") 186 case nil: 187 sfmiBannerDismissed, err = 188 strconv.ParseBool(string(sfmiBannerDismissedBytes)) 189 if err != nil { 190 return keybase1.FSSettings{}, err 191 } 192 default: 193 db.logger.CWarningf(ctx, 194 "reading sfmiBannerDismissed from leveldb error: %+v", err) 195 return keybase1.FSSettings{}, err 196 } 197 198 var syncOnCellular bool 199 syncOnCellularBytes, err := 200 db.Get(getSettingsDbKey(uid, syncOnCellularKey), nil) 201 switch errors.Cause(err) { 202 case leveldb.ErrNotFound: 203 db.vlogger.CLogf(ctx, libkb.VLog1, 204 "syncOnCellular not set; using default value") 205 case nil: 206 syncOnCellular, err = strconv.ParseBool(string(syncOnCellularBytes)) 207 if err != nil { 208 return keybase1.FSSettings{}, err 209 } 210 default: 211 db.logger.CWarningf(ctx, 212 "reading syncOnCellular from leveldb error: %+v", err) 213 return keybase1.FSSettings{}, err 214 } 215 216 return keybase1.FSSettings{ 217 SpaceAvailableNotificationThreshold: notificationThreshold, 218 SfmiBannerDismissed: sfmiBannerDismissed, 219 SyncOnCellular: syncOnCellular, 220 }, nil 221 } 222 223 // SetNotificationThreshold sets the notification threshold setting for the 224 // logged-in user. 225 func (db *SettingsDB) SetNotificationThreshold( 226 ctx context.Context, threshold int64) error { 227 uid := db.getUID(ctx) 228 if uid == keybase1.UID("") { 229 return errNoSession 230 } 231 return db.Put(getSettingsDbKey(uid, spaceAvailableNotificationThresholdKey), 232 []byte(strconv.FormatInt(threshold, 10)), nil) 233 } 234 235 // SetSfmiBannerDismissed sets whether the smfi banner has been dismissed. 236 func (db *SettingsDB) SetSfmiBannerDismissed( 237 ctx context.Context, dismissed bool) error { 238 uid := db.getUID(ctx) 239 if uid == keybase1.UID("") { 240 return errNoSession 241 } 242 return db.Put(getSettingsDbKey(uid, sfmiBannerDismissedKey), 243 []byte(strconv.FormatBool(dismissed)), nil) 244 } 245 246 // SetSyncOnCellular sets whether we should do TLF syncing on a 247 // cellular network. 248 func (db *SettingsDB) SetSyncOnCellular( 249 ctx context.Context, syncOnCellular bool) error { 250 uid := db.getUID(ctx) 251 if uid == keybase1.UID("") { 252 return errNoSession 253 } 254 return db.Put(getSettingsDbKey(uid, syncOnCellularKey), 255 []byte(strconv.FormatBool(syncOnCellular)), nil) 256 }