fixes for issues uncovered in integration

This commit is contained in:
Ian Norden
2019-12-02 13:24:51 -06:00
parent b83c0371d9
commit 34393ffb3f
290 changed files with 108690 additions and 893 deletions
+32
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,28 @@
# go-ds-badger
[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io)
[![](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io/)
[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs)
[![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)
[![GoDoc](https://godoc.org/github.com/ipfs/go-ds-badger?status.svg)](https://godoc.org/github.com/ipfs/go-ds-badger)
[![Build Status](https://travis-ci.org/ipfs/go-ds-badger.svg?branch=master)](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://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/contributing.md)
## License
MIT
+3
View File
@@ -0,0 +1,3 @@
coverage:
range: "50...100"
comment: off
+608
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1 @@
*.swp
+32
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,92 @@
# go-ds-flatfs
[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io)
[![](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io/)
[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs)
[![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)
[![GoDoc](https://godoc.org/github.com/ipfs/go-ds-flatfs?status.svg)](https://godoc.org/github.com/ipfs/go-ds-flatfs)
[![Build Status](https://travis-ci.org/ipfs/go-ds-flatfs.svg?branch=master)](https://travis-ci.org/ipfs/go-ds-flatfs)
[![Coverage Status](https://img.shields.io/codecov/c/github/ipfs/go-ds-flatfs.svg)](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
View File
@@ -0,0 +1 @@
comment: off
+182
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+7
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1 @@
*.swp
+30
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,47 @@
# go-ds-leveldb
[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io)
[![](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io/)
[![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)
[![GoDoc](https://godoc.org/github.com/ipfs/go-ds-leveldb?status.svg)](https://godoc.org/github.com/ipfs/go-ds-leveldb)
[![Build Status](https://travis-ci.org/ipfs/go-ds-leveldb.svg?branch=master)](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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,99 @@
Git ipld format
==================
[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io)
[![](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io/)
[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs)
[![Coverage Status](https://codecov.io/gh/ipfs/go-ipld-git/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/go-ipld-git/branch/master)
[![Travis CI](https://travis-ci.org/ipfs/go-ipld-git.svg?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
View File
@@ -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
View File
@@ -0,0 +1,3 @@
coverage:
range: "50...100"
comment: off
+286
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
Binary file not shown.
+170
View File
@@ -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)