| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- package app
- import (
- "encoding/json"
- "os"
- "path/filepath"
- "sync"
- "time"
- "fyne.io/fyne/v2"
- "fyne.io/fyne/v2/internal"
- )
- type preferences struct {
- *internal.InMemoryPreferences
- prefLock sync.RWMutex
- loadingInProgress bool
- savedRecently bool
- changedDuringSaving bool
- app *fyneApp
- needsSaveBeforeExit bool
- }
- // Declare conformity with Preferences interface
- var _ fyne.Preferences = (*preferences)(nil)
- // forceImmediateSave writes preferences to file immediately, ignoring the debouncing
- // logic in the change listener. Does nothing if preferences are not backed with a file.
- func (p *preferences) forceImmediateSave() {
- if !p.needsSaveBeforeExit {
- return
- }
- err := p.save()
- if err != nil {
- fyne.LogError("Failed on force saving preferences", err)
- }
- }
- func (p *preferences) resetSavedRecently() {
- go func() {
- time.Sleep(time.Millisecond * 100) // writes are not always atomic. 10ms worked, 100 is safer.
- p.prefLock.Lock()
- p.savedRecently = false
- changedDuringSaving := p.changedDuringSaving
- p.changedDuringSaving = false
- p.prefLock.Unlock()
- if changedDuringSaving {
- p.save()
- }
- }()
- }
- func (p *preferences) save() error {
- return p.saveToFile(p.storagePath())
- }
- func (p *preferences) saveToFile(path string) error {
- p.prefLock.Lock()
- p.savedRecently = true
- p.prefLock.Unlock()
- defer p.resetSavedRecently()
- err := os.MkdirAll(filepath.Dir(path), 0700)
- if err != nil { // this is not an exists error according to docs
- return err
- }
- file, err := os.Create(path)
- if err != nil {
- if !os.IsExist(err) {
- return err
- }
- file, err = os.Open(path) // #nosec
- if err != nil {
- return err
- }
- }
- defer file.Close()
- encode := json.NewEncoder(file)
- p.InMemoryPreferences.ReadValues(func(values map[string]interface{}) {
- err = encode.Encode(&values)
- })
- err2 := file.Sync()
- if err == nil {
- err = err2
- }
- return err
- }
- func (p *preferences) load() {
- err := p.loadFromFile(p.storagePath())
- if err != nil {
- fyne.LogError("Preferences load error:", err)
- }
- }
- func (p *preferences) loadFromFile(path string) (err error) {
- file, err := os.Open(path) // #nosec
- if err != nil {
- if os.IsNotExist(err) {
- if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
- return err
- }
- return nil
- }
- return err
- }
- defer func() {
- if r := file.Close(); r != nil && err == nil {
- err = r
- }
- }()
- decode := json.NewDecoder(file)
- p.prefLock.Lock()
- p.loadingInProgress = true
- p.prefLock.Unlock()
- p.InMemoryPreferences.WriteValues(func(values map[string]interface{}) {
- err = decode.Decode(&values)
- if err != nil {
- return
- }
- convertLists(values)
- })
- p.prefLock.Lock()
- p.loadingInProgress = false
- p.prefLock.Unlock()
- return err
- }
- func newPreferences(app *fyneApp) *preferences {
- p := &preferences{}
- p.app = app
- p.InMemoryPreferences = internal.NewInMemoryPreferences()
- // don't load or watch if not setup
- if app.uniqueID == "" && app.Metadata().ID == "" {
- return p
- }
- p.needsSaveBeforeExit = true
- p.AddChangeListener(func() {
- if p != app.prefs {
- return
- }
- p.prefLock.Lock()
- shouldIgnoreChange := p.savedRecently || p.loadingInProgress
- if p.savedRecently && !p.loadingInProgress {
- p.changedDuringSaving = true
- }
- p.prefLock.Unlock()
- if shouldIgnoreChange { // callback after loading file, or too many updates in a row
- return
- }
- err := p.save()
- if err != nil {
- fyne.LogError("Failed on saving preferences", err)
- }
- })
- p.watch()
- return p
- }
- func convertLists(values map[string]interface{}) {
- for k, v := range values {
- if items, ok := v.([]interface{}); ok {
- if len(items) == 0 {
- continue
- }
- switch items[0].(type) {
- case bool:
- bools := make([]bool, len(items))
- for i, item := range items {
- bools[i] = item.(bool)
- }
- values[k] = bools
- case float64:
- floats := make([]float64, len(items))
- for i, item := range items {
- floats[i] = item.(float64)
- }
- values[k] = floats
- case int:
- ints := make([]int, len(items))
- for i, item := range items {
- ints[i] = item.(int)
- }
- values[k] = ints
- case string:
- strings := make([]string, len(items))
- for i, item := range items {
- strings[i] = item.(string)
- }
- values[k] = strings
- }
- }
- }
- }
|