preferences.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. package app
  2. import (
  3. "encoding/json"
  4. "os"
  5. "path/filepath"
  6. "sync"
  7. "time"
  8. "fyne.io/fyne/v2"
  9. "fyne.io/fyne/v2/internal"
  10. )
  11. type preferences struct {
  12. *internal.InMemoryPreferences
  13. prefLock sync.RWMutex
  14. loadingInProgress bool
  15. savedRecently bool
  16. changedDuringSaving bool
  17. app *fyneApp
  18. needsSaveBeforeExit bool
  19. }
  20. // Declare conformity with Preferences interface
  21. var _ fyne.Preferences = (*preferences)(nil)
  22. // forceImmediateSave writes preferences to file immediately, ignoring the debouncing
  23. // logic in the change listener. Does nothing if preferences are not backed with a file.
  24. func (p *preferences) forceImmediateSave() {
  25. if !p.needsSaveBeforeExit {
  26. return
  27. }
  28. err := p.save()
  29. if err != nil {
  30. fyne.LogError("Failed on force saving preferences", err)
  31. }
  32. }
  33. func (p *preferences) resetSavedRecently() {
  34. go func() {
  35. time.Sleep(time.Millisecond * 100) // writes are not always atomic. 10ms worked, 100 is safer.
  36. p.prefLock.Lock()
  37. p.savedRecently = false
  38. changedDuringSaving := p.changedDuringSaving
  39. p.changedDuringSaving = false
  40. p.prefLock.Unlock()
  41. if changedDuringSaving {
  42. p.save()
  43. }
  44. }()
  45. }
  46. func (p *preferences) save() error {
  47. return p.saveToFile(p.storagePath())
  48. }
  49. func (p *preferences) saveToFile(path string) error {
  50. p.prefLock.Lock()
  51. p.savedRecently = true
  52. p.prefLock.Unlock()
  53. defer p.resetSavedRecently()
  54. err := os.MkdirAll(filepath.Dir(path), 0700)
  55. if err != nil { // this is not an exists error according to docs
  56. return err
  57. }
  58. file, err := os.Create(path)
  59. if err != nil {
  60. if !os.IsExist(err) {
  61. return err
  62. }
  63. file, err = os.Open(path) // #nosec
  64. if err != nil {
  65. return err
  66. }
  67. }
  68. defer file.Close()
  69. encode := json.NewEncoder(file)
  70. p.InMemoryPreferences.ReadValues(func(values map[string]interface{}) {
  71. err = encode.Encode(&values)
  72. })
  73. err2 := file.Sync()
  74. if err == nil {
  75. err = err2
  76. }
  77. return err
  78. }
  79. func (p *preferences) load() {
  80. err := p.loadFromFile(p.storagePath())
  81. if err != nil {
  82. fyne.LogError("Preferences load error:", err)
  83. }
  84. }
  85. func (p *preferences) loadFromFile(path string) (err error) {
  86. file, err := os.Open(path) // #nosec
  87. if err != nil {
  88. if os.IsNotExist(err) {
  89. if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
  90. return err
  91. }
  92. return nil
  93. }
  94. return err
  95. }
  96. defer func() {
  97. if r := file.Close(); r != nil && err == nil {
  98. err = r
  99. }
  100. }()
  101. decode := json.NewDecoder(file)
  102. p.prefLock.Lock()
  103. p.loadingInProgress = true
  104. p.prefLock.Unlock()
  105. p.InMemoryPreferences.WriteValues(func(values map[string]interface{}) {
  106. err = decode.Decode(&values)
  107. if err != nil {
  108. return
  109. }
  110. convertLists(values)
  111. })
  112. p.prefLock.Lock()
  113. p.loadingInProgress = false
  114. p.prefLock.Unlock()
  115. return err
  116. }
  117. func newPreferences(app *fyneApp) *preferences {
  118. p := &preferences{}
  119. p.app = app
  120. p.InMemoryPreferences = internal.NewInMemoryPreferences()
  121. // don't load or watch if not setup
  122. if app.uniqueID == "" && app.Metadata().ID == "" {
  123. return p
  124. }
  125. p.needsSaveBeforeExit = true
  126. p.AddChangeListener(func() {
  127. if p != app.prefs {
  128. return
  129. }
  130. p.prefLock.Lock()
  131. shouldIgnoreChange := p.savedRecently || p.loadingInProgress
  132. if p.savedRecently && !p.loadingInProgress {
  133. p.changedDuringSaving = true
  134. }
  135. p.prefLock.Unlock()
  136. if shouldIgnoreChange { // callback after loading file, or too many updates in a row
  137. return
  138. }
  139. err := p.save()
  140. if err != nil {
  141. fyne.LogError("Failed on saving preferences", err)
  142. }
  143. })
  144. p.watch()
  145. return p
  146. }
  147. func convertLists(values map[string]interface{}) {
  148. for k, v := range values {
  149. if items, ok := v.([]interface{}); ok {
  150. if len(items) == 0 {
  151. continue
  152. }
  153. switch items[0].(type) {
  154. case bool:
  155. bools := make([]bool, len(items))
  156. for i, item := range items {
  157. bools[i] = item.(bool)
  158. }
  159. values[k] = bools
  160. case float64:
  161. floats := make([]float64, len(items))
  162. for i, item := range items {
  163. floats[i] = item.(float64)
  164. }
  165. values[k] = floats
  166. case int:
  167. ints := make([]int, len(items))
  168. for i, item := range items {
  169. ints[i] = item.(int)
  170. }
  171. values[k] = ints
  172. case string:
  173. strings := make([]string, len(items))
  174. for i, item := range items {
  175. strings[i] = item.(string)
  176. }
  177. values[k] = strings
  178. }
  179. }
  180. }
  181. }