github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/chat/search/md.go (about)

     1  package search
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"unsafe"
     8  
     9  	"github.com/keybase/client/go/protocol/chat1"
    10  )
    11  
    12  const indexMetadataVersion = 3
    13  
    14  type indexMetadata struct {
    15  	SeenIDs map[chat1.MessageID]chat1.EmptyStruct `codec:"s"`
    16  	Version string                                `codec:"v"`
    17  }
    18  
    19  func newIndexMetadata() *indexMetadata {
    20  	return &indexMetadata{
    21  		Version: fmt.Sprintf("%d:%d", indexVersion, indexMetadataVersion),
    22  		SeenIDs: make(map[chat1.MessageID]chat1.EmptyStruct),
    23  	}
    24  }
    25  
    26  var refIndexMetadata = newIndexMetadata()
    27  
    28  func (m *indexMetadata) dup() (res *indexMetadata) {
    29  	if m == nil {
    30  		return nil
    31  	}
    32  	res = new(indexMetadata)
    33  	res.Version = m.Version
    34  	res.SeenIDs = make(map[chat1.MessageID]chat1.EmptyStruct, len(m.SeenIDs))
    35  	for m := range m.SeenIDs {
    36  		res.SeenIDs[m] = chat1.EmptyStruct{}
    37  	}
    38  	return res
    39  }
    40  
    41  func (m *indexMetadata) Size() int64 {
    42  	size := unsafe.Sizeof(m.Version)
    43  	size += uintptr(len(m.SeenIDs)) * unsafe.Sizeof(chat1.MessageID(0))
    44  	return int64(size)
    45  }
    46  
    47  func (m *indexMetadata) MissingIDForConv(conv chat1.Conversation) (res []chat1.MessageID) {
    48  	min, max := MinMaxIDs(conv)
    49  	for i := min; i <= max; i++ {
    50  		if _, ok := m.SeenIDs[i]; !ok {
    51  			res = append(res, i)
    52  		}
    53  	}
    54  	return res
    55  }
    56  
    57  func (m *indexMetadata) numMissing(min, max chat1.MessageID) (numMissing int) {
    58  	for i := min; i <= max; i++ {
    59  		if _, ok := m.SeenIDs[i]; !ok {
    60  			numMissing++
    61  		}
    62  	}
    63  	return numMissing
    64  }
    65  
    66  func (m *indexMetadata) indexStatus(conv chat1.Conversation) indexStatus {
    67  	min, max := MinMaxIDs(conv)
    68  	numMsgs := int(max) - int(min) + 1
    69  	if numMsgs <= 1 {
    70  		return indexStatus{numMsgs: numMsgs}
    71  	}
    72  	numMissing := m.numMissing(min, max)
    73  	return indexStatus{numMissing: numMissing, numMsgs: numMsgs}
    74  }
    75  
    76  func (m *indexMetadata) PercentIndexed(conv chat1.Conversation) int {
    77  	status := m.indexStatus(conv)
    78  	if status.numMsgs <= 1 {
    79  		return 100
    80  	}
    81  	return int(100 * (1 - (float64(status.numMissing) / float64(status.numMsgs))))
    82  }
    83  
    84  func (m *indexMetadata) FullyIndexed(conv chat1.Conversation) bool {
    85  	min, max := MinMaxIDs(conv)
    86  	if max <= min {
    87  		return true
    88  	}
    89  	return m.numMissing(min, max) == 0
    90  }
    91  
    92  type indexStatus struct {
    93  	numMissing int
    94  	numMsgs    int
    95  }
    96  
    97  type inboxIndexStatus struct {
    98  	sync.Mutex
    99  	inbox         map[chat1.ConvIDStr]indexStatus
   100  	uiCh          chan chat1.ChatSearchIndexStatus
   101  	dirty         bool
   102  	cachedPercent int
   103  }
   104  
   105  func newInboxIndexStatus(uiCh chan chat1.ChatSearchIndexStatus) *inboxIndexStatus {
   106  	return &inboxIndexStatus{
   107  		inbox: make(map[chat1.ConvIDStr]indexStatus),
   108  		uiCh:  uiCh,
   109  	}
   110  }
   111  
   112  func (p *inboxIndexStatus) updateUI(ctx context.Context) (int, error) {
   113  	p.Lock()
   114  	defer p.Unlock()
   115  	percentIndexed := p.percentIndexedLocked()
   116  	if p.uiCh != nil {
   117  		status := chat1.ChatSearchIndexStatus{
   118  			PercentIndexed: percentIndexed,
   119  		}
   120  		select {
   121  		case <-ctx.Done():
   122  			return 0, ctx.Err()
   123  		case p.uiCh <- status:
   124  		default:
   125  		}
   126  	}
   127  	return percentIndexed, nil
   128  }
   129  
   130  func (p *inboxIndexStatus) numConvs() int {
   131  	p.Lock()
   132  	defer p.Unlock()
   133  	return len(p.inbox)
   134  }
   135  
   136  func (p *inboxIndexStatus) addConv(m *indexMetadata, conv chat1.Conversation) {
   137  	p.Lock()
   138  	defer p.Unlock()
   139  	p.dirty = true
   140  	p.inbox[conv.GetConvID().ConvIDStr()] = m.indexStatus(conv)
   141  }
   142  
   143  func (p *inboxIndexStatus) rmConv(conv chat1.Conversation) {
   144  	p.Lock()
   145  	defer p.Unlock()
   146  	p.dirty = true
   147  	delete(p.inbox, conv.GetConvID().ConvIDStr())
   148  }
   149  
   150  func (p *inboxIndexStatus) percentIndexed() int {
   151  	p.Lock()
   152  	defer p.Unlock()
   153  	return p.percentIndexedLocked()
   154  }
   155  
   156  func (p *inboxIndexStatus) percentIndexedLocked() int {
   157  	if p.dirty {
   158  		var numMissing, numMsgs int
   159  		for _, status := range p.inbox {
   160  			numMissing += status.numMissing
   161  			numMsgs += status.numMsgs
   162  		}
   163  		if numMsgs == 0 {
   164  			p.cachedPercent = 100
   165  		} else {
   166  			p.cachedPercent = int(100 * (1 - (float64(numMissing) / float64(numMsgs))))
   167  		}
   168  		p.dirty = false
   169  	}
   170  	return p.cachedPercent
   171  }