Skip to content

Commit

Permalink
feat(actions): added new repo actions package to encapsulate repo biz…
Browse files Browse the repository at this point in the history
… logic

For some time I've been longing for a package that encapsulates the business logic for "doing stuff
with repos". This is lower level than core (which has to deal with the question of when to
touch the p2p network), and higher level than repo implementations to avoid duplicating this
code everywhere.

This actions package comes from refactoring methods like CreateDataset _out_ of the Repo interface,
and adjusting these business logic-heavy methods to work only with Repo interface methods. Already
feels better.

I've named the package actions for anyone who's coming to qri from the world of react & redux,
where the concept of an action plays a similar role. In this version actions don't emit events
to work on stores, but instead work directly on stores (repos) to do their work.

This analogy gives more insight into the role repos should play moving forward. In frontend dev,
the store is _a_ store of truth, but the role of being _the_ store of truth is always the server.
In our case, repos serve a similar purpose, only this time the "server" is the distributed web.

In both cases the store/repo is comparitively cheap to query, at the expense of being potentially
out of date. So it kinda like a "necessary cache".

Dope.
  • Loading branch information
b5 committed Mar 21, 2018
1 parent 7a7de42 commit 20322e1
Show file tree
Hide file tree
Showing 18 changed files with 633 additions and 484 deletions.
5 changes: 3 additions & 2 deletions core/datasets.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ import (
"github.com/qri-io/jsonschema"
"github.com/qri-io/qri/p2p"
"github.com/qri-io/qri/repo"
"github.com/qri-io/qri/repo/actions"
"github.com/qri-io/qri/repo/profile"
"github.com/qri-io/varName"
)

// DatasetRequests encapsulates business logic for this node's
// user profile
type DatasetRequests struct {
repo repo.Repo
repo actions.Dataset
cli *rpc.Client
Node *p2p.QriNode
}
Expand All @@ -53,7 +54,7 @@ func NewDatasetRequests(r repo.Repo, cli *rpc.Client) *DatasetRequests {
}

return &DatasetRequests{
repo: r,
repo: actions.Dataset{r},
cli: cli,
}
}
Expand Down
8 changes: 3 additions & 5 deletions core/datasets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/qri-io/dsdiff"
"github.com/qri-io/jsonschema"
"github.com/qri-io/qri/repo"
"github.com/qri-io/qri/repo/actions"
testrepo "github.com/qri-io/qri/repo/test"
)

Expand Down Expand Up @@ -456,17 +457,14 @@ func TestDataRequestsDiff(t *testing.T) {
t.Errorf("couldn't load file 1: %s", err.Error())
return
}
if err := mr.ReadDataset(dsRef1); err != nil {
act := actions.Dataset{mr}
if err := act.ReadDataset(dsRef1); err != nil {
t.Errorf("error reading dataset 1: %s", err.Error())
return
}

dsBase := dsRef1.Dataset

// dsBase, err := dsfs.LoadDataset(mr.Store(), datastore.NewKey(dsRef1.Path))
// if err != nil {
// return
// }
// File 2
dsRef2 := &repo.DatasetRef{}
initParams = &InitParams{
Expand Down
5 changes: 3 additions & 2 deletions core/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import (

"github.com/qri-io/qri/p2p"
"github.com/qri-io/qri/repo"
"github.com/qri-io/qri/repo/actions"
)

// HistoryRequests encapsulates business logic for the log
// of changes to datasets, think "git log"
type HistoryRequests struct {
repo repo.Repo
repo actions.Dataset
cli *rpc.Client
Node *p2p.QriNode
}
Expand All @@ -26,7 +27,7 @@ func NewHistoryRequests(r repo.Repo, cli *rpc.Client) *HistoryRequests {
panic(fmt.Errorf("both repo and client supplied to NewHistoryRequests"))
}
return &HistoryRequests{
repo: r,
repo: actions.Dataset{r},
cli: cli,
}
}
Expand Down
8 changes: 6 additions & 2 deletions p2p/dataset.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"

"github.com/qri-io/qri/repo"
"github.com/qri-io/qri/repo/actions"

peer "gx/ipfs/QmXYjuNuxVzXKJCfWasQk1RqkhVLDM9jtUKhqc2WPQmFSB/go-libp2p-peer"
)
Expand All @@ -23,12 +24,14 @@ func (n *QriNode) RequestDataset(ref *repo.DatasetRef) (err error) {
return fmt.Errorf("path is required")
}

act := actions.Dataset{n.Repo}

// if peer ID is *our* peer.ID check for local dataset
// note that data may be on another machine, so this can still fail back to a
// network request
if ref.PeerID != "" {
if pro, err := n.Repo.Profile(); err == nil && pro.ID == ref.PeerID {
if err := n.Repo.ReadDataset(ref); err == nil {
if err := act.ReadDataset(ref); err == nil {
return nil
}
}
Expand Down Expand Up @@ -88,11 +91,12 @@ func (n *QriNode) handleDataset(ws *WrappedStream, msg Message) (hangup bool) {
return
}
res := msg
act := actions.Dataset{n.Repo}

if err := repo.CanonicalizeDatasetRef(n.Repo, &dsr); err == nil {
if ref, err := n.Repo.GetRef(dsr); err == nil {

if err := n.Repo.ReadDataset(&ref); err != nil {
if err := act.ReadDataset(&ref); err != nil {
log.Debug(err.Error())
}

Expand Down
6 changes: 1 addition & 5 deletions p2p/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,8 @@ package p2p

import (
"encoding/json"
// "strings"
"time"
// "github.com/ipfs/go-datastore"
// "github.com/qri-io/cafs"
// ipfs "github.com/qri-io/cafs/ipfs"
// "github.com/qri-io/dataset/dsfs"

"github.com/qri-io/qri/repo"

peer "gx/ipfs/QmXYjuNuxVzXKJCfWasQk1RqkhVLDM9jtUKhqc2WPQmFSB/go-libp2p-peer"
Expand Down
10 changes: 10 additions & 0 deletions repo/actions/actions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Package actions provides canonical business logic that operates on Repos
// to get higher-order functionality. Actions use only Repo methods
// to do their work, allowing them to be used across any repo.Repo implementation
package actions

import (
golog "github.com/ipfs/go-log"
)

var log = golog.Logger("actions")
120 changes: 120 additions & 0 deletions repo/actions/dataset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package actions

import (
"github.com/ipfs/go-datastore"
"github.com/qri-io/cafs"
"github.com/qri-io/dataset"
"github.com/qri-io/dataset/dsfs"
"github.com/qri-io/qri/repo"
"github.com/qri-io/qri/repo/profile"
)

// Dataset wraps a repo.Repo, adding actions related to working
// with datasets
type Dataset struct {
repo.Repo
}

// CreateDataset initializes a dataset from a dataset pointer and data file
func (act Dataset) CreateDataset(name string, ds *dataset.Dataset, data cafs.File, pin bool) (ref repo.DatasetRef, err error) {
var (
path datastore.Key
pro *profile.Profile
)
pro, err = act.Profile()
if err != nil {
return
}

path, err = dsfs.CreateDataset(act.Store(), ds, data, act.PrivateKey(), pin)
if err != nil {
return
}

if ds.PreviousPath != "" && ds.PreviousPath != "/" {
prev := repo.DatasetRef{
PeerID: pro.ID,
Peername: pro.Peername,
Name: name,
Path: ds.PreviousPath,
}
if err = act.DeleteRef(prev); err != nil {
log.Error(err.Error())
err = nil
}
}

ref = repo.DatasetRef{
PeerID: pro.ID,
Peername: pro.Peername,
Name: name,
Path: path.String(),
}

if err = act.PutRef(ref); err != nil {
log.Error(err.Error())
return
}

if err = act.LogEvent(repo.ETDsCreated, ref); err != nil {
return
}

_, storeIsPinner := act.Store().(cafs.Pinner)
if pin && storeIsPinner {
act.LogEvent(repo.ETDsPinned, ref)
}
return
}

// ReadDataset grabs a dataset from the store
func (act Dataset) ReadDataset(ref *repo.DatasetRef) (err error) {
if act.Repo.Store() != nil {
ref.Dataset, err = dsfs.LoadDataset(act.Store(), datastore.NewKey(ref.Path))
return
}

return datastore.ErrNotFound
}

// RenameDataset alters a dataset name
func (act Dataset) RenameDataset(a, b repo.DatasetRef) (err error) {
if err = act.DeleteRef(a); err != nil {
return err
}
if err = act.PutRef(b); err != nil {
return err
}

return act.LogEvent(repo.ETDsRenamed, b)
}

// PinDataset marks a dataset for retention in a store
func (act Dataset) PinDataset(ref repo.DatasetRef) error {
if pinner, ok := act.Store().(cafs.Pinner); ok {
pinner.Pin(datastore.NewKey(ref.Path), true)
return act.LogEvent(repo.ETDsPinned, ref)
}
return repo.ErrNotPinner
}

// UnpinDataset unmarks a dataset for retention in a store
func (act Dataset) UnpinDataset(ref repo.DatasetRef) error {
if pinner, ok := act.Store().(cafs.Pinner); ok {
pinner.Unpin(datastore.NewKey(ref.Path), true)
return act.LogEvent(repo.ETDsUnpinned, ref)
}
return repo.ErrNotPinner
}

// DeleteDataset removes a dataset from the store
func (act Dataset) DeleteDataset(ref repo.DatasetRef) error {
if err := act.DeleteRef(ref); err != nil {
return err
}
if err := act.UnpinDataset(ref); err != nil && err != repo.ErrNotPinner {
return err
}

return act.LogEvent(repo.ETDsDeleted, ref)
}
Loading

0 comments on commit 20322e1

Please sign in to comment.