forked from cerc-io/ipld-eth-server
fixes for issues uncovered in integration
This commit is contained in:
+32
@@ -0,0 +1,32 @@
|
||||
os:
|
||||
- linux
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.11.x
|
||||
|
||||
env:
|
||||
global:
|
||||
- GOTFLAGS="-race"
|
||||
matrix:
|
||||
- BUILD_DEPTYPE=gx
|
||||
- BUILD_DEPTYPE=gomod
|
||||
|
||||
|
||||
# disable travis install
|
||||
install:
|
||||
- true
|
||||
|
||||
script:
|
||||
- bash <(curl -s https://raw.githubusercontent.com/ipfs/ci-helpers/master/travis-ci/run-standard-tests.sh)
|
||||
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $GOPATH/src/gx
|
||||
- $GOPATH/pkg/mod
|
||||
- $HOME/.cache/go-build
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2016 Łukasz Magiera
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
|
||||
test: deps
|
||||
go test -race -v ./...
|
||||
|
||||
export IPFS_API ?= v04x.ipfs.io
|
||||
|
||||
gx:
|
||||
go get github.com/whyrusleeping/gx
|
||||
go get github.com/whyrusleeping/gx-go
|
||||
|
||||
deps: gx
|
||||
gx --verbose install --global
|
||||
gx-go rewrite
|
||||
go get -t ./...
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
# go-ds-badger
|
||||
|
||||
[](http://ipn.io)
|
||||
[](http://ipfs.io/)
|
||||
[](http://webchat.freenode.net/?channels=%23ipfs)
|
||||
[](https://github.com/RichardLitt/standard-readme)
|
||||
[](https://godoc.org/github.com/ipfs/go-ds-badger)
|
||||
[](https://travis-ci.org/ipfs/go-ds-badger)
|
||||
|
||||
> Datastore implementation using [badger](https://github.com/dgraph-io/badger) as backend.
|
||||
|
||||
## Documentation
|
||||
|
||||
https://godoc.org/github.com/ipfs/go-ds-badger
|
||||
|
||||
## Contribute
|
||||
|
||||
Feel free to join in. All welcome. Open an [issue](https://github.com/ipfs/go-ds-badger/issues)!
|
||||
|
||||
This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md).
|
||||
|
||||
### Want to hack on IPFS?
|
||||
|
||||
[](https://github.com/ipfs/community/blob/master/contributing.md)
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
coverage:
|
||||
range: "50...100"
|
||||
comment: off
|
||||
+608
@@ -0,0 +1,608 @@
|
||||
package badger
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
badger "github.com/dgraph-io/badger"
|
||||
ds "github.com/ipfs/go-datastore"
|
||||
dsq "github.com/ipfs/go-datastore/query"
|
||||
logger "github.com/ipfs/go-log"
|
||||
goprocess "github.com/jbenet/goprocess"
|
||||
)
|
||||
|
||||
var log = logger.Logger("badger")
|
||||
|
||||
var ErrClosed = errors.New("datastore closed")
|
||||
|
||||
type Datastore struct {
|
||||
DB *badger.DB
|
||||
|
||||
closeLk sync.RWMutex
|
||||
closed bool
|
||||
closeOnce sync.Once
|
||||
closing chan struct{}
|
||||
|
||||
gcDiscardRatio float64
|
||||
}
|
||||
|
||||
// Implements the datastore.Txn interface, enabling transaction support for
|
||||
// the badger Datastore.
|
||||
type txn struct {
|
||||
ds *Datastore
|
||||
txn *badger.Txn
|
||||
|
||||
// Whether this transaction has been implicitly created as a result of a direct Datastore
|
||||
// method invocation.
|
||||
implicit bool
|
||||
}
|
||||
|
||||
// Options are the badger datastore options, reexported here for convenience.
|
||||
type Options struct {
|
||||
gcDiscardRatio float64
|
||||
|
||||
badger.Options
|
||||
}
|
||||
|
||||
// DefaultOptions are the default options for the badger datastore.
|
||||
var DefaultOptions Options
|
||||
|
||||
func init() {
|
||||
DefaultOptions = Options{
|
||||
gcDiscardRatio: 0.1,
|
||||
Options: badger.DefaultOptions,
|
||||
}
|
||||
DefaultOptions.Options.CompactL0OnClose = false
|
||||
DefaultOptions.Options.Truncate = true
|
||||
}
|
||||
|
||||
var _ ds.Datastore = (*Datastore)(nil)
|
||||
var _ ds.TxnDatastore = (*Datastore)(nil)
|
||||
var _ ds.TTLDatastore = (*Datastore)(nil)
|
||||
|
||||
// NewDatastore creates a new badger datastore.
|
||||
//
|
||||
// DO NOT set the Dir and/or ValuePath fields of opt, they will be set for you.
|
||||
func NewDatastore(path string, options *Options) (*Datastore, error) {
|
||||
// Copy the options because we modify them.
|
||||
var opt badger.Options
|
||||
var gcDiscardRatio float64
|
||||
if options == nil {
|
||||
opt = badger.DefaultOptions
|
||||
gcDiscardRatio = DefaultOptions.gcDiscardRatio
|
||||
} else {
|
||||
opt = options.Options
|
||||
gcDiscardRatio = options.gcDiscardRatio
|
||||
}
|
||||
|
||||
opt.Dir = path
|
||||
opt.ValueDir = path
|
||||
opt.Logger = log
|
||||
|
||||
kv, err := badger.Open(opt)
|
||||
if err != nil {
|
||||
if strings.HasPrefix(err.Error(), "manifest has unsupported version:") {
|
||||
err = fmt.Errorf("unsupported badger version, use github.com/ipfs/badgerds-upgrade to upgrade: %s", err.Error())
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Datastore{
|
||||
DB: kv,
|
||||
closing: make(chan struct{}),
|
||||
gcDiscardRatio: gcDiscardRatio,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewTransaction starts a new transaction. The resulting transaction object
|
||||
// can be mutated without incurring changes to the underlying Datastore until
|
||||
// the transaction is Committed.
|
||||
func (d *Datastore) NewTransaction(readOnly bool) (ds.Txn, error) {
|
||||
d.closeLk.RLock()
|
||||
defer d.closeLk.RUnlock()
|
||||
if d.closed {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
|
||||
return &txn{d, d.DB.NewTransaction(!readOnly), false}, nil
|
||||
}
|
||||
|
||||
// newImplicitTransaction creates a transaction marked as 'implicit'.
|
||||
// Implicit transactions are created by Datastore methods performing single operations.
|
||||
func (d *Datastore) newImplicitTransaction(readOnly bool) *txn {
|
||||
return &txn{d, d.DB.NewTransaction(!readOnly), true}
|
||||
}
|
||||
|
||||
func (d *Datastore) Put(key ds.Key, value []byte) error {
|
||||
d.closeLk.RLock()
|
||||
defer d.closeLk.RUnlock()
|
||||
if d.closed {
|
||||
return ErrClosed
|
||||
}
|
||||
|
||||
txn := d.newImplicitTransaction(false)
|
||||
defer txn.discard()
|
||||
|
||||
if err := txn.put(key, value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return txn.commit()
|
||||
}
|
||||
|
||||
func (d *Datastore) PutWithTTL(key ds.Key, value []byte, ttl time.Duration) error {
|
||||
d.closeLk.RLock()
|
||||
defer d.closeLk.RUnlock()
|
||||
if d.closed {
|
||||
return ErrClosed
|
||||
}
|
||||
|
||||
txn := d.newImplicitTransaction(false)
|
||||
defer txn.discard()
|
||||
|
||||
if err := txn.putWithTTL(key, value, ttl); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return txn.commit()
|
||||
}
|
||||
|
||||
func (d *Datastore) SetTTL(key ds.Key, ttl time.Duration) error {
|
||||
d.closeLk.RLock()
|
||||
defer d.closeLk.RUnlock()
|
||||
if d.closed {
|
||||
return ErrClosed
|
||||
}
|
||||
|
||||
txn := d.newImplicitTransaction(false)
|
||||
defer txn.discard()
|
||||
|
||||
if err := txn.setTTL(key, ttl); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return txn.commit()
|
||||
}
|
||||
|
||||
func (d *Datastore) GetExpiration(key ds.Key) (time.Time, error) {
|
||||
d.closeLk.RLock()
|
||||
defer d.closeLk.RUnlock()
|
||||
if d.closed {
|
||||
return time.Time{}, ErrClosed
|
||||
}
|
||||
|
||||
txn := d.newImplicitTransaction(false)
|
||||
defer txn.discard()
|
||||
|
||||
return txn.getExpiration(key)
|
||||
}
|
||||
|
||||
func (d *Datastore) Get(key ds.Key) (value []byte, err error) {
|
||||
d.closeLk.RLock()
|
||||
defer d.closeLk.RUnlock()
|
||||
if d.closed {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
|
||||
txn := d.newImplicitTransaction(true)
|
||||
defer txn.discard()
|
||||
|
||||
return txn.get(key)
|
||||
}
|
||||
|
||||
func (d *Datastore) Has(key ds.Key) (bool, error) {
|
||||
d.closeLk.RLock()
|
||||
defer d.closeLk.RUnlock()
|
||||
if d.closed {
|
||||
return false, ErrClosed
|
||||
}
|
||||
|
||||
txn := d.newImplicitTransaction(true)
|
||||
defer txn.discard()
|
||||
|
||||
return txn.has(key)
|
||||
}
|
||||
|
||||
func (d *Datastore) GetSize(key ds.Key) (size int, err error) {
|
||||
d.closeLk.RLock()
|
||||
defer d.closeLk.RUnlock()
|
||||
if d.closed {
|
||||
return -1, ErrClosed
|
||||
}
|
||||
|
||||
txn := d.newImplicitTransaction(true)
|
||||
defer txn.discard()
|
||||
|
||||
return txn.getSize(key)
|
||||
}
|
||||
|
||||
func (d *Datastore) Delete(key ds.Key) error {
|
||||
d.closeLk.RLock()
|
||||
defer d.closeLk.RUnlock()
|
||||
|
||||
txn := d.newImplicitTransaction(false)
|
||||
defer txn.discard()
|
||||
|
||||
err := txn.delete(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return txn.commit()
|
||||
}
|
||||
|
||||
func (d *Datastore) Query(q dsq.Query) (dsq.Results, error) {
|
||||
d.closeLk.RLock()
|
||||
defer d.closeLk.RUnlock()
|
||||
|
||||
txn := d.newImplicitTransaction(true)
|
||||
// We cannot defer txn.Discard() here, as the txn must remain active while the iterator is open.
|
||||
// https://github.com/dgraph-io/badger/commit/b1ad1e93e483bbfef123793ceedc9a7e34b09f79
|
||||
// The closing logic in the query goprocess takes care of discarding the implicit transaction.
|
||||
return txn.query(q)
|
||||
}
|
||||
|
||||
// DiskUsage implements the PersistentDatastore interface.
|
||||
// It returns the sum of lsm and value log files sizes in bytes.
|
||||
func (d *Datastore) DiskUsage() (uint64, error) {
|
||||
d.closeLk.RLock()
|
||||
defer d.closeLk.RUnlock()
|
||||
if d.closed {
|
||||
return 0, ErrClosed
|
||||
}
|
||||
lsm, vlog := d.DB.Size()
|
||||
return uint64(lsm + vlog), nil
|
||||
}
|
||||
|
||||
func (d *Datastore) Close() error {
|
||||
d.closeOnce.Do(func() {
|
||||
close(d.closing)
|
||||
})
|
||||
d.closeLk.Lock()
|
||||
defer d.closeLk.Unlock()
|
||||
if d.closed {
|
||||
return ErrClosed
|
||||
}
|
||||
d.closed = true
|
||||
return d.DB.Close()
|
||||
}
|
||||
|
||||
func (d *Datastore) Batch() (ds.Batch, error) {
|
||||
tx, _ := d.NewTransaction(false)
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
func (d *Datastore) CollectGarbage() error {
|
||||
d.closeLk.RLock()
|
||||
defer d.closeLk.RUnlock()
|
||||
if d.closed {
|
||||
return ErrClosed
|
||||
}
|
||||
|
||||
err := d.DB.RunValueLogGC(d.gcDiscardRatio)
|
||||
if err == badger.ErrNoRewrite {
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var _ ds.Datastore = (*txn)(nil)
|
||||
var _ ds.TTLDatastore = (*txn)(nil)
|
||||
|
||||
func (t *txn) Put(key ds.Key, value []byte) error {
|
||||
t.ds.closeLk.RLock()
|
||||
defer t.ds.closeLk.RUnlock()
|
||||
if t.ds.closed {
|
||||
return ErrClosed
|
||||
}
|
||||
return t.put(key, value)
|
||||
}
|
||||
|
||||
func (t *txn) put(key ds.Key, value []byte) error {
|
||||
return t.txn.Set(key.Bytes(), value)
|
||||
}
|
||||
|
||||
func (t *txn) PutWithTTL(key ds.Key, value []byte, ttl time.Duration) error {
|
||||
t.ds.closeLk.RLock()
|
||||
defer t.ds.closeLk.RUnlock()
|
||||
if t.ds.closed {
|
||||
return ErrClosed
|
||||
}
|
||||
return t.putWithTTL(key, value, ttl)
|
||||
}
|
||||
|
||||
func (t *txn) putWithTTL(key ds.Key, value []byte, ttl time.Duration) error {
|
||||
return t.txn.SetWithTTL(key.Bytes(), value, ttl)
|
||||
}
|
||||
|
||||
func (t *txn) GetExpiration(key ds.Key) (time.Time, error) {
|
||||
t.ds.closeLk.RLock()
|
||||
defer t.ds.closeLk.RUnlock()
|
||||
if t.ds.closed {
|
||||
return time.Time{}, ErrClosed
|
||||
}
|
||||
|
||||
return t.getExpiration(key)
|
||||
}
|
||||
|
||||
func (t *txn) getExpiration(key ds.Key) (time.Time, error) {
|
||||
item, err := t.txn.Get(key.Bytes())
|
||||
if err == badger.ErrKeyNotFound {
|
||||
return time.Time{}, ds.ErrNotFound
|
||||
} else if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return time.Unix(int64(item.ExpiresAt()), 0), nil
|
||||
}
|
||||
|
||||
func (t *txn) SetTTL(key ds.Key, ttl time.Duration) error {
|
||||
t.ds.closeLk.RLock()
|
||||
defer t.ds.closeLk.RUnlock()
|
||||
if t.ds.closed {
|
||||
return ErrClosed
|
||||
}
|
||||
|
||||
return t.setTTL(key, ttl)
|
||||
}
|
||||
|
||||
func (t *txn) setTTL(key ds.Key, ttl time.Duration) error {
|
||||
item, err := t.txn.Get(key.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return item.Value(func(data []byte) error {
|
||||
return t.putWithTTL(key, data, ttl)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (t *txn) Get(key ds.Key) ([]byte, error) {
|
||||
t.ds.closeLk.RLock()
|
||||
defer t.ds.closeLk.RUnlock()
|
||||
if t.ds.closed {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
|
||||
return t.get(key)
|
||||
}
|
||||
|
||||
func (t *txn) get(key ds.Key) ([]byte, error) {
|
||||
item, err := t.txn.Get(key.Bytes())
|
||||
if err == badger.ErrKeyNotFound {
|
||||
err = ds.ErrNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return item.ValueCopy(nil)
|
||||
}
|
||||
|
||||
func (t *txn) Has(key ds.Key) (bool, error) {
|
||||
t.ds.closeLk.RLock()
|
||||
defer t.ds.closeLk.RUnlock()
|
||||
if t.ds.closed {
|
||||
return false, ErrClosed
|
||||
}
|
||||
|
||||
return t.has(key)
|
||||
}
|
||||
|
||||
func (t *txn) has(key ds.Key) (bool, error) {
|
||||
_, err := t.txn.Get(key.Bytes())
|
||||
switch err {
|
||||
case badger.ErrKeyNotFound:
|
||||
return false, nil
|
||||
case nil:
|
||||
return true, nil
|
||||
default:
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
func (t *txn) GetSize(key ds.Key) (int, error) {
|
||||
t.ds.closeLk.RLock()
|
||||
defer t.ds.closeLk.RUnlock()
|
||||
if t.ds.closed {
|
||||
return -1, ErrClosed
|
||||
}
|
||||
|
||||
return t.getSize(key)
|
||||
}
|
||||
|
||||
func (t *txn) getSize(key ds.Key) (int, error) {
|
||||
item, err := t.txn.Get(key.Bytes())
|
||||
switch err {
|
||||
case nil:
|
||||
return int(item.ValueSize()), nil
|
||||
case badger.ErrKeyNotFound:
|
||||
return -1, ds.ErrNotFound
|
||||
default:
|
||||
return -1, err
|
||||
}
|
||||
}
|
||||
|
||||
func (t *txn) Delete(key ds.Key) error {
|
||||
t.ds.closeLk.RLock()
|
||||
defer t.ds.closeLk.RUnlock()
|
||||
if t.ds.closed {
|
||||
return ErrClosed
|
||||
}
|
||||
|
||||
return t.delete(key)
|
||||
}
|
||||
|
||||
func (t *txn) delete(key ds.Key) error {
|
||||
return t.txn.Delete(key.Bytes())
|
||||
}
|
||||
|
||||
func (t *txn) Query(q dsq.Query) (dsq.Results, error) {
|
||||
t.ds.closeLk.RLock()
|
||||
defer t.ds.closeLk.RUnlock()
|
||||
if t.ds.closed {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
|
||||
return t.query(q)
|
||||
}
|
||||
|
||||
func (t *txn) query(q dsq.Query) (dsq.Results, error) {
|
||||
prefix := []byte(q.Prefix)
|
||||
opt := badger.DefaultIteratorOptions
|
||||
opt.PrefetchValues = !q.KeysOnly
|
||||
|
||||
// Special case order by key.
|
||||
orders := q.Orders
|
||||
if len(orders) > 0 {
|
||||
switch q.Orders[0].(type) {
|
||||
case dsq.OrderByKey, *dsq.OrderByKey:
|
||||
// Already ordered by key.
|
||||
orders = nil
|
||||
case dsq.OrderByKeyDescending, *dsq.OrderByKeyDescending:
|
||||
orders = nil
|
||||
opt.Reverse = true
|
||||
}
|
||||
}
|
||||
|
||||
txn := t.txn
|
||||
|
||||
it := txn.NewIterator(opt)
|
||||
it.Seek(prefix)
|
||||
|
||||
if q.Offset > 0 {
|
||||
for j := 0; j < q.Offset; j++ {
|
||||
it.Next()
|
||||
}
|
||||
}
|
||||
|
||||
qrb := dsq.NewResultBuilder(q)
|
||||
|
||||
qrb.Process.Go(func(worker goprocess.Process) {
|
||||
t.ds.closeLk.RLock()
|
||||
closedEarly := false
|
||||
defer func() {
|
||||
t.ds.closeLk.RUnlock()
|
||||
if closedEarly {
|
||||
select {
|
||||
case qrb.Output <- dsq.Result{
|
||||
Error: ErrClosed,
|
||||
}:
|
||||
case <-qrb.Process.Closing():
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
if t.ds.closed {
|
||||
closedEarly = true
|
||||
return
|
||||
}
|
||||
|
||||
// this iterator is part of an implicit transaction, so when
|
||||
// we're done we must discard the transaction. It's safe to
|
||||
// discard the txn it because it contains the iterator only.
|
||||
if t.implicit {
|
||||
defer t.discard()
|
||||
}
|
||||
|
||||
defer it.Close()
|
||||
|
||||
for sent := 0; it.ValidForPrefix(prefix); sent++ {
|
||||
if qrb.Query.Limit > 0 && sent >= qrb.Query.Limit {
|
||||
break
|
||||
}
|
||||
|
||||
item := it.Item()
|
||||
|
||||
k := string(item.Key())
|
||||
e := dsq.Entry{Key: k}
|
||||
|
||||
var result dsq.Result
|
||||
if !q.KeysOnly {
|
||||
b, err := item.ValueCopy(nil)
|
||||
if err != nil {
|
||||
result = dsq.Result{Error: err}
|
||||
} else {
|
||||
e.Value = b
|
||||
result = dsq.Result{Entry: e}
|
||||
}
|
||||
} else {
|
||||
result = dsq.Result{Entry: e}
|
||||
}
|
||||
|
||||
if q.ReturnExpirations {
|
||||
result.Expiration = time.Unix(int64(item.ExpiresAt()), 0)
|
||||
}
|
||||
|
||||
select {
|
||||
case qrb.Output <- result:
|
||||
case <-t.ds.closing: // datastore closing.
|
||||
closedEarly = true
|
||||
return
|
||||
case <-worker.Closing(): // client told us to close early
|
||||
return
|
||||
}
|
||||
|
||||
it.Next()
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
|
||||
go qrb.Process.CloseAfterChildren()
|
||||
|
||||
// Now, apply remaining things (filters, order)
|
||||
qr := qrb.Results()
|
||||
for _, f := range q.Filters {
|
||||
qr = dsq.NaiveFilter(qr, f)
|
||||
}
|
||||
if len(orders) > 0 {
|
||||
qr = dsq.NaiveOrder(qr, orders...)
|
||||
}
|
||||
|
||||
return qr, nil
|
||||
}
|
||||
|
||||
func (t *txn) Commit() error {
|
||||
t.ds.closeLk.RLock()
|
||||
defer t.ds.closeLk.RUnlock()
|
||||
if t.ds.closed {
|
||||
return ErrClosed
|
||||
}
|
||||
|
||||
return t.commit()
|
||||
}
|
||||
|
||||
func (t *txn) commit() error {
|
||||
return t.txn.Commit()
|
||||
}
|
||||
|
||||
// Alias to commit
|
||||
func (t *txn) Close() error {
|
||||
t.ds.closeLk.RLock()
|
||||
defer t.ds.closeLk.RUnlock()
|
||||
if t.ds.closed {
|
||||
return ErrClosed
|
||||
}
|
||||
return t.close()
|
||||
}
|
||||
|
||||
func (t *txn) close() error {
|
||||
return t.txn.Commit()
|
||||
}
|
||||
|
||||
func (t *txn) Discard() {
|
||||
t.ds.closeLk.RLock()
|
||||
defer t.ds.closeLk.RUnlock()
|
||||
if t.ds.closed {
|
||||
return
|
||||
}
|
||||
|
||||
t.discard()
|
||||
}
|
||||
|
||||
func (t *txn) discard() {
|
||||
t.txn.Discard()
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
module github.com/ipfs/go-ds-badger
|
||||
|
||||
require (
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7 // indirect
|
||||
github.com/dgraph-io/badger v2.0.0-rc.2+incompatible
|
||||
github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/golang/protobuf v1.3.0 // indirect
|
||||
github.com/ipfs/go-datastore v0.0.1
|
||||
github.com/ipfs/go-log v0.0.1
|
||||
github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8
|
||||
github.com/pkg/errors v0.8.1 // indirect
|
||||
)
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7 h1:PqzgE6kAMi81xWQA2QIVxjWkFHptGgC547vchpUbtFo=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgraph-io/badger v1.5.4 h1:gVTrpUTbbr/T24uvoCaqY2KSHfNLVGm0w+hbee2HMeg=
|
||||
github.com/dgraph-io/badger v1.5.4/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ=
|
||||
github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f h1:6itBiEUtu+gOzXZWn46bM5/qm8LlV6/byR7Yflx/y6M=
|
||||
github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ=
|
||||
github.com/dgraph-io/badger v2.0.0-rc.2+incompatible h1:7KPp6xv5+wymkVUbkAnZZXvmDrJlf09m/7u1HG5lAYA=
|
||||
github.com/dgraph-io/badger v2.0.0-rc.2+incompatible/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ=
|
||||
github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f h1:dDxpBYafY/GYpcl+LS4Bn3ziLPuEdGRkRjYAbSlWxSA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI=
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk=
|
||||
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/ipfs/go-datastore v0.0.1 h1:AW/KZCScnBWlSb5JbnEnLKFWXL224LBEh/9KXXOrUms=
|
||||
github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE=
|
||||
github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw=
|
||||
github.com/ipfs/go-log v0.0.1 h1:9XTUN/rW64BCG1YhPK9Hoy3q8nr4gOmHHBpgFdfw6Lc=
|
||||
github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM=
|
||||
github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8 h1:bspPhN+oKYFk5fcGNuQzp6IGzYQSenLEgH3s6jkXrWw=
|
||||
github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg=
|
||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc h1:9lDbC6Rz4bwmou+oE6Dt4Cb2BGMur5eR/GYptkKUVHo=
|
||||
github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190227160552-c95aed5357e7 h1:C2F/nMkR/9sfUTpvR3QrjBuTdvMUC/cFajkphs1YLQo=
|
||||
golang.org/x/net v0.0.0-20190227160552-c95aed5357e7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"author": "magik6k",
|
||||
"bugs": {
|
||||
"url": "https://github.com/ipfs/go-ds-badger"
|
||||
},
|
||||
"gx": {
|
||||
"dvcsimport": "github.com/ipfs/go-ds-badger"
|
||||
},
|
||||
"gxDependencies": [
|
||||
{
|
||||
"author": "whyrusleeping",
|
||||
"hash": "QmSF8fPo3jgVBAy8fpdjjYqgG87dkJgUprRBHRd2tmfgpP",
|
||||
"name": "goprocess",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"author": "jbenet",
|
||||
"hash": "QmUadX5EcvrBmxAV9sE7wUWtWSqxns5K84qKJBixmcT1w9",
|
||||
"name": "go-datastore",
|
||||
"version": "3.6.1"
|
||||
},
|
||||
{
|
||||
"author": "dgraph-io",
|
||||
"hash": "QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd",
|
||||
"name": "badger",
|
||||
"version": "2.11.4"
|
||||
},
|
||||
{
|
||||
"hash": "QmbkT7eMTyXfpeyB3ZMxxcxg7XH8t6uXp49jqzz4HB7BGF",
|
||||
"name": "go-log",
|
||||
"version": "1.5.9"
|
||||
}
|
||||
],
|
||||
"gxVersion": "0.8.0",
|
||||
"language": "go",
|
||||
"license": "",
|
||||
"name": "go-ds-badger",
|
||||
"releaseCmd": "git commit -a -m \"gx publish $VERSION\"",
|
||||
"version": "1.12.4"
|
||||
}
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
*.swp
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
os:
|
||||
- linux
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.11.x
|
||||
|
||||
env:
|
||||
global:
|
||||
- GOTFLAGS="-race"
|
||||
matrix:
|
||||
- BUILD_DEPTYPE=gx
|
||||
- BUILD_DEPTYPE=gomod
|
||||
|
||||
|
||||
# disable travis install
|
||||
install:
|
||||
- true
|
||||
|
||||
script:
|
||||
- bash <(curl -s https://raw.githubusercontent.com/ipfs/ci-helpers/master/travis-ci/run-standard-tests.sh)
|
||||
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $GOPATH/src/gx
|
||||
- $GOPATH/pkg/mod
|
||||
- $HOME/.cache/go-build
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2016 Juan Batiz-Benet
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
export IPFS_API ?= v04x.ipfs.io
|
||||
|
||||
gx:
|
||||
go get -u github.com/whyrusleeping/gx
|
||||
go get -u github.com/whyrusleeping/gx-go
|
||||
|
||||
deps: gx
|
||||
gx --verbose install --global
|
||||
gx-go rewrite
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
# go-ds-flatfs
|
||||
|
||||
[](http://ipn.io)
|
||||
[](http://ipfs.io/)
|
||||
[](http://webchat.freenode.net/?channels=%23ipfs)
|
||||
[](https://github.com/RichardLitt/standard-readme)
|
||||
[](https://godoc.org/github.com/ipfs/go-ds-flatfs)
|
||||
[](https://travis-ci.org/ipfs/go-ds-flatfs)
|
||||
[](https://codecov.io/gh/ipfs/go-ds-flatfs)
|
||||
|
||||
|
||||
> A datastore implementation using sharded directories and flat files to store data
|
||||
|
||||
`go-ds-flatfs` is used by `go-ipfs` to store raw block contents on disk. It supports several sharding functions (prefix, suffix, next-to-last/*).
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Install](#install)
|
||||
- [Usage](#usage)
|
||||
- [Contribute](#contribute)
|
||||
- [License](#license)
|
||||
|
||||
## Install
|
||||
|
||||
`go-ds-flatfs` can be used like any Go module:
|
||||
|
||||
|
||||
```
|
||||
import "github.com/ipfs/go-ds-flatfs"
|
||||
```
|
||||
|
||||
`go-ds-flatfs` uses [`Gx`](https://github.com/whyrusleeping/gx) and [`Gx-go`](https://github.com/whyrusleeping/gx-go) to handle dependendencies. Run `make deps` to download and rewrite the imports to their fixed dependencies.
|
||||
|
||||
## Usage
|
||||
|
||||
Check the [GoDoc module documentation](https://godoc.org/github.com/ipfs/go-ds-flatfs) for an overview of this module's
|
||||
functionality.
|
||||
|
||||
### DiskUsage and Accuracy
|
||||
|
||||
This datastore implements the [`PersistentDatastore`](https://godoc.org/github.com/ipfs/go-datastore#PersistentDatastore) interface. It offers a `DiskUsage()` method which strives to find a balance between accuracy and performance. This implies:
|
||||
|
||||
* The total disk usage of a datastore is calculated when opening the datastore
|
||||
* The current disk usage is cached frequently in a file in the datastore root (`diskUsage.cache` by default). This file is also
|
||||
written when the datastore is closed.
|
||||
* If this file is not present when the datastore is opened:
|
||||
* The disk usage will be calculated by walking the datastore's directory tree and estimating the size of each folder.
|
||||
* This may be a very slow operation for huge datastores or datastores with slow disks
|
||||
* The operation is time-limited (5 minutes by default).
|
||||
* Upon timeout, the remaining folders will be assumed to have the average of the previously processed ones.
|
||||
* After opening, the disk usage is updated in every write/delete operation.
|
||||
|
||||
This means that for certain datastores (huge ones, those with very slow disks or special content), the values reported by
|
||||
`DiskUsage()` might be reduced accuracy and the first startup (without a `diskUsage.cache` file present), might be slow.
|
||||
|
||||
If you need increased accuracy or a fast start from the first time, you can manually create or update the
|
||||
`diskUsage.cache` file.
|
||||
|
||||
The file `diskUsage.cache` is a JSON file with two fields `diskUsage` and `accuracy`. For example the JSON file for a
|
||||
small repo might be:
|
||||
|
||||
```
|
||||
{"diskUsage":6357,"accuracy":"initial-exact"}
|
||||
```
|
||||
|
||||
`diskUsage` is the calculated disk usage and `accuracy` is a note on the accuracy of the initial calculation. If the
|
||||
initial calculation was accurate the file will contain the value `initial-exact`. If some of the directories have too
|
||||
many entries and the disk usage for that directory was estimated based on the first 2000 entries, the file will contain
|
||||
`initial-approximate`. If the calculation took too long and timed out as indicated above, the file will contain
|
||||
`initial-timed-out`.
|
||||
|
||||
If the initial calculation timed out the JSON file might be:
|
||||
```
|
||||
{"diskUsage":7589482442898,"accuracy":"initial-timed-out"}
|
||||
|
||||
```
|
||||
|
||||
To fix this with a more accurate value you could do (in the datastore root):
|
||||
|
||||
$ du -sb .
|
||||
7536515831332 .
|
||||
$ echo -n '{"diskUsage":7536515831332,"accuracy":"initial-exact"}' > diskUsage.cache
|
||||
|
||||
## Contribute
|
||||
|
||||
PRs accepted.
|
||||
|
||||
Small note: If editing the README, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification.
|
||||
|
||||
## License
|
||||
|
||||
MIT © Protocol Labs, Inc.
|
||||
+1
@@ -0,0 +1 @@
|
||||
comment: off
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
// Package flatfs is a Datastore implementation that stores all
|
||||
// objects in a two-level directory structure in the local file
|
||||
// system, regardless of the hierarchy of the keys.
|
||||
package flatfs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/ipfs/go-datastore"
|
||||
"github.com/ipfs/go-datastore/query"
|
||||
)
|
||||
|
||||
func UpgradeV0toV1(path string, prefixLen int) error {
|
||||
fun := Prefix(prefixLen)
|
||||
err := WriteShardFunc(path, fun)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = WriteReadme(path, fun)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DowngradeV1toV0(path string) error {
|
||||
fun, err := ReadShardFunc(path)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if fun.funName != "prefix" {
|
||||
return fmt.Errorf("%s: can only downgrade datastore that use the 'prefix' sharding function", path)
|
||||
}
|
||||
|
||||
err = os.Remove(filepath.Join(path, SHARDING_FN))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Remove(filepath.Join(path, README_FN))
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Move(oldPath string, newPath string, out io.Writer) error {
|
||||
oldDS, err := Open(oldPath, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %v", oldPath, err)
|
||||
}
|
||||
oldDS.deactivate()
|
||||
newDS, err := Open(newPath, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %v", newPath, err)
|
||||
}
|
||||
newDS.deactivate()
|
||||
|
||||
res, err := oldDS.Query(query.Query{KeysOnly: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if out != nil {
|
||||
fmt.Fprintf(out, "Moving Keys...\n")
|
||||
}
|
||||
|
||||
// first move the keys
|
||||
count := 0
|
||||
for {
|
||||
e, ok := res.NextSync()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if e.Error != nil {
|
||||
return e.Error
|
||||
}
|
||||
|
||||
err := moveKey(oldDS, newDS, datastore.RawKey(e.Key))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
count++
|
||||
if out != nil && count%10 == 0 {
|
||||
fmt.Fprintf(out, "\r%d keys so far", count)
|
||||
}
|
||||
}
|
||||
|
||||
if out != nil {
|
||||
fmt.Fprintf(out, "\nCleaning Up...\n")
|
||||
}
|
||||
|
||||
// now walk the old top-level directory
|
||||
dir, err := os.Open(oldDS.path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dir.Close()
|
||||
names, err := dir.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fn := range names {
|
||||
if fn == "." || fn == ".." {
|
||||
continue
|
||||
}
|
||||
oldPath := filepath.Join(oldDS.path, fn)
|
||||
inf, err := os.Stat(oldPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if inf.IsDir() {
|
||||
indir, err := os.Open(oldPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
names, err := indir.Readdirnames(-1)
|
||||
indir.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, n := range names {
|
||||
p := filepath.Join(oldPath, n)
|
||||
// part of unfinished write transaction
|
||||
// remove it
|
||||
if strings.HasPrefix(n, "put-") {
|
||||
err := os.Remove(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return errors.New("unknown file in flatfs: " + p)
|
||||
}
|
||||
}
|
||||
|
||||
err = os.Remove(oldPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if fn == SHARDING_FN || fn == README_FN {
|
||||
// generated file so just remove it
|
||||
err := os.Remove(oldPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// else we found something unexpected, so to be safe just move it
|
||||
log.Warningf("found unexpected file in datastore directory: \"%s\", moving anyway\n", fn)
|
||||
newPath := filepath.Join(newDS.path, fn)
|
||||
err := os.Rename(oldPath, newPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if out != nil {
|
||||
fmt.Fprintf(out, "All Done.\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func moveKey(oldDS *Datastore, newDS *Datastore, key datastore.Key) error {
|
||||
_, oldPath := oldDS.encode(key)
|
||||
dir, newPath := newDS.encode(key)
|
||||
err := os.Mkdir(dir, 0755)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
err = os.Rename(oldPath, newPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+1101
File diff suppressed because it is too large
Load Diff
+7
@@ -0,0 +1,7 @@
|
||||
module github.com/ipfs/go-ds-flatfs
|
||||
|
||||
require (
|
||||
github.com/ipfs/go-datastore v0.0.1
|
||||
github.com/ipfs/go-log v0.0.1
|
||||
github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8
|
||||
)
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI=
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/ipfs/go-datastore v0.0.1 h1:AW/KZCScnBWlSb5JbnEnLKFWXL224LBEh/9KXXOrUms=
|
||||
github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE=
|
||||
github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw=
|
||||
github.com/ipfs/go-log v0.0.1 h1:9XTUN/rW64BCG1YhPK9Hoy3q8nr4gOmHHBpgFdfw6Lc=
|
||||
github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM=
|
||||
github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8 h1:bspPhN+oKYFk5fcGNuQzp6IGzYQSenLEgH3s6jkXrWw=
|
||||
github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg=
|
||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc h1:9lDbC6Rz4bwmou+oE6Dt4Cb2BGMur5eR/GYptkKUVHo=
|
||||
github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM=
|
||||
golang.org/x/net v0.0.0-20190227160552-c95aed5357e7 h1:C2F/nMkR/9sfUTpvR3QrjBuTdvMUC/cFajkphs1YLQo=
|
||||
golang.org/x/net v0.0.0-20190227160552-c95aed5357e7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"author": "whyrusleeping",
|
||||
"bugs": {
|
||||
"url": "https://github.com/ipfs/go-ds-flatfs"
|
||||
},
|
||||
"gx": {
|
||||
"dvcsimport": "github.com/ipfs/go-ds-flatfs"
|
||||
},
|
||||
"gxDependencies": [
|
||||
{
|
||||
"hash": "QmbkT7eMTyXfpeyB3ZMxxcxg7XH8t6uXp49jqzz4HB7BGF",
|
||||
"name": "go-log",
|
||||
"version": "1.5.9"
|
||||
},
|
||||
{
|
||||
"author": "jbenet",
|
||||
"hash": "QmUadX5EcvrBmxAV9sE7wUWtWSqxns5K84qKJBixmcT1w9",
|
||||
"name": "go-datastore",
|
||||
"version": "3.6.1"
|
||||
},
|
||||
{
|
||||
"author": "whyrusleeping",
|
||||
"hash": "QmSF8fPo3jgVBAy8fpdjjYqgG87dkJgUprRBHRd2tmfgpP",
|
||||
"name": "goprocess",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
],
|
||||
"gxVersion": "0.8.0",
|
||||
"language": "go",
|
||||
"license": "",
|
||||
"name": "go-ds-flatfs",
|
||||
"releaseCmd": "git commit -a -m \"gx publish $VERSION\"",
|
||||
"version": "1.3.7"
|
||||
}
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package flatfs
|
||||
|
||||
var README_IPFS_DEF_SHARD = `This is a repository of IPLD objects. Each IPLD object is in a single file,
|
||||
named <base32 encoding of cid>.data. Where <base32 encoding of cid> is the
|
||||
"base32" encoding of the CID (as specified in
|
||||
https://github.com/multiformats/multibase) without the 'B' prefix.
|
||||
All the object files are placed in a tree of directories, based on a
|
||||
function of the CID. This is a form of sharding similar to
|
||||
the objects directory in git repositories. Previously, we used
|
||||
prefixes, we now use the next-to-last two charters.
|
||||
|
||||
func NextToLast(base32cid string) {
|
||||
nextToLastLen := 2
|
||||
offset := len(base32cid) - nextToLastLen - 1
|
||||
return str[offset : offset+nextToLastLen]
|
||||
}
|
||||
|
||||
For example, an object with a base58 CIDv1 of
|
||||
|
||||
zb2rhYSxw4ZjuzgCnWSt19Q94ERaeFhu9uSqRgjSdx9bsgM6f
|
||||
|
||||
has a base32 CIDv1 of
|
||||
|
||||
BAFKREIA22FLID5AJ2KU7URG47MDLROZIH6YF2KALU2PWEFPVI37YLKRSCA
|
||||
|
||||
and will be placed at
|
||||
|
||||
SC/AFKREIA22FLID5AJ2KU7URG47MDLROZIH6YF2KALU2PWEFPVI37YLKRSCA.data
|
||||
|
||||
with 'SC' being the last-to-next two characters and the 'B' at the
|
||||
beginning of the CIDv1 string is the multibase prefix that is not
|
||||
stored in the filename.
|
||||
`
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
package flatfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var IPFS_DEF_SHARD = NextToLast(2)
|
||||
var IPFS_DEF_SHARD_STR = IPFS_DEF_SHARD.String()
|
||||
|
||||
const PREFIX = "/repo/flatfs/shard/"
|
||||
|
||||
const SHARDING_FN = "SHARDING"
|
||||
const README_FN = "_README"
|
||||
|
||||
type ShardIdV1 struct {
|
||||
funName string
|
||||
param int
|
||||
fun ShardFunc
|
||||
}
|
||||
|
||||
func (f *ShardIdV1) String() string {
|
||||
return fmt.Sprintf("%sv1/%s/%d", PREFIX, f.funName, f.param)
|
||||
}
|
||||
|
||||
func (f *ShardIdV1) Func() ShardFunc {
|
||||
return f.fun
|
||||
}
|
||||
|
||||
func Prefix(prefixLen int) *ShardIdV1 {
|
||||
padding := strings.Repeat("_", prefixLen)
|
||||
return &ShardIdV1{
|
||||
funName: "prefix",
|
||||
param: prefixLen,
|
||||
fun: func(noslash string) string {
|
||||
return (noslash + padding)[:prefixLen]
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func Suffix(suffixLen int) *ShardIdV1 {
|
||||
padding := strings.Repeat("_", suffixLen)
|
||||
return &ShardIdV1{
|
||||
funName: "suffix",
|
||||
param: suffixLen,
|
||||
fun: func(noslash string) string {
|
||||
str := padding + noslash
|
||||
return str[len(str)-suffixLen:]
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NextToLast(suffixLen int) *ShardIdV1 {
|
||||
padding := strings.Repeat("_", suffixLen+1)
|
||||
return &ShardIdV1{
|
||||
funName: "next-to-last",
|
||||
param: suffixLen,
|
||||
fun: func(noslash string) string {
|
||||
str := padding + noslash
|
||||
offset := len(str) - suffixLen - 1
|
||||
return str[offset : offset+suffixLen]
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func ParseShardFunc(str string) (*ShardIdV1, error) {
|
||||
str = strings.TrimSpace(str)
|
||||
|
||||
if len(str) == 0 {
|
||||
return nil, fmt.Errorf("empty shard identifier")
|
||||
}
|
||||
|
||||
trimmed := strings.TrimPrefix(str, PREFIX)
|
||||
if str == trimmed { // nothing trimmed
|
||||
return nil, fmt.Errorf("invalid or no prefix in shard identifier: %s", str)
|
||||
}
|
||||
str = trimmed
|
||||
|
||||
parts := strings.Split(str, "/")
|
||||
if len(parts) != 3 {
|
||||
return nil, fmt.Errorf("invalid shard identifier: %s", str)
|
||||
}
|
||||
|
||||
version := parts[0]
|
||||
if version != "v1" {
|
||||
return nil, fmt.Errorf("expected 'v1' for version string got: %s\n", version)
|
||||
}
|
||||
|
||||
funName := parts[1]
|
||||
|
||||
param, err := strconv.Atoi(parts[2])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid parameter: %v", err)
|
||||
}
|
||||
|
||||
switch funName {
|
||||
case "prefix":
|
||||
return Prefix(param), nil
|
||||
case "suffix":
|
||||
return Suffix(param), nil
|
||||
case "next-to-last":
|
||||
return NextToLast(param), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("expected 'prefix', 'suffix' or 'next-to-last' got: %s", funName)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func ReadShardFunc(dir string) (*ShardIdV1, error) {
|
||||
buf, err := ioutil.ReadFile(filepath.Join(dir, SHARDING_FN))
|
||||
if os.IsNotExist(err) {
|
||||
return nil, ErrShardingFileMissing
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseShardFunc(string(buf))
|
||||
}
|
||||
|
||||
func WriteShardFunc(dir string, id *ShardIdV1) error {
|
||||
file, err := os.OpenFile(filepath.Join(dir, SHARDING_FN), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
_, err = file.WriteString(id.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = file.WriteString("\n")
|
||||
return err
|
||||
}
|
||||
|
||||
func WriteReadme(dir string, id *ShardIdV1) error {
|
||||
if id.String() == IPFS_DEF_SHARD.String() {
|
||||
err := ioutil.WriteFile(filepath.Join(dir, README_FN), []byte(README_IPFS_DEF_SHARD), 0444)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package flatfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// don't block more than 16 threads on sync opearation
|
||||
// 16 should be able to sataurate most RAIDs
|
||||
// in case of two used disks per write (RAID 1, 5) and queue depth of 2,
|
||||
// 16 concurrent Sync calls should be able to saturate 16 HDDs RAID
|
||||
//TODO: benchmark it out, maybe provide tweak parmeter
|
||||
const SyncThreadsMax = 16
|
||||
|
||||
var syncSemaphore chan struct{} = make(chan struct{}, SyncThreadsMax)
|
||||
|
||||
func syncDir(dir string) error {
|
||||
if runtime.GOOS == "windows" {
|
||||
// dir sync on windows doesn't work: https://git.io/vPnCI
|
||||
return nil
|
||||
}
|
||||
|
||||
dirF, err := os.Open(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dirF.Close()
|
||||
|
||||
syncSemaphore <- struct{}{}
|
||||
defer func() { <-syncSemaphore }()
|
||||
|
||||
if err := dirF.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func syncFile(file *os.File) error {
|
||||
syncSemaphore <- struct{}{}
|
||||
defer func() { <-syncSemaphore }()
|
||||
return file.Sync()
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package flatfs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// From: http://stackoverflow.com/questions/30697324/how-to-check-if-directory-on-path-is-empty
|
||||
func DirIsEmpty(name string) (bool, error) {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.Readdirnames(1) // Or f.Readdir(1)
|
||||
if err == io.EOF {
|
||||
return true, nil
|
||||
}
|
||||
return false, err // Either not empty or error, suits both cases
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
*.swp
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
os:
|
||||
- linux
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.11.x
|
||||
|
||||
env:
|
||||
global:
|
||||
- GOTFLAGS="-race -cpu=5"
|
||||
matrix:
|
||||
- BUILD_DEPTYPE=gomod
|
||||
|
||||
|
||||
# disable travis install
|
||||
install:
|
||||
- true
|
||||
|
||||
script:
|
||||
- bash <(curl -s https://raw.githubusercontent.com/ipfs/ci-helpers/master/travis-ci/run-standard-tests.sh)
|
||||
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $GOPATH/pkg/mod
|
||||
- $HOME/.cache/go-build
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2016 Jeromy Johnson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
export IPFS_API ?= v04x.ipfs.io
|
||||
|
||||
gx:
|
||||
go get -u github.com/whyrusleeping/gx
|
||||
go get -u github.com/whyrusleeping/gx-go
|
||||
|
||||
deps: gx
|
||||
gx --verbose install --global
|
||||
gx-go rewrite
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
# go-ds-leveldb
|
||||
|
||||
[](http://ipn.io)
|
||||
[](http://ipfs.io/)
|
||||
[](https://github.com/RichardLitt/standard-readme)
|
||||
[](https://godoc.org/github.com/ipfs/go-ds-leveldb)
|
||||
[](https://travis-ci.org/ipfs/go-ds-leveldb)
|
||||
|
||||
> A go-datastore implementation using LevelDB
|
||||
|
||||
`go-ds-leveldb` implements the [go-datastore](https://github.com/ipfs/go-datastore) interface using a LevelDB backend.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Install](#install)
|
||||
- [Usage](#usage)
|
||||
- [Contribute](#contribute)
|
||||
- [License](#license)
|
||||
|
||||
## Install
|
||||
|
||||
This module can be installed like a regular go module:
|
||||
|
||||
```
|
||||
go get github.com/ipfs/go-ds-leveldb
|
||||
```
|
||||
|
||||
It uses [Gx](https://github.com/whyrusleeping/gx) to manage dependencies. You can use `make deps` to rewrite imports to the gx-specified versions.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
import "github.com/ipfs/go-ds-leveldb"
|
||||
```
|
||||
|
||||
Check the [GoDoc documentation](https://godoc.org/github.com/ipfs/go-ds-leveldb)
|
||||
|
||||
|
||||
## Contribute
|
||||
|
||||
PRs accepted.
|
||||
|
||||
Small note: If editing the README, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification.
|
||||
|
||||
## License
|
||||
|
||||
MIT © Protocol Labs, Inc.
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
package leveldb
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
ds "github.com/ipfs/go-datastore"
|
||||
dsq "github.com/ipfs/go-datastore/query"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/errors"
|
||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
"github.com/syndtr/goleveldb/leveldb/storage"
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
type Datastore struct {
|
||||
*accessor
|
||||
DB *leveldb.DB
|
||||
path string
|
||||
}
|
||||
|
||||
var _ ds.Datastore = (*Datastore)(nil)
|
||||
var _ ds.TxnDatastore = (*Datastore)(nil)
|
||||
|
||||
// Options is an alias of syndtr/goleveldb/opt.Options which might be extended
|
||||
// in the future.
|
||||
type Options opt.Options
|
||||
|
||||
// NewDatastore returns a new datastore backed by leveldb
|
||||
//
|
||||
// for path == "", an in memory bachend will be chosen
|
||||
func NewDatastore(path string, opts *Options) (*Datastore, error) {
|
||||
var nopts opt.Options
|
||||
if opts != nil {
|
||||
nopts = opt.Options(*opts)
|
||||
}
|
||||
|
||||
var err error
|
||||
var db *leveldb.DB
|
||||
|
||||
if path == "" {
|
||||
db, err = leveldb.Open(storage.NewMemStorage(), &nopts)
|
||||
} else {
|
||||
db, err = leveldb.OpenFile(path, &nopts)
|
||||
if errors.IsCorrupted(err) && !nopts.GetReadOnly() {
|
||||
db, err = leveldb.RecoverFile(path, &nopts)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Datastore{
|
||||
accessor: &accessor{ldb: db},
|
||||
DB: db,
|
||||
path: path,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// An extraction of the common interface between LevelDB Transactions and the DB itself.
|
||||
//
|
||||
// It allows to plug in either inside the `accessor`.
|
||||
type levelDbOps interface {
|
||||
Put(key, value []byte, wo *opt.WriteOptions) error
|
||||
Get(key []byte, ro *opt.ReadOptions) (value []byte, err error)
|
||||
Has(key []byte, ro *opt.ReadOptions) (ret bool, err error)
|
||||
Delete(key []byte, wo *opt.WriteOptions) error
|
||||
NewIterator(slice *util.Range, ro *opt.ReadOptions) iterator.Iterator
|
||||
}
|
||||
|
||||
// Datastore operations using either the DB or a transaction as the backend.
|
||||
type accessor struct {
|
||||
ldb levelDbOps
|
||||
}
|
||||
|
||||
func (a *accessor) Put(key ds.Key, value []byte) (err error) {
|
||||
return a.ldb.Put(key.Bytes(), value, nil)
|
||||
}
|
||||
|
||||
func (a *accessor) Get(key ds.Key) (value []byte, err error) {
|
||||
val, err := a.ldb.Get(key.Bytes(), nil)
|
||||
if err != nil {
|
||||
if err == leveldb.ErrNotFound {
|
||||
return nil, ds.ErrNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (a *accessor) Has(key ds.Key) (exists bool, err error) {
|
||||
return a.ldb.Has(key.Bytes(), nil)
|
||||
}
|
||||
|
||||
func (d *accessor) GetSize(key ds.Key) (size int, err error) {
|
||||
return ds.GetBackedSize(d, key)
|
||||
}
|
||||
|
||||
func (a *accessor) Delete(key ds.Key) (err error) {
|
||||
// leveldb Delete will not return an error if the key doesn't
|
||||
// exist (see https://github.com/syndtr/goleveldb/issues/109),
|
||||
// so check that the key exists first and if not return an
|
||||
// error
|
||||
exists, err := a.ldb.Has(key.Bytes(), nil)
|
||||
if !exists {
|
||||
return ds.ErrNotFound
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.ldb.Delete(key.Bytes(), nil)
|
||||
}
|
||||
|
||||
func (a *accessor) Query(q dsq.Query) (dsq.Results, error) {
|
||||
var rnge *util.Range
|
||||
|
||||
// make a copy of the query for the fallback naive query implementation.
|
||||
// don't modify the original so res.Query() returns the correct results.
|
||||
qNaive := q
|
||||
if q.Prefix != "" {
|
||||
rnge = util.BytesPrefix([]byte(q.Prefix))
|
||||
qNaive.Prefix = ""
|
||||
}
|
||||
i := a.ldb.NewIterator(rnge, nil)
|
||||
next := i.Next
|
||||
if len(q.Orders) > 0 {
|
||||
switch q.Orders[0].(type) {
|
||||
case dsq.OrderByKey, *dsq.OrderByKey:
|
||||
qNaive.Orders = nil
|
||||
case dsq.OrderByKeyDescending, *dsq.OrderByKeyDescending:
|
||||
next = func() bool {
|
||||
next = i.Prev
|
||||
return i.Last()
|
||||
}
|
||||
qNaive.Orders = nil
|
||||
default:
|
||||
}
|
||||
}
|
||||
r := dsq.ResultsFromIterator(q, dsq.Iterator{
|
||||
Next: func() (dsq.Result, bool) {
|
||||
if !next() {
|
||||
return dsq.Result{}, false
|
||||
}
|
||||
k := string(i.Key())
|
||||
e := dsq.Entry{Key: k}
|
||||
|
||||
if !q.KeysOnly {
|
||||
buf := make([]byte, len(i.Value()))
|
||||
copy(buf, i.Value())
|
||||
e.Value = buf
|
||||
}
|
||||
return dsq.Result{Entry: e}, true
|
||||
},
|
||||
Close: func() error {
|
||||
i.Release()
|
||||
return nil
|
||||
},
|
||||
})
|
||||
return dsq.NaiveQueryApply(qNaive, r), nil
|
||||
}
|
||||
|
||||
// DiskUsage returns the current disk size used by this levelDB.
|
||||
// For in-mem datastores, it will return 0.
|
||||
func (d *Datastore) DiskUsage() (uint64, error) {
|
||||
if d.path == "" { // in-mem
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
var du uint64
|
||||
|
||||
err := filepath.Walk(d.path, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
du += uint64(info.Size())
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return du, nil
|
||||
}
|
||||
|
||||
// LevelDB needs to be closed.
|
||||
func (d *Datastore) Close() (err error) {
|
||||
return d.DB.Close()
|
||||
}
|
||||
|
||||
type leveldbBatch struct {
|
||||
b *leveldb.Batch
|
||||
db *leveldb.DB
|
||||
}
|
||||
|
||||
func (d *Datastore) Batch() (ds.Batch, error) {
|
||||
return &leveldbBatch{
|
||||
b: new(leveldb.Batch),
|
||||
db: d.DB,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *leveldbBatch) Put(key ds.Key, value []byte) error {
|
||||
b.b.Put(key.Bytes(), value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *leveldbBatch) Commit() error {
|
||||
return b.db.Write(b.b, nil)
|
||||
}
|
||||
|
||||
func (b *leveldbBatch) Delete(key ds.Key) error {
|
||||
b.b.Delete(key.Bytes())
|
||||
return nil
|
||||
}
|
||||
|
||||
// A leveldb transaction embedding the accessor backed by the transaction.
|
||||
type transaction struct {
|
||||
*accessor
|
||||
tx *leveldb.Transaction
|
||||
}
|
||||
|
||||
func (t *transaction) Commit() error {
|
||||
return t.tx.Commit()
|
||||
}
|
||||
|
||||
func (t *transaction) Discard() {
|
||||
t.tx.Discard()
|
||||
}
|
||||
|
||||
func (d *Datastore) NewTransaction(readOnly bool) (ds.Txn, error) {
|
||||
tx, err := d.DB.OpenTransaction()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accessor := &accessor{tx}
|
||||
return &transaction{accessor, tx}, nil
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
module github.com/ipfs/go-ds-leveldb
|
||||
|
||||
require (
|
||||
github.com/ipfs/go-datastore v0.0.3
|
||||
github.com/syndtr/goleveldb v1.0.0
|
||||
)
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI=
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ipfs/go-datastore v0.0.3 h1:/eP3nMDmLzMJNoWSSYvEkmMTTrm9FFCN+JraP9NdlwU=
|
||||
github.com/ipfs/go-datastore v0.0.3/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE=
|
||||
github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw=
|
||||
github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8 h1:bspPhN+oKYFk5fcGNuQzp6IGzYQSenLEgH3s6jkXrWw=
|
||||
github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package coredag
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
ipldcbor "github.com/ipfs/go-ipld-cbor"
|
||||
ipld "github.com/ipfs/go-ipld-format"
|
||||
)
|
||||
|
||||
func cborJSONParser(r io.Reader, mhType uint64, mhLen int) ([]ipld.Node, error) {
|
||||
nd, err := ipldcbor.FromJSON(r, mhType, mhLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []ipld.Node{nd}, nil
|
||||
}
|
||||
|
||||
func cborRawParser(r io.Reader, mhType uint64, mhLen int) ([]ipld.Node, error) {
|
||||
data, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nd, err := ipldcbor.Decode(data, mhType, mhLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []ipld.Node{nd}, nil
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package coredag
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
|
||||
"github.com/ipfs/go-merkledag"
|
||||
|
||||
cid "github.com/ipfs/go-cid"
|
||||
ipld "github.com/ipfs/go-ipld-format"
|
||||
mh "github.com/multiformats/go-multihash"
|
||||
)
|
||||
|
||||
func dagpbJSONParser(r io.Reader, mhType uint64, mhLen int) ([]ipld.Node, error) {
|
||||
data, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nd := &merkledag.ProtoNode{}
|
||||
|
||||
err = nd.UnmarshalJSON(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nd.SetCidBuilder(cidPrefix(mhType, mhLen))
|
||||
|
||||
return []ipld.Node{nd}, nil
|
||||
}
|
||||
|
||||
func dagpbRawParser(r io.Reader, mhType uint64, mhLen int) ([]ipld.Node, error) {
|
||||
data, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nd, err := merkledag.DecodeProtobuf(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nd.SetCidBuilder(cidPrefix(mhType, mhLen))
|
||||
|
||||
return []ipld.Node{nd}, nil
|
||||
}
|
||||
|
||||
func cidPrefix(mhType uint64, mhLen int) *cid.Prefix {
|
||||
if mhType == math.MaxUint64 {
|
||||
mhType = mh.SHA2_256
|
||||
}
|
||||
|
||||
prefix := &cid.Prefix{
|
||||
MhType: mhType,
|
||||
MhLength: mhLen,
|
||||
Version: 1,
|
||||
Codec: cid.DagProtobuf,
|
||||
}
|
||||
|
||||
if mhType == mh.SHA2_256 {
|
||||
prefix.Version = 0
|
||||
}
|
||||
|
||||
return prefix
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package coredag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
ipld "github.com/ipfs/go-ipld-format"
|
||||
)
|
||||
|
||||
// DagParser is function used for parsing stream into Node
|
||||
type DagParser func(r io.Reader, mhType uint64, mhLen int) ([]ipld.Node, error)
|
||||
|
||||
// FormatParsers is used for mapping format descriptors to DagParsers
|
||||
type FormatParsers map[string]DagParser
|
||||
|
||||
// InputEncParsers is used for mapping input encodings to FormatParsers
|
||||
type InputEncParsers map[string]FormatParsers
|
||||
|
||||
// DefaultInputEncParsers is InputEncParser that is used everywhere
|
||||
var DefaultInputEncParsers = InputEncParsers{
|
||||
"json": defaultJSONParsers,
|
||||
"raw": defaultRawParsers,
|
||||
"cbor": defaultCborParsers,
|
||||
"protobuf": defaultProtobufParsers,
|
||||
}
|
||||
|
||||
var defaultJSONParsers = FormatParsers{
|
||||
"cbor": cborJSONParser,
|
||||
"dag-cbor": cborJSONParser,
|
||||
|
||||
"protobuf": dagpbJSONParser,
|
||||
"dag-pb": dagpbJSONParser,
|
||||
}
|
||||
|
||||
var defaultRawParsers = FormatParsers{
|
||||
"cbor": cborRawParser,
|
||||
"dag-cbor": cborRawParser,
|
||||
|
||||
"protobuf": dagpbRawParser,
|
||||
"dag-pb": dagpbRawParser,
|
||||
|
||||
"raw": rawRawParser,
|
||||
}
|
||||
|
||||
var defaultCborParsers = FormatParsers{
|
||||
"cbor": cborRawParser,
|
||||
"dag-cbor": cborRawParser,
|
||||
}
|
||||
|
||||
var defaultProtobufParsers = FormatParsers{
|
||||
"protobuf": dagpbRawParser,
|
||||
"dag-pb": dagpbRawParser,
|
||||
}
|
||||
|
||||
// ParseInputs uses DefaultInputEncParsers to parse io.Reader described by
|
||||
// input encoding and format to an instance of ipld Node
|
||||
func ParseInputs(ienc, format string, r io.Reader, mhType uint64, mhLen int) ([]ipld.Node, error) {
|
||||
return DefaultInputEncParsers.ParseInputs(ienc, format, r, mhType, mhLen)
|
||||
}
|
||||
|
||||
// AddParser adds DagParser under give input encoding and format
|
||||
func (iep InputEncParsers) AddParser(ienc, format string, f DagParser) {
|
||||
m, ok := iep[ienc]
|
||||
if !ok {
|
||||
m = make(FormatParsers)
|
||||
iep[ienc] = m
|
||||
}
|
||||
|
||||
m[format] = f
|
||||
}
|
||||
|
||||
// ParseInputs parses io.Reader described by input encoding and format to
|
||||
// an instance of ipld Node
|
||||
func (iep InputEncParsers) ParseInputs(ienc, format string, r io.Reader, mhType uint64, mhLen int) ([]ipld.Node, error) {
|
||||
parsers, ok := iep[ienc]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no input parser for %q", ienc)
|
||||
}
|
||||
|
||||
parser, ok := parsers[format]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no parser for format %q using input type %q", format, ienc)
|
||||
}
|
||||
|
||||
return parser(r, mhType, mhLen)
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package coredag
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
|
||||
"github.com/ipfs/go-merkledag"
|
||||
|
||||
block "github.com/ipfs/go-block-format"
|
||||
cid "github.com/ipfs/go-cid"
|
||||
ipld "github.com/ipfs/go-ipld-format"
|
||||
mh "github.com/multiformats/go-multihash"
|
||||
)
|
||||
|
||||
func rawRawParser(r io.Reader, mhType uint64, mhLen int) ([]ipld.Node, error) {
|
||||
if mhType == math.MaxUint64 {
|
||||
mhType = mh.SHA2_256
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h, err := mh.Sum(data, mhType, mhLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := cid.NewCidV1(cid.Raw, h)
|
||||
blk, err := block.NewBlockWithCid(data, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nd := &merkledag.RawNode{Block: blk}
|
||||
return []ipld.Node{nd}, nil
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
include mk/header.mk
|
||||
|
||||
dir := $(d)/loader
|
||||
include $(dir)/Rules.mk
|
||||
|
||||
dir := $(d)/plugins
|
||||
include $(dir)/Rules.mk
|
||||
|
||||
include mk/footer.mk
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
coreiface "github.com/ipfs/interface-go-ipfs-core"
|
||||
)
|
||||
|
||||
// PluginDaemon is an interface for daemon plugins. These plugins will be run on
|
||||
// the daemon and will be given access to an implementation of the CoreAPI.
|
||||
type PluginDaemon interface {
|
||||
Plugin
|
||||
|
||||
Start(coreiface.CoreAPI) error
|
||||
Close() error
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"github.com/ipfs/go-ipfs/repo/fsrepo"
|
||||
)
|
||||
|
||||
// PluginDatastore is an interface that can be implemented to add handlers for
|
||||
// for different datastores
|
||||
type PluginDatastore interface {
|
||||
Plugin
|
||||
|
||||
DatastoreTypeName() string
|
||||
DatastoreConfigParser() fsrepo.ConfigFromMap
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"github.com/ipfs/go-ipfs/core/coredag"
|
||||
|
||||
ipld "github.com/ipfs/go-ipld-format"
|
||||
)
|
||||
|
||||
// PluginIPLD is an interface that can be implemented to add handlers for
|
||||
// for different IPLD formats
|
||||
type PluginIPLD interface {
|
||||
Plugin
|
||||
|
||||
RegisterBlockDecoders(dec ipld.BlockDecoder) error
|
||||
RegisterInputEncParsers(iec coredag.InputEncParsers) error
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
include mk/header.mk
|
||||
|
||||
$(d)/preload.go: d:=$(d)
|
||||
$(d)/preload.go: $(d)/preload_list $(d)/preload.sh
|
||||
$(d)/preload.sh > $@
|
||||
go fmt $@ >/dev/null
|
||||
|
||||
DEPS_GO += $(d)/preload.go
|
||||
|
||||
include mk/footer.mk
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
// +build !noplugin
|
||||
|
||||
package loader
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"plugin"
|
||||
|
||||
iplugin "github.com/ipfs/go-ipfs/plugin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
loadPluginsFunc = linuxLoadFunc
|
||||
}
|
||||
|
||||
func linuxLoadFunc(pluginDir string) ([]iplugin.Plugin, error) {
|
||||
var plugins []iplugin.Plugin
|
||||
|
||||
err := filepath.Walk(pluginDir, func(fi string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
if fi != pluginDir {
|
||||
log.Warningf("found directory inside plugins directory: %s", fi)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if info.Mode().Perm()&0111 == 0 {
|
||||
// file is not executable let's not load it
|
||||
// this is to prevent loading plugins from for example non-executable
|
||||
// mounts, some /tmp mounts are marked as such for security
|
||||
log.Errorf("non-executable file in plugins directory: %s", fi)
|
||||
return nil
|
||||
}
|
||||
|
||||
if newPlugins, err := loadPlugin(fi); err == nil {
|
||||
plugins = append(plugins, newPlugins...)
|
||||
} else {
|
||||
return fmt.Errorf("loading plugin %s: %s", fi, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return plugins, err
|
||||
}
|
||||
|
||||
func loadPlugin(fi string) ([]iplugin.Plugin, error) {
|
||||
pl, err := plugin.Open(fi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pls, err := pl.Lookup("Plugins")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
typePls, ok := pls.(*[]iplugin.Plugin)
|
||||
if !ok {
|
||||
return nil, errors.New("filed 'Plugins' didn't contain correct type")
|
||||
}
|
||||
|
||||
return *typePls, nil
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
coredag "github.com/ipfs/go-ipfs/core/coredag"
|
||||
plugin "github.com/ipfs/go-ipfs/plugin"
|
||||
fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
|
||||
|
||||
ipld "github.com/ipfs/go-ipld-format"
|
||||
logging "github.com/ipfs/go-log"
|
||||
coreiface "github.com/ipfs/interface-go-ipfs-core"
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
)
|
||||
|
||||
var log = logging.Logger("plugin/loader")
|
||||
|
||||
var loadPluginsFunc = func(string) ([]plugin.Plugin, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// PluginLoader keeps track of loaded plugins
|
||||
type PluginLoader struct {
|
||||
plugins []plugin.Plugin
|
||||
}
|
||||
|
||||
// NewPluginLoader creates new plugin loader
|
||||
func NewPluginLoader(pluginDir string) (*PluginLoader, error) {
|
||||
plMap := make(map[string]plugin.Plugin)
|
||||
for _, v := range preloadPlugins {
|
||||
plMap[v.Name()] = v
|
||||
}
|
||||
|
||||
if pluginDir != "" {
|
||||
newPls, err := loadDynamicPlugins(pluginDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, pl := range newPls {
|
||||
if ppl, ok := plMap[pl.Name()]; ok {
|
||||
// plugin is already preloaded
|
||||
return nil, fmt.Errorf(
|
||||
"plugin: %s, is duplicated in version: %s, "+
|
||||
"while trying to load dynamically: %s",
|
||||
ppl.Name(), ppl.Version(), pl.Version())
|
||||
}
|
||||
plMap[pl.Name()] = pl
|
||||
}
|
||||
}
|
||||
|
||||
loader := &PluginLoader{plugins: make([]plugin.Plugin, 0, len(plMap))}
|
||||
|
||||
for _, v := range plMap {
|
||||
loader.plugins = append(loader.plugins, v)
|
||||
}
|
||||
|
||||
return loader, nil
|
||||
}
|
||||
|
||||
func loadDynamicPlugins(pluginDir string) ([]plugin.Plugin, error) {
|
||||
_, err := os.Stat(pluginDir)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return loadPluginsFunc(pluginDir)
|
||||
}
|
||||
|
||||
// Initialize initializes all loaded plugins
|
||||
func (loader *PluginLoader) Initialize() error {
|
||||
for _, p := range loader.plugins {
|
||||
err := p.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Inject hooks all the plugins into the appropriate subsystems.
|
||||
func (loader *PluginLoader) Inject() error {
|
||||
for _, pl := range loader.plugins {
|
||||
if pl, ok := pl.(plugin.PluginIPLD); ok {
|
||||
err := injectIPLDPlugin(pl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if pl, ok := pl.(plugin.PluginTracer); ok {
|
||||
err := injectTracerPlugin(pl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if pl, ok := pl.(plugin.PluginDatastore); ok {
|
||||
err := injectDatastorePlugin(pl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts all long-running plugins.
|
||||
func (loader *PluginLoader) Start(iface coreiface.CoreAPI) error {
|
||||
for i, pl := range loader.plugins {
|
||||
if pl, ok := pl.(plugin.PluginDaemon); ok {
|
||||
err := pl.Start(iface)
|
||||
if err != nil {
|
||||
_ = closePlugins(loader.plugins[i:])
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopDaemon stops all long-running plugins.
|
||||
func (loader *PluginLoader) Close() error {
|
||||
return closePlugins(loader.plugins)
|
||||
}
|
||||
|
||||
func closePlugins(plugins []plugin.Plugin) error {
|
||||
var errs []string
|
||||
for _, pl := range plugins {
|
||||
if pl, ok := pl.(plugin.PluginDaemon); ok {
|
||||
err := pl.Close()
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Sprintf(
|
||||
"error closing plugin %s: %s",
|
||||
pl.Name(),
|
||||
err.Error(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
if errs != nil {
|
||||
return fmt.Errorf(strings.Join(errs, "\n"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func injectDatastorePlugin(pl plugin.PluginDatastore) error {
|
||||
return fsrepo.AddDatastoreConfigHandler(pl.DatastoreTypeName(), pl.DatastoreConfigParser())
|
||||
}
|
||||
|
||||
func injectIPLDPlugin(pl plugin.PluginIPLD) error {
|
||||
err := pl.RegisterBlockDecoders(ipld.DefaultBlockDecoder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return pl.RegisterInputEncParsers(coredag.DefaultInputEncParsers)
|
||||
}
|
||||
|
||||
func injectTracerPlugin(pl plugin.PluginTracer) error {
|
||||
tracer, err := pl.InitTracer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opentracing.SetGlobalTracer(tracer)
|
||||
return nil
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"github.com/ipfs/go-ipfs/plugin"
|
||||
pluginbadgerds "github.com/ipfs/go-ipfs/plugin/plugins/badgerds"
|
||||
pluginflatfs "github.com/ipfs/go-ipfs/plugin/plugins/flatfs"
|
||||
pluginipldgit "github.com/ipfs/go-ipfs/plugin/plugins/git"
|
||||
pluginlevelds "github.com/ipfs/go-ipfs/plugin/plugins/levelds"
|
||||
)
|
||||
|
||||
// DO NOT EDIT THIS FILE
|
||||
// This file is being generated as part of plugin build process
|
||||
// To change it, modify the plugin/loader/preload.sh
|
||||
|
||||
var preloadPlugins = []plugin.Plugin{
|
||||
pluginipldgit.Plugins[0],
|
||||
pluginbadgerds.Plugins[0],
|
||||
pluginflatfs.Plugins[0],
|
||||
pluginlevelds.Plugins[0],
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
to_preload() {
|
||||
awk 'NF' "$DIR/preload_list" | sed '/^#/d'
|
||||
}
|
||||
|
||||
cat <<EOL
|
||||
package loader
|
||||
|
||||
import (
|
||||
"github.com/ipfs/go-ipfs/plugin"
|
||||
EOL
|
||||
|
||||
to_preload | while read -r name path num; do
|
||||
echo "plugin$name \"$path\""
|
||||
done | sort -u
|
||||
|
||||
cat <<EOL
|
||||
)
|
||||
|
||||
|
||||
// DO NOT EDIT THIS FILE
|
||||
// This file is being generated as part of plugin build process
|
||||
// To change it, modify the plugin/loader/preload.sh
|
||||
|
||||
var preloadPlugins = []plugin.Plugin{
|
||||
EOL
|
||||
|
||||
to_preload | while read -r name path num; do
|
||||
echo "plugin$name.Plugins[$num],"
|
||||
done
|
||||
|
||||
|
||||
echo "}"
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
# this file contains plugins to be preloaded
|
||||
# empty lines or starting with '#' are ignored
|
||||
#
|
||||
# name go-path number of the sub-plugin
|
||||
|
||||
ipldgit github.com/ipfs/go-ipfs/plugin/plugins/git 0
|
||||
|
||||
badgerds github.com/ipfs/go-ipfs/plugin/plugins/badgerds 0
|
||||
flatfs github.com/ipfs/go-ipfs/plugin/plugins/flatfs 0
|
||||
levelds github.com/ipfs/go-ipfs/plugin/plugins/levelds 0
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package plugin
|
||||
|
||||
// Plugin is base interface for all kinds of go-ipfs plugins
|
||||
// It will be included in interfaces of different Plugins
|
||||
type Plugin interface {
|
||||
// Name should return unique name of the plugin
|
||||
Name() string
|
||||
// Version returns current version of the plugin
|
||||
Version() string
|
||||
// Init is called once when the Plugin is being loaded
|
||||
Init() error
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
package badgerds
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ipfs/go-ipfs/plugin"
|
||||
"github.com/ipfs/go-ipfs/repo"
|
||||
"github.com/ipfs/go-ipfs/repo/fsrepo"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
badgerds "github.com/ipfs/go-ds-badger"
|
||||
)
|
||||
|
||||
// Plugins is exported list of plugins that will be loaded
|
||||
var Plugins = []plugin.Plugin{
|
||||
&badgerdsPlugin{},
|
||||
}
|
||||
|
||||
type badgerdsPlugin struct{}
|
||||
|
||||
var _ plugin.PluginDatastore = (*badgerdsPlugin)(nil)
|
||||
|
||||
func (*badgerdsPlugin) Name() string {
|
||||
return "ds-badgerds"
|
||||
}
|
||||
|
||||
func (*badgerdsPlugin) Version() string {
|
||||
return "0.1.0"
|
||||
}
|
||||
|
||||
func (*badgerdsPlugin) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*badgerdsPlugin) DatastoreTypeName() string {
|
||||
return "badgerds"
|
||||
}
|
||||
|
||||
type datastoreConfig struct {
|
||||
path string
|
||||
syncWrites bool
|
||||
truncate bool
|
||||
|
||||
vlogFileSize int64
|
||||
}
|
||||
|
||||
// BadgerdsDatastoreConfig returns a configuration stub for a badger datastore
|
||||
// from the given parameters
|
||||
func (*badgerdsPlugin) DatastoreConfigParser() fsrepo.ConfigFromMap {
|
||||
return func(params map[string]interface{}) (fsrepo.DatastoreConfig, error) {
|
||||
var c datastoreConfig
|
||||
var ok bool
|
||||
|
||||
c.path, ok = params["path"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("'path' field is missing or not string")
|
||||
}
|
||||
|
||||
sw, ok := params["syncWrites"]
|
||||
if !ok {
|
||||
c.syncWrites = true
|
||||
} else {
|
||||
if swb, ok := sw.(bool); ok {
|
||||
c.syncWrites = swb
|
||||
} else {
|
||||
return nil, fmt.Errorf("'syncWrites' field was not a boolean")
|
||||
}
|
||||
}
|
||||
|
||||
truncate, ok := params["truncate"]
|
||||
if !ok {
|
||||
c.truncate = true
|
||||
} else {
|
||||
if truncate, ok := truncate.(bool); ok {
|
||||
c.truncate = truncate
|
||||
} else {
|
||||
return nil, fmt.Errorf("'truncate' field was not a boolean")
|
||||
}
|
||||
}
|
||||
|
||||
vls, ok := params["vlogFileSize"]
|
||||
if !ok {
|
||||
// default to 1GiB
|
||||
c.vlogFileSize = badgerds.DefaultOptions.ValueLogFileSize
|
||||
} else {
|
||||
if vlogSize, ok := vls.(string); ok {
|
||||
s, err := humanize.ParseBytes(vlogSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.vlogFileSize = int64(s)
|
||||
} else {
|
||||
return nil, fmt.Errorf("'vlogFileSize' field was not a string")
|
||||
}
|
||||
}
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *datastoreConfig) DiskSpec() fsrepo.DiskSpec {
|
||||
return map[string]interface{}{
|
||||
"type": "badgerds",
|
||||
"path": c.path,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *datastoreConfig) Create(path string) (repo.Datastore, error) {
|
||||
p := c.path
|
||||
if !filepath.IsAbs(p) {
|
||||
p = filepath.Join(path, p)
|
||||
}
|
||||
|
||||
err := os.MkdirAll(p, 0755)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defopts := badgerds.DefaultOptions
|
||||
defopts.SyncWrites = c.syncWrites
|
||||
defopts.Truncate = c.truncate
|
||||
defopts.ValueLogFileSize = c.vlogFileSize
|
||||
|
||||
return badgerds.NewDatastore(p, &defopts)
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package flatfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ipfs/go-ipfs/plugin"
|
||||
"github.com/ipfs/go-ipfs/repo"
|
||||
"github.com/ipfs/go-ipfs/repo/fsrepo"
|
||||
|
||||
flatfs "github.com/ipfs/go-ds-flatfs"
|
||||
)
|
||||
|
||||
// Plugins is exported list of plugins that will be loaded
|
||||
var Plugins = []plugin.Plugin{
|
||||
&flatfsPlugin{},
|
||||
}
|
||||
|
||||
type flatfsPlugin struct{}
|
||||
|
||||
var _ plugin.PluginDatastore = (*flatfsPlugin)(nil)
|
||||
|
||||
func (*flatfsPlugin) Name() string {
|
||||
return "ds-flatfs"
|
||||
}
|
||||
|
||||
func (*flatfsPlugin) Version() string {
|
||||
return "0.1.0"
|
||||
}
|
||||
|
||||
func (*flatfsPlugin) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*flatfsPlugin) DatastoreTypeName() string {
|
||||
return "flatfs"
|
||||
}
|
||||
|
||||
type datastoreConfig struct {
|
||||
path string
|
||||
shardFun *flatfs.ShardIdV1
|
||||
syncField bool
|
||||
}
|
||||
|
||||
// BadgerdsDatastoreConfig returns a configuration stub for a badger datastore
|
||||
// from the given parameters
|
||||
func (*flatfsPlugin) DatastoreConfigParser() fsrepo.ConfigFromMap {
|
||||
return func(params map[string]interface{}) (fsrepo.DatastoreConfig, error) {
|
||||
var c datastoreConfig
|
||||
var ok bool
|
||||
var err error
|
||||
|
||||
c.path, ok = params["path"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("'path' field is missing or not boolean")
|
||||
}
|
||||
|
||||
sshardFun, ok := params["shardFunc"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("'shardFunc' field is missing or not a string")
|
||||
}
|
||||
c.shardFun, err = flatfs.ParseShardFunc(sshardFun)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.syncField, ok = params["sync"].(bool)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("'sync' field is missing or not boolean")
|
||||
}
|
||||
return &c, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *datastoreConfig) DiskSpec() fsrepo.DiskSpec {
|
||||
return map[string]interface{}{
|
||||
"type": "flatfs",
|
||||
"path": c.path,
|
||||
"shardFunc": c.shardFun.String(),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *datastoreConfig) Create(path string) (repo.Datastore, error) {
|
||||
p := c.path
|
||||
if !filepath.IsAbs(p) {
|
||||
p = filepath.Join(path, p)
|
||||
}
|
||||
|
||||
return flatfs.CreateOrOpen(p, c.shardFun, c.syncField)
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"compress/zlib"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/ipfs/go-ipfs/core/coredag"
|
||||
"github.com/ipfs/go-ipfs/plugin"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipfs/go-ipld-format"
|
||||
git "github.com/ipfs/go-ipld-git"
|
||||
mh "github.com/multiformats/go-multihash"
|
||||
)
|
||||
|
||||
// Plugins is exported list of plugins that will be loaded
|
||||
var Plugins = []plugin.Plugin{
|
||||
&gitPlugin{},
|
||||
}
|
||||
|
||||
type gitPlugin struct{}
|
||||
|
||||
var _ plugin.PluginIPLD = (*gitPlugin)(nil)
|
||||
|
||||
func (*gitPlugin) Name() string {
|
||||
return "ipld-git"
|
||||
}
|
||||
|
||||
func (*gitPlugin) Version() string {
|
||||
return "0.0.1"
|
||||
}
|
||||
|
||||
func (*gitPlugin) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*gitPlugin) RegisterBlockDecoders(dec format.BlockDecoder) error {
|
||||
dec.Register(cid.GitRaw, git.DecodeBlock)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*gitPlugin) RegisterInputEncParsers(iec coredag.InputEncParsers) error {
|
||||
iec.AddParser("raw", "git", parseRawGit)
|
||||
iec.AddParser("zlib", "git", parseZlibGit)
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseRawGit(r io.Reader, mhType uint64, mhLen int) ([]format.Node, error) {
|
||||
if mhType != math.MaxUint64 && mhType != mh.SHA1 {
|
||||
return nil, fmt.Errorf("unsupported mhType %d", mhType)
|
||||
}
|
||||
|
||||
if mhLen != -1 && mhLen != mh.DefaultLengths[mh.SHA1] {
|
||||
return nil, fmt.Errorf("invalid mhLen %d", mhLen)
|
||||
}
|
||||
|
||||
nd, err := git.ParseObject(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []format.Node{nd}, nil
|
||||
}
|
||||
|
||||
func parseZlibGit(r io.Reader, mhType uint64, mhLen int) ([]format.Node, error) {
|
||||
rc, err := zlib.NewReader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rc.Close()
|
||||
return parseRawGit(rc, mhType, mhLen)
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package levelds
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ipfs/go-ipfs/plugin"
|
||||
"github.com/ipfs/go-ipfs/repo"
|
||||
"github.com/ipfs/go-ipfs/repo/fsrepo"
|
||||
|
||||
levelds "github.com/ipfs/go-ds-leveldb"
|
||||
ldbopts "github.com/syndtr/goleveldb/leveldb/opt"
|
||||
)
|
||||
|
||||
// Plugins is exported list of plugins that will be loaded
|
||||
var Plugins = []plugin.Plugin{
|
||||
&leveldsPlugin{},
|
||||
}
|
||||
|
||||
type leveldsPlugin struct{}
|
||||
|
||||
var _ plugin.PluginDatastore = (*leveldsPlugin)(nil)
|
||||
|
||||
func (*leveldsPlugin) Name() string {
|
||||
return "ds-level"
|
||||
}
|
||||
|
||||
func (*leveldsPlugin) Version() string {
|
||||
return "0.1.0"
|
||||
}
|
||||
|
||||
func (*leveldsPlugin) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*leveldsPlugin) DatastoreTypeName() string {
|
||||
return "levelds"
|
||||
}
|
||||
|
||||
type datastoreConfig struct {
|
||||
path string
|
||||
compression ldbopts.Compression
|
||||
}
|
||||
|
||||
// BadgerdsDatastoreConfig returns a configuration stub for a badger datastore
|
||||
// from the given parameters
|
||||
func (*leveldsPlugin) DatastoreConfigParser() fsrepo.ConfigFromMap {
|
||||
return func(params map[string]interface{}) (fsrepo.DatastoreConfig, error) {
|
||||
var c datastoreConfig
|
||||
var ok bool
|
||||
|
||||
c.path, ok = params["path"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("'path' field is missing or not string")
|
||||
}
|
||||
|
||||
switch cm := params["compression"].(string); cm {
|
||||
case "none":
|
||||
c.compression = ldbopts.NoCompression
|
||||
case "snappy":
|
||||
c.compression = ldbopts.SnappyCompression
|
||||
case "":
|
||||
c.compression = ldbopts.DefaultCompression
|
||||
default:
|
||||
return nil, fmt.Errorf("unrecognized value for compression: %s", cm)
|
||||
}
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *datastoreConfig) DiskSpec() fsrepo.DiskSpec {
|
||||
return map[string]interface{}{
|
||||
"type": "levelds",
|
||||
"path": c.path,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *datastoreConfig) Create(path string) (repo.Datastore, error) {
|
||||
p := c.path
|
||||
if !filepath.IsAbs(p) {
|
||||
p = filepath.Join(path, p)
|
||||
}
|
||||
|
||||
return levelds.NewDatastore(p, &levelds.Options{
|
||||
Compression: c.compression,
|
||||
})
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"github.com/opentracing/opentracing-go"
|
||||
)
|
||||
|
||||
// PluginTracer is an interface that can be implemented to add a tracer
|
||||
type PluginTracer interface {
|
||||
Plugin
|
||||
InitTracer() (opentracing.Tracer, error)
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
os:
|
||||
- linux
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.11.x
|
||||
|
||||
env:
|
||||
global:
|
||||
- GOTFLAGS="-race"
|
||||
matrix:
|
||||
- BUILD_DEPTYPE=gx
|
||||
- BUILD_DEPTYPE=gomod
|
||||
|
||||
|
||||
# disable travis install
|
||||
install:
|
||||
- true
|
||||
|
||||
script:
|
||||
- bash <(curl -s https://raw.githubusercontent.com/ipfs/ci-helpers/master/travis-ci/run-standard-tests.sh)
|
||||
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $GOPATH/src/gx
|
||||
- $GOPATH/pkg/mod
|
||||
- $HOME/.cache/go-build
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
Git ipld format
|
||||
==================
|
||||
|
||||
[](http://ipn.io)
|
||||
[](http://ipfs.io/)
|
||||
[](http://webchat.freenode.net/?channels=%23ipfs)
|
||||
[](https://codecov.io/gh/ipfs/go-ipld-git/branch/master)
|
||||
[](https://travis-ci.org/ipfs/go-ipld-git)
|
||||
|
||||
> An ipld codec for git objects allowing path traversals across the git graph!
|
||||
|
||||
Note: This is WIP and may not be an entirely correct parser.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Install](#install)
|
||||
- [About](#about)
|
||||
- [Contribute](#contribute)
|
||||
- [License](#license)
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
go get github.com/ipfs/go-ipld-git
|
||||
```
|
||||
|
||||
## About
|
||||
This is an IPLD codec which handles git objects. Objects are transformed
|
||||
into IPLD graph in the following way:
|
||||
|
||||
* Commit:
|
||||
```json
|
||||
{
|
||||
"author": {
|
||||
"date": "1503667703 +0200",
|
||||
"email": "author@mail",
|
||||
"name": "Author Name"
|
||||
},
|
||||
"committer": {
|
||||
"date": "1503667703 +0200",
|
||||
"email": "author@mail",
|
||||
"name": "Author Name"
|
||||
},
|
||||
"message": "Commit Message\n",
|
||||
"parents": [
|
||||
<LINK>, <LINK>, ...
|
||||
],
|
||||
"tree": <LINK>
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
* Tag:
|
||||
```json
|
||||
{
|
||||
"message": "message\n",
|
||||
"object": {
|
||||
"/": "baf4bcfg3mbz3yj3njqyr3ifdaqyfv3prei6h6bq"
|
||||
},
|
||||
"tag": "tagname",
|
||||
"tagger": {
|
||||
"date": "1503667703 +0200",
|
||||
"email": "author@mail",
|
||||
"name": "Author Name"
|
||||
},
|
||||
"type": "commit"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
* Tree:
|
||||
```json
|
||||
{
|
||||
"file.name": {
|
||||
"mode": "100664",
|
||||
"hash": <LINK>
|
||||
},
|
||||
"directoryname": {
|
||||
"mode": "40000",
|
||||
"hash": <LINK>
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
* Blob:
|
||||
```json
|
||||
"<base64 of 'blob <size>\0<data>'>"
|
||||
```
|
||||
## Contribute
|
||||
|
||||
PRs are welcome!
|
||||
|
||||
Small note: If editing the Readme, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification.
|
||||
|
||||
## License
|
||||
|
||||
MIT © Jeromy Johnson
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package ipldgit
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
cid "github.com/ipfs/go-cid"
|
||||
node "github.com/ipfs/go-ipld-format"
|
||||
)
|
||||
|
||||
type Blob struct {
|
||||
rawData []byte
|
||||
cid cid.Cid
|
||||
}
|
||||
|
||||
func (b *Blob) Cid() cid.Cid {
|
||||
return b.cid
|
||||
}
|
||||
|
||||
func (b *Blob) Copy() node.Node {
|
||||
nb := *b
|
||||
return &nb
|
||||
}
|
||||
|
||||
func (b *Blob) Links() []*node.Link {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Blob) Resolve(_ []string) (interface{}, []string, error) {
|
||||
return nil, nil, errors.New("no such link")
|
||||
}
|
||||
|
||||
func (b *Blob) ResolveLink(_ []string) (*node.Link, []string, error) {
|
||||
return nil, nil, errors.New("no such link")
|
||||
}
|
||||
|
||||
func (b *Blob) Loggable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"type": "git_blob",
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Blob) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(b.rawData)
|
||||
}
|
||||
|
||||
func (b *Blob) RawData() []byte {
|
||||
return []byte(b.rawData)
|
||||
}
|
||||
|
||||
func (b *Blob) Size() (uint64, error) {
|
||||
return uint64(len(b.rawData)), nil
|
||||
}
|
||||
|
||||
func (b *Blob) Stat() (*node.NodeStat, error) {
|
||||
return &node.NodeStat{}, nil
|
||||
}
|
||||
|
||||
func (b *Blob) String() string {
|
||||
return "[git blob]"
|
||||
}
|
||||
|
||||
func (b *Blob) Tree(p string, depth int) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Blob) GitSha() []byte {
|
||||
return cidToSha(b.Cid())
|
||||
}
|
||||
|
||||
var _ node.Node = (*Blob)(nil)
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
coverage:
|
||||
range: "50...100"
|
||||
comment: off
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
package ipldgit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
cid "github.com/ipfs/go-cid"
|
||||
node "github.com/ipfs/go-ipld-format"
|
||||
)
|
||||
|
||||
type Commit struct {
|
||||
DataSize string `json:"-"`
|
||||
GitTree cid.Cid `json:"tree"`
|
||||
Parents []cid.Cid `json:"parents"`
|
||||
Message string `json:"message"`
|
||||
Author *PersonInfo `json:"author"`
|
||||
Committer *PersonInfo `json:"committer"`
|
||||
Encoding string `json:"encoding,omitempty"`
|
||||
Sig *GpgSig `json:"signature,omitempty"`
|
||||
MergeTag []*MergeTag `json:"mergetag,omitempty"`
|
||||
|
||||
// Other contains all the non-standard headers, such as 'HG:extra'
|
||||
Other []string `json:"other,omitempty"`
|
||||
|
||||
cid cid.Cid
|
||||
|
||||
rawData []byte
|
||||
rawDataOnce sync.Once
|
||||
}
|
||||
|
||||
type PersonInfo struct {
|
||||
Name string
|
||||
Email string
|
||||
Date string
|
||||
Timezone string
|
||||
}
|
||||
|
||||
func (pi *PersonInfo) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]string{
|
||||
"name": pi.Name,
|
||||
"email": pi.Email,
|
||||
"date": pi.Date + " " + pi.Timezone,
|
||||
})
|
||||
}
|
||||
|
||||
func (pi *PersonInfo) String() string {
|
||||
f := "%s <%s>"
|
||||
arg := []interface{}{pi.Name, pi.Email}
|
||||
if pi.Date != "" {
|
||||
f = f + " %s"
|
||||
arg = append(arg, pi.Date)
|
||||
}
|
||||
|
||||
if pi.Timezone != "" {
|
||||
f = f + " %s"
|
||||
arg = append(arg, pi.Timezone)
|
||||
}
|
||||
return fmt.Sprintf(f, arg...)
|
||||
}
|
||||
|
||||
func (pi *PersonInfo) tree(name string, depth int) []string {
|
||||
if depth == 1 {
|
||||
return []string{name}
|
||||
}
|
||||
return []string{name + "/name", name + "/email", name + "/date"}
|
||||
}
|
||||
|
||||
func (pi *PersonInfo) resolve(p []string) (interface{}, []string, error) {
|
||||
switch p[0] {
|
||||
case "name":
|
||||
return pi.Name, p[1:], nil
|
||||
case "email":
|
||||
return pi.Email, p[1:], nil
|
||||
case "date":
|
||||
return pi.Date + " " + pi.Timezone, p[1:], nil
|
||||
default:
|
||||
return nil, nil, errors.New("no such link")
|
||||
}
|
||||
}
|
||||
|
||||
type MergeTag struct {
|
||||
Object cid.Cid `json:"object"`
|
||||
Type string `json:"type"`
|
||||
Tag string `json:"tag"`
|
||||
Tagger *PersonInfo `json:"tagger"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type GpgSig struct {
|
||||
Text string
|
||||
}
|
||||
|
||||
func (c *Commit) Cid() cid.Cid {
|
||||
return c.cid
|
||||
}
|
||||
|
||||
func (c *Commit) Copy() node.Node {
|
||||
nc := *c
|
||||
return &nc
|
||||
}
|
||||
|
||||
func (c *Commit) Links() []*node.Link {
|
||||
out := []*node.Link{
|
||||
{Cid: c.GitTree},
|
||||
}
|
||||
|
||||
for _, p := range c.Parents {
|
||||
out = append(out, &node.Link{Cid: p})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (c *Commit) Loggable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"type": "git_commit",
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commit) RawData() []byte {
|
||||
c.rawDataOnce.Do(func() {
|
||||
buf := new(bytes.Buffer)
|
||||
fmt.Fprintf(buf, "commit %s\x00", c.DataSize)
|
||||
fmt.Fprintf(buf, "tree %s\n", hex.EncodeToString(cidToSha(c.GitTree)))
|
||||
for _, p := range c.Parents {
|
||||
fmt.Fprintf(buf, "parent %s\n", hex.EncodeToString(cidToSha(p)))
|
||||
}
|
||||
fmt.Fprintf(buf, "author %s\n", c.Author.String())
|
||||
fmt.Fprintf(buf, "committer %s\n", c.Committer.String())
|
||||
if len(c.Encoding) > 0 {
|
||||
fmt.Fprintf(buf, "encoding %s\n", c.Encoding)
|
||||
}
|
||||
for _, mtag := range c.MergeTag {
|
||||
fmt.Fprintf(buf, "mergetag object %s\n", hex.EncodeToString(cidToSha(mtag.Object)))
|
||||
fmt.Fprintf(buf, " type %s\n", mtag.Type)
|
||||
fmt.Fprintf(buf, " tag %s\n", mtag.Tag)
|
||||
fmt.Fprintf(buf, " tagger %s\n \n", mtag.Tagger.String())
|
||||
fmt.Fprintf(buf, "%s", mtag.Text)
|
||||
}
|
||||
if c.Sig != nil {
|
||||
fmt.Fprintln(buf, "gpgsig -----BEGIN PGP SIGNATURE-----")
|
||||
fmt.Fprint(buf, c.Sig.Text)
|
||||
fmt.Fprintln(buf, " -----END PGP SIGNATURE-----")
|
||||
}
|
||||
for _, line := range c.Other {
|
||||
fmt.Fprintln(buf, line)
|
||||
}
|
||||
fmt.Fprintf(buf, "\n%s", c.Message)
|
||||
c.rawData = buf.Bytes()
|
||||
})
|
||||
|
||||
return c.rawData
|
||||
}
|
||||
|
||||
func (c *Commit) Resolve(path []string) (interface{}, []string, error) {
|
||||
if len(path) == 0 {
|
||||
return nil, nil, fmt.Errorf("zero length path")
|
||||
}
|
||||
|
||||
switch path[0] {
|
||||
case "parents":
|
||||
if len(path) == 1 {
|
||||
return c.Parents, nil, nil
|
||||
}
|
||||
|
||||
i, err := strconv.Atoi(path[1])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if i < 0 || i >= len(c.Parents) {
|
||||
return nil, nil, fmt.Errorf("index out of range")
|
||||
}
|
||||
|
||||
return &node.Link{Cid: c.Parents[i]}, path[2:], nil
|
||||
case "author":
|
||||
if len(path) == 1 {
|
||||
return c.Author, nil, nil
|
||||
}
|
||||
return c.Author.resolve(path[1:])
|
||||
case "committer":
|
||||
if len(path) == 1 {
|
||||
return c.Committer, nil, nil
|
||||
}
|
||||
return c.Committer.resolve(path[1:])
|
||||
case "signature":
|
||||
return c.Sig.Text, path[1:], nil
|
||||
case "message":
|
||||
return c.Message, path[1:], nil
|
||||
case "tree":
|
||||
return &node.Link{Cid: c.GitTree}, path[1:], nil
|
||||
case "mergetag":
|
||||
if len(path) == 1 {
|
||||
return c.MergeTag, nil, nil
|
||||
}
|
||||
|
||||
i, err := strconv.Atoi(path[1])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if i < 0 || i >= len(c.MergeTag) {
|
||||
return nil, nil, fmt.Errorf("index out of range")
|
||||
}
|
||||
|
||||
if len(path) == 2 {
|
||||
return c.MergeTag[i], nil, nil
|
||||
}
|
||||
return c.MergeTag[i].resolve(path[2:])
|
||||
default:
|
||||
return nil, nil, errors.New("no such link")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commit) ResolveLink(path []string) (*node.Link, []string, error) {
|
||||
out, rest, err := c.Resolve(path)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
lnk, ok := out.(*node.Link)
|
||||
if !ok {
|
||||
return nil, nil, errors.New("not a link")
|
||||
}
|
||||
|
||||
return lnk, rest, nil
|
||||
}
|
||||
|
||||
func (c *Commit) Size() (uint64, error) {
|
||||
return uint64(len(c.RawData())), nil
|
||||
}
|
||||
|
||||
func (c *Commit) Stat() (*node.NodeStat, error) {
|
||||
return &node.NodeStat{}, nil
|
||||
}
|
||||
|
||||
func (c *Commit) String() string {
|
||||
return "[git commit object]"
|
||||
}
|
||||
|
||||
func (c *Commit) Tree(p string, depth int) []string {
|
||||
if depth != -1 {
|
||||
panic("proper tree not yet implemented")
|
||||
}
|
||||
tree := []string{"tree", "parents", "message", "gpgsig"}
|
||||
tree = append(tree, c.Author.tree("author", depth)...)
|
||||
tree = append(tree, c.Committer.tree("committer", depth)...)
|
||||
for i := range c.Parents {
|
||||
tree = append(tree, fmt.Sprintf("parents/%d", i))
|
||||
}
|
||||
return tree
|
||||
}
|
||||
|
||||
func (c *Commit) GitSha() []byte {
|
||||
return cidToSha(c.Cid())
|
||||
}
|
||||
|
||||
func (t *MergeTag) resolve(path []string) (interface{}, []string, error) {
|
||||
if len(path) == 0 {
|
||||
return nil, nil, fmt.Errorf("zero length path")
|
||||
}
|
||||
|
||||
switch path[0] {
|
||||
case "object":
|
||||
return &node.Link{Cid: t.Object}, path[1:], nil
|
||||
case "tag":
|
||||
return t.Tag, path[1:], nil
|
||||
case "tagger":
|
||||
if len(path) == 1 {
|
||||
return t.Tagger, nil, nil
|
||||
}
|
||||
return t.Tagger.resolve(path[1:])
|
||||
case "text":
|
||||
return t.Text, path[1:], nil
|
||||
case "type":
|
||||
return t.Type, path[1:], nil
|
||||
default:
|
||||
return nil, nil, errors.New("no such link")
|
||||
}
|
||||
}
|
||||
|
||||
var _ node.Node = (*Commit)(nil)
|
||||
+478
@@ -0,0 +1,478 @@
|
||||
package ipldgit
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
blocks "github.com/ipfs/go-block-format"
|
||||
cid "github.com/ipfs/go-cid"
|
||||
node "github.com/ipfs/go-ipld-format"
|
||||
mh "github.com/multiformats/go-multihash"
|
||||
)
|
||||
|
||||
func DecodeBlock(block blocks.Block) (node.Node, error) {
|
||||
prefix := block.Cid().Prefix()
|
||||
|
||||
if prefix.Codec != cid.GitRaw || prefix.MhType != mh.SHA1 || prefix.MhLength != mh.DefaultLengths[mh.SHA1] {
|
||||
return nil, errors.New("invalid CID prefix")
|
||||
}
|
||||
|
||||
return ParseObjectFromBuffer(block.RawData())
|
||||
}
|
||||
|
||||
var _ node.DecodeBlockFunc = DecodeBlock
|
||||
|
||||
func ParseObjectFromBuffer(b []byte) (node.Node, error) {
|
||||
return ParseObject(bytes.NewReader(b))
|
||||
}
|
||||
|
||||
func ParseCompressedObject(r io.Reader) (node.Node, error) {
|
||||
rc, err := zlib.NewReader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
return ParseObject(rc)
|
||||
}
|
||||
|
||||
func ParseObject(r io.Reader) (node.Node, error) {
|
||||
rd := bufio.NewReader(r)
|
||||
|
||||
typ, err := rd.ReadString(' ')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
typ = typ[:len(typ)-1]
|
||||
|
||||
switch typ {
|
||||
case "tree":
|
||||
return ReadTree(rd)
|
||||
case "commit":
|
||||
return ReadCommit(rd)
|
||||
case "blob":
|
||||
return ReadBlob(rd)
|
||||
case "tag":
|
||||
return ReadTag(rd)
|
||||
default:
|
||||
return nil, fmt.Errorf("unrecognized object type: %s", typ)
|
||||
}
|
||||
}
|
||||
|
||||
func ReadBlob(rd *bufio.Reader) (*Blob, error) {
|
||||
size, err := rd.ReadString(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sizen, err := strconv.Atoi(size[:len(size)-1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
fmt.Fprintf(buf, "blob %d\x00", sizen)
|
||||
|
||||
n, err := io.Copy(buf, rd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if n != int64(sizen) {
|
||||
return nil, fmt.Errorf("blob size was not accurate")
|
||||
}
|
||||
|
||||
out := &Blob{}
|
||||
out.rawData = buf.Bytes()
|
||||
out.cid = hashObject(out.RawData())
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func ReadCommit(rd *bufio.Reader) (*Commit, error) {
|
||||
size, err := rd.ReadString(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := &Commit{
|
||||
DataSize: size[:len(size)-1],
|
||||
}
|
||||
|
||||
for {
|
||||
line, _, err := rd.ReadLine()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = parseCommitLine(out, line, rd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
out.cid = hashObject(out.RawData())
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func parseCommitLine(out *Commit, line []byte, rd *bufio.Reader) error {
|
||||
switch {
|
||||
case bytes.HasPrefix(line, []byte("tree ")):
|
||||
sha, err := hex.DecodeString(string(line[5:]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.GitTree = shaToCid(sha)
|
||||
case bytes.HasPrefix(line, []byte("parent ")):
|
||||
psha, err := hex.DecodeString(string(line[7:]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.Parents = append(out.Parents, shaToCid(psha))
|
||||
case bytes.HasPrefix(line, []byte("author ")):
|
||||
a, err := parsePersonInfo(line)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.Author = a
|
||||
case bytes.HasPrefix(line, []byte("committer ")):
|
||||
c, err := parsePersonInfo(line)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.Committer = c
|
||||
case bytes.HasPrefix(line, []byte("encoding ")):
|
||||
out.Encoding = string(line[9:])
|
||||
case bytes.HasPrefix(line, []byte("mergetag object ")):
|
||||
sha, err := hex.DecodeString(string(line)[16:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mt, rest, err := ReadMergeTag(sha, rd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.MergeTag = append(out.MergeTag, mt)
|
||||
|
||||
if rest != nil {
|
||||
err = parseCommitLine(out, rest, rd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case bytes.HasPrefix(line, []byte("gpgsig ")):
|
||||
sig, err := ReadGpgSig(rd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out.Sig = sig
|
||||
case len(line) == 0:
|
||||
rest, err := ioutil.ReadAll(rd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.Message = string(rest)
|
||||
default:
|
||||
out.Other = append(out.Other, string(line))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadTag(rd *bufio.Reader) (*Tag, error) {
|
||||
size, err := rd.ReadString(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := &Tag{
|
||||
dataSize: size[:len(size)-1],
|
||||
}
|
||||
|
||||
for {
|
||||
line, _, err := rd.ReadLine()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch {
|
||||
case bytes.HasPrefix(line, []byte("object ")):
|
||||
sha, err := hex.DecodeString(string(line[7:]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out.Object = shaToCid(sha)
|
||||
case bytes.HasPrefix(line, []byte("tag ")):
|
||||
out.Tag = string(line[4:])
|
||||
case bytes.HasPrefix(line, []byte("tagger ")):
|
||||
c, err := parsePersonInfo(line)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out.Tagger = c
|
||||
case bytes.HasPrefix(line, []byte("type ")):
|
||||
out.Type = string(line[5:])
|
||||
case len(line) == 0:
|
||||
rest, err := ioutil.ReadAll(rd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out.Message = string(rest)
|
||||
default:
|
||||
fmt.Println("unhandled line: ", string(line))
|
||||
}
|
||||
}
|
||||
|
||||
out.cid = hashObject(out.RawData())
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func hashObject(data []byte) cid.Cid {
|
||||
c, err := cid.Prefix{
|
||||
MhType: mh.SHA1,
|
||||
MhLength: -1,
|
||||
Codec: cid.GitRaw,
|
||||
Version: 1,
|
||||
}.Sum(data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func ReadMergeTag(hash []byte, rd *bufio.Reader) (*MergeTag, []byte, error) {
|
||||
out := new(MergeTag)
|
||||
|
||||
out.Object = shaToCid(hash)
|
||||
for {
|
||||
line, _, err := rd.ReadLine()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
switch {
|
||||
case bytes.HasPrefix(line, []byte(" type ")):
|
||||
out.Type = string(line[6:])
|
||||
case bytes.HasPrefix(line, []byte(" tag ")):
|
||||
out.Tag = string(line[5:])
|
||||
case bytes.HasPrefix(line, []byte(" tagger ")):
|
||||
tagger, err := parsePersonInfo(line[1:])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
out.Tagger = tagger
|
||||
case string(line) == " ":
|
||||
for {
|
||||
line, _, err := rd.ReadLine()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if !bytes.HasPrefix(line, []byte(" ")) {
|
||||
return out, line, nil
|
||||
}
|
||||
|
||||
out.Text += string(line) + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
return out, nil, nil
|
||||
}
|
||||
|
||||
func ReadGpgSig(rd *bufio.Reader) (*GpgSig, error) {
|
||||
line, _, err := rd.ReadLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := new(GpgSig)
|
||||
|
||||
if string(line) != " " {
|
||||
if strings.HasPrefix(string(line), " Version: ") || strings.HasPrefix(string(line), " Comment: ") {
|
||||
out.Text += string(line) + "\n"
|
||||
} else {
|
||||
return nil, fmt.Errorf("expected first line of sig to be a single space or version")
|
||||
}
|
||||
} else {
|
||||
out.Text += " \n"
|
||||
}
|
||||
|
||||
for {
|
||||
line, _, err := rd.ReadLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if bytes.Equal(line, []byte(" -----END PGP SIGNATURE-----")) {
|
||||
break
|
||||
}
|
||||
|
||||
out.Text += string(line) + "\n"
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func parsePersonInfo(line []byte) (*PersonInfo, error) {
|
||||
parts := bytes.Split(line, []byte{' '})
|
||||
if len(parts) < 3 {
|
||||
fmt.Println(string(line))
|
||||
return nil, fmt.Errorf("incorrectly formatted person info line")
|
||||
}
|
||||
|
||||
//TODO: just use regex?
|
||||
//skip prefix
|
||||
at := 1
|
||||
|
||||
var pi PersonInfo
|
||||
var name string
|
||||
|
||||
for {
|
||||
if at == len(parts) {
|
||||
return nil, fmt.Errorf("invalid personInfo: %s\n", line)
|
||||
}
|
||||
part := parts[at]
|
||||
if len(part) != 0 {
|
||||
if part[0] == '<' {
|
||||
break
|
||||
}
|
||||
name += string(part) + " "
|
||||
} else if len(name) > 0 {
|
||||
name += " "
|
||||
}
|
||||
at++
|
||||
}
|
||||
if len(name) != 0 {
|
||||
pi.Name = name[:len(name)-1]
|
||||
}
|
||||
|
||||
var email string
|
||||
for {
|
||||
if at == len(parts) {
|
||||
return nil, fmt.Errorf("invalid personInfo: %s\n", line)
|
||||
}
|
||||
part := parts[at]
|
||||
if part[0] == '<' {
|
||||
part = part[1:]
|
||||
}
|
||||
|
||||
at++
|
||||
if part[len(part)-1] == '>' {
|
||||
email += string(part[:len(part)-1])
|
||||
break
|
||||
}
|
||||
email += string(part) + " "
|
||||
}
|
||||
pi.Email = email
|
||||
|
||||
if at == len(parts) {
|
||||
return &pi, nil
|
||||
}
|
||||
pi.Date = string(parts[at])
|
||||
|
||||
at++
|
||||
if at == len(parts) {
|
||||
return &pi, nil
|
||||
}
|
||||
pi.Timezone = string(parts[at])
|
||||
return &pi, nil
|
||||
}
|
||||
|
||||
func ReadTree(rd *bufio.Reader) (*Tree, error) {
|
||||
lstr, err := rd.ReadString(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lstr = lstr[:len(lstr)-1]
|
||||
|
||||
n, err := strconv.Atoi(lstr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t := &Tree{
|
||||
entries: make(map[string]*TreeEntry),
|
||||
size: n,
|
||||
}
|
||||
var order []string
|
||||
for {
|
||||
e, err := ReadEntry(rd)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
order = append(order, e.name)
|
||||
t.entries[e.name] = e
|
||||
}
|
||||
t.order = order
|
||||
t.cid = hashObject(t.RawData())
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func cidToSha(c cid.Cid) []byte {
|
||||
h := c.Hash()
|
||||
return h[len(h)-20:]
|
||||
}
|
||||
|
||||
func shaToCid(sha []byte) cid.Cid {
|
||||
h, _ := mh.Encode(sha, mh.SHA1)
|
||||
return cid.NewCidV1(cid.GitRaw, h)
|
||||
}
|
||||
|
||||
func ReadEntry(r *bufio.Reader) (*TreeEntry, error) {
|
||||
data, err := r.ReadString(' ')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = data[:len(data)-1]
|
||||
|
||||
name, err := r.ReadString(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name = name[:len(name)-1]
|
||||
|
||||
sha := make([]byte, 20)
|
||||
_, err = io.ReadFull(r, sha)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &TreeEntry{
|
||||
name: name,
|
||||
Mode: data,
|
||||
Hash: shaToCid(sha),
|
||||
}, nil
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
module github.com/ipfs/go-ipld-git
|
||||
|
||||
require (
|
||||
github.com/ipfs/go-block-format v0.0.2
|
||||
github.com/ipfs/go-cid v0.0.2
|
||||
github.com/ipfs/go-ipld-format v0.0.1
|
||||
github.com/multiformats/go-multihash v0.0.1
|
||||
)
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
github.com/gxed/hashland/keccakpg v0.0.1 h1:wrk3uMNaMxbXiHibbPO4S0ymqJMm41WiudyFSs7UnsU=
|
||||
github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU=
|
||||
github.com/gxed/hashland/murmur3 v0.0.1 h1:SheiaIt0sda5K+8FLz952/1iWS9zrnKsEJaOJu4ZbSc=
|
||||
github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48=
|
||||
github.com/ipfs/go-block-format v0.0.2 h1:qPDvcP19izTjU8rgo6p7gTXZlkMkF5bz5G3fqIsSCPE=
|
||||
github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY=
|
||||
github.com/ipfs/go-cid v0.0.1 h1:GBjWPktLnNyX0JiQCNFpUuUSoMw5KMyqrsejHYlILBE=
|
||||
github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
|
||||
github.com/ipfs/go-cid v0.0.2 h1:tuuKaZPU1M6HcejsO3AcYWW8sZ8MTvyxfc4uqB4eFE8=
|
||||
github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
|
||||
github.com/ipfs/go-ipfs-util v0.0.1 h1:Wz9bL2wB2YBJqggkA4dD7oSmqB4cAnpNbGrlHJulv50=
|
||||
github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc=
|
||||
github.com/ipfs/go-ipld-format v0.0.1 h1:HCu4eB/Gh+KD/Q0M8u888RFkorTWNIL3da4oc5dwc80=
|
||||
github.com/ipfs/go-ipld-format v0.0.1/go.mod h1:kyJtbkDALmFHv3QR6et67i35QzO3S0dCDnkOJhcZkms=
|
||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g=
|
||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
|
||||
github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16 h1:5W7KhL8HVF3XCFOweFD3BNESdnO8ewyYTFT2R+/b8FQ=
|
||||
github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
|
||||
github.com/mr-tron/base58 v1.1.0 h1:Y51FGVJ91WBqCEabAi5OPUz38eAx8DakuAm5svLcsfQ=
|
||||
github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
|
||||
github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI=
|
||||
github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA=
|
||||
github.com/multiformats/go-multibase v0.0.1 h1:PN9/v21eLywrFWdFNsFKaU04kLJzuYzmrJR+ubhT9qA=
|
||||
github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs=
|
||||
github.com/multiformats/go-multihash v0.0.1 h1:HHwN1K12I+XllBCrqKnhX949Orn4oawPkegHMu2vDqQ=
|
||||
github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 h1:ng3VDlRp5/DHpSWl02R4rM9I+8M2rhmsuLwAMmkLQWE=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/sys v0.0.0-20190219092855-153ac476189d h1:Z0Ahzd7HltpJtjAHHxX8QFP3j1yYgiuvjbjRzDj/KH0=
|
||||
golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -x
|
||||
CUR_DIR=$(pwd)
|
||||
TEST_DIR=$(mktemp -d)
|
||||
cd ${TEST_DIR}
|
||||
|
||||
git init
|
||||
|
||||
# Test generic commit/blob
|
||||
|
||||
git config user.name "John Doe"
|
||||
git config user.email johndoe@example.com
|
||||
|
||||
echo "Hello world" > file
|
||||
git add file
|
||||
|
||||
git commit -m "Init"
|
||||
|
||||
# Test generic commit/tree/blob, weird person info
|
||||
|
||||
mkdir dir
|
||||
mkdir dir/subdir
|
||||
mkdir dir2
|
||||
|
||||
echo "qwerty" > dir/f1
|
||||
echo "123456" > dir/subdir/f2
|
||||
echo "',.pyf" > dir2/f3
|
||||
|
||||
git add .
|
||||
|
||||
git config user.name "John Doe & John Other"
|
||||
git config user.email "johndoe@example.com, johnother@example.com"
|
||||
git commit -m "Commit 2"
|
||||
|
||||
# Test merge-tag
|
||||
git config user.name "John Doe"
|
||||
git config user.email johndoe@example.com
|
||||
|
||||
git branch dev
|
||||
git checkout dev
|
||||
|
||||
echo ";qjkxb" > dir/f4
|
||||
|
||||
git add dir/f4
|
||||
git commit -m "Release"
|
||||
git tag -a v1 -m "Some version"
|
||||
git checkout master
|
||||
|
||||
## defer eyes.Open()
|
||||
## eyes.Close()
|
||||
|
||||
git cat-file tag $(cat .git/refs/tags/v1) | head -n4 | sed 's/v1/v1sig/g' > sigobj
|
||||
cat >>sigobj <<EOF
|
||||
|
||||
Some signed version
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
NotReallyABase64Signature
|
||||
ButItsGoodEnough
|
||||
-----END PGP SIGNATURE-----
|
||||
EOF
|
||||
|
||||
cat <(printf "tag %d\0" $(wc -c sigobj | cut -d' ' -f1); cat sigobj) > sigtag
|
||||
FILE=.git/objects/$(sha1sum sigtag | cut -d' ' -f1 | sed 's/../\0\//')
|
||||
mkdir -p $(dirname ${FILE})
|
||||
cat sigtag | zlib-flate -compress > ${FILE}
|
||||
echo $(sha1sum sigtag | cut -d' ' -f1) > .git/refs/tags/v1sig
|
||||
|
||||
git merge v1sig --no-ff -m "Merge tag v1"
|
||||
|
||||
# Test encoding
|
||||
git config i18n.commitencoding "ISO-8859-1"
|
||||
echo "fgcrl" > f6
|
||||
git add f6
|
||||
git commit -m "Encoded"
|
||||
|
||||
# Test iplBlob/tree tags
|
||||
git tag -a v1-file -m "Some file" 933b7583b7767b07ea4cf242c1be29162eb8bb85
|
||||
git tag -a v1-tree -m "Some tree" 672ef117424f54b71e5e058d1184de6a07450d0e
|
||||
|
||||
# Create test 'signed' objects
|
||||
|
||||
git cat-file commit $(cat .git/refs/heads/master) | head -n4 > sigobj
|
||||
echo "gpgsig -----BEGIN PGP SIGNATURE-----" >> sigobj
|
||||
echo " " >> sigobj
|
||||
echo " NotReallyABase64Signature" >> sigobj
|
||||
echo " ButItsGoodEnough" >> sigobj
|
||||
echo " -----END PGP SIGNATURE-----" >> sigobj
|
||||
echo "" >> sigobj
|
||||
echo "Encoded" >> sigobj
|
||||
|
||||
cat <(printf "commit %d\0" $(wc -c sigobj | cut -d' ' -f1); cat sigobj) > sigcommit
|
||||
FILE=.git/objects/$(sha1sum sigcommit | cut -d' ' -f1 | sed 's/../\0\//')
|
||||
mkdir -p $(dirname ${FILE})
|
||||
cat sigcommit | zlib-flate -compress > ${FILE}
|
||||
|
||||
git cat-file commit $(cat .git/refs/heads/master) | head -n4 > sigobj
|
||||
echo "gpgsig -----BEGIN PGP SIGNATURE-----" >> sigobj
|
||||
echo " Version: 0.1.2" >> sigobj
|
||||
echo " " >> sigobj
|
||||
echo " NotReallyABase64Signature" >> sigobj
|
||||
echo " ButItsGoodEnough" >> sigobj
|
||||
echo " -----END PGP SIGNATURE-----" >> sigobj
|
||||
echo " " >> sigobj
|
||||
echo "" >> sigobj
|
||||
echo "Encoded" >> sigobj
|
||||
|
||||
cat <(printf "commit %d\0" $(wc -c sigobj | cut -d' ' -f1); cat sigobj) > sigcommit
|
||||
FILE=.git/objects/$(sha1sum sigcommit | cut -d' ' -f1 | sed 's/../\0\//')
|
||||
mkdir -p $(dirname ${FILE})
|
||||
cat sigcommit | zlib-flate -compress >> ${FILE}
|
||||
rm sigobj sigcommit
|
||||
|
||||
# Create test archive, clean up
|
||||
|
||||
tar czf git.tar.gz .git
|
||||
mv git.tar.gz ${CUR_DIR}/testdata.tar.gz
|
||||
cd ${CUR_DIR}
|
||||
rm -rf ${TEST_DIR}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"author": "whyrusleeping",
|
||||
"bugs": {
|
||||
"url": "https://github.com/ipfs/go-ipld-git"
|
||||
},
|
||||
"gx": {
|
||||
"dvcsimport": "github.com/ipfs/go-ipld-git"
|
||||
},
|
||||
"gxDependencies": [
|
||||
{
|
||||
"author": "whyrusleeping",
|
||||
"hash": "QmZ6nzCLwGLVfRzYLpD7pW6UNuBDKEcA2imJtVpbEx2rxy",
|
||||
"name": "go-ipld-format",
|
||||
"version": "0.8.1"
|
||||
},
|
||||
{
|
||||
"author": "whyrusleeping",
|
||||
"hash": "QmTbxNB1NwDesLmKTscr4udL2tVP7MaxvXnD1D9yX7g3PN",
|
||||
"name": "go-cid",
|
||||
"version": "0.9.3"
|
||||
},
|
||||
{
|
||||
"author": "stebalien",
|
||||
"hash": "QmYYLnAzR28nAQ4U5MFniLprnktu6eTFKibeNt96V21EZK",
|
||||
"name": "go-block-format",
|
||||
"version": "0.2.2"
|
||||
},
|
||||
{
|
||||
"author": "multiformats",
|
||||
"hash": "QmerPMzPk1mJVowm8KgmoknWa4yCYvvugMPsgWmDNUvDLW",
|
||||
"name": "go-multihash",
|
||||
"version": "1.0.9"
|
||||
}
|
||||
],
|
||||
"gxVersion": "0.10.0",
|
||||
"language": "go",
|
||||
"license": "",
|
||||
"name": "go-ipld-git",
|
||||
"releaseCmd": "git commit -a -m \"gx publish $VERSION\"",
|
||||
"version": "0.3.6"
|
||||
}
|
||||
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
package ipldgit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
cid "github.com/ipfs/go-cid"
|
||||
node "github.com/ipfs/go-ipld-format"
|
||||
)
|
||||
|
||||
type Tag struct {
|
||||
Object cid.Cid `json:"object"`
|
||||
Type string `json:"type"`
|
||||
Tag string `json:"tag"`
|
||||
Tagger *PersonInfo `json:"tagger"`
|
||||
Message string `json:"message"`
|
||||
dataSize string
|
||||
|
||||
cid cid.Cid
|
||||
|
||||
rawData []byte
|
||||
rawDataOnce sync.Once
|
||||
}
|
||||
|
||||
func (t *Tag) Cid() cid.Cid {
|
||||
return t.cid
|
||||
}
|
||||
|
||||
func (t *Tag) Copy() node.Node {
|
||||
nt := *t
|
||||
return &nt
|
||||
}
|
||||
|
||||
func (t *Tag) Links() []*node.Link {
|
||||
return []*node.Link{{Cid: t.Object}}
|
||||
}
|
||||
|
||||
func (t *Tag) Loggable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"type": "git_tag",
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tag) RawData() []byte {
|
||||
t.rawDataOnce.Do(func() {
|
||||
buf := new(bytes.Buffer)
|
||||
fmt.Fprintf(buf, "tag %s\x00", t.dataSize)
|
||||
fmt.Fprintf(buf, "object %s\n", hex.EncodeToString(cidToSha(t.Object)))
|
||||
fmt.Fprintf(buf, "type %s\n", t.Type)
|
||||
fmt.Fprintf(buf, "tag %s\n", t.Tag)
|
||||
if t.Tagger != nil {
|
||||
fmt.Fprintf(buf, "tagger %s\n", t.Tagger.String())
|
||||
}
|
||||
if t.Message != "" {
|
||||
fmt.Fprintf(buf, "\n%s", t.Message)
|
||||
}
|
||||
t.rawData = buf.Bytes()
|
||||
})
|
||||
|
||||
return t.rawData
|
||||
}
|
||||
|
||||
func (t *Tag) Resolve(path []string) (interface{}, []string, error) {
|
||||
if len(path) == 0 {
|
||||
return nil, nil, fmt.Errorf("zero length path")
|
||||
}
|
||||
|
||||
switch path[0] {
|
||||
case "object":
|
||||
return &node.Link{Cid: t.Object}, path[1:], nil
|
||||
case "type":
|
||||
return t.Type, path[1:], nil
|
||||
case "tagger":
|
||||
if len(path) == 1 {
|
||||
return t.Tagger, nil, nil
|
||||
}
|
||||
return t.Tagger.resolve(path[1:])
|
||||
case "message":
|
||||
return t.Message, path[1:], nil
|
||||
case "tag":
|
||||
return t.Tag, path[1:], nil
|
||||
default:
|
||||
return nil, nil, errors.New("no such link")
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tag) ResolveLink(path []string) (*node.Link, []string, error) {
|
||||
out, rest, err := t.Resolve(path)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
lnk, ok := out.(*node.Link)
|
||||
if !ok {
|
||||
return nil, nil, errors.New("not a link")
|
||||
}
|
||||
|
||||
return lnk, rest, nil
|
||||
}
|
||||
|
||||
func (t *Tag) Size() (uint64, error) {
|
||||
return uint64(len(t.RawData())), nil
|
||||
}
|
||||
|
||||
func (t *Tag) Stat() (*node.NodeStat, error) {
|
||||
return &node.NodeStat{}, nil
|
||||
}
|
||||
|
||||
func (t *Tag) String() string {
|
||||
return "[git tag object]"
|
||||
}
|
||||
|
||||
func (t *Tag) Tree(p string, depth int) []string {
|
||||
if p != "" {
|
||||
if p == "tagger" {
|
||||
return []string{"name", "email", "date"}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if depth == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
tree := []string{"object", "type", "tag", "message"}
|
||||
tree = append(tree, t.Tagger.tree("tagger", depth)...)
|
||||
return tree
|
||||
}
|
||||
|
||||
func (t *Tag) GitSha() []byte {
|
||||
return cidToSha(t.Cid())
|
||||
}
|
||||
|
||||
var _ node.Node = (*Tag)(nil)
|
||||
BIN
Binary file not shown.
+170
@@ -0,0 +1,170 @@
|
||||
package ipldgit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
cid "github.com/ipfs/go-cid"
|
||||
node "github.com/ipfs/go-ipld-format"
|
||||
)
|
||||
|
||||
type Tree struct {
|
||||
entries map[string]*TreeEntry
|
||||
size int
|
||||
order []string
|
||||
cid cid.Cid
|
||||
rawData []byte
|
||||
rawDataOnce sync.Once
|
||||
}
|
||||
|
||||
type TreeEntry struct {
|
||||
name string
|
||||
Mode string `json:"mode"`
|
||||
Hash cid.Cid `json:"hash"`
|
||||
}
|
||||
|
||||
func (t *Tree) Cid() cid.Cid {
|
||||
return t.cid
|
||||
}
|
||||
|
||||
func (t *Tree) String() string {
|
||||
return "[git tree object]"
|
||||
}
|
||||
|
||||
func (t *Tree) GitSha() []byte {
|
||||
return cidToSha(t.cid)
|
||||
}
|
||||
|
||||
func (t *Tree) Copy() node.Node {
|
||||
out := &Tree{
|
||||
entries: make(map[string]*TreeEntry),
|
||||
cid: t.cid,
|
||||
size: t.size,
|
||||
order: t.order, // TODO: make a deep copy of this
|
||||
}
|
||||
|
||||
for k, v := range t.entries {
|
||||
nv := *v
|
||||
out.entries[k] = &nv
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (t *Tree) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(t.entries)
|
||||
}
|
||||
|
||||
func (t *Tree) Tree(p string, depth int) []string {
|
||||
if p != "" {
|
||||
_, ok := t.entries[p]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return []string{"mode", "type", "hash"}
|
||||
}
|
||||
|
||||
if depth == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if depth == 1 {
|
||||
return t.order
|
||||
}
|
||||
|
||||
var out []string
|
||||
for k, _ := range t.entries {
|
||||
out = append(out, k, k+"/mode", k+"/type", k+"/hash")
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (t *Tree) Links() []*node.Link {
|
||||
var out []*node.Link
|
||||
for _, v := range t.entries {
|
||||
out = append(out, &node.Link{Cid: v.Hash})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (t *Tree) Loggable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"type": "git tree object",
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tree) RawData() []byte {
|
||||
t.rawDataOnce.Do(func() {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
fmt.Fprintf(buf, "tree %d\x00", t.size)
|
||||
for _, s := range t.order {
|
||||
t.entries[s].WriteTo(buf)
|
||||
}
|
||||
t.rawData = buf.Bytes()
|
||||
})
|
||||
|
||||
return t.rawData
|
||||
}
|
||||
|
||||
func (t *Tree) Resolve(p []string) (interface{}, []string, error) {
|
||||
e, ok := t.entries[p[0]]
|
||||
if !ok {
|
||||
return nil, nil, errors.New("no such link")
|
||||
}
|
||||
|
||||
if len(p) == 1 {
|
||||
return e, nil, nil
|
||||
}
|
||||
|
||||
switch p[1] {
|
||||
case "hash":
|
||||
return &node.Link{Cid: e.Hash}, p[2:], nil
|
||||
case "mode":
|
||||
return e.Mode, p[2:], nil
|
||||
default:
|
||||
return nil, nil, errors.New("no such link")
|
||||
}
|
||||
}
|
||||
|
||||
func (t Tree) ResolveLink(path []string) (*node.Link, []string, error) {
|
||||
out, rest, err := t.Resolve(path)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
lnk, ok := out.(*node.Link)
|
||||
if !ok {
|
||||
return nil, nil, errors.New("not a link")
|
||||
}
|
||||
|
||||
return lnk, rest, nil
|
||||
}
|
||||
|
||||
func (t *Tree) Size() (uint64, error) {
|
||||
return uint64(len(t.RawData())), nil
|
||||
}
|
||||
|
||||
func (t *Tree) Stat() (*node.NodeStat, error) {
|
||||
return &node.NodeStat{}, nil
|
||||
}
|
||||
|
||||
func (te *TreeEntry) WriteTo(w io.Writer) (int, error) {
|
||||
n, err := fmt.Fprintf(w, "%s %s\x00", te.Mode, te.name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
nn, err := w.Write(cidToSha(te.Hash))
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
return n + nn, nil
|
||||
}
|
||||
|
||||
var _ node.Node = (*Tree)(nil)
|
||||
Reference in New Issue
Block a user