datastore.go 3.4 KB
Newer Older
1 2 3
package component

import (
4 5 6
	"errors"
	"sync"

7 8 9 10
	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"
11
	counter "github.com/jbenet/go-ipfs/repo/fsrepo/counter"
12 13 14 15 16 17
	dir "github.com/jbenet/go-ipfs/repo/fsrepo/dir"
	util "github.com/jbenet/go-ipfs/util"
	ds2 "github.com/jbenet/go-ipfs/util/datastore2"
	debugerror "github.com/jbenet/go-ipfs/util/debugerror"
)

18 19 20 21 22 23 24 25 26 27 28 29 30 31
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)
}
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59

func InitDatastoreComponent(path string, conf *config.Config) error {
	// The actual datastore contents are initialized lazily when Opened.
	// During Init, we merely check that the directory is writeable.
	dspath, err := config.DataStorePath(path)
	if err != nil {
		return err
	}
	if err := dir.Writable(dspath); err != nil {
		return debugerror.Errorf("datastore: %s", err)
	}
	return nil
}

// DatastoreComponentIsInitialized returns true if the datastore dir exists.
func DatastoreComponentIsInitialized(path string) bool {
	dspath, err := config.DataStorePath(path)
	if err != nil {
		return false
	}
	if !util.FileExists(dspath) {
		return false
	}
	return true
}

// DatastoreComponent abstracts the datastore component of the FSRepo.
type DatastoreComponent struct {
60 61
	path string                        // required
	ds   ds2.ThreadSafeDatastoreCloser // assigned when repo is opened
62 63
}

64 65 66
func (dsc *DatastoreComponent) SetPath(p string)                         { dsc.path = p }
func (dsc *DatastoreComponent) Datastore() datastore.ThreadSafeDatastore { return dsc.ds }

67 68
// Open returns an error if the config file is not present.
func (dsc *DatastoreComponent) Open() error {
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90

	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 errors.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")
91 92
	}
	dsc.ds = ds
93
	openersCounter.AddOpener(dsc.path) // only after success
94 95 96
	return nil
}

97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
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
}