mirror of
https://github.com/safing/portbase
synced 2025-09-01 18:19:57 +00:00
Remove datastore based storage backends
This commit is contained in:
parent
0318521ca5
commit
0b60809c70
9 changed files with 0 additions and 1240 deletions
|
@ -1,4 +0,0 @@
|
|||
from: https://github.com/ipfs/go-datastore/blob/master/basic_ds.go
|
||||
commit: https://github.com/ipfs/go-datastore/commit/545f59008f75bdb6b28abafd8391d7d7d19422be
|
||||
original files imported:
|
||||
- basic_ds.go sha256:f6bd8d26e3511539358b1712c1f1359b9da6425f2d28c8305f4017e61688fe4d
|
|
@ -1,21 +0,0 @@
|
|||
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.
|
|
@ -1,288 +0,0 @@
|
|||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
||||
|
||||
package channelshim
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
datastore "github.com/ipfs/go-datastore"
|
||||
dsq "github.com/ipfs/go-datastore/query"
|
||||
"github.com/tevino/abool"
|
||||
)
|
||||
|
||||
var ErrDatastoreClosed = errors.New("datastore: this instance was closed")
|
||||
|
||||
// ChanneledDatastore makes datastore thread-safe
|
||||
type ChanneledDatastore struct {
|
||||
child datastore.Datastore
|
||||
setChild chan datastore.Datastore
|
||||
|
||||
putRequest chan *VKeyValue
|
||||
putReply chan *error
|
||||
getRequest chan *datastore.Key
|
||||
getReply chan *VValueErr
|
||||
hasRequest chan *datastore.Key
|
||||
hasReply chan *VExistsErr
|
||||
deleteRequest chan *datastore.Key
|
||||
deleteReply chan *error
|
||||
queryRequest chan *dsq.Query
|
||||
queryReply chan *VResultsErr
|
||||
batchRequest chan interface{} // nothing actually
|
||||
batchReply chan *VBatchErr
|
||||
closeRequest chan interface{} // nothing actually
|
||||
closeReply chan *error
|
||||
|
||||
closedFlag *abool.AtomicBool
|
||||
}
|
||||
|
||||
type VKeyValue struct {
|
||||
Key datastore.Key
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
type VValueErr struct {
|
||||
Value interface{}
|
||||
Err error
|
||||
}
|
||||
|
||||
type VExistsErr struct {
|
||||
Exists bool
|
||||
Err error
|
||||
}
|
||||
|
||||
type VResultsErr struct {
|
||||
Results dsq.Results
|
||||
Err error
|
||||
}
|
||||
|
||||
type VBatchErr struct {
|
||||
Batch datastore.Batch
|
||||
Err error
|
||||
}
|
||||
|
||||
func (cds *ChanneledDatastore) run() {
|
||||
if cds.child == nil {
|
||||
cds.child = <-cds.setChild
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case v := <-cds.putRequest:
|
||||
cds.put(v)
|
||||
case v := <-cds.getRequest:
|
||||
cds.get(v)
|
||||
case v := <-cds.hasRequest:
|
||||
cds.has(v)
|
||||
case v := <-cds.deleteRequest:
|
||||
cds.delete(v)
|
||||
case v := <-cds.queryRequest:
|
||||
cds.query(v)
|
||||
case <-cds.batchRequest:
|
||||
cds.batch()
|
||||
case <-cds.closeRequest:
|
||||
err := cds.close()
|
||||
if err == nil {
|
||||
cds.closeReply <- &err
|
||||
defer cds.stop()
|
||||
return
|
||||
}
|
||||
cds.closeReply <- &err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cds *ChanneledDatastore) stop() {
|
||||
for {
|
||||
select {
|
||||
case <-cds.putRequest:
|
||||
cds.putReply <- &ErrDatastoreClosed
|
||||
case <-cds.getRequest:
|
||||
cds.getReply <- &VValueErr{nil, ErrDatastoreClosed}
|
||||
case <-cds.hasRequest:
|
||||
cds.hasReply <- &VExistsErr{false, ErrDatastoreClosed}
|
||||
case <-cds.deleteRequest:
|
||||
cds.deleteReply <- &ErrDatastoreClosed
|
||||
case <-cds.queryRequest:
|
||||
cds.queryReply <- &VResultsErr{nil, ErrDatastoreClosed}
|
||||
case <-cds.batchRequest:
|
||||
cds.batchReply <- &VBatchErr{nil, ErrDatastoreClosed}
|
||||
case <-cds.closeRequest:
|
||||
cds.closeReply <- &ErrDatastoreClosed
|
||||
case <-time.After(1 * time.Minute):
|
||||
// TODO: theoretically a race condition, as some goroutines _could_ still be stuck in front of the request channel
|
||||
close(cds.putRequest)
|
||||
close(cds.putReply)
|
||||
close(cds.getRequest)
|
||||
close(cds.getReply)
|
||||
close(cds.hasRequest)
|
||||
close(cds.hasReply)
|
||||
close(cds.deleteRequest)
|
||||
close(cds.deleteReply)
|
||||
close(cds.queryRequest)
|
||||
close(cds.queryReply)
|
||||
close(cds.batchRequest)
|
||||
close(cds.batchReply)
|
||||
close(cds.closeRequest)
|
||||
close(cds.closeReply)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewChanneledDatastore constructs a datastore accessed through channels.
|
||||
func NewChanneledDatastore(ds datastore.Datastore) *ChanneledDatastore {
|
||||
cds := &ChanneledDatastore{child: ds}
|
||||
cds.setChild = make(chan datastore.Datastore)
|
||||
|
||||
cds.putRequest = make(chan *VKeyValue)
|
||||
cds.putReply = make(chan *error)
|
||||
cds.getRequest = make(chan *datastore.Key)
|
||||
cds.getReply = make(chan *VValueErr)
|
||||
cds.hasRequest = make(chan *datastore.Key)
|
||||
cds.hasReply = make(chan *VExistsErr)
|
||||
cds.deleteRequest = make(chan *datastore.Key)
|
||||
cds.deleteReply = make(chan *error)
|
||||
cds.queryRequest = make(chan *dsq.Query)
|
||||
cds.queryReply = make(chan *VResultsErr)
|
||||
cds.batchRequest = make(chan interface{})
|
||||
cds.batchReply = make(chan *VBatchErr)
|
||||
cds.closeRequest = make(chan interface{})
|
||||
cds.closeReply = make(chan *error)
|
||||
|
||||
cds.closedFlag = abool.NewBool(false)
|
||||
|
||||
go cds.run()
|
||||
|
||||
return cds
|
||||
}
|
||||
|
||||
// SetChild sets the child of the datastore, if not yet set
|
||||
func (cds *ChanneledDatastore) SetChild(ds datastore.Datastore) error {
|
||||
select {
|
||||
case cds.setChild <- ds:
|
||||
default:
|
||||
return errors.New("channelshim: child already set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Children implements Shim
|
||||
func (cds *ChanneledDatastore) Children() []datastore.Datastore {
|
||||
return []datastore.Datastore{cds.child}
|
||||
}
|
||||
|
||||
// Put implements Datastore.Put
|
||||
func (cds *ChanneledDatastore) Put(key datastore.Key, value interface{}) error {
|
||||
if cds.closedFlag.IsSet() {
|
||||
return ErrDatastoreClosed
|
||||
}
|
||||
cds.putRequest <- &VKeyValue{key, value}
|
||||
err := <-cds.putReply
|
||||
return *err
|
||||
}
|
||||
|
||||
func (cds *ChanneledDatastore) put(v *VKeyValue) {
|
||||
err := cds.child.Put(v.Key, v.Value)
|
||||
cds.putReply <- &err
|
||||
}
|
||||
|
||||
// Get implements Datastore.Get
|
||||
func (cds *ChanneledDatastore) Get(key datastore.Key) (value interface{}, err error) {
|
||||
if cds.closedFlag.IsSet() {
|
||||
return nil, ErrDatastoreClosed
|
||||
}
|
||||
cds.getRequest <- &key
|
||||
v := <-cds.getReply
|
||||
return v.Value, v.Err
|
||||
}
|
||||
|
||||
func (cds *ChanneledDatastore) get(key *datastore.Key) {
|
||||
value, err := cds.child.Get(*key)
|
||||
cds.getReply <- &VValueErr{value, err}
|
||||
}
|
||||
|
||||
// Has implements Datastore.Has
|
||||
func (cds *ChanneledDatastore) Has(key datastore.Key) (exists bool, err error) {
|
||||
if cds.closedFlag.IsSet() {
|
||||
return false, ErrDatastoreClosed
|
||||
}
|
||||
cds.hasRequest <- &key
|
||||
v := <-cds.hasReply
|
||||
return v.Exists, v.Err
|
||||
}
|
||||
|
||||
func (cds *ChanneledDatastore) has(key *datastore.Key) {
|
||||
exists, err := cds.child.Has(*key)
|
||||
cds.hasReply <- &VExistsErr{exists, err}
|
||||
}
|
||||
|
||||
// Delete implements Datastore.Delete
|
||||
func (cds *ChanneledDatastore) Delete(key datastore.Key) error {
|
||||
if cds.closedFlag.IsSet() {
|
||||
return ErrDatastoreClosed
|
||||
}
|
||||
cds.deleteRequest <- &key
|
||||
err := <-cds.deleteReply
|
||||
return *err
|
||||
}
|
||||
|
||||
func (cds *ChanneledDatastore) delete(key *datastore.Key) {
|
||||
err := cds.child.Delete(*key)
|
||||
cds.deleteReply <- &err
|
||||
}
|
||||
|
||||
// Query implements Datastore.Query
|
||||
func (cds *ChanneledDatastore) Query(q dsq.Query) (dsq.Results, error) {
|
||||
if cds.closedFlag.IsSet() {
|
||||
return nil, ErrDatastoreClosed
|
||||
}
|
||||
cds.queryRequest <- &q
|
||||
v := <-cds.queryReply
|
||||
return v.Results, v.Err
|
||||
}
|
||||
|
||||
func (cds *ChanneledDatastore) query(q *dsq.Query) {
|
||||
results, err := cds.child.Query(*q)
|
||||
cds.queryReply <- &VResultsErr{results, err}
|
||||
}
|
||||
|
||||
// Query implements Datastore.Batch
|
||||
func (cds *ChanneledDatastore) Batch() (datastore.Batch, error) {
|
||||
if cds.closedFlag.IsSet() {
|
||||
return nil, ErrDatastoreClosed
|
||||
}
|
||||
cds.batchRequest <- nil
|
||||
v := <-cds.batchReply
|
||||
return v.Batch, v.Err
|
||||
}
|
||||
|
||||
func (cds *ChanneledDatastore) batch() {
|
||||
if bds, ok := cds.child.(datastore.Batching); ok {
|
||||
batch, err := bds.Batch()
|
||||
cds.batchReply <- &VBatchErr{batch, err}
|
||||
} else {
|
||||
cds.batchReply <- &VBatchErr{nil, datastore.ErrBatchUnsupported}
|
||||
}
|
||||
}
|
||||
|
||||
// Query closed child Datastore and this Shim
|
||||
func (cds *ChanneledDatastore) Close() error {
|
||||
if cds.closedFlag.IsSet() {
|
||||
return ErrDatastoreClosed
|
||||
}
|
||||
cds.closeRequest <- nil
|
||||
err := <-cds.closeReply
|
||||
return *err
|
||||
}
|
||||
|
||||
func (cds *ChanneledDatastore) close() error {
|
||||
if cds, ok := cds.child.(io.Closer); ok {
|
||||
return cds.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cds *ChanneledDatastore) IsThreadSafe() {
|
||||
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
||||
|
||||
package channelshim
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
datastore "github.com/ipfs/go-datastore"
|
||||
"github.com/ipfs/go-datastore/query"
|
||||
)
|
||||
|
||||
var cds datastore.Datastore
|
||||
var key datastore.Key
|
||||
var q query.Query
|
||||
var wg sync.WaitGroup
|
||||
|
||||
func testFunctions(testClose bool) {
|
||||
wg.Add(1)
|
||||
defer wg.Done()
|
||||
|
||||
cds.Put(key, "value")
|
||||
cds.Get(key)
|
||||
cds.Has(key)
|
||||
cds.Delete(key)
|
||||
cds.Query(q)
|
||||
if batchingDS, ok := cds.(datastore.Batching); ok {
|
||||
batchingDS.Batch()
|
||||
}
|
||||
|
||||
if testClose {
|
||||
if closingDS, ok := cds.(io.Closer); ok {
|
||||
closingDS.Close()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestChanneledDatastore(t *testing.T) {
|
||||
|
||||
cds = NewChanneledDatastore(datastore.NewNullDatastore())
|
||||
key = datastore.RandomKey()
|
||||
|
||||
// test normal concurrency-safe operation
|
||||
for i := 0; i < 100; i++ {
|
||||
go testFunctions(false)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// test shutdown procedure
|
||||
for i := 0; i < 50; i++ {
|
||||
go testFunctions(false)
|
||||
}
|
||||
for i := 0; i < 50; i++ {
|
||||
go testFunctions(true)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// test closed functions, just to be sure
|
||||
go testFunctions(true)
|
||||
wg.Wait()
|
||||
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
from: https://github.com/ipfs/go-ds-leveldb
|
||||
commit: https://github.com/ipfs/go-ds-leveldb/commit/d2d7b2c585634fcf7edb0de3602d060de85616a5
|
||||
original files imported:
|
||||
- datastore.go sha256:03994e4ddc33e4b9f13a637c95753b808eb02d97a80b8f7539ef9f0498567ce1
|
||||
- ds_test.go sha256:3f49b4c7769e8a69ba058411f763f7425128769fab25b58650368581dc303a7c
|
|
@ -1,21 +0,0 @@
|
|||
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.
|
|
@ -1,250 +0,0 @@
|
|||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
||||
|
||||
package leveldb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Safing/safing-core/database/dbutils"
|
||||
"github.com/Safing/safing-core/formats/dsd"
|
||||
|
||||
ds "github.com/ipfs/go-datastore"
|
||||
dsq "github.com/ipfs/go-datastore/query"
|
||||
"github.com/jbenet/goprocess"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
type datastore struct {
|
||||
DB *leveldb.DB
|
||||
}
|
||||
|
||||
type Options opt.Options
|
||||
|
||||
func NewDatastore(path string, opts *Options) (*datastore, error) {
|
||||
var nopts opt.Options
|
||||
if opts != nil {
|
||||
nopts = opt.Options(*opts)
|
||||
}
|
||||
db, err := leveldb.OpenFile(path, &nopts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &datastore{
|
||||
DB: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Returns ErrInvalidType if value is not of type []byte.
|
||||
//
|
||||
// Note: using sync = false.
|
||||
// see http://godoc.org/github.com/syndtr/goleveldb/leveldb/opt#WriteOptions
|
||||
func (d *datastore) Put(key ds.Key, value interface{}) (err error) {
|
||||
bytes, err := dbutils.DumpModel(value, dsd.AUTO) // or other dsd format
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.DB.Put(key.Bytes(), bytes, nil)
|
||||
}
|
||||
|
||||
func (d *datastore) Get(key ds.Key) (interface{}, error) {
|
||||
data, err := d.DB.Get(key.Bytes(), nil)
|
||||
if err != nil {
|
||||
if err == leveldb.ErrNotFound {
|
||||
return nil, ds.ErrNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
model, err := dbutils.NewWrapper(&key, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return model, nil
|
||||
}
|
||||
|
||||
func (d *datastore) Has(key ds.Key) (exists bool, err error) {
|
||||
return d.DB.Has(key.Bytes(), nil)
|
||||
}
|
||||
|
||||
func (d *datastore) 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 := d.DB.Has(key.Bytes(), nil)
|
||||
if !exists {
|
||||
return ds.ErrNotFound
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.DB.Delete(key.Bytes(), nil)
|
||||
}
|
||||
|
||||
func (d *datastore) Query(q dsq.Query) (dsq.Results, error) {
|
||||
return d.QueryNew(q)
|
||||
}
|
||||
|
||||
func (d *datastore) QueryNew(q dsq.Query) (dsq.Results, error) {
|
||||
if len(q.Filters) > 0 ||
|
||||
len(q.Orders) > 0 ||
|
||||
q.Limit > 0 ||
|
||||
q.Offset > 0 {
|
||||
return d.QueryOrig(q)
|
||||
}
|
||||
var rnge *util.Range
|
||||
if q.Prefix != "" {
|
||||
rnge = util.BytesPrefix([]byte(q.Prefix))
|
||||
}
|
||||
i := d.DB.NewIterator(rnge, nil)
|
||||
return dsq.ResultsFromIterator(q, dsq.Iterator{
|
||||
Next: func() (dsq.Result, bool) {
|
||||
ok := i.Next()
|
||||
if !ok {
|
||||
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())
|
||||
|
||||
newKey := ds.RawKey(k)
|
||||
wrapper, err := dbutils.NewWrapper(&newKey, buf)
|
||||
if err != nil {
|
||||
return dsq.Result{Error: fmt.Errorf("failed to create wrapper for %s: %s", k, err)}, false
|
||||
}
|
||||
e.Value = wrapper
|
||||
}
|
||||
return dsq.Result{Entry: e}, true
|
||||
},
|
||||
Close: func() error {
|
||||
i.Release()
|
||||
return nil
|
||||
},
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (d *datastore) QueryOrig(q dsq.Query) (dsq.Results, error) {
|
||||
// we can use multiple iterators concurrently. see:
|
||||
// https://godoc.org/github.com/syndtr/goleveldb/leveldb#DB.NewIterator
|
||||
// advance the iterator only if the reader reads
|
||||
//
|
||||
// run query in own sub-process tied to Results.Process(), so that
|
||||
// it waits for us to finish AND so that clients can signal to us
|
||||
// that resources should be reclaimed.
|
||||
qrb := dsq.NewResultBuilder(q)
|
||||
qrb.Process.Go(func(worker goprocess.Process) {
|
||||
d.runQuery(worker, qrb)
|
||||
})
|
||||
|
||||
// go wait on the worker (without signaling close)
|
||||
go qrb.Process.CloseAfterChildren()
|
||||
|
||||
// Now, apply remaining things (filters, order)
|
||||
qr := qrb.Results()
|
||||
for _, f := range q.Filters {
|
||||
qr = dsq.NaiveFilter(qr, f)
|
||||
}
|
||||
for _, o := range q.Orders {
|
||||
qr = dsq.NaiveOrder(qr, o)
|
||||
}
|
||||
return qr, nil
|
||||
}
|
||||
|
||||
func (d *datastore) runQuery(worker goprocess.Process, qrb *dsq.ResultBuilder) {
|
||||
|
||||
var rnge *util.Range
|
||||
if qrb.Query.Prefix != "" {
|
||||
rnge = util.BytesPrefix([]byte(qrb.Query.Prefix))
|
||||
}
|
||||
i := d.DB.NewIterator(rnge, nil)
|
||||
defer i.Release()
|
||||
|
||||
// advance iterator for offset
|
||||
if qrb.Query.Offset > 0 {
|
||||
for j := 0; j < qrb.Query.Offset; j++ {
|
||||
i.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// iterate, and handle limit, too
|
||||
for sent := 0; i.Next(); sent++ {
|
||||
// end early if we hit the limit
|
||||
if qrb.Query.Limit > 0 && sent >= qrb.Query.Limit {
|
||||
break
|
||||
}
|
||||
|
||||
k := string(i.Key())
|
||||
e := dsq.Entry{Key: k}
|
||||
|
||||
if !qrb.Query.KeysOnly {
|
||||
buf := make([]byte, len(i.Value()))
|
||||
copy(buf, i.Value())
|
||||
|
||||
newKey := ds.RawKey(k)
|
||||
wrapper, err := dbutils.NewWrapper(&newKey, buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
e.Value = wrapper
|
||||
}
|
||||
|
||||
select {
|
||||
case qrb.Output <- dsq.Result{Entry: e}: // we sent it out
|
||||
case <-worker.Closing(): // client told us to end early.
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err := i.Error(); err != nil {
|
||||
select {
|
||||
case qrb.Output <- dsq.Result{Error: err}: // client read our error
|
||||
case <-worker.Closing(): // client told us to end.
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LevelDB needs to be closed.
|
||||
func (d *datastore) Close() (err error) {
|
||||
return d.DB.Close()
|
||||
}
|
||||
|
||||
func (d *datastore) IsThreadSafe() {}
|
||||
|
||||
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 interface{}) error {
|
||||
val, err := dsd.Dump(value, dsd.AUTO)
|
||||
if err != nil {
|
||||
// return ds.ErrInvalidType
|
||||
return err
|
||||
}
|
||||
|
||||
b.b.Put(key.Bytes(), val)
|
||||
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
|
||||
}
|
|
@ -1,159 +0,0 @@
|
|||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
||||
|
||||
package leveldb
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
ds "github.com/ipfs/go-datastore"
|
||||
dsq "github.com/ipfs/go-datastore/query"
|
||||
)
|
||||
|
||||
var testcases = map[string]string{
|
||||
"/a": "a",
|
||||
"/a/b": "ab",
|
||||
"/a/b/c": "abc",
|
||||
"/a/b/d": "a/b/d",
|
||||
"/a/c": "ac",
|
||||
"/a/d": "ad",
|
||||
"/e": "e",
|
||||
"/f": "f",
|
||||
}
|
||||
|
||||
// returns datastore, and a function to call on exit.
|
||||
// (this garbage collects). So:
|
||||
//
|
||||
// d, close := newDS(t)
|
||||
// defer close()
|
||||
func newDS(t *testing.T) (*datastore, func()) {
|
||||
path, err := ioutil.TempDir("/tmp", "testing_leveldb_")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
d, err := NewDatastore(path, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return d, func() {
|
||||
os.RemoveAll(path)
|
||||
d.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func addTestCases(t *testing.T, d *datastore, testcases map[string]string) {
|
||||
for k, v := range testcases {
|
||||
dsk := ds.NewKey(k)
|
||||
if err := d.Put(dsk, []byte(v)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range testcases {
|
||||
dsk := ds.NewKey(k)
|
||||
v2, err := d.Get(dsk)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v2b := v2.(*[]byte)
|
||||
if string(*v2b) != v {
|
||||
t.Errorf("%s values differ: %s != %s", k, v, v2)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestQuery(t *testing.T) {
|
||||
d, close := newDS(t)
|
||||
defer close()
|
||||
addTestCases(t, d, testcases)
|
||||
|
||||
rs, err := d.Query(dsq.Query{Prefix: "/a/"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expectMatches(t, []string{
|
||||
"/a/b",
|
||||
"/a/b/c",
|
||||
"/a/b/d",
|
||||
"/a/c",
|
||||
"/a/d",
|
||||
}, rs)
|
||||
|
||||
// test offset and limit
|
||||
|
||||
rs, err = d.Query(dsq.Query{Prefix: "/a/", Offset: 2, Limit: 2})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expectMatches(t, []string{
|
||||
"/a/b/d",
|
||||
"/a/c",
|
||||
}, rs)
|
||||
|
||||
}
|
||||
|
||||
func TestQueryRespectsProcess(t *testing.T) {
|
||||
d, close := newDS(t)
|
||||
defer close()
|
||||
addTestCases(t, d, testcases)
|
||||
}
|
||||
|
||||
func expectMatches(t *testing.T, expect []string, actualR dsq.Results) {
|
||||
actual, err := actualR.Rest()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if len(actual) != len(expect) {
|
||||
t.Error("not enough", expect, actual)
|
||||
}
|
||||
for _, k := range expect {
|
||||
found := false
|
||||
for _, e := range actual {
|
||||
if e.Key == k {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Error(k, "not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatching(t *testing.T) {
|
||||
d, done := newDS(t)
|
||||
defer done()
|
||||
|
||||
b, err := d.Batch()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for k, v := range testcases {
|
||||
err := b.Put(ds.NewKey(k), []byte(v))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
err = b.Commit()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for k, v := range testcases {
|
||||
val, err := d.Get(ds.NewKey(k))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if v != string(*val.(*[]byte)) {
|
||||
t.Fatal("got wrong data!")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,428 +0,0 @@
|
|||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package simplefs provides a dead simple file-based datastore backed.
|
||||
It is primarily meant for easy testing or storing big files that can easily be accesses directly, without datastore.
|
||||
|
||||
/path/path/type:key.json
|
||||
/path/path/type:key/type:key
|
||||
|
||||
*/
|
||||
package simplefs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
ds "github.com/ipfs/go-datastore"
|
||||
dsq "github.com/ipfs/go-datastore/query"
|
||||
"github.com/tevino/abool"
|
||||
|
||||
"github.com/Safing/safing-core/database/dbutils"
|
||||
"github.com/Safing/safing-core/formats/dsd"
|
||||
"github.com/Safing/safing-core/log"
|
||||
)
|
||||
|
||||
const (
|
||||
SIMPLEFS_TAG = "330adcf3924003a59ae93289bc2cbb236391588f"
|
||||
DEFAULT_FILEMODE = os.FileMode(int(0644))
|
||||
DEFAULT_DIRMODE = os.FileMode(int(0755))
|
||||
)
|
||||
|
||||
type datastore struct {
|
||||
basePath string
|
||||
basePathLen int
|
||||
}
|
||||
|
||||
func NewDatastore(path string) (*datastore, error) {
|
||||
basePath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to validate path %s: %s", path, err)
|
||||
}
|
||||
tagfile := filepath.Join(basePath, ".simplefs")
|
||||
|
||||
file, err := os.Stat(basePath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = os.MkdirAll(basePath, DEFAULT_DIRMODE)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create directory: %s", err)
|
||||
}
|
||||
err = ioutil.WriteFile(tagfile, []byte(SIMPLEFS_TAG), DEFAULT_FILEMODE)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create tag file (%s): %s", tagfile, err)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("failed to stat path: %s", err)
|
||||
}
|
||||
} else {
|
||||
if !file.IsDir() {
|
||||
return nil, fmt.Errorf("provided path (%s) is a file", basePath)
|
||||
}
|
||||
// check if valid simplefs storage dir
|
||||
content, err := ioutil.ReadFile(tagfile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read tag file (%s): %s", tagfile, err)
|
||||
}
|
||||
if string(content) != SIMPLEFS_TAG {
|
||||
return nil, fmt.Errorf("invalid tag file (%s)", tagfile)
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("simplefs: opened database at %s", basePath)
|
||||
return &datastore{
|
||||
basePath: basePath,
|
||||
basePathLen: len(basePath),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *datastore) buildPath(path string) (string, error) {
|
||||
if len(path) < 2 {
|
||||
return "", fmt.Errorf("key too short: %s", path)
|
||||
}
|
||||
newPath := filepath.Clean(filepath.Join(d.basePath, path[1:])) + ".dsd"
|
||||
if !strings.HasPrefix(newPath, d.basePath) {
|
||||
return "", fmt.Errorf("key integrity check failed, compiled path is %s", newPath)
|
||||
}
|
||||
return newPath, nil
|
||||
}
|
||||
|
||||
func (d *datastore) Put(key ds.Key, value interface{}) (err error) {
|
||||
objPath, err := d.buildPath(key.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bytes, err := dbutils.DumpModel(value, dsd.AUTO) // or other dsd format
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(objPath, bytes, DEFAULT_FILEMODE)
|
||||
if err != nil {
|
||||
// create dir and try again
|
||||
err = os.MkdirAll(filepath.Dir(objPath), DEFAULT_DIRMODE)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create directory %s: %s", filepath.Dir(objPath), err)
|
||||
}
|
||||
err = ioutil.WriteFile(objPath, bytes, DEFAULT_FILEMODE)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write file %s: %s", objPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *datastore) Get(key ds.Key) (interface{}, error) {
|
||||
objPath, err := d.buildPath(key.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(objPath)
|
||||
if err != nil {
|
||||
// TODO: distinguish between error and inexistance
|
||||
return nil, ds.ErrNotFound
|
||||
}
|
||||
|
||||
model, err := dbutils.NewWrapper(&key, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return model, nil
|
||||
}
|
||||
|
||||
func (d *datastore) Has(key ds.Key) (exists bool, err error) {
|
||||
objPath, err := d.buildPath(key.String())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
_, err = os.Stat(objPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("failed to stat path %s: %s", objPath, err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (d *datastore) Delete(key ds.Key) (err error) {
|
||||
objPath, err := d.buildPath(key.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// remove entry
|
||||
err = os.Remove(objPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not delete (all) in path %s: %s", objPath, err)
|
||||
}
|
||||
// remove children
|
||||
err = os.RemoveAll(objPath[:len(objPath)-4])
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not delete (all) in path %s: %s", objPath, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *datastore) Query(q dsq.Query) (dsq.Results, error) {
|
||||
if len(q.Orders) > 0 {
|
||||
return nil, fmt.Errorf("simplefs: no support for ordering queries yet")
|
||||
}
|
||||
|
||||
// log.Tracef("new query: %s", q.Prefix)
|
||||
|
||||
// log.Tracef("simplefs: new query with prefix %s", q.Prefix)
|
||||
|
||||
walkPath, err := d.buildPath(q.Prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
walkPath = walkPath[:strings.LastIndex(walkPath, string(os.PathSeparator))]
|
||||
|
||||
files := make(chan *dsq.Entry)
|
||||
stopWalkingFlag := abool.NewBool(false)
|
||||
stopWalking := make(chan interface{})
|
||||
counter := 0
|
||||
|
||||
go func() {
|
||||
|
||||
err := filepath.Walk(walkPath, func(path string, info os.FileInfo, err error) error {
|
||||
|
||||
// log.Tracef("walking: %s", path)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("simplfs: error in query: %s", err)
|
||||
}
|
||||
|
||||
// skip directories
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// check if we are still were we should be
|
||||
if !strings.HasPrefix(path, d.basePath) {
|
||||
log.Criticalf("simplfs: query jailbreaked: %s", path)
|
||||
return errors.New("jailbreaked")
|
||||
}
|
||||
path = path[d.basePathLen:]
|
||||
|
||||
// check if there is enough space to remove ".dsd"
|
||||
if len(path) < 6 {
|
||||
return nil
|
||||
}
|
||||
path = path[:len(path)-4]
|
||||
|
||||
// check if we still match prefix
|
||||
if !strings.HasPrefix(path, q.Prefix) {
|
||||
return nil
|
||||
}
|
||||
|
||||
entry := dsq.Entry{
|
||||
Key: path,
|
||||
// TODO: phew, do we really want to load every single file we might not need? use nil for now.
|
||||
Value: nil,
|
||||
}
|
||||
for _, filter := range q.Filters {
|
||||
if !filter.Filter(entry) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// yay, entry matches!
|
||||
counter++
|
||||
|
||||
if q.Offset > counter {
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case files <- &entry:
|
||||
case <-stopWalking:
|
||||
return errors.New("finished")
|
||||
}
|
||||
|
||||
if q.Limit != 0 && q.Limit <= counter {
|
||||
return errors.New("finished")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Warningf("simplefs: filewalker for query failed: %s", err)
|
||||
}
|
||||
|
||||
close(files)
|
||||
|
||||
}()
|
||||
|
||||
return dsq.ResultsFromIterator(q, dsq.Iterator{
|
||||
Next: func() (dsq.Result, bool) {
|
||||
select {
|
||||
case entry := <-files:
|
||||
if entry == nil {
|
||||
return dsq.Result{}, false
|
||||
}
|
||||
// log.Tracef("processing: %s", entry.Key)
|
||||
if !q.KeysOnly {
|
||||
objPath, err := d.buildPath(entry.Key)
|
||||
if err != nil {
|
||||
return dsq.Result{Error: err}, false
|
||||
}
|
||||
data, err := ioutil.ReadFile(objPath)
|
||||
if err != nil {
|
||||
return dsq.Result{Error: fmt.Errorf("error reading file %s: %s", entry.Key, err)}, false
|
||||
}
|
||||
newKey := ds.RawKey(entry.Key)
|
||||
wrapper, err := dbutils.NewWrapper(&newKey, data)
|
||||
if err != nil {
|
||||
return dsq.Result{Error: fmt.Errorf("failed to create wrapper for %s: %s", entry.Key, err)}, false
|
||||
}
|
||||
entry.Value = wrapper
|
||||
}
|
||||
return dsq.Result{Entry: *entry, Error: nil}, true
|
||||
case <-time.After(10 * time.Second):
|
||||
return dsq.Result{Error: errors.New("filesystem timeout")}, false
|
||||
}
|
||||
},
|
||||
Close: func() error {
|
||||
if stopWalkingFlag.SetToIf(false, true) {
|
||||
close(stopWalking)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}), nil
|
||||
}
|
||||
|
||||
//
|
||||
// func (d *datastore) Query(q dsq.Query) (dsq.Results, error) {
|
||||
// return d.QueryNew(q)
|
||||
// }
|
||||
//
|
||||
// func (d *datastore) QueryNew(q dsq.Query) (dsq.Results, error) {
|
||||
// if len(q.Filters) > 0 ||
|
||||
// len(q.Orders) > 0 ||
|
||||
// q.Limit > 0 ||
|
||||
// q.Offset > 0 {
|
||||
// return d.QueryOrig(q)
|
||||
// }
|
||||
// var rnge *util.Range
|
||||
// if q.Prefix != "" {
|
||||
// rnge = util.BytesPrefix([]byte(q.Prefix))
|
||||
// }
|
||||
// i := d.DB.NewIterator(rnge, nil)
|
||||
// return dsq.ResultsFromIterator(q, dsq.Iterator{
|
||||
// Next: func() (dsq.Result, bool) {
|
||||
// ok := i.Next()
|
||||
// if !ok {
|
||||
// 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
|
||||
// },
|
||||
// }), nil
|
||||
// }
|
||||
//
|
||||
// func (d *datastore) QueryOrig(q dsq.Query) (dsq.Results, error) {
|
||||
// // we can use multiple iterators concurrently. see:
|
||||
// // https://godoc.org/github.com/syndtr/goleveldb/leveldb#DB.NewIterator
|
||||
// // advance the iterator only if the reader reads
|
||||
// //
|
||||
// // run query in own sub-process tied to Results.Process(), so that
|
||||
// // it waits for us to finish AND so that clients can signal to us
|
||||
// // that resources should be reclaimed.
|
||||
// qrb := dsq.NewResultBuilder(q)
|
||||
// qrb.Process.Go(func(worker goprocess.Process) {
|
||||
// d.runQuery(worker, qrb)
|
||||
// })
|
||||
//
|
||||
// // go wait on the worker (without signaling close)
|
||||
// go qrb.Process.CloseAfterChildren()
|
||||
//
|
||||
// // Now, apply remaining things (filters, order)
|
||||
// qr := qrb.Results()
|
||||
// for _, f := range q.Filters {
|
||||
// qr = dsq.NaiveFilter(qr, f)
|
||||
// }
|
||||
// for _, o := range q.Orders {
|
||||
// qr = dsq.NaiveOrder(qr, o)
|
||||
// }
|
||||
// return qr, nil
|
||||
// }
|
||||
//
|
||||
// func (d *datastore) runQuery(worker goprocess.Process, qrb *dsq.ResultBuilder) {
|
||||
//
|
||||
// var rnge *util.Range
|
||||
// if qrb.Query.Prefix != "" {
|
||||
// rnge = util.BytesPrefix([]byte(qrb.Query.Prefix))
|
||||
// }
|
||||
// i := d.DB.NewIterator(rnge, nil)
|
||||
// defer i.Release()
|
||||
//
|
||||
// // advance iterator for offset
|
||||
// if qrb.Query.Offset > 0 {
|
||||
// for j := 0; j < qrb.Query.Offset; j++ {
|
||||
// i.Next()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // iterate, and handle limit, too
|
||||
// for sent := 0; i.Next(); sent++ {
|
||||
// // end early if we hit the limit
|
||||
// if qrb.Query.Limit > 0 && sent >= qrb.Query.Limit {
|
||||
// break
|
||||
// }
|
||||
//
|
||||
// k := string(i.Key())
|
||||
// e := dsq.Entry{Key: k}
|
||||
//
|
||||
// if !qrb.Query.KeysOnly {
|
||||
// buf := make([]byte, len(i.Value()))
|
||||
// copy(buf, i.Value())
|
||||
// e.Value = buf
|
||||
// }
|
||||
//
|
||||
// select {
|
||||
// case qrb.Output <- dsq.Result{Entry: e}: // we sent it out
|
||||
// case <-worker.Closing(): // client told us to end early.
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if err := i.Error(); err != nil {
|
||||
// select {
|
||||
// case qrb.Output <- dsq.Result{Error: err}: // client read our error
|
||||
// case <-worker.Closing(): // client told us to end.
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
func (d *datastore) Close() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *datastore) IsThreadSafe() {}
|
Loading…
Add table
Reference in a new issue