github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/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 }