github.com/attic-labs/noms@v0.0.0-20210827224422-e5fa29d95e8b/samples/go/decent/lib/model.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 "fmt" 12 "regexp" 13 "strings" 14 "time" 15 16 "github.com/attic-labs/noms/go/d" 17 "github.com/attic-labs/noms/go/datas" 18 "github.com/attic-labs/noms/go/marshal" 19 "github.com/attic-labs/noms/go/types" 20 "github.com/attic-labs/noms/go/util/datetime" 21 "github.com/attic-labs/noms/samples/go/decent/dbg" 22 ) 23 24 type Root struct { 25 // Map<Key, Message> 26 // Keys are strings like: <Ordinal>,<Author> 27 // This scheme allows: 28 // - map is naturally sorted in the right order 29 // - conflicts will generally be avoided 30 // - messages are editable 31 Messages types.Map 32 Index types.Map 33 Users []string `noms:",set"` 34 } 35 36 type Message struct { 37 Ordinal uint64 38 Author string 39 Body string 40 ClientTime datetime.DateTime 41 } 42 43 func (m Message) ID() string { 44 return fmt.Sprintf("%020x/%s", m.ClientTime.UnixNano(), m.Author) 45 } 46 47 func AddMessage(body string, author string, clientTime time.Time, ds datas.Dataset) (datas.Dataset, error) { 48 defer dbg.BoxF("AddMessage, body: %s", body)() 49 root, err := getRoot(ds) 50 if err != nil { 51 return datas.Dataset{}, err 52 } 53 54 db := ds.Database() 55 56 nm := Message{ 57 Author: author, 58 Body: body, 59 ClientTime: datetime.DateTime{clientTime}, 60 Ordinal: root.Messages.Len(), 61 } 62 root.Messages = root.Messages.Edit().Set(types.String(nm.ID()), marshal.MustMarshal(db, nm)).Map() 63 IndexNewMessage(db, &root, nm) 64 newRoot := marshal.MustMarshal(db, root) 65 ds, err = db.CommitValue(ds, newRoot) 66 return ds, err 67 } 68 69 func InitDatabase(ds datas.Dataset) (datas.Dataset, error) { 70 if ds.HasHead() { 71 return ds, nil 72 } 73 db := ds.Database() 74 root := Root{ 75 Index: types.NewMap(db), 76 Messages: types.NewMap(db), 77 } 78 return db.CommitValue(ds, marshal.MustMarshal(db, root)) 79 } 80 81 func GetAuthors(ds datas.Dataset) []string { 82 r, err := getRoot(ds) 83 d.PanicIfError(err) 84 return r.Users 85 } 86 87 func IndexNewMessage(vrw types.ValueReadWriter, root *Root, m Message) { 88 defer dbg.BoxF("IndexNewMessage")() 89 90 ti := NewTermIndex(vrw, root.Index) 91 id := types.String(m.ID()) 92 root.Index = ti.Edit().InsertAll(GetTerms(m), id).Value().TermDocs 93 root.Users = append(root.Users, m.Author) 94 } 95 96 func SearchIndex(ds datas.Dataset, search []string) types.Map { 97 root, err := getRoot(ds) 98 d.PanicIfError(err) 99 idx := root.Index 100 ti := NewTermIndex(ds.Database(), idx) 101 ids := ti.Search(search) 102 dbg.Debug("search for: %s, returned: %d", strings.Join(search, " "), ids.Len()) 103 return ids 104 } 105 106 var ( 107 punctPat = regexp.MustCompile("[[:punct:]]+") 108 wsPat = regexp.MustCompile("\\s+") 109 ) 110 111 func TermsFromString(s string) []string { 112 s1 := punctPat.ReplaceAllString(strings.TrimSpace(s), " ") 113 terms := wsPat.Split(s1, -1) 114 clean := []string{} 115 for _, t := range terms { 116 if t == "" { 117 continue 118 } 119 clean = append(clean, strings.ToLower(t)) 120 } 121 return clean 122 } 123 124 func GetTerms(m Message) []string { 125 terms := TermsFromString(m.Body) 126 terms = append(terms, TermsFromString(m.Author)...) 127 return terms 128 } 129 130 func ListMessages(ds datas.Dataset, searchIds *types.Map, doneChan chan struct{}) (msgMap types.Map, mc chan types.String, err error) { 131 //dbg.Debug("##### listMessages: entered") 132 133 root, err := getRoot(ds) 134 db := ds.Database() 135 if err != nil { 136 return types.NewMap(db), nil, err 137 } 138 msgMap = root.Messages 139 140 mc = make(chan types.String) 141 done := false 142 go func() { 143 <-doneChan 144 done = true 145 <-mc 146 //dbg.Debug("##### listMessages: exiting 'done' goroutine") 147 }() 148 149 go func() { 150 keyMap := msgMap 151 if searchIds != nil { 152 keyMap = *searchIds 153 } 154 i := uint64(0) 155 for ; i < keyMap.Len() && !done; i++ { 156 key, _ := keyMap.At(keyMap.Len() - i - 1) 157 mc <- key.(types.String) 158 } 159 //dbg.Debug("##### listMessages: exiting 'for loop' goroutine, examined: %d", i) 160 close(mc) 161 }() 162 return 163 } 164 165 func getRoot(ds datas.Dataset) (Root, error) { 166 defer dbg.BoxF("getRoot")() 167 168 db := ds.Database() 169 root := Root{ 170 Messages: types.NewMap(db), 171 Index: types.NewMap(db), 172 } 173 // TODO: It would be nice if Dataset.MaybeHeadValue() or HeadValue() 174 // would return just <value>, and it would be nil if not there, so you 175 // could chain calls. 176 if !ds.HasHead() { 177 return root, nil 178 } 179 err := marshal.Unmarshal(ds.HeadValue(), &root) 180 if err != nil { 181 return Root{}, err 182 } 183 return root, nil 184 }