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  }