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  }