inifilesettings.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. // Copyright 2011 The Walk Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // +build windows
  5. package walk
  6. import (
  7. "bufio"
  8. "os"
  9. "path/filepath"
  10. "sort"
  11. "strings"
  12. "time"
  13. )
  14. const iniFileTimeStampFormat = "2006-01-02"
  15. type IniFileSettings struct {
  16. fileName string
  17. key2Record map[string]iniFileRecord
  18. expireDuration time.Duration
  19. }
  20. type iniFileRecord struct {
  21. value string
  22. timestamp time.Time
  23. }
  24. func NewIniFileSettings(fileName string) *IniFileSettings {
  25. return &IniFileSettings{
  26. fileName: fileName,
  27. key2Record: make(map[string]iniFileRecord),
  28. }
  29. }
  30. func (ifs *IniFileSettings) Get(key string) (string, bool) {
  31. record, ok := ifs.key2Record[key]
  32. return record.value, ok
  33. }
  34. func (ifs *IniFileSettings) Timestamp(key string) (time.Time, bool) {
  35. record, ok := ifs.key2Record[key]
  36. return record.timestamp, ok
  37. }
  38. func (ifs *IniFileSettings) Put(key, value string) error {
  39. return ifs.put(key, value, false)
  40. }
  41. func (ifs *IniFileSettings) PutExpiring(key, value string) error {
  42. return ifs.put(key, value, true)
  43. }
  44. func (ifs *IniFileSettings) put(key, value string, expiring bool) error {
  45. if key == "" {
  46. return newError("key must not be empty")
  47. }
  48. if strings.IndexAny(key, "|=\r\n") > -1 {
  49. return newError("key contains at least one of the invalid characters '|=\\r\\n'")
  50. }
  51. if strings.IndexAny(value, "\r\n") > -1 {
  52. return newError("value contains at least one of the invalid characters '\\r\\n'")
  53. }
  54. var timestamp time.Time
  55. if expiring {
  56. timestamp = time.Now()
  57. }
  58. ifs.key2Record[key] = iniFileRecord{value, timestamp}
  59. return nil
  60. }
  61. func (ifs *IniFileSettings) Remove(key string) error {
  62. delete(ifs.key2Record, key)
  63. return nil
  64. }
  65. func (ifs *IniFileSettings) ExpireDuration() time.Duration {
  66. return ifs.expireDuration
  67. }
  68. func (ifs *IniFileSettings) SetExpireDuration(expireDuration time.Duration) {
  69. ifs.expireDuration = expireDuration
  70. }
  71. func (ifs *IniFileSettings) FilePath() string {
  72. appDataPath, err := AppDataPath()
  73. if err != nil {
  74. return ""
  75. }
  76. return filepath.Join(
  77. appDataPath,
  78. appSingleton.OrganizationName(),
  79. appSingleton.ProductName(),
  80. ifs.fileName)
  81. }
  82. func (ifs *IniFileSettings) fileExists() (bool, error) {
  83. filePath := ifs.FilePath()
  84. if _, err := os.Stat(filePath); err != nil {
  85. // FIXME: Not necessarily a file does not exist error.
  86. return false, nil
  87. }
  88. return true, nil
  89. }
  90. func (ifs *IniFileSettings) withFile(flags int, f func(file *os.File) error) error {
  91. filePath := ifs.FilePath()
  92. dirPath, _ := filepath.Split(filePath)
  93. if err := os.MkdirAll(dirPath, 0644); err != nil {
  94. return wrapError(err)
  95. }
  96. file, err := os.OpenFile(filePath, flags, 0644)
  97. if err != nil {
  98. return wrapError(err)
  99. }
  100. defer file.Close()
  101. return f(file)
  102. }
  103. func (ifs *IniFileSettings) Load() error {
  104. exists, err := ifs.fileExists()
  105. if err != nil {
  106. return err
  107. }
  108. if !exists {
  109. return nil
  110. }
  111. return ifs.withFile(os.O_RDONLY, func(file *os.File) error {
  112. scanner := bufio.NewScanner(file)
  113. for scanner.Scan() {
  114. line := scanner.Text()
  115. assignIndex := strings.Index(line, "=")
  116. if assignIndex == -1 {
  117. return newError("bad line format: missing '='")
  118. }
  119. key := strings.TrimSpace(line[:assignIndex])
  120. var ts time.Time
  121. if parts := strings.Split(key, "|"); len(parts) > 1 {
  122. key = parts[0]
  123. if ts, _ = time.Parse(iniFileTimeStampFormat, parts[1]); ts.IsZero() {
  124. ts = time.Now()
  125. }
  126. }
  127. value := strings.TrimSpace(line[assignIndex+1:])
  128. ifs.key2Record[key] = iniFileRecord{value, ts}
  129. }
  130. return scanner.Err()
  131. })
  132. }
  133. func (ifs *IniFileSettings) Save() error {
  134. return ifs.withFile(os.O_CREATE|os.O_TRUNC|os.O_WRONLY, func(file *os.File) error {
  135. bufWriter := bufio.NewWriter(file)
  136. keys := make([]string, 0, len(ifs.key2Record))
  137. for key, record := range ifs.key2Record {
  138. if ifs.expireDuration <= 0 || record.timestamp.IsZero() || time.Since(record.timestamp) < ifs.expireDuration {
  139. keys = append(keys, key)
  140. }
  141. }
  142. sort.Strings(keys)
  143. for _, key := range keys {
  144. record := ifs.key2Record[key]
  145. if _, err := bufWriter.WriteString(key); err != nil {
  146. return wrapError(err)
  147. }
  148. if !record.timestamp.IsZero() {
  149. if _, err := bufWriter.WriteString("|"); err != nil {
  150. return wrapError(err)
  151. }
  152. if _, err := bufWriter.WriteString(record.timestamp.Format(iniFileTimeStampFormat)); err != nil {
  153. return wrapError(err)
  154. }
  155. }
  156. if _, err := bufWriter.WriteString("="); err != nil {
  157. return wrapError(err)
  158. }
  159. if _, err := bufWriter.WriteString(record.value); err != nil {
  160. return wrapError(err)
  161. }
  162. if _, err := bufWriter.WriteString("\r\n"); err != nil {
  163. return wrapError(err)
  164. }
  165. }
  166. return bufWriter.Flush()
  167. })
  168. }