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 }