mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-15 16:39:32 +08:00
229 lines
5.2 KiB
Go
229 lines
5.2 KiB
Go
package kvstore
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"sync"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/wailsapp/wails/v3/pkg/application"
|
|
)
|
|
|
|
type Config struct {
|
|
// Filename specifies the path of the on-disk file associated to the key-value store.
|
|
Filename string
|
|
|
|
// AutoSave specifies whether the store
|
|
// must be written to disk automatically after every modification.
|
|
// When AutoSave is false, stores are only saved to disk upon shutdown
|
|
// or when the [Service.Save] method is called manually.
|
|
AutoSave bool
|
|
}
|
|
|
|
type Service struct {
|
|
lock sync.RWMutex
|
|
|
|
config *Config
|
|
|
|
data map[string]any
|
|
unsaved bool
|
|
}
|
|
|
|
// New initialises an in-memory key-value store. See [NewWithConfig] for details.
|
|
func New() *Service {
|
|
return NewWithConfig(nil)
|
|
}
|
|
|
|
// NewWithConfig initialises a key-value store with the given configuration:
|
|
// - if config is nil, the new store is in-memory, i.e. not associated with a file;
|
|
// - if config is non-nil, the associated file is not loaded until [Service.Load] is called.
|
|
//
|
|
// If the store is registered with the application as a service,
|
|
// [Service.Load] will be called automatically at startup.
|
|
func NewWithConfig(config *Config) *Service {
|
|
result := &Service{data: make(map[string]any)}
|
|
result.Configure(config)
|
|
return result
|
|
}
|
|
|
|
// ServiceName returns the name of the plugin.
|
|
func (kvs *Service) ServiceName() string {
|
|
return "github.com/wailsapp/wails/v3/plugins/kvstore"
|
|
}
|
|
|
|
// ServiceStartup loads the store from disk if it is associated with a file.
|
|
// It returns a non-nil error in case of failure.
|
|
func (kvs *Service) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
|
return errors.Wrap(kvs.Load(), "error loading store")
|
|
}
|
|
|
|
// ServiceShutdown saves the store to disk if it is associated with a file.
|
|
// It returns a non-nil error in case of failure.
|
|
func (kvs *Service) ServiceShutdown() error {
|
|
return errors.Wrap(kvs.Save(), "error saving store")
|
|
}
|
|
|
|
// Configure changes the store's configuration.
|
|
// The contents of the store at call time are preserved and marked unsaved.
|
|
// Consumers will need to call [Service.Load] manually after Configure
|
|
// in order to load a new file.
|
|
//
|
|
// If the store is unsaved upon calling Configure, no attempt is made at saving it.
|
|
// Consumers will need to call [Service.Save] manually beforehand.
|
|
//
|
|
// See [NewWithConfig] for details on configuration.
|
|
//
|
|
//wails:ignore
|
|
func (kvs *Service) Configure(config *Config) {
|
|
if config != nil {
|
|
// Clone to prevent changes from the outside.
|
|
clone := new(Config)
|
|
*clone = *config
|
|
config = clone
|
|
}
|
|
|
|
kvs.lock.Lock()
|
|
defer kvs.lock.Unlock()
|
|
|
|
kvs.config = config
|
|
kvs.unsaved = true
|
|
}
|
|
|
|
// Load loads the store from disk.
|
|
// If the store is in-memory, i.e. not associated with a file, Load has no effect.
|
|
// If the operation fails, a non-nil error is returned
|
|
// and the store's content and state at call time are preserved.
|
|
func (kvs *Service) Load() error {
|
|
kvs.lock.Lock()
|
|
defer kvs.lock.Unlock()
|
|
|
|
if kvs.config == nil {
|
|
return nil
|
|
}
|
|
|
|
bytes, err := os.ReadFile(kvs.config.Filename)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
} else {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Init new map because [json.Unmarshal] does not clear the previous one.
|
|
data := make(map[string]any)
|
|
|
|
if len(bytes) > 0 {
|
|
if err := json.Unmarshal(bytes, &data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
kvs.data = data
|
|
kvs.unsaved = false
|
|
return nil
|
|
}
|
|
|
|
// Save saves the store to disk.
|
|
// If the store is in-memory, i.e. not associated with a file, Save has no effect.
|
|
func (kvs *Service) Save() error {
|
|
kvs.lock.Lock()
|
|
defer kvs.lock.Unlock()
|
|
|
|
if kvs.config == nil {
|
|
return nil
|
|
}
|
|
|
|
bytes, err := json.Marshal(kvs.data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = os.WriteFile(kvs.config.Filename, bytes, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
kvs.unsaved = false
|
|
return nil
|
|
}
|
|
|
|
// Get returns the value for the given key. If key is empty, the entire store is returned.
|
|
func (kvs *Service) Get(key string) any {
|
|
kvs.lock.RLock()
|
|
defer kvs.lock.RUnlock()
|
|
|
|
if key == "" {
|
|
return kvs.data
|
|
}
|
|
|
|
return kvs.data[key]
|
|
}
|
|
|
|
// Set sets the value for the given key. If AutoSave is true, the store is saved to disk.
|
|
func (kvs *Service) Set(key string, value any) error {
|
|
var autosave bool
|
|
func() {
|
|
kvs.lock.Lock()
|
|
defer kvs.lock.Unlock()
|
|
|
|
kvs.data[key] = value
|
|
kvs.unsaved = true
|
|
|
|
if kvs.config != nil {
|
|
autosave = kvs.config.AutoSave
|
|
}
|
|
}()
|
|
|
|
if autosave {
|
|
return kvs.Save()
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Delete deletes the given key from the store. If AutoSave is true, the store is saved to disk.
|
|
func (kvs *Service) Delete(key string) error {
|
|
var autosave bool
|
|
func() {
|
|
kvs.lock.Lock()
|
|
defer kvs.lock.Unlock()
|
|
|
|
delete(kvs.data, key)
|
|
kvs.unsaved = true
|
|
|
|
if kvs.config != nil {
|
|
autosave = kvs.config.AutoSave
|
|
}
|
|
}()
|
|
|
|
if autosave {
|
|
return kvs.Save()
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Clear deletes all keys from the store. If AutoSave is true, the store is saved to disk.
|
|
func (kvs *Service) Clear() error {
|
|
var autosave bool
|
|
func() {
|
|
kvs.lock.Lock()
|
|
defer kvs.lock.Unlock()
|
|
|
|
kvs.data = make(map[string]any)
|
|
kvs.unsaved = true
|
|
|
|
if kvs.config != nil {
|
|
autosave = kvs.config.AutoSave
|
|
}
|
|
}()
|
|
|
|
if autosave {
|
|
return kvs.Save()
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|