github.com/attic-labs/noms@v0.0.0-20210827224422-e5fa29d95e8b/samples/go/decent/lib/event.go (about)

     1  // See: https://github.com/attic-labs/noms/issues/3808
     2  // +build ignore
     3  
     4  // Copyright 2017 Attic Labs, Inc. All rights reserved.
     5  // Licensed under the Apache License, version 2.0:
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  
     8  package lib
     9  
    10  import (
    11  	"context"
    12  	"fmt"
    13  	"time"
    14  
    15  	"github.com/attic-labs/noms/go/d"
    16  	"github.com/attic-labs/noms/go/datas"
    17  	"github.com/attic-labs/noms/go/hash"
    18  	"github.com/attic-labs/noms/go/ipfs"
    19  	"github.com/attic-labs/noms/go/merge"
    20  	"github.com/attic-labs/noms/go/spec"
    21  	"github.com/attic-labs/noms/go/types"
    22  	"github.com/attic-labs/noms/go/util/math"
    23  	"github.com/attic-labs/noms/samples/go/decent/dbg"
    24  	"github.com/ipfs/go-ipfs/core"
    25  )
    26  
    27  const (
    28  	InputEvent  ChatEventType = "input"
    29  	SearchEvent ChatEventType = "search"
    30  	SyncEvent   ChatEventType = "sync"
    31  	QuitEvent   ChatEventType = "quit"
    32  )
    33  
    34  type ClientInfo struct {
    35  	Topic    string
    36  	Username string
    37  	Interval time.Duration
    38  	Idx      int
    39  	IsDaemon bool
    40  	Dir      string
    41  	Spec     spec.Spec
    42  	Delegate EventDelegate
    43  }
    44  
    45  type ChatEventType string
    46  
    47  type ChatEvent struct {
    48  	EventType ChatEventType
    49  	Event     string
    50  }
    51  
    52  type EventDelegate interface {
    53  	PinBlocks(node *core.IpfsNode, sourceDB, sinkDB datas.Database, sourceCommit types.Value)
    54  	SourceCommitFromMsgData(db datas.Database, msgData string) (datas.Database, types.Value)
    55  	HashFromMsgData(msgData string) (hash.Hash, error)
    56  	GenMessageData(cInfo ClientInfo, h hash.Hash) string
    57  }
    58  
    59  // ProcessChatEvent reads events from the event channel and processes them
    60  // sequentially. Is ClientInfo.IsDaemon is true, it also publishes the current
    61  // head of the dataset continously.
    62  func ProcessChatEvents(node *core.IpfsNode, ds datas.Dataset, events chan ChatEvent, t *TermUI, cInfo ClientInfo) {
    63  	stopChan := make(chan struct{})
    64  	if cInfo.IsDaemon {
    65  		go func() {
    66  			tickChan := time.NewTicker(cInfo.Interval).C
    67  			for {
    68  				select {
    69  				case <-stopChan:
    70  					break
    71  				case <-tickChan:
    72  					Publish(node, cInfo, ds.HeadRef().TargetHash())
    73  				}
    74  			}
    75  		}()
    76  	}
    77  
    78  	for event := range events {
    79  		switch event.EventType {
    80  		case SyncEvent:
    81  			ds = processHash(t, node, ds, event.Event, cInfo)
    82  			Publish(node, cInfo, ds.HeadRef().TargetHash())
    83  		case InputEvent:
    84  			ds = processInput(t, node, ds, event.Event, cInfo)
    85  			Publish(node, cInfo, ds.HeadRef().TargetHash())
    86  		case SearchEvent:
    87  			processSearch(t, node, ds, event.Event, cInfo)
    88  		case QuitEvent:
    89  			dbg.Debug("QuitEvent received, stopping program")
    90  			stopChan <- struct{}{}
    91  			return
    92  		}
    93  	}
    94  }
    95  
    96  // processHash processes msgs published by other chat nodes and does the work to
    97  // integrate new data into this nodes local database and display it as needed.
    98  func processHash(t *TermUI, node *core.IpfsNode, ds datas.Dataset, msgData string, cInfo ClientInfo) datas.Dataset {
    99  	h, err := cInfo.Delegate.HashFromMsgData(msgData)
   100  	d.PanicIfError(err)
   101  	defer dbg.BoxF("processHash, msgData: %s, hash: %s, cid: %s", msgData, h, ipfs.NomsHashToCID(h))()
   102  
   103  	sinkDB := ds.Database()
   104  	d.PanicIfFalse(ds.HasHead())
   105  
   106  	headRef := ds.HeadRef()
   107  	if h == headRef.TargetHash() {
   108  		dbg.Debug("received hash same as current head, nothing to do")
   109  		return ds
   110  	}
   111  
   112  	dbg.Debug("reading value for hash: %s", h)
   113  	sourceDB, sourceCommit := cInfo.Delegate.SourceCommitFromMsgData(sinkDB, msgData)
   114  	if sourceCommit == nil {
   115  		dbg.Debug("FAILED to read value for hash: %s", h)
   116  		return ds
   117  	}
   118  
   119  	sourceRef := types.NewRef(sourceCommit)
   120  
   121  	_, isP2P := cInfo.Delegate.(P2PEventDelegate)
   122  	if cInfo.IsDaemon || isP2P {
   123  		cInfo.Delegate.PinBlocks(node, sourceDB, sinkDB, sourceCommit)
   124  	}
   125  
   126  	dbg.Debug("Finding common ancestor for merge, sourceRef: %s, headRef: %s", sourceRef.TargetHash(), headRef.TargetHash())
   127  	a, ok := datas.FindCommonAncestor(sourceRef, headRef, sinkDB)
   128  	if !ok {
   129  		dbg.Debug("no common ancestor, cannot merge update!")
   130  		return ds
   131  	}
   132  	dbg.Debug("Checking if source commit is ancestor")
   133  	if a.Equals(sourceRef) {
   134  		dbg.Debug("source commit was ancestor, nothing to do")
   135  		return ds
   136  	}
   137  	if a.Equals(headRef) {
   138  		dbg.Debug("fast-forward to source commit")
   139  		ds, err := sinkDB.SetHead(ds, sourceRef)
   140  		d.Chk.NoError(err)
   141  		if !cInfo.IsDaemon {
   142  			t.UpdateMessagesFromSync(ds)
   143  		}
   144  		return ds
   145  	}
   146  
   147  	dbg.Debug("We have a mergeable commit")
   148  	left := ds.HeadValue()
   149  	right := sourceCommit.(types.Struct).Get("value")
   150  	parent := a.TargetValue(sinkDB).(types.Struct).Get("value")
   151  
   152  	dbg.Debug("Starting three-way commit")
   153  	merged, err := merge.ThreeWay(left, right, parent, sinkDB, nil, nil)
   154  	if err != nil {
   155  		dbg.Debug("could not merge received data: " + err.Error())
   156  		return ds
   157  	}
   158  
   159  	dbg.Debug("setting new datasetHead on localDB")
   160  	newCommit := datas.NewCommit(merged, types.NewSet(sinkDB, ds.HeadRef(), sourceRef), types.EmptyStruct)
   161  	commitRef := sinkDB.WriteValue(newCommit)
   162  	dbg.Debug("wrote new commit: %s", commitRef.TargetHash())
   163  	ds, err = sinkDB.SetHead(ds, commitRef)
   164  	if err != nil {
   165  		dbg.Debug("call to db.SetHead on failed, err: %s", err)
   166  	}
   167  	dbg.Debug("set new head ref: %s on ds.ID: %s", commitRef.TargetHash(), ds.ID())
   168  	newH := ds.HeadRef().TargetHash()
   169  	dbg.Debug("merged commit, dataset: %s, head: %s, cid: %s", ds.ID(), newH, ipfs.NomsHashToCID(newH))
   170  	if cInfo.IsDaemon {
   171  		cInfo.Delegate.PinBlocks(node, sourceDB, sinkDB, newCommit)
   172  	} else {
   173  		t.UpdateMessagesFromSync(ds)
   174  	}
   175  	return ds
   176  }
   177  
   178  // processInput adds a new msg (entered through the UI) updates it's dataset.
   179  func processInput(t *TermUI, node *core.IpfsNode, ds datas.Dataset, msg string, cInfo ClientInfo) datas.Dataset {
   180  	defer dbg.BoxF("processInput, msg: %s", msg)()
   181  	t.InSearch = false
   182  	if msg != "" {
   183  		var err error
   184  		ds, err = AddMessage(msg, cInfo.Username, time.Now(), ds)
   185  		d.PanicIfError(err)
   186  	}
   187  	t.UpdateMessagesAsync(ds, nil, nil)
   188  	return ds
   189  }
   190  
   191  // updates the UI to display search results.
   192  func processSearch(t *TermUI, node *core.IpfsNode, ds datas.Dataset, terms string, cInfo ClientInfo) {
   193  	defer dbg.BoxF("processSearch")()
   194  	if terms == "" {
   195  		return
   196  	}
   197  	t.InSearch = true
   198  	searchTerms := TermsFromString(terms)
   199  	searchIds := SearchIndex(ds, searchTerms)
   200  	t.UpdateMessagesAsync(ds, &searchIds, searchTerms)
   201  	return
   202  }
   203  
   204  // recurses over the chunks originating at 'h' and pins them to the IPFS repo.
   205  func pinBlocks(node *core.IpfsNode, h hash.Hash, db datas.Database, depth, cnt int) (maxDepth, newCnt int) {
   206  	maxDepth, newCnt = depth, cnt
   207  
   208  	cid := ipfs.NomsHashToCID(h)
   209  	_, pinned, err := node.Pinning.IsPinned(cid)
   210  	d.Chk.NoError(err)
   211  	if pinned {
   212  		return
   213  	}
   214  
   215  	ctx, cancel := context.WithCancel(context.Background())
   216  	defer cancel()
   217  
   218  	v := db.ReadValue(h)
   219  	d.Chk.NotNil(v)
   220  
   221  	v.WalkRefs(func(r types.Ref) {
   222  		var newDepth int
   223  		newDepth, newCnt = pinBlocks(node, r.TargetHash(), db, depth+1, newCnt)
   224  		maxDepth = math.MaxInt(newDepth, maxDepth)
   225  	})
   226  
   227  	n, err := node.DAG.Get(ctx, cid)
   228  	d.Chk.NoError(err)
   229  	err = node.Pinning.Pin(ctx, n, false)
   230  	d.Chk.NoError(err)
   231  	newCnt++
   232  	return
   233  }
   234  
   235  type IPFSEventDelegate struct{}
   236  
   237  func (d IPFSEventDelegate) PinBlocks(node *core.IpfsNode, sourceDB, sinkDB datas.Database, sourceCommit types.Value) {
   238  	h := sourceCommit.Hash()
   239  	dbg.Debug("Starting pinBlocks")
   240  	depth, cnt := pinBlocks(node, h, sinkDB, 0, 0)
   241  	dbg.Debug("Finished pinBlocks, depth: %d, cnt: %d", depth, cnt)
   242  	node.Pinning.Flush()
   243  }
   244  
   245  func (d IPFSEventDelegate) SourceCommitFromMsgData(db datas.Database, msgData string) (datas.Database, types.Value) {
   246  	h := hash.Parse(msgData)
   247  	v := db.ReadValue(h)
   248  	return db, v
   249  }
   250  
   251  func (d IPFSEventDelegate) HashFromMsgData(msgData string) (hash.Hash, error) {
   252  	var err error
   253  	h, ok := hash.MaybeParse(msgData)
   254  	if !ok {
   255  		err = fmt.Errorf("Failed to parse hash from msgData: %s", msgData)
   256  	}
   257  	return h, err
   258  }
   259  
   260  func (d IPFSEventDelegate) GenMessageData(cInfo ClientInfo, h hash.Hash) string {
   261  	return h.String()
   262  }
   263  
   264  type P2PEventDelegate struct{}
   265  
   266  func (d P2PEventDelegate) PinBlocks(node *core.IpfsNode, sourceDB, sinkDB datas.Database, sourceCommit types.Value) {
   267  	sourceRef := types.NewRef(sourceCommit)
   268  	datas.Pull(sourceDB, sinkDB, sourceRef, nil)
   269  }
   270  
   271  func (d P2PEventDelegate) SourceCommitFromMsgData(db datas.Database, msgData string) (datas.Database, types.Value) {
   272  	sp, _ := spec.ForPath(msgData)
   273  	v := sp.GetValue()
   274  	return sp.GetDatabase(), v
   275  }
   276  
   277  func (d P2PEventDelegate) HashFromMsgData(msgData string) (hash.Hash, error) {
   278  	sp, err := spec.ForPath(msgData)
   279  	return sp.Path.Hash, err
   280  }
   281  
   282  func (d P2PEventDelegate) GenMessageData(cInfo ClientInfo, h hash.Hash) string {
   283  	return fmt.Sprintf("%s::#%s", cInfo.Spec, h)
   284  }