hancock

Check-in [4950d4133b]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:store: put/get testimony via consistent interface; file system and bolt implementation
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 4950d4133b9dae36903e76b02419c3527336f108ad2a473a6c9a5999ec3764e2
User & Date: dnc 2019-12-11 20:21:53
Context
2019-12-13
04:19
model: NewAuthority helper formats authority string consistently check-in: e0fadea8cc user: dnc tags: trunk
2019-12-11
20:21
store: put/get testimony via consistent interface; file system and bolt implementation check-in: 4950d4133b user: dnc tags: trunk
20:20
model: helpers for tests (in other packages) check-in: 40f95cd722 user: dnc tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Added store/boltstore/boltstore.go.



















































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// Copyright (C) 2019  David N. Cohen

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.

// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

package boltstore

import (
	"log"

	"github.com/boltdb/bolt"
	"src.d10.dev/hancock/model"
)

type boltStore struct {
	*bolt.DB
	bucket []byte
}

func NewBoltStore(db *bolt.DB) (*boltStore, error) {
	this := &boltStore{db, []byte("hancockd")}
	err := db.Update(func(tx *bolt.Tx) error {
		_, err := tx.CreateBucketIfNotExists(this.bucket)
		return err
	})
	return this, err
}

func (this *boltStore) PutTestimony(testimony model.Testimony) (*model.TestimonyKey, error) {
	log.Println("boltStore.PutTestimony()") // troubleshoot

	// use key format consistent with kademlia
	tkey := testimony.Key()
	key := tkey.KID().Sum()

	value, err := model.Encode(testimony)
	if err != nil {
		return nil, err
	}

	// persist to underlying db
	err = this.DB.Batch(func(tx *bolt.Tx) error {
		b := tx.Bucket(this.bucket)
		return b.Put(key, value)
	})
	return tkey, err
}

func (this *boltStore) GetTestimony(tkey model.TestimonyKey) (testimony *model.Testimony, err error) {
	log.Println("boltStore.FindTestimony()", this) // troubleshoot

	key := tkey.KID().Sum()
	err = this.DB.View(func(tx *bolt.Tx) (err error) {
		b := tx.Bucket(this.bucket)
		v := b.Get(key)
		if v != nil {
			testimony = new(model.Testimony)
			err = model.Decode(testimony, v)
		}
		return
	})
	return
}

Added store/boltstore/boltstore_test.go.





























































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// Copyright (C) 2019  David N. Cohen

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.

// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

package boltstore

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"testing"

	"github.com/boltdb/bolt"
	"src.d10.dev/hancock/model/modeltest"
	"src.d10.dev/hancock/store"
)

// Ensure interface properly defined and implemented
func TestStoreIsImplemented(t *testing.T) {
	var _ store.Store = &boltStore{} // compile time test
}

func TestBoltStore(t *testing.T) {
	testimony, err := modeltest.NewTestTestimony("hello world")
	if err != nil {
		t.Error(err)
		return
	}

	dir, err := ioutil.TempDir("", "TestBoltStore")
	if err != nil {
		t.Error(err)
		return
	}
	db, err := bolt.Open(filepath.Join(dir, "TestBoltStore.db"), 0600, nil)
	if err != nil {
		t.Error(err)
		return
	}
	defer db.Close()

	s, err := NewBoltStore(db)
	if err != nil {
		t.Error(err)
		return
	}

	k, err := s.PutTestimony(*testimony)
	if err != nil {
		t.Error(err)
		return
	}

	test, err := s.GetTestimony(*k)
	if err != nil {
		t.Error(err)
	}
	if !reflect.DeepEqual(test, testimony) {
		t.Errorf("got \n%v, wanted \n%v; see boltstore in %q", test, testimony, dir)
	} else {
		// passed test, clean up
		t.Logf("put, then got, testimony (%v)", k)
		defer os.RemoveAll(dir)
	}

}

Added store/store.go.























































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// Copyright (C) 2019  David N. Cohen

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.

// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

package store

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"src.d10.dev/hancock/model"
)

type Store interface {
	PutTestimony(model.Testimony) (*model.TestimonyKey, error)
	GetTestimony(model.TestimonyKey) (*model.Testimony, error)
}

type fileStore struct {
	directory string
}

func NewFileStore(directory string) fileStore {
	return fileStore{directory: directory}
}

func (this fileStore) PutTestimony(testimony model.Testimony) (*model.TestimonyKey, error) {
	tkey := testimony.Key()

	// filepath.Join() turns the key values into directories.  If we
	// used flat file names, we might exceed filesystem limits for
	// number of files.
	path := filepath.Join(this.directory, filepath.Join(tkey.Split()...))
	err := os.MkdirAll(filepath.Dir(path), os.ModePerm)
	if err != nil {
		return nil, fmt.Errorf("failed to store testimony file (%q): %w", path, err)
	}
	encoded, err := model.Encode(testimony)
	err = ioutil.WriteFile(path, encoded, 0644)
	if err != nil {
		return nil, fmt.Errorf("failed to store testimony file (%q): %w", path, err)
	}

	return tkey, nil
}

func (this fileStore) GetTestimony(key model.TestimonyKey) (*model.Testimony, error) {

	path := filepath.Join(this.directory, filepath.Join(key.Split()...))
	encoded, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}

	testimony := &model.Testimony{}
	err = model.Decode(testimony, encoded)
	if err != nil {
		return nil, err
	}

	return testimony, nil
}

Added store/store_test.go.

































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package store

import (
	"io/ioutil"
	"os"
	"reflect"
	"testing"

	"src.d10.dev/hancock/model/modeltest"
)

func TestFileStore(t *testing.T) {
	testimony, err := modeltest.NewTestTestimony("hello world")
	if err != nil {
		t.Error(err)
		return
	}

	var _ Store = NewFileStore("TestFileStore") // compile time test that interface is implemented

	dir, err := ioutil.TempDir("", "TestFileStore")
	if err != nil {
		t.Error(err)
		return
	}

	var store Store = NewFileStore(dir)

	k, err := store.PutTestimony(*testimony)
	if err != nil {
		t.Error(err)
		return
	}
	//t.Logf("stored testimony (%v) to store (%v)", k, store)

	test, err := store.GetTestimony(*k)
	if err != nil {
		t.Error(err)
		return
	}
	if !reflect.DeepEqual(test, testimony) {
		t.Errorf("got %v, wanted %v; files in %q", test, testimony, dir)
	} else {
		// passed test, clean up
		t.Logf("put, then got, testimony (%v)", k)
		defer os.RemoveAll(dir)
	}
}