2016-07-01 03:18:55 +00:00
|
|
|
package core
|
|
|
|
|
|
|
|
import (
|
2016-08-02 23:04:39 +00:00
|
|
|
"encoding/json"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
2016-08-29 03:36:42 +00:00
|
|
|
"os"
|
|
|
|
"sync"
|
2016-07-01 03:18:55 +00:00
|
|
|
)
|
|
|
|
|
2016-10-27 19:57:04 +00:00
|
|
|
// XXX This is partially but not fully compatible with Ricochet's JSON
|
|
|
|
// configs. It might be better to be explicitly not compatible, but have
|
|
|
|
// an automatic import function.
|
|
|
|
|
2016-07-01 03:18:55 +00:00
|
|
|
type Config struct {
|
2016-08-29 03:36:42 +00:00
|
|
|
filePath string
|
|
|
|
|
|
|
|
root ConfigRoot
|
|
|
|
mutex sync.RWMutex
|
2016-07-01 03:18:55 +00:00
|
|
|
}
|
|
|
|
|
2016-08-02 23:04:39 +00:00
|
|
|
type ConfigRoot struct {
|
|
|
|
Contacts map[string]ConfigContact
|
|
|
|
Identity ConfigIdentity
|
2016-08-29 03:36:42 +00:00
|
|
|
|
|
|
|
// Not used by the permanent instance in Config
|
|
|
|
writable bool
|
|
|
|
config *Config
|
2016-08-02 23:04:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type ConfigContact struct {
|
|
|
|
Hostname string
|
2016-10-27 19:57:04 +00:00
|
|
|
LastConnected string `json:",omitempty"`
|
2016-08-02 23:04:39 +00:00
|
|
|
Nickname string
|
|
|
|
WhenCreated string
|
2016-10-27 19:57:04 +00:00
|
|
|
Request ConfigContactRequest `json:",omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type ConfigContactRequest struct {
|
|
|
|
Pending bool
|
|
|
|
MyNickname string
|
|
|
|
Message string
|
|
|
|
WhenDelivered string `json:",omitempty"`
|
|
|
|
WhenRejected string `json:",omitempty"`
|
|
|
|
RemoteError string `json:",omitempty"`
|
2016-08-02 23:04:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type ConfigIdentity struct {
|
2016-10-27 19:57:04 +00:00
|
|
|
DataDirectory string `json:",omitempty"`
|
|
|
|
ServiceKey string
|
2016-08-02 23:04:39 +00:00
|
|
|
}
|
|
|
|
|
2016-11-05 22:48:54 +00:00
|
|
|
func LoadConfig(filePath string) (*Config, error) {
|
2016-08-29 03:36:42 +00:00
|
|
|
config := &Config{
|
2016-11-05 22:48:54 +00:00
|
|
|
filePath: filePath,
|
2016-08-29 03:36:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
data, err := ioutil.ReadFile(config.filePath)
|
2016-08-02 23:04:39 +00:00
|
|
|
if err != nil {
|
2016-08-29 03:36:42 +00:00
|
|
|
log.Printf("Config read error from %s: %v", config.filePath, err)
|
2016-08-02 23:04:39 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2016-08-29 03:36:42 +00:00
|
|
|
if err := json.Unmarshal(data, &config.root); err != nil {
|
2016-08-02 23:04:39 +00:00
|
|
|
log.Printf("Config parse error: %v", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2016-08-29 03:36:42 +00:00
|
|
|
return config, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return a read-only snapshot of the current configuration. This object
|
|
|
|
// _must not_ be stored, and you must call Close() when finished with it.
|
|
|
|
// This function may block.
|
|
|
|
func (c *Config) OpenRead() *ConfigRoot {
|
|
|
|
c.mutex.RLock()
|
2016-11-06 02:05:37 +00:00
|
|
|
root := c.root.Clone()
|
2016-08-29 03:36:42 +00:00
|
|
|
root.writable = false
|
|
|
|
root.config = c
|
2016-11-06 02:05:37 +00:00
|
|
|
return root
|
2016-08-29 03:36:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Return a writable snapshot of the current configuration. This object
|
|
|
|
// _must not_ be stored, and you must call Save() or Discard() when
|
|
|
|
// finished with it. This function may block.
|
|
|
|
func (c *Config) OpenWrite() *ConfigRoot {
|
|
|
|
c.mutex.Lock()
|
2016-11-06 02:05:37 +00:00
|
|
|
root := c.root.Clone()
|
2016-08-29 03:36:42 +00:00
|
|
|
root.writable = true
|
|
|
|
root.config = c
|
2016-11-06 02:05:37 +00:00
|
|
|
return root
|
|
|
|
}
|
|
|
|
|
|
|
|
func (root *ConfigRoot) Clone() *ConfigRoot {
|
|
|
|
re := *root
|
|
|
|
re.Contacts = make(map[string]ConfigContact)
|
|
|
|
for k, v := range root.Contacts {
|
|
|
|
re.Contacts[k] = v
|
|
|
|
}
|
|
|
|
return &re
|
2016-08-29 03:36:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (root *ConfigRoot) Close() {
|
|
|
|
if root.writable {
|
|
|
|
log.Panic("Close called on writable config object; use Save or Discard")
|
|
|
|
}
|
|
|
|
if root.config == nil {
|
|
|
|
log.Panic("Close called on closed config object")
|
|
|
|
}
|
|
|
|
root.config.mutex.RUnlock()
|
|
|
|
root.config = nil
|
|
|
|
}
|
|
|
|
|
2016-11-06 02:05:37 +00:00
|
|
|
// Save writes the state to disk, and updates the Config object if
|
|
|
|
// successful. Changes to the object are discarded on error.
|
2016-08-29 03:36:42 +00:00
|
|
|
func (root *ConfigRoot) Save() error {
|
|
|
|
if !root.writable {
|
|
|
|
log.Panic("Save called on read-only config object")
|
|
|
|
}
|
|
|
|
if root.config == nil {
|
|
|
|
log.Panic("Save called on closed config object")
|
|
|
|
}
|
|
|
|
c := root.config
|
|
|
|
root.writable = false
|
|
|
|
root.config = nil
|
2016-11-06 02:05:37 +00:00
|
|
|
err := c.save(root)
|
2016-08-29 03:36:42 +00:00
|
|
|
c.mutex.Unlock()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-11-06 02:05:37 +00:00
|
|
|
// Discard closes a config write without saving any changes to disk
|
|
|
|
// or to the Config object.
|
2016-08-29 03:36:42 +00:00
|
|
|
func (root *ConfigRoot) Discard() {
|
|
|
|
if !root.writable {
|
|
|
|
log.Panic("Discard called on read-only config object")
|
|
|
|
}
|
|
|
|
if root.config == nil {
|
|
|
|
log.Panic("Discard called on closed config object")
|
|
|
|
}
|
|
|
|
c := root.config
|
|
|
|
root.config = nil
|
|
|
|
c.mutex.Unlock()
|
|
|
|
}
|
|
|
|
|
2016-11-06 02:05:37 +00:00
|
|
|
func (c *Config) save(newRoot *ConfigRoot) error {
|
|
|
|
data, err := json.MarshalIndent(newRoot, "", " ")
|
2016-08-29 03:36:42 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("Config encoding error: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 := c.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
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := file.Write(data); err != nil {
|
|
|
|
log.Printf("Config write error: %v", err)
|
|
|
|
file.Close()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
file.Close()
|
|
|
|
if err := os.Rename(tempPath, c.filePath); err != nil {
|
|
|
|
log.Printf("Config replace error: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
2016-08-02 23:04:39 +00:00
|
|
|
|
2016-11-06 02:05:37 +00:00
|
|
|
c.root = *newRoot
|
2016-08-29 03:36:42 +00:00
|
|
|
return nil
|
2016-07-01 03:18:55 +00:00
|
|
|
}
|