提交 565505c4 作者: Juan Batiz-Benet

Merge pull request #917 from jbenet/repo-cleanup

Repo cleanup
package component
import (
"io"
"github.com/jbenet/go-ipfs/repo/config"
)
type Component interface {
Open(*config.Config) error
io.Closer
SetPath(string)
}
type Initializer func(path string, conf *config.Config) error
type InitializationChecker func(path string) bool
package component
import (
"strconv"
common "github.com/jbenet/go-ipfs/repo/common"
config "github.com/jbenet/go-ipfs/repo/config"
serialize "github.com/jbenet/go-ipfs/repo/fsrepo/serialize"
util "github.com/jbenet/go-ipfs/util"
)
var _ Component = &ConfigComponent{}
var _ Initializer = InitConfigComponent
var _ InitializationChecker = ConfigComponentIsInitialized
// ConfigComponent abstracts the config component of the FSRepo.
// NB: create with makeConfigComponent function.
// NOT THREAD-SAFE
type ConfigComponent struct {
path string // required at instantiation
config *config.Config // assigned on Open()
}
// fsrepoConfigInit initializes the FSRepo's ConfigComponent.
func InitConfigComponent(path string, conf *config.Config) error {
if ConfigComponentIsInitialized(path) {
return nil
}
configFilename, err := config.Filename(path)
if err != nil {
return err
}
// initialization is the one time when it's okay to write to the config
// without reading the config from disk and merging any user-provided keys
// that may exist.
if err := serialize.WriteConfigFile(configFilename, conf); err != nil {
return err
}
return nil
}
// Open returns an error if the config file is not present. This component is
// always called with a nil config parameter. Other components rely on the
// config, to keep the interface uniform, it is special-cased.
func (c *ConfigComponent) Open(_ *config.Config) error {
configFilename, err := config.Filename(c.path)
if err != nil {
return err
}
conf, err := serialize.Load(configFilename)
if err != nil {
return err
}
c.config = conf
return nil
}
// Close satisfies the fsrepoComponent interface.
func (c *ConfigComponent) Close() error {
return nil // config doesn't need to be closed.
}
func (c *ConfigComponent) Config() *config.Config {
return c.config
}
// SetConfig updates the config file.
func (c *ConfigComponent) SetConfig(updated *config.Config) error {
return c.setConfigUnsynced(updated)
}
// GetConfigKey retrieves only the value of a particular key.
func (c *ConfigComponent) GetConfigKey(key string) (interface{}, error) {
filename, err := config.Filename(c.path)
if err != nil {
return nil, err
}
var cfg map[string]interface{}
if err := serialize.ReadConfigFile(filename, &cfg); err != nil {
return nil, err
}
return common.MapGetKV(cfg, key)
}
// SetConfigKey writes the value of a particular key.
func (c *ConfigComponent) SetConfigKey(key string, value interface{}) error {
filename, err := config.Filename(c.path)
if err != nil {
return err
}
switch v := value.(type) {
case string:
if i, err := strconv.Atoi(v); err == nil {
value = i
}
}
var mapconf map[string]interface{}
if err := serialize.ReadConfigFile(filename, &mapconf); err != nil {
return err
}
if err := common.MapSetKV(mapconf, key, value); err != nil {
return err
}
conf, err := config.FromMap(mapconf)
if err != nil {
return err
}
if err := serialize.WriteConfigFile(filename, mapconf); err != nil {
return err
}
return c.setConfigUnsynced(conf) // TODO roll this into this method
}
func (c *ConfigComponent) SetPath(p string) {
c.path = p
}
// ConfigComponentIsInitialized returns true if the repo is initialized at
// provided |path|.
func ConfigComponentIsInitialized(path string) bool {
configFilename, err := config.Filename(path)
if err != nil {
return false
}
if !util.FileExists(configFilename) {
return false
}
return true
}
// setConfigUnsynced is for private use.
func (r *ConfigComponent) setConfigUnsynced(updated *config.Config) error {
configFilename, err := config.Filename(r.path)
if err != nil {
return err
}
// to avoid clobbering user-provided keys, must read the config from disk
// as a map, write the updated struct values to the map and write the map
// to disk.
var mapconf map[string]interface{}
if err := serialize.ReadConfigFile(configFilename, &mapconf); err != nil {
return err
}
m, err := config.ToMap(updated)
if err != nil {
return err
}
for k, v := range m {
mapconf[k] = v
}
if err := serialize.WriteConfigFile(configFilename, mapconf); err != nil {
return err
}
*r.config = *updated // copy so caller cannot modify this private config
return nil
}
package component
import (
"errors"
"path"
"sync"
datastore "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-datastore"
levelds "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-datastore/leveldb"
ldbopts "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/opt"
config "github.com/jbenet/go-ipfs/repo/config"
counter "github.com/jbenet/go-ipfs/repo/fsrepo/counter"
dir "github.com/jbenet/go-ipfs/thirdparty/dir"
util "github.com/jbenet/go-ipfs/util"
ds2 "github.com/jbenet/go-ipfs/util/datastore2"
debugerror "github.com/jbenet/go-ipfs/util/debugerror"
)
const (
DefaultDataStoreDirectory = "datastore"
)
var (
_ Component = &DatastoreComponent{}
_ Initializer = InitDatastoreComponent
_ InitializationChecker = DatastoreComponentIsInitialized
dsLock sync.Mutex // protects openersCounter and datastores
openersCounter *counter.Openers
datastores map[string]ds2.ThreadSafeDatastoreCloser
)
func init() {
openersCounter = counter.NewOpenersCounter()
datastores = make(map[string]ds2.ThreadSafeDatastoreCloser)
}
func InitDatastoreComponent(dspath string, conf *config.Config) error {
// The actual datastore contents are initialized lazily when Opened.
// During Init, we merely check that the directory is writeable.
p := path.Join(dspath, DefaultDataStoreDirectory)
if err := dir.Writable(p); err != nil {
return debugerror.Errorf("datastore: %s", err)
}
return nil
}
// DatastoreComponentIsInitialized returns true if the datastore dir exists.
func DatastoreComponentIsInitialized(dspath string) bool {
if !util.FileExists(path.Join(dspath, DefaultDataStoreDirectory)) {
return false
}
return true
}
// DatastoreComponent abstracts the datastore component of the FSRepo.
type DatastoreComponent struct {
path string // required
ds ds2.ThreadSafeDatastoreCloser // assigned when repo is opened
}
func (dsc *DatastoreComponent) SetPath(p string) {
dsc.path = path.Join(p, DefaultDataStoreDirectory)
}
func (dsc *DatastoreComponent) Datastore() datastore.ThreadSafeDatastore { return dsc.ds }
// Open returns an error if the config file is not present.
func (dsc *DatastoreComponent) Open(*config.Config) error {
dsLock.Lock()
defer dsLock.Unlock()
// if no other goroutines have the datastore Open, initialize it and assign
// it to the package-scoped map for the goroutines that follow.
if openersCounter.NumOpeners(dsc.path) == 0 {
ds, err := levelds.NewDatastore(dsc.path, &levelds.Options{
Compression: ldbopts.NoCompression,
})
if err != nil {
return debugerror.New("unable to open leveldb datastore")
}
datastores[dsc.path] = ds
}
// get the datastore from the package-scoped map and record self as an
// opener.
ds, dsIsPresent := datastores[dsc.path]
if !dsIsPresent {
// This indicates a programmer error has occurred.
return errors.New("datastore should be available, but it isn't")
}
dsc.ds = ds
openersCounter.AddOpener(dsc.path) // only after success
return nil
}
func (dsc *DatastoreComponent) Close() error {
dsLock.Lock()
defer dsLock.Unlock()
// decrement the Opener count. if this goroutine is the last, also close
// the underlying datastore (and remove its reference from the map)
openersCounter.RemoveOpener(dsc.path)
if openersCounter.NumOpeners(dsc.path) == 0 {
delete(datastores, dsc.path) // remove the reference
return dsc.ds.Close()
}
return nil
}
package component
import (
"io/ioutil"
"path/filepath"
"testing"
"github.com/jbenet/go-ipfs/thirdparty/assert"
)
// swap arg order
func testRepoPath(t *testing.T, path ...string) string {
name, err := ioutil.TempDir("", filepath.Join(path...))
if err != nil {
t.Fatal(err)
}
return name
}
func TestOpenMoreThanOnceInSameProcess(t *testing.T) {
t.Parallel()
path := testRepoPath(t)
dsc1 := DatastoreComponent{path: path}
dsc2 := DatastoreComponent{path: path}
assert.Nil(dsc1.Open(nil), t, "first repo should open successfully")
assert.Nil(dsc2.Open(nil), t, "second repo should open successfully")
assert.Nil(dsc1.Close(), t)
assert.Nil(dsc2.Close(), t)
}
package component
import (
"os"
"path"
config "github.com/jbenet/go-ipfs/repo/config"
dir "github.com/jbenet/go-ipfs/thirdparty/dir"
eventlog "github.com/jbenet/go-ipfs/thirdparty/eventlog"
)
func InitEventlogComponent(repoPath string, conf *config.Config) error {
if err := dir.Writable(path.Join(repoPath, "logs")); err != nil {
return err
}
return nil
}
func EventlogComponentIsInitialized(path string) bool {
return true
}
type EventlogComponent struct {
path string
}
func (c *EventlogComponent) SetPath(path string) {
c.path = path // FIXME necessary?
}
func (c *EventlogComponent) Close() error {
// TODO It isn't part of the current contract, but callers may like for us
// to disable logging once the component is closed.
eventlog.Configure(eventlog.Output(os.Stderr))
return nil
}
func (c *EventlogComponent) Open(config *config.Config) error {
// log.Debugf("writing eventlogs to ...", c.path)
return configureEventLoggerAtRepoPath(config, c.path)
}
func configureEventLoggerAtRepoPath(c *config.Config, repoPath string) error {
eventlog.Configure(eventlog.LevelInfo)
eventlog.Configure(eventlog.LdJSONFormatter)
rotateConf := eventlog.LogRotatorConfig{
Filename: path.Join(repoPath, "logs", "events.log"),
MaxSizeMB: c.Log.MaxSizeMB,
MaxBackups: c.Log.MaxBackups,
MaxAgeDays: c.Log.MaxAgeDays,
}
eventlog.Configure(eventlog.OutputRotatingLogFile(rotateConf))
return nil
}
var _ Component = &EventlogComponent{}
......@@ -135,6 +135,7 @@ func TestOpenMoreThanOnceInSameProcess(t *testing.T) {
r2 := At(path)
assert.Nil(r1.Open(), t, "first repo should open successfully")
assert.Nil(r2.Open(), t, "second repo should open successfully")
assert.True(r1.ds == r2.ds, t, "repos should share the datastore")
assert.Nil(r1.Close(), t)
assert.Nil(r2.Close(), t)
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论