mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-19 18:39:30 +08:00
Standardise and enhance kvstore service
This commit is contained in:
parent
63f47bc9ed
commit
6edb2b0189
@ -3,7 +3,6 @@ package kvstore
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -11,111 +10,147 @@ import (
|
|||||||
"github.com/wailsapp/wails/v3/pkg/application"
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
)
|
)
|
||||||
|
|
||||||
type KeyValueStore struct {
|
|
||||||
config *Config
|
|
||||||
filename string
|
|
||||||
data map[string]any
|
|
||||||
unsaved bool
|
|
||||||
lock sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
// Filename specifies the path of the on-disk file associated to the key-value store.
|
||||||
Filename string
|
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
|
AutoSave bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Service struct{}
|
type Service struct {
|
||||||
|
lock sync.RWMutex
|
||||||
|
|
||||||
func New(config *Config) *KeyValueStore {
|
config *Config
|
||||||
return &KeyValueStore{
|
|
||||||
config: config,
|
data map[string]any
|
||||||
data: make(map[string]any),
|
unsaved bool
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceShutdown will save the store to disk if there are unsaved changes.
|
// New initialises an in-memory key-value store. See [NewWithConfig] for details.
|
||||||
func (kvs *KeyValueStore) ServiceShutdown() error {
|
func New() *Service {
|
||||||
if kvs.unsaved {
|
return NewWithConfig(nil)
|
||||||
err := kvs.Save()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "Error saving store")
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return 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.
|
// ServiceName returns the name of the plugin.
|
||||||
func (kvs *KeyValueStore) ServiceName() string {
|
func (kvs *Service) ServiceName() string {
|
||||||
return "github.com/wailsapp/wails/v3/plugins/kvstore"
|
return "github.com/wailsapp/wails/v3/plugins/kvstore"
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceStartup is called when the plugin is loaded. This is where you should do any setup.
|
// ServiceStartup loads the store from disk if it is associated with a file.
|
||||||
func (kvs *KeyValueStore) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
// It returns a non-nil error in case of failure.
|
||||||
err := kvs.open(kvs.config.Filename)
|
func (kvs *Service) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
||||||
if err != nil {
|
return errors.Wrap(kvs.Load(), "error loading store")
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (kvs *KeyValueStore) open(filename string) (err error) {
|
bytes, err := os.ReadFile(kvs.config.Filename)
|
||||||
kvs.filename = filename
|
|
||||||
kvs.data = make(map[string]any)
|
|
||||||
|
|
||||||
file, err := os.Open(filename)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return nil
|
return nil
|
||||||
}
|
} else {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer func() {
|
|
||||||
err2 := file.Close()
|
|
||||||
if err2 != nil {
|
|
||||||
application.Get().Logger.Error("Key/Value Store Plugin Error:", "error", err.Error())
|
|
||||||
if err == nil {
|
|
||||||
err = err2
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
bytes, err := io.ReadAll(file)
|
// Init new map because [json.Unmarshal] does not clear the previous one.
|
||||||
if err != nil {
|
data := make(map[string]any)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(bytes) > 0 {
|
if len(bytes) > 0 {
|
||||||
if err := json.Unmarshal(bytes, &kvs.data); err != nil {
|
if err := json.Unmarshal(bytes, &data); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kvs.data = data
|
||||||
|
kvs.unsaved = false
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save saves the store to disk
|
// Save saves the store to disk.
|
||||||
func (kvs *KeyValueStore) Save() error {
|
// 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()
|
kvs.lock.Lock()
|
||||||
defer kvs.lock.Unlock()
|
defer kvs.lock.Unlock()
|
||||||
|
|
||||||
|
if kvs.config == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
bytes, err := json.Marshal(kvs.data)
|
bytes, err := json.Marshal(kvs.data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile(kvs.filename, bytes, 0644)
|
err = os.WriteFile(kvs.config.Filename, bytes, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
kvs.unsaved = false
|
kvs.unsaved = false
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the value for the given key. If key is empty, the entire store is returned.
|
// Get returns the value for the given key. If key is empty, the entire store is returned.
|
||||||
func (kvs *KeyValueStore) Get(key string) any {
|
func (kvs *Service) Get(key string) any {
|
||||||
kvs.lock.RLock()
|
kvs.lock.RLock()
|
||||||
defer kvs.lock.RUnlock()
|
defer kvs.lock.RUnlock()
|
||||||
|
|
||||||
@ -127,35 +162,67 @@ func (kvs *KeyValueStore) Get(key string) any {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set sets the value for the given key. If AutoSave is true, the store is saved to disk.
|
// Set sets the value for the given key. If AutoSave is true, the store is saved to disk.
|
||||||
func (kvs *KeyValueStore) Set(key string, value any) error {
|
func (kvs *Service) Set(key string, value any) error {
|
||||||
|
var autosave bool
|
||||||
|
func() {
|
||||||
kvs.lock.Lock()
|
kvs.lock.Lock()
|
||||||
|
defer kvs.lock.Unlock()
|
||||||
|
|
||||||
kvs.data[key] = value
|
kvs.data[key] = value
|
||||||
kvs.lock.Unlock()
|
|
||||||
if kvs.config.AutoSave {
|
|
||||||
err := kvs.Save()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
kvs.unsaved = false
|
|
||||||
} else {
|
|
||||||
kvs.unsaved = true
|
kvs.unsaved = true
|
||||||
|
|
||||||
|
if kvs.config != nil {
|
||||||
|
autosave = kvs.config.AutoSave
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if autosave {
|
||||||
|
return kvs.Save()
|
||||||
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Delete deletes the key from the store. If AutoSave is true, the store is saved to disk.
|
// Delete deletes the given key from the store. If AutoSave is true, the store is saved to disk.
|
||||||
func (kvs *KeyValueStore) Delete(key string) error {
|
func (kvs *Service) Delete(key string) error {
|
||||||
|
var autosave bool
|
||||||
|
func() {
|
||||||
kvs.lock.Lock()
|
kvs.lock.Lock()
|
||||||
|
defer kvs.lock.Unlock()
|
||||||
|
|
||||||
delete(kvs.data, key)
|
delete(kvs.data, key)
|
||||||
kvs.lock.Unlock()
|
|
||||||
if kvs.config.AutoSave {
|
|
||||||
err := kvs.Save()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
kvs.unsaved = false
|
|
||||||
} else {
|
|
||||||
kvs.unsaved = true
|
kvs.unsaved = true
|
||||||
|
|
||||||
|
if kvs.config != nil {
|
||||||
|
autosave = kvs.config.AutoSave
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if autosave {
|
||||||
|
return kvs.Save()
|
||||||
|
} else {
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user