ricochet-go/core/config/config.go

116 lines
2.9 KiB
Go
Raw Normal View History

package config
import (
"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
"github.com/ricochet-im/ricochet-go/rpc"
"log"
"os"
"sync"
"sync/atomic"
)
type ConfigFile struct {
filePath string
root *ricochet.Config
readSnapshot atomic.Value
mutex sync.Mutex
}
func NewConfigFile(path string) (*ConfigFile, error) {
cfg := &ConfigFile{
filePath: path,
root: &ricochet.Config{},
}
cfg.readSnapshot.Store(cfg.root)
if err := cfg.save(); err != nil {
return nil, err
}
return cfg, nil
}
func LoadConfigFile(path string) (*ConfigFile, error) {
cfg := &ConfigFile{
filePath: path,
root: &ricochet.Config{},
}
file, err := os.Open(cfg.filePath)
if err != nil {
return nil, err
}
defer file.Close()
json := jsonpb.Unmarshaler{
AllowUnknownFields: true,
}
if err := json.Unmarshal(file, cfg.root); err != nil {
return nil, err
}
cfg.readSnapshot.Store(cfg.root)
return cfg, nil
}
// Read returns a **read-only** snapshot of the current configuration. This
// function is threadsafe, and the values in this instance of the configuration
// will not change when the configuration changes.
//
// Do not under any circumstances modify any part of this object.
func (cfg *ConfigFile) Read() *ricochet.Config {
return cfg.readSnapshot.Load().(*ricochet.Config)
}
// Lock gains exclusive control of the configuration state, allowing the caller
// to safely make changes to the configuration. Concurrent readers will not see
// any changes until Unlock() is called, at which point they will atomically be
// visible in future calls to Read() as well as being saved persistently.
func (cfg *ConfigFile) Lock() *ricochet.Config {
cfg.mutex.Lock()
// Clone the current configuration for a mutable copy
cfg.root = proto.Clone(cfg.root).(*ricochet.Config)
return cfg.root
}
func (cfg *ConfigFile) Unlock() {
// Clone cfg.root again to guarantee that any messages are detached from
// instances that exist elsewhere in the code. Inefficient but safe.
cfg.root = proto.Clone(cfg.root).(*ricochet.Config)
cfg.readSnapshot.Store(cfg.root)
err := cfg.save()
if err != nil {
log.Printf("WARNING: Unable to save configuration: %s", err)
}
cfg.mutex.Unlock()
}
func (cfg *ConfigFile) save() error {
json := jsonpb.Marshaler{Indent: " "}
// Make a pathetic attempt at atomic file write by writing into a
// temporary file and renaming over the original; this is probably
// imperfect as-implemented, but better than truncating and writing
// directly.
tempPath := cfg.filePath + ".new"
file, err := os.OpenFile(tempPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
if err != nil {
log.Printf("Config save error: %v", err)
return err
}
err = json.Marshal(file, cfg.root)
if err != nil {
log.Printf("Config encoding error: %v", err)
file.Close()
return err
}
file.Close()
if err := os.Rename(tempPath, cfg.filePath); err != nil {
log.Printf("Config replace error: %v", err)
return err
}
return nil
}