232 lines
5.0 KiB
Go
232 lines
5.0 KiB
Go
// Copyright 2011 The Walk Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// +build windows
|
|
|
|
package walk
|
|
|
|
import (
|
|
"bufio"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const iniFileTimeStampFormat = "2006-01-02"
|
|
|
|
type IniFileSettings struct {
|
|
fileName string
|
|
key2Record map[string]iniFileRecord
|
|
expireDuration time.Duration
|
|
portable bool
|
|
}
|
|
|
|
type iniFileRecord struct {
|
|
value string
|
|
timestamp time.Time
|
|
}
|
|
|
|
func NewIniFileSettings(fileName string) *IniFileSettings {
|
|
return &IniFileSettings{
|
|
fileName: fileName,
|
|
key2Record: make(map[string]iniFileRecord),
|
|
}
|
|
}
|
|
|
|
func (ifs *IniFileSettings) Get(key string) (string, bool) {
|
|
record, ok := ifs.key2Record[key]
|
|
return record.value, ok
|
|
}
|
|
|
|
func (ifs *IniFileSettings) Timestamp(key string) (time.Time, bool) {
|
|
record, ok := ifs.key2Record[key]
|
|
return record.timestamp, ok
|
|
}
|
|
|
|
func (ifs *IniFileSettings) Put(key, value string) error {
|
|
return ifs.put(key, value, false)
|
|
}
|
|
|
|
func (ifs *IniFileSettings) PutExpiring(key, value string) error {
|
|
return ifs.put(key, value, true)
|
|
}
|
|
|
|
func (ifs *IniFileSettings) put(key, value string, expiring bool) error {
|
|
if key == "" {
|
|
return newError("key must not be empty")
|
|
}
|
|
if strings.IndexAny(key, "|=\r\n") > -1 {
|
|
return newError("key contains at least one of the invalid characters '|=\\r\\n'")
|
|
}
|
|
if strings.IndexAny(value, "\r\n") > -1 {
|
|
return newError("value contains at least one of the invalid characters '\\r\\n'")
|
|
}
|
|
|
|
var timestamp time.Time
|
|
if expiring {
|
|
timestamp = time.Now()
|
|
}
|
|
|
|
ifs.key2Record[key] = iniFileRecord{value, timestamp}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ifs *IniFileSettings) Remove(key string) error {
|
|
delete(ifs.key2Record, key)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ifs *IniFileSettings) ExpireDuration() time.Duration {
|
|
return ifs.expireDuration
|
|
}
|
|
|
|
func (ifs *IniFileSettings) SetExpireDuration(expireDuration time.Duration) {
|
|
ifs.expireDuration = expireDuration
|
|
}
|
|
|
|
func (ifs *IniFileSettings) Portable() bool {
|
|
return ifs.portable
|
|
}
|
|
|
|
func (ifs *IniFileSettings) SetPortable(portable bool) {
|
|
ifs.portable = portable
|
|
}
|
|
|
|
func (ifs *IniFileSettings) FilePath() string {
|
|
if ifs.portable {
|
|
absPath, err := filepath.Abs(ifs.fileName)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
return absPath
|
|
}
|
|
|
|
appDataPath, err := AppDataPath()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
return filepath.Join(
|
|
appDataPath,
|
|
App().OrganizationName(),
|
|
App().ProductName(),
|
|
ifs.fileName)
|
|
}
|
|
|
|
func (ifs *IniFileSettings) fileExists() (bool, error) {
|
|
filePath := ifs.FilePath()
|
|
|
|
if _, err := os.Stat(filePath); err != nil {
|
|
// FIXME: Not necessarily a file does not exist error.
|
|
return false, nil
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (ifs *IniFileSettings) withFile(flags int, f func(file *os.File) error) error {
|
|
filePath := ifs.FilePath()
|
|
|
|
dirPath, _ := filepath.Split(filePath)
|
|
if err := os.MkdirAll(dirPath, 0644); err != nil {
|
|
return wrapError(err)
|
|
}
|
|
|
|
file, err := os.OpenFile(filePath, flags, 0644)
|
|
if err != nil {
|
|
return wrapError(err)
|
|
}
|
|
defer file.Close()
|
|
|
|
return f(file)
|
|
}
|
|
|
|
func (ifs *IniFileSettings) Load() error {
|
|
exists, err := ifs.fileExists()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !exists {
|
|
return nil
|
|
}
|
|
|
|
return ifs.withFile(os.O_RDONLY, func(file *os.File) error {
|
|
scanner := bufio.NewScanner(file)
|
|
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
|
|
assignIndex := strings.Index(line, "=")
|
|
if assignIndex == -1 {
|
|
return newError("bad line format: missing '='")
|
|
}
|
|
|
|
key := strings.TrimSpace(line[:assignIndex])
|
|
|
|
var ts time.Time
|
|
if parts := strings.Split(key, "|"); len(parts) > 1 {
|
|
key = parts[0]
|
|
if ts, _ = time.Parse(iniFileTimeStampFormat, parts[1]); ts.IsZero() {
|
|
ts = time.Now()
|
|
}
|
|
}
|
|
|
|
value := strings.TrimSpace(line[assignIndex+1:])
|
|
|
|
ifs.key2Record[key] = iniFileRecord{value, ts}
|
|
}
|
|
|
|
return scanner.Err()
|
|
})
|
|
}
|
|
|
|
func (ifs *IniFileSettings) Save() error {
|
|
return ifs.withFile(os.O_CREATE|os.O_TRUNC|os.O_WRONLY, func(file *os.File) error {
|
|
bufWriter := bufio.NewWriter(file)
|
|
|
|
keys := make([]string, 0, len(ifs.key2Record))
|
|
|
|
for key, record := range ifs.key2Record {
|
|
if ifs.expireDuration <= 0 || record.timestamp.IsZero() || time.Since(record.timestamp) < ifs.expireDuration {
|
|
keys = append(keys, key)
|
|
}
|
|
}
|
|
|
|
sort.Strings(keys)
|
|
|
|
for _, key := range keys {
|
|
record := ifs.key2Record[key]
|
|
|
|
if _, err := bufWriter.WriteString(key); err != nil {
|
|
return wrapError(err)
|
|
}
|
|
if !record.timestamp.IsZero() {
|
|
if _, err := bufWriter.WriteString("|"); err != nil {
|
|
return wrapError(err)
|
|
}
|
|
if _, err := bufWriter.WriteString(record.timestamp.Format(iniFileTimeStampFormat)); err != nil {
|
|
return wrapError(err)
|
|
}
|
|
}
|
|
if _, err := bufWriter.WriteString("="); err != nil {
|
|
return wrapError(err)
|
|
}
|
|
if _, err := bufWriter.WriteString(record.value); err != nil {
|
|
return wrapError(err)
|
|
}
|
|
if _, err := bufWriter.WriteString("\r\n"); err != nil {
|
|
return wrapError(err)
|
|
}
|
|
}
|
|
|
|
return bufWriter.Flush()
|
|
})
|
|
}
|