github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/storage/inbox.go (about)

     1  package storage
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/sha1"
     6  	"encoding/hex"
     7  	"fmt"
     8  	"time"
     9  
    10  	"github.com/keybase/client/go/chat/globals"
    11  	"github.com/keybase/client/go/chat/types"
    12  	"github.com/keybase/client/go/chat/utils"
    13  	"github.com/keybase/client/go/libkb"
    14  	"github.com/keybase/client/go/protocol/chat1"
    15  	"github.com/keybase/client/go/protocol/gregor1"
    16  	"github.com/keybase/client/go/protocol/keybase1"
    17  	"golang.org/x/net/context"
    18  )
    19  
    20  const inboxVersion = 32
    21  
    22  type InboxFlushMode int
    23  
    24  const (
    25  	InboxFlushModeActive InboxFlushMode = iota
    26  	InboxFlushModeDelegate
    27  )
    28  
    29  type queryHash []byte
    30  
    31  func (q queryHash) Empty() bool {
    32  	return len(q) == 0
    33  }
    34  
    35  func (q queryHash) String() string {
    36  	return hex.EncodeToString(q)
    37  }
    38  
    39  func (q queryHash) Eq(r queryHash) bool {
    40  	return bytes.Equal(q, r)
    41  }
    42  
    43  type inboxDiskQuery struct {
    44  	QueryHash  queryHash         `codec:"Q"`
    45  	Pagination *chat1.Pagination `codec:"P"`
    46  }
    47  
    48  func (q inboxDiskQuery) queryMatch(other inboxDiskQuery) bool {
    49  	if q.QueryHash.Empty() && other.QueryHash.Empty() {
    50  		return true
    51  	}
    52  	if !q.QueryHash.Empty() && !other.QueryHash.Empty() {
    53  		return q.QueryHash.Eq(other.QueryHash)
    54  	}
    55  	return false
    56  }
    57  
    58  func (q inboxDiskQuery) match(other inboxDiskQuery) bool {
    59  	return q.queryMatch(other) && q.Pagination.Eq(other.Pagination)
    60  }
    61  
    62  type inboxDiskIndex struct {
    63  	ConversationIDs []chat1.ConversationID `codec:"C"`
    64  	Queries         []inboxDiskQuery       `codec:"Q"`
    65  }
    66  
    67  func (i inboxDiskIndex) DeepCopy() (res inboxDiskIndex) {
    68  	res.ConversationIDs = make([]chat1.ConversationID, len(i.ConversationIDs))
    69  	res.Queries = make([]inboxDiskQuery, len(i.Queries))
    70  	copy(res.ConversationIDs, i.ConversationIDs)
    71  	copy(res.Queries, i.Queries)
    72  	return res
    73  }
    74  
    75  func (i *inboxDiskIndex) mergeConvs(convIDs []chat1.ConversationID) {
    76  	m := make(map[string]chat1.ConversationID, len(convIDs))
    77  	for _, convID := range convIDs {
    78  		m[convID.String()] = convID
    79  	}
    80  	for _, convID := range i.ConversationIDs {
    81  		delete(m, convID.String())
    82  	}
    83  	for _, convID := range m {
    84  		i.ConversationIDs = append(i.ConversationIDs, convID)
    85  	}
    86  }
    87  
    88  func (i *inboxDiskIndex) merge(convIDs []chat1.ConversationID, hash queryHash) {
    89  	i.mergeConvs(convIDs)
    90  	queryExists := false
    91  	qp := inboxDiskQuery{QueryHash: hash}
    92  	for _, q := range i.Queries {
    93  		if q.queryMatch(qp) {
    94  			queryExists = true
    95  			break
    96  		}
    97  	}
    98  	if !queryExists {
    99  		i.Queries = append(i.Queries, qp)
   100  	}
   101  }
   102  
   103  type inboxDiskVersions struct {
   104  	Version       int             `codec:"V"`
   105  	ServerVersion int             `codec:"S"`
   106  	InboxVersion  chat1.InboxVers `codec:"I"`
   107  }
   108  
   109  type InboxLayoutChangedNotifier interface {
   110  	UpdateLayout(ctx context.Context, reselectMode chat1.InboxLayoutReselectMode, reason string)
   111  	UpdateLayoutFromNewMessage(ctx context.Context, conv types.RemoteConversation)
   112  	UpdateLayoutFromSubteamRename(ctx context.Context, convs []types.RemoteConversation)
   113  }
   114  
   115  type dummyInboxLayoutChangedNotifier struct{}
   116  
   117  func (d dummyInboxLayoutChangedNotifier) UpdateLayout(ctx context.Context,
   118  	reselectMode chat1.InboxLayoutReselectMode, reason string) {
   119  }
   120  
   121  func (d dummyInboxLayoutChangedNotifier) UpdateLayoutFromNewMessage(ctx context.Context,
   122  	conv types.RemoteConversation) {
   123  }
   124  
   125  func (d dummyInboxLayoutChangedNotifier) UpdateLayoutFromSubteamRename(ctx context.Context,
   126  	convs []types.RemoteConversation) {
   127  }
   128  
   129  func LayoutChangedNotifier(notifier InboxLayoutChangedNotifier) func(*Inbox) {
   130  	return func(i *Inbox) {
   131  		i.SetInboxLayoutChangedNotifier(notifier)
   132  	}
   133  }
   134  
   135  type Inbox struct {
   136  	globals.Contextified
   137  	*baseBox
   138  	utils.DebugLabeler
   139  
   140  	layoutNotifier InboxLayoutChangedNotifier
   141  }
   142  
   143  func NewInbox(g *globals.Context, config ...func(*Inbox)) *Inbox {
   144  	i := &Inbox{
   145  		Contextified:   globals.NewContextified(g),
   146  		DebugLabeler:   utils.NewDebugLabeler(g.ExternalG(), "Inbox", false),
   147  		baseBox:        newBaseBox(g),
   148  		layoutNotifier: dummyInboxLayoutChangedNotifier{},
   149  	}
   150  	for _, c := range config {
   151  		c(i)
   152  	}
   153  	return i
   154  }
   155  
   156  func (i *Inbox) SetInboxLayoutChangedNotifier(notifier InboxLayoutChangedNotifier) {
   157  	i.layoutNotifier = notifier
   158  }
   159  
   160  func (i *Inbox) dbVersionsKey(uid gregor1.UID) libkb.DbKey {
   161  	return libkb.DbKey{
   162  		Typ: libkb.DBChatInbox,
   163  		Key: uid.String(),
   164  	}
   165  }
   166  
   167  func (i *Inbox) dbIndexKey(uid gregor1.UID) libkb.DbKey {
   168  	return libkb.DbKey{
   169  		Typ: libkb.DBChatInboxIndex,
   170  		Key: uid.String(),
   171  	}
   172  }
   173  
   174  func (i *Inbox) dbConvKey(uid gregor1.UID, convID chat1.ConversationID) libkb.DbKey {
   175  	return libkb.DbKey{
   176  		Typ: libkb.DBChatInboxConvs,
   177  		Key: uid.String() + convID.DbShortFormString(),
   178  	}
   179  }
   180  
   181  func (i *Inbox) maybeNuke(ctx context.Context, ef func() Error, uid gregor1.UID) {
   182  	err := ef()
   183  	if err != nil && err.ShouldClear() {
   184  		i.Debug(ctx, "maybeNuke: nuking on err: %v", err)
   185  		if ierr := i.clearLocked(ctx, uid); ierr != nil {
   186  			i.Debug(ctx, "maybeNuke: unable to clear box on error! err: %s", ierr)
   187  		}
   188  	}
   189  }
   190  
   191  func (i *Inbox) readDiskVersions(ctx context.Context, uid gregor1.UID, useInMemory bool) (inboxDiskVersions, Error) {
   192  	var ibox inboxDiskVersions
   193  	// Check context for an aborted request
   194  	if err := isAbortedRequest(ctx); err != nil {
   195  		return ibox, err
   196  	}
   197  	// Check in memory cache first
   198  	if memibox := inboxMemCache.GetVersions(uid); useInMemory && memibox != nil {
   199  		i.Debug(ctx, "readDiskVersions: hit in memory cache")
   200  		ibox = *memibox
   201  	} else {
   202  		found, err := i.readDiskBox(ctx, i.dbVersionsKey(uid), &ibox)
   203  		if err != nil {
   204  			if _, ok := err.(libkb.LoginRequiredError); ok {
   205  				return ibox, MiscError{Msg: err.Error()}
   206  			}
   207  			return ibox, NewInternalError(ctx, i.DebugLabeler,
   208  				"failed to read inbox: uid: %d err: %s", uid, err)
   209  		}
   210  		if !found {
   211  			return ibox, MissError{}
   212  		}
   213  		if useInMemory {
   214  			inboxMemCache.PutVersions(uid, &ibox)
   215  		}
   216  	}
   217  	// Check on disk server version against known server version
   218  	if _, err := i.G().ServerCacheVersions.MatchInbox(ctx, ibox.ServerVersion); err != nil {
   219  		i.Debug(ctx, "readDiskVersions: server version match error, clearing: %s", err)
   220  		if cerr := i.clearLocked(ctx, uid); cerr != nil {
   221  			i.Debug(ctx, "readDiskVersions: failed to clear after server mismatch: %s", cerr)
   222  		}
   223  		return ibox, MissError{}
   224  	}
   225  	// Check on disk version against configured
   226  	if ibox.Version != inboxVersion {
   227  		i.Debug(ctx,
   228  			"readDiskVersions: on disk version not equal to program version, clearing: disk :%d program: %d",
   229  			ibox.Version, inboxVersion)
   230  		if cerr := i.clearLocked(ctx, uid); cerr != nil {
   231  			i.Debug(ctx, "readDiskVersions: failed to clear after inbox mismatch: %s", cerr)
   232  		}
   233  		return ibox, MissError{}
   234  	}
   235  
   236  	i.Debug(ctx, "readDiskVersions: version: %d disk version: %d server version: %d",
   237  		ibox.InboxVersion, ibox.Version, ibox.ServerVersion)
   238  
   239  	return ibox, nil
   240  }
   241  
   242  func (i *Inbox) writeDiskVersions(ctx context.Context, uid gregor1.UID, ibox inboxDiskVersions) Error {
   243  	// Get latest server version
   244  	vers, err := i.G().ServerCacheVersions.Fetch(ctx)
   245  	if err != nil {
   246  		return NewInternalError(ctx, i.DebugLabeler, "failed to fetch server versions: %s", err)
   247  	}
   248  	ibox.ServerVersion = vers.InboxVers
   249  	ibox.Version = inboxVersion
   250  	i.Debug(ctx, "writeDiskVersions: uid: %s version: %d disk version: %d server version: %d",
   251  		uid, ibox.InboxVersion, ibox.Version, ibox.ServerVersion)
   252  	inboxMemCache.PutVersions(uid, &ibox)
   253  	if err := i.writeDiskBox(ctx, i.dbVersionsKey(uid), ibox); err != nil {
   254  		return NewInternalError(ctx, i.DebugLabeler, "failed to write inbox versions: %s", err)
   255  	}
   256  	return nil
   257  }
   258  
   259  func (i *Inbox) readDiskIndex(ctx context.Context, uid gregor1.UID, useInMemory bool) (inboxDiskIndex, Error) {
   260  	var ibox inboxDiskIndex
   261  	// Check context for an aborted request
   262  	if err := isAbortedRequest(ctx); err != nil {
   263  		return ibox, err
   264  	}
   265  	// Check in memory cache first
   266  	if memibox := inboxMemCache.GetIndex(uid); useInMemory && memibox != nil {
   267  		i.Debug(ctx, "readDiskIndex: hit in memory cache")
   268  		ibox = *memibox
   269  	} else {
   270  		found, err := i.readDiskBox(ctx, i.dbIndexKey(uid), &ibox)
   271  		if err != nil {
   272  			if _, ok := err.(libkb.LoginRequiredError); ok {
   273  				return ibox, MiscError{Msg: err.Error()}
   274  			}
   275  			return ibox, NewInternalError(ctx, i.DebugLabeler,
   276  				"failed to read inbox: uid: %d err: %s", uid, err)
   277  		}
   278  		if !found {
   279  			return ibox, MissError{}
   280  		}
   281  		if useInMemory {
   282  			inboxMemCache.PutIndex(uid, &ibox)
   283  		}
   284  	}
   285  	i.Debug(ctx, "readDiskIndex: convs: %d queries: %d", len(ibox.ConversationIDs), len(ibox.Queries))
   286  	return ibox, nil
   287  }
   288  
   289  func (i *Inbox) writeDiskIndex(ctx context.Context, uid gregor1.UID, ibox inboxDiskIndex) Error {
   290  	i.Debug(ctx, "writeDiskIndex: convs: %d queries: %d", len(ibox.ConversationIDs), len(ibox.Queries))
   291  	inboxMemCache.PutIndex(uid, &ibox)
   292  	if err := i.writeDiskBox(ctx, i.dbIndexKey(uid), ibox); err != nil {
   293  		return NewInternalError(ctx, i.DebugLabeler, "failed to write inbox index: %s", err)
   294  	}
   295  	return nil
   296  }
   297  
   298  func (i *Inbox) readConvs(ctx context.Context, uid gregor1.UID, convIDs []chat1.ConversationID) (res []types.RemoteConversation, err Error) {
   299  	res = make([]types.RemoteConversation, 0, len(convIDs))
   300  	memHits := make(map[chat1.ConvIDStr]bool, len(convIDs))
   301  	for _, convID := range convIDs {
   302  		if conv := inboxMemCache.GetConv(uid, convID); conv != nil {
   303  			res = append(res, *conv)
   304  			memHits[convID.ConvIDStr()] = true
   305  		}
   306  	}
   307  	if len(memHits) == len(convIDs) {
   308  		return res, nil
   309  	}
   310  	dbReads := 0
   311  	defer func() {
   312  		i.Debug(ctx, "readConvs: read %d convs from db", dbReads)
   313  	}()
   314  	for _, convID := range convIDs {
   315  		if memHits[convID.ConvIDStr()] {
   316  			continue
   317  		}
   318  		var conv types.RemoteConversation
   319  		dbReads++
   320  		found, err := i.readDiskBox(ctx, i.dbConvKey(uid, convID), &conv)
   321  		if err != nil {
   322  			if _, ok := err.(libkb.LoginRequiredError); ok {
   323  				return res, MiscError{Msg: err.Error()}
   324  			}
   325  			return res, NewInternalError(ctx, i.DebugLabeler,
   326  				"failed to read inbox: uid: %d err: %s", uid, err)
   327  		}
   328  		if !found {
   329  			return res, MissError{}
   330  		}
   331  		inboxMemCache.PutConv(uid, conv)
   332  		res = append(res, conv)
   333  	}
   334  	return res, nil
   335  }
   336  
   337  func (i *Inbox) readConv(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID) (res types.RemoteConversation, err Error) {
   338  	convs, err := i.readConvs(ctx, uid, []chat1.ConversationID{convID})
   339  	if err != nil {
   340  		return res, err
   341  	}
   342  	if len(convs) == 0 {
   343  		return res, MissError{}
   344  	}
   345  	return convs[0], nil
   346  }
   347  
   348  func (i *Inbox) writeConvs(ctx context.Context, uid gregor1.UID, convs []types.RemoteConversation,
   349  	withVersionCheck bool) Error {
   350  	i.summarizeConvs(convs)
   351  	for _, conv := range convs {
   352  		if withVersionCheck {
   353  			existing, err := i.readConv(ctx, uid, conv.GetConvID())
   354  			if err == nil && existing.GetVersion() >= conv.GetVersion() {
   355  				i.Debug(ctx, "writeConvs: skipping write because of newer stored version: convID: %s old: %d new: %d",
   356  					conv.ConvIDStr, existing.GetVersion(), conv.GetVersion())
   357  				continue
   358  			}
   359  		}
   360  		i.Debug(ctx, "writeConvs: writing conv: %s", conv.ConvIDStr)
   361  		inboxMemCache.PutConv(uid, conv)
   362  		if err := i.writeDiskBox(ctx, i.dbConvKey(uid, conv.GetConvID()), conv); err != nil {
   363  			return NewInternalError(ctx, i.DebugLabeler, "failed to write conv: %s err: %s", conv.ConvIDStr,
   364  				err)
   365  		}
   366  	}
   367  	return nil
   368  }
   369  
   370  func (i *Inbox) writeConv(ctx context.Context, uid gregor1.UID, conv types.RemoteConversation,
   371  	withVersionCheck bool) Error {
   372  	return i.writeConvs(ctx, uid, []types.RemoteConversation{conv}, withVersionCheck)
   373  }
   374  
   375  type ByDatabaseOrder []types.RemoteConversation
   376  
   377  func (a ByDatabaseOrder) Len() int      { return len(a) }
   378  func (a ByDatabaseOrder) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
   379  func (a ByDatabaseOrder) Less(i, j int) bool {
   380  	return utils.DBConvLess(a[i], a[j])
   381  }
   382  
   383  func (i *Inbox) summarizeConv(rc *types.RemoteConversation) {
   384  	if len(rc.Conv.MaxMsgs) == 0 {
   385  		// early out here since we don't do anything if this is empty
   386  		return
   387  	}
   388  
   389  	summaries := make(map[chat1.MessageType]chat1.MessageSummary)
   390  	// Collect the existing summaries
   391  	for _, m := range rc.Conv.MaxMsgSummaries {
   392  		summaries[m.GetMessageType()] = m
   393  	}
   394  
   395  	// Collect the locally-grown summaries
   396  	for _, m := range rc.Conv.MaxMsgs {
   397  		summaries[m.GetMessageType()] = m.Summary()
   398  	}
   399  
   400  	// Insert all the summaries
   401  	rc.Conv.MaxMsgs = nil
   402  	rc.Conv.MaxMsgSummaries = nil
   403  	for _, m := range summaries {
   404  		rc.Conv.MaxMsgSummaries = append(rc.Conv.MaxMsgSummaries, m)
   405  	}
   406  }
   407  
   408  func (i *Inbox) summarizeConvs(convs []types.RemoteConversation) {
   409  	for index := range convs {
   410  		i.summarizeConv(&convs[index])
   411  	}
   412  }
   413  
   414  func (i *Inbox) hashQuery(ctx context.Context, query *chat1.GetInboxQuery) (queryHash, Error) {
   415  	if query == nil {
   416  		return nil, nil
   417  	}
   418  
   419  	dat, err := encode(*query)
   420  	if err != nil {
   421  		return nil, NewInternalError(ctx, i.DebugLabeler, "failed to encode query: %s", err.Error())
   422  	}
   423  
   424  	hasher := sha1.New()
   425  	_, err = hasher.Write(dat)
   426  	if err != nil {
   427  		return nil, NewInternalError(ctx, i.DebugLabeler, "failed to write query: %s", err.Error())
   428  	}
   429  	return hasher.Sum(nil), nil
   430  }
   431  
   432  func (i *Inbox) readDiskVersionsIndexMissOk(ctx context.Context, uid gregor1.UID, useInMemory bool) (vers inboxDiskVersions, index inboxDiskIndex, err Error) {
   433  	if vers, err = i.readDiskVersions(ctx, uid, true); err != nil {
   434  		if _, ok := err.(MissError); !ok {
   435  			return vers, index, err
   436  		}
   437  	}
   438  	if index, err = i.readDiskIndex(ctx, uid, true); err != nil {
   439  		if _, ok := err.(MissError); !ok {
   440  			return vers, index, err
   441  		}
   442  	}
   443  	return vers, index, nil
   444  }
   445  
   446  func (i *Inbox) castInternalError(ierr Error) error {
   447  	err, ok := ierr.(error)
   448  	if ok {
   449  		return err
   450  	}
   451  	return nil
   452  }
   453  
   454  func (i *Inbox) MergeLocalMetadata(ctx context.Context, uid gregor1.UID, convs []chat1.ConversationLocal) (err Error) {
   455  	var ierr error
   456  	defer i.Trace(ctx, &ierr, "MergeLocalMetadata")()
   457  	defer func() { ierr = i.castInternalError(err) }()
   458  	locks.Inbox.Lock()
   459  	defer locks.Inbox.Unlock()
   460  	for _, convLocal := range convs {
   461  		conv, err := i.readConv(ctx, uid, convLocal.GetConvID())
   462  		if err != nil {
   463  			i.Debug(ctx, "MergeLocalMetadata: skipping metadata for %s: err: %s", convLocal.GetConvID(),
   464  				err)
   465  			continue
   466  		}
   467  		// Don't write this out for error convos
   468  		if convLocal.Error != nil || convLocal.GetTopicType() != chat1.TopicType_CHAT {
   469  			continue
   470  		}
   471  		topicName := convLocal.Info.TopicName
   472  		snippetDecoration, snippet, _ := utils.GetConvSnippet(ctx, i.G(), uid, convLocal,
   473  			i.G().GetEnv().GetUsername().String())
   474  		rcm := &types.RemoteConversationMetadata{
   475  			Name:              convLocal.Info.TlfName,
   476  			TopicName:         topicName,
   477  			Headline:          convLocal.Info.Headline,
   478  			HeadlineEmojis:    convLocal.Info.HeadlineEmojis,
   479  			Snippet:           snippet,
   480  			SnippetDecoration: snippetDecoration,
   481  		}
   482  		switch convLocal.GetMembersType() {
   483  		case chat1.ConversationMembersType_TEAM:
   484  		default:
   485  			rcm.WriterNames = convLocal.AllNames()
   486  			rcm.FullNamesForSearch = convLocal.FullNamesForSearch()
   487  			rcm.ResetParticipants = convLocal.Info.ResetNames
   488  		}
   489  		conv.LocalMetadata = rcm
   490  		if err := i.writeConv(ctx, uid, conv, false); err != nil {
   491  			return err
   492  		}
   493  	}
   494  	return nil
   495  }
   496  
   497  // Merge add/updates conversations into the inbox. If a given conversation is either missing
   498  // from the inbox, or is of greater version than what is currently stored, we write it down. Otherwise,
   499  // we ignore it. If the inbox is currently blank, then we write down the given inbox version.
   500  func (i *Inbox) Merge(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers,
   501  	convsIn []chat1.Conversation, query *chat1.GetInboxQuery) (err Error) {
   502  	var ierr error
   503  	defer i.Trace(ctx, &ierr, "Merge")()
   504  	defer func() { ierr = i.castInternalError(err) }()
   505  	locks.Inbox.Lock()
   506  	defer locks.Inbox.Unlock()
   507  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
   508  
   509  	i.Debug(ctx, "Merge: vers: %d convs: %d", vers, len(convsIn))
   510  	if len(convsIn) == 1 {
   511  		i.Debug(ctx, "Merge: single conversation: %s", convsIn[0].GetConvID())
   512  	}
   513  	convIDs := make([]chat1.ConversationID, 0, len(convsIn))
   514  	for _, conv := range convsIn {
   515  		convIDs = append(convIDs, conv.GetConvID())
   516  	}
   517  	convs := make([]chat1.Conversation, len(convsIn))
   518  	copy(convs, convsIn)
   519  
   520  	iboxVers, iboxIndex, err := i.readDiskVersionsIndexMissOk(ctx, uid, true)
   521  	if err != nil {
   522  		return err
   523  	}
   524  
   525  	// write all the convs out
   526  	if err := i.writeConvs(ctx, uid, utils.RemoteConvs(convs), true); err != nil {
   527  		return err
   528  	}
   529  
   530  	// update index
   531  	hquery, err := i.hashQuery(ctx, query)
   532  	if err != nil {
   533  		return err
   534  	}
   535  	i.Debug(ctx, "Merge: query hash: %s", hquery)
   536  	iboxIndex.merge(convIDs, hquery)
   537  	if err := i.writeDiskIndex(ctx, uid, iboxIndex); err != nil {
   538  		return err
   539  	}
   540  
   541  	// updat eversion info
   542  	if iboxVers.InboxVersion != 0 {
   543  		vers = iboxVers.InboxVersion
   544  	} else {
   545  		i.Debug(ctx, "Merge: using given version: %d", vers)
   546  	}
   547  	i.Debug(ctx, "Merge: merging inbox: vers: %d convs: %d", vers, len(iboxIndex.ConversationIDs))
   548  	// Write out new inbox
   549  	return i.writeDiskVersions(ctx, uid, inboxDiskVersions{
   550  		Version:      inboxVersion,
   551  		InboxVersion: vers,
   552  	})
   553  }
   554  
   555  func (i *Inbox) queryNameExists(ctx context.Context, uid gregor1.UID, ibox inboxDiskIndex,
   556  	tlfID chat1.TLFID, membersType chat1.ConversationMembersType, topicName string,
   557  	topicType chat1.TopicType) bool {
   558  	convs, err := i.readConvs(ctx, uid, ibox.ConversationIDs)
   559  	if err != nil {
   560  		i.Debug(ctx, "queryNameExists: unexpected miss on index conv read: %s", err)
   561  		return false
   562  	}
   563  	for _, conv := range convs {
   564  		if conv.Conv.Metadata.IdTriple.Tlfid.Eq(tlfID) && conv.GetMembersType() == membersType &&
   565  			conv.GetTopicName() == topicName && conv.GetTopicType() == topicType {
   566  			return true
   567  		}
   568  	}
   569  	return false
   570  }
   571  
   572  func (i *Inbox) queryExists(ctx context.Context, uid gregor1.UID, ibox inboxDiskIndex,
   573  	query *chat1.GetInboxQuery) bool {
   574  
   575  	// Check for a name query that is after a single conversation
   576  	if query != nil && query.TlfID != nil && query.TopicType != nil && query.TopicName != nil &&
   577  		len(query.MembersTypes) == 1 {
   578  		if i.queryNameExists(ctx, uid, ibox, *query.TlfID, query.MembersTypes[0], *query.TopicName,
   579  			*query.TopicType) {
   580  			i.Debug(ctx, "Read: queryExists: single name query hit")
   581  			return true
   582  		}
   583  	}
   584  
   585  	// Normally a query that has not been seen before will return an error.
   586  	// With AllowUnseenQuery, an unfamiliar query is accepted.
   587  	if query != nil && query.AllowUnseenQuery {
   588  		return true
   589  	}
   590  
   591  	hquery, err := i.hashQuery(ctx, query)
   592  	if err != nil {
   593  		i.Debug(ctx, "Read: queryExists: error hashing query: %s", err)
   594  		return false
   595  	}
   596  	i.Debug(ctx, "Read: queryExists: query hash: %s", hquery)
   597  
   598  	qp := inboxDiskQuery{QueryHash: hquery}
   599  	for _, q := range ibox.Queries {
   600  		if q.match(qp) {
   601  			return true
   602  		}
   603  	}
   604  	return false
   605  }
   606  
   607  func (i *Inbox) ReadAll(ctx context.Context, uid gregor1.UID, useInMemory bool) (vers chat1.InboxVers, res []types.RemoteConversation, err Error) {
   608  	var ierr error
   609  	defer i.Trace(ctx, &ierr, "ReadAll")()
   610  	defer func() { ierr = i.castInternalError(err) }()
   611  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
   612  	locks.Inbox.Lock()
   613  	defer locks.Inbox.Unlock()
   614  
   615  	iboxIndex, err := i.readDiskIndex(ctx, uid, useInMemory)
   616  	if err != nil {
   617  		if _, ok := err.(MissError); ok {
   618  			i.Debug(ctx, "Read: miss: no inbox index found")
   619  		}
   620  		return 0, nil, err
   621  	}
   622  	iboxVers, err := i.readDiskVersions(ctx, uid, useInMemory)
   623  	if err != nil {
   624  		if _, ok := err.(MissError); ok {
   625  			i.Debug(ctx, "Read: miss: no inbox version found")
   626  		}
   627  		return 0, nil, err
   628  	}
   629  	convs, err := i.readConvs(ctx, uid, iboxIndex.ConversationIDs)
   630  	if err != nil {
   631  		i.Debug(ctx, "ReadAll: unexpected miss on index conv read: %s", err)
   632  		return 0, nil, err
   633  	}
   634  
   635  	return iboxVers.InboxVersion, convs, nil
   636  }
   637  
   638  func (i *Inbox) GetConversation(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID) (res types.RemoteConversation, err Error) {
   639  	var ierr error
   640  	defer i.Trace(ctx, &ierr, fmt.Sprintf("GetConversation(%s,%s)", uid, convID))()
   641  	defer func() { ierr = i.castInternalError(err) }()
   642  	_, iboxRes, err := i.Read(ctx, uid, &chat1.GetInboxQuery{
   643  		ConvID: &convID,
   644  	})
   645  	if err != nil {
   646  		return res, err
   647  	}
   648  	if len(iboxRes) != 1 {
   649  		return res, MissError{}
   650  	}
   651  	return iboxRes[0], nil
   652  }
   653  
   654  func (i *Inbox) Read(ctx context.Context, uid gregor1.UID, query *chat1.GetInboxQuery) (vers chat1.InboxVers, res []types.RemoteConversation, err Error) {
   655  	var ierr error
   656  	defer i.Trace(ctx, &ierr, fmt.Sprintf("Read(%s)", uid))()
   657  	defer func() { ierr = i.castInternalError(err) }()
   658  	locks.Inbox.Lock()
   659  	defer locks.Inbox.Unlock()
   660  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
   661  
   662  	iboxVers, err := i.readDiskVersions(ctx, uid, true)
   663  	if err != nil {
   664  		if _, ok := err.(MissError); ok {
   665  			i.Debug(ctx, "Read: miss: no inbox versions found")
   666  		}
   667  		return 0, nil, err
   668  	}
   669  	var convs []types.RemoteConversation
   670  	if query != nil && (query.ConvID != nil || len(query.ConvIDs) > 0) {
   671  		convIDs := query.ConvIDs
   672  		if query.ConvID != nil {
   673  			convIDs = append(convIDs, *query.ConvID)
   674  		}
   675  		if convs, err = i.readConvs(ctx, uid, convIDs); err != nil {
   676  			return 0, nil, err
   677  		}
   678  	} else {
   679  		iboxIndex, err := i.readDiskIndex(ctx, uid, true)
   680  		if err != nil {
   681  			if _, ok := err.(MissError); ok {
   682  				i.Debug(ctx, "Read: miss: no inbox found")
   683  			}
   684  			return 0, nil, err
   685  		}
   686  
   687  		// Check to make sure query parameters have been seen before
   688  		if !i.queryExists(ctx, uid, iboxIndex, query) {
   689  			i.Debug(ctx, "Read: miss: query or pagination unknown")
   690  			return 0, nil, MissError{}
   691  		}
   692  
   693  		if convs, err = i.readConvs(ctx, uid, iboxIndex.ConversationIDs); err != nil {
   694  			i.Debug(ctx, "Read: unexpected miss on index read: %s", err)
   695  			return 0, nil, NewInternalError(ctx, i.DebugLabeler, "index out of sync with convs")
   696  		}
   697  	}
   698  
   699  	// Apply query and pagination
   700  	res = utils.ApplyInboxQuery(ctx, i.DebugLabeler, query, convs)
   701  
   702  	i.Debug(ctx, "Read: hit: version: %d", iboxVers.InboxVersion)
   703  	return iboxVers.InboxVersion, res, nil
   704  }
   705  
   706  func (i *Inbox) clearLocked(ctx context.Context, uid gregor1.UID) (err Error) {
   707  	var ierr error
   708  	defer i.Trace(ctx, &ierr, "clearLocked")()
   709  	defer func() { ierr = i.castInternalError(err) }()
   710  	var iboxIndex inboxDiskIndex
   711  	if iboxIndex, err = i.readDiskIndex(ctx, uid, true); err != nil {
   712  		i.Debug(ctx, "Clear: failed to read index: %s", err)
   713  	}
   714  	for _, convID := range iboxIndex.ConversationIDs {
   715  		if ierr := i.G().LocalChatDb.Delete(i.dbConvKey(uid, convID)); ierr != nil {
   716  			msg := fmt.Sprintf("error clearing conv: convID: %s err: %s", convID, ierr)
   717  			err = NewInternalError(ctx, i.DebugLabeler, msg)
   718  			i.Debug(ctx, msg)
   719  		}
   720  	}
   721  	if ierr := i.G().LocalChatDb.Delete(i.dbVersionsKey(uid)); ierr != nil {
   722  		msg := fmt.Sprintf("error clearing inbox versions: err: %s", ierr)
   723  		err = NewInternalError(ctx, i.DebugLabeler, msg)
   724  		i.Debug(ctx, msg)
   725  	}
   726  	if ierr := i.G().LocalChatDb.Delete(i.dbIndexKey(uid)); ierr != nil {
   727  		msg := fmt.Sprintf("error clearing inbox index: err: %s", ierr)
   728  		err = NewInternalError(ctx, i.DebugLabeler, msg)
   729  		i.Debug(ctx, msg)
   730  	}
   731  	inboxMemCache.Clear(uid)
   732  	return err
   733  }
   734  
   735  func (i *Inbox) Clear(ctx context.Context, uid gregor1.UID) (err Error) {
   736  	var ierr error
   737  	defer i.Trace(ctx, &ierr, "Clear")()
   738  	defer func() { ierr = i.castInternalError(err) }()
   739  	locks.Inbox.Lock()
   740  	defer locks.Inbox.Unlock()
   741  	return i.clearLocked(ctx, uid)
   742  }
   743  
   744  func (i *Inbox) ClearInMemory(ctx context.Context, uid gregor1.UID) (err Error) {
   745  	var ierr error
   746  	defer i.Trace(ctx, &ierr, "ClearInMemory")()
   747  	defer func() { ierr = i.castInternalError(err) }()
   748  	inboxMemCache.Clear(uid)
   749  	return nil
   750  }
   751  
   752  func (i *Inbox) handleVersion(ctx context.Context, ourvers chat1.InboxVers, updatevers chat1.InboxVers) (chat1.InboxVers, bool, Error) {
   753  	// Our version is at least as new as this update, let's not continue
   754  	if updatevers == 0 {
   755  		// Don't do anything to the version if we are just writing into ourselves, we'll
   756  		// get the correct version when Gregor bounces the update back at us
   757  		i.Debug(ctx, "handleVersion: received a self update: ours: %d update: %d", ourvers, updatevers)
   758  		return ourvers, true, nil
   759  	} else if ourvers >= updatevers {
   760  		i.Debug(ctx, "handleVersion: received an old update: ours: %d update: %d", ourvers, updatevers)
   761  		return ourvers, false, nil
   762  	} else if updatevers == ourvers+1 {
   763  		i.Debug(ctx, "handleVersion: received an incremental update: ours: %d update: %d", ourvers, updatevers)
   764  		return updatevers, true, nil
   765  	}
   766  
   767  	i.Debug(ctx, "handleVersion: received a non-incremental update: ours: %d update: %d",
   768  		ourvers, updatevers)
   769  
   770  	// The update is far ahead of what we have.
   771  	// Leave our state alone, but request a resync using a VersionMismatchError.
   772  	return ourvers, false, NewVersionMismatchError(ourvers, updatevers)
   773  }
   774  
   775  func (i *Inbox) NewConversation(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers,
   776  	conv chat1.Conversation) (err Error) {
   777  	var ierr error
   778  	defer i.Trace(ctx, &ierr, "NewConversation")()
   779  	defer func() { ierr = i.castInternalError(err) }()
   780  	locks.Inbox.Lock()
   781  	defer locks.Inbox.Unlock()
   782  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
   783  	layoutChanged := true
   784  	defer func() {
   785  		if layoutChanged {
   786  			i.layoutNotifier.UpdateLayout(ctx, chat1.InboxLayoutReselectMode_DEFAULT, "new conversation")
   787  		}
   788  	}()
   789  
   790  	i.Debug(ctx, "NewConversation: vers: %d convID: %s", vers, conv.GetConvID())
   791  	iboxVers, err := i.readDiskVersions(ctx, uid, true)
   792  	if err != nil {
   793  		if _, ok := err.(MissError); ok {
   794  			return nil
   795  		}
   796  		return err
   797  	}
   798  
   799  	// Check inbox versions, make sure it makes sense (clear otherwise)
   800  	var cont bool
   801  	if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont {
   802  		return err
   803  	}
   804  
   805  	// Do a pass to make sure we don't already know about this convo
   806  	_, err = i.readConv(ctx, uid, conv.GetConvID())
   807  	known := err == nil
   808  	if !known {
   809  		iboxIndex, err := i.readDiskIndex(ctx, uid, true)
   810  		if err != nil {
   811  			return err
   812  		}
   813  		// Find any conversations this guy might supersede and set supersededBy pointer
   814  		if len(conv.Metadata.Supersedes) > 0 {
   815  			for _, convID := range iboxIndex.ConversationIDs {
   816  				iconv, err := i.readConv(ctx, uid, convID)
   817  				if err != nil {
   818  					return err
   819  				}
   820  				if iconv.Conv.Metadata.FinalizeInfo == nil {
   821  					continue
   822  				}
   823  				for _, super := range conv.Metadata.Supersedes {
   824  					if iconv.GetConvID().Eq(super.ConversationID) {
   825  						i.Debug(ctx, "NewConversation: setting supersededBy: target: %s superseder: %s",
   826  							iconv.ConvIDStr, conv.GetConvID())
   827  						iconv.Conv.Metadata.SupersededBy = append(iconv.Conv.Metadata.SupersededBy, conv.Metadata)
   828  						iconv.Conv.Metadata.Version = vers.ToConvVers()
   829  						if err := i.writeConv(ctx, uid, iconv, false); err != nil {
   830  							return err
   831  						}
   832  					}
   833  				}
   834  			}
   835  		}
   836  		if err := i.writeConv(ctx, uid, utils.RemoteConv(conv), false); err != nil {
   837  			return err
   838  		}
   839  		// only chat convs for layout changed
   840  		layoutChanged = conv.GetTopicType() == chat1.TopicType_CHAT
   841  		iboxIndex.ConversationIDs = append(iboxIndex.ConversationIDs, conv.GetConvID())
   842  		if err := i.writeDiskIndex(ctx, uid, iboxIndex); err != nil {
   843  			return err
   844  		}
   845  	} else {
   846  		i.Debug(ctx, "NewConversation: skipping update, conversation exists in inbox")
   847  	}
   848  
   849  	// Write out to disk
   850  	iboxVers.InboxVersion = vers
   851  	return i.writeDiskVersions(ctx, uid, iboxVers)
   852  }
   853  
   854  // Return pointers into `convs` for the convs belonging to `teamID`.
   855  func (i *Inbox) getConvsForTeam(ctx context.Context, uid gregor1.UID, teamID keybase1.TeamID,
   856  	index inboxDiskIndex) (res []types.RemoteConversation) {
   857  	tlfID, err := chat1.TeamIDToTLFID(teamID)
   858  	if err != nil {
   859  		i.Debug(ctx, "getConvsForTeam: teamIDToTLFID failed: %v", err)
   860  		return nil
   861  	}
   862  	for _, convID := range index.ConversationIDs {
   863  		conv, err := i.readConv(ctx, uid, convID)
   864  		if err != nil {
   865  			i.Debug(ctx, "getConvsForTeam: failed to get conv: %s", convID)
   866  			continue
   867  		}
   868  		if conv.Conv.GetMembersType() == chat1.ConversationMembersType_TEAM &&
   869  			conv.Conv.Metadata.IdTriple.Tlfid.Eq(tlfID) {
   870  			res = append(res, conv)
   871  		}
   872  	}
   873  	return res
   874  }
   875  
   876  func (i *Inbox) promoteWriter(ctx context.Context, sender gregor1.UID, writers []gregor1.UID) []gregor1.UID {
   877  	res := make([]gregor1.UID, len(writers))
   878  	copy(res, writers)
   879  	for index, w := range writers {
   880  		if bytes.Equal(w.Bytes(), sender.Bytes()) {
   881  			res = append(res[:index], res[index+1:]...)
   882  			res = append([]gregor1.UID{sender}, res...)
   883  			return res
   884  		}
   885  	}
   886  
   887  	i.Debug(ctx, "promoteWriter: failed to promote sender, adding to front: sender: %s", sender)
   888  	res = append([]gregor1.UID{sender}, res...)
   889  	return res
   890  }
   891  
   892  func (i *Inbox) UpdateInboxVersion(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers) (err Error) {
   893  	var ierr error
   894  	defer i.Trace(ctx, &ierr, "UpdateInboxVersion")()
   895  	defer func() { ierr = i.castInternalError(err) }()
   896  	locks.Inbox.Lock()
   897  	defer locks.Inbox.Unlock()
   898  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
   899  	ibox, err := i.readDiskVersions(ctx, uid, true)
   900  	if err != nil {
   901  		if _, ok := err.(MissError); ok {
   902  			return nil
   903  		}
   904  		return err
   905  	}
   906  	var cont bool
   907  	if vers, cont, err = i.handleVersion(ctx, ibox.InboxVersion, vers); !cont {
   908  		return err
   909  	}
   910  	ibox.InboxVersion = vers
   911  	return i.writeDiskVersions(ctx, uid, ibox)
   912  }
   913  
   914  func (i *Inbox) getConv(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID) (res types.RemoteConversation, found bool, err Error) {
   915  	conv, err := i.readConv(ctx, uid, convID)
   916  	if err != nil {
   917  		if _, ok := err.(MissError); ok {
   918  			return res, false, nil
   919  		}
   920  		return res, false, err
   921  	}
   922  	return conv, true, nil
   923  }
   924  
   925  func (i *Inbox) IncrementLocalConvVersion(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID) (err Error) {
   926  	var ierr error
   927  	defer i.Trace(ctx, &ierr, "IncrementLocalConvVersion")()
   928  	defer func() { ierr = i.castInternalError(err) }()
   929  	locks.Inbox.Lock()
   930  	defer locks.Inbox.Unlock()
   931  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
   932  	conv, found, err := i.getConv(ctx, uid, convID)
   933  	if err != nil {
   934  		return err
   935  	}
   936  	if !found {
   937  		i.Debug(ctx, "IncrementLocalConvVersion: no conversation found: convID: %s", convID)
   938  		return nil
   939  	}
   940  	conv.Conv.Metadata.LocalVersion++
   941  	return i.writeConv(ctx, uid, conv, false)
   942  }
   943  
   944  func (i *Inbox) MarkLocalRead(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID,
   945  	msgID chat1.MessageID) (err Error) {
   946  	var ierr error
   947  	defer i.Trace(ctx, &ierr, "MarkLocalRead")()
   948  	defer func() { ierr = i.castInternalError(err) }()
   949  	locks.Inbox.Lock()
   950  	defer locks.Inbox.Unlock()
   951  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
   952  	conv, found, err := i.getConv(ctx, uid, convID)
   953  	if err != nil {
   954  		return err
   955  	}
   956  	if !found {
   957  		i.Debug(ctx, "MarkLocalRead: no conversation found: convID: %s", convID)
   958  		return nil
   959  	}
   960  	conv.LocalReadMsgID = msgID
   961  	return i.writeConv(ctx, uid, conv, false)
   962  }
   963  
   964  func (i *Inbox) Draft(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID,
   965  	text *string) (modified bool, err Error) {
   966  	locks.Inbox.Lock()
   967  	defer locks.Inbox.Unlock()
   968  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
   969  	conv, found, err := i.getConv(ctx, uid, convID)
   970  	if err != nil {
   971  		return false, err
   972  	}
   973  	if !found {
   974  		i.Debug(ctx, "Draft: no conversation found: convID: %s", convID)
   975  		return false, nil
   976  	}
   977  	if text == nil && conv.LocalDraft == nil {
   978  		// don't do anything if we are clearing
   979  		return false, nil
   980  	}
   981  	conv.LocalDraft = text
   982  	conv.Conv.Metadata.LocalVersion++
   983  	return true, i.writeConv(ctx, uid, conv, false)
   984  }
   985  
   986  func (i *Inbox) NewMessage(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers,
   987  	convID chat1.ConversationID, msg chat1.MessageBoxed, maxMsgs []chat1.MessageSummary) (err Error) {
   988  	var ierr error
   989  	defer i.Trace(ctx, &ierr, "NewMessage")()
   990  	defer func() { ierr = i.castInternalError(err) }()
   991  	locks.Inbox.Lock()
   992  	defer locks.Inbox.Unlock()
   993  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
   994  
   995  	i.Debug(ctx, "NewMessage: vers: %d convID: %s", vers, convID)
   996  	iboxVers, err := i.readDiskVersions(ctx, uid, true)
   997  	if err != nil {
   998  		if _, ok := err.(MissError); ok {
   999  			return nil
  1000  		}
  1001  		return err
  1002  	}
  1003  
  1004  	// Check inbox versions, make sure it makes sense (clear otherwise)
  1005  	var cont bool
  1006  	updateVers := vers
  1007  	if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont {
  1008  		return err
  1009  	}
  1010  
  1011  	// Find conversation
  1012  	conv, found, err := i.getConv(ctx, uid, convID)
  1013  	if err != nil {
  1014  		return err
  1015  	}
  1016  	if !found {
  1017  		i.Debug(ctx, "NewMessage: no conversation found: convID: %s", convID)
  1018  		// Write out to disk
  1019  		iboxVers.InboxVersion = vers
  1020  		return i.writeDiskVersions(ctx, uid, iboxVers)
  1021  	}
  1022  
  1023  	// Update conversation. Use given max messages if the param is non-empty, otherwise just fill
  1024  	// it in ourselves
  1025  	if len(maxMsgs) == 0 {
  1026  		// Check for a delete, if so just auto return a version mismatch to resync. The reason
  1027  		// is it is tricky to update max messages in this case. NOTE: this update must also not be a
  1028  		// self update, we only do this clear if the server transmitted the update to us.
  1029  		if updateVers > 0 {
  1030  			switch msg.GetMessageType() {
  1031  			case chat1.MessageType_DELETE, chat1.MessageType_DELETEHISTORY:
  1032  				i.Debug(ctx, "NewMessage: returning fake version mismatch error because of delete: vers: %d",
  1033  					vers)
  1034  				return NewVersionMismatchError(iboxVers.InboxVersion, vers)
  1035  			}
  1036  		}
  1037  		found := false
  1038  		typ := msg.GetMessageType()
  1039  		for mindex, maxmsg := range conv.Conv.MaxMsgSummaries {
  1040  			if maxmsg.GetMessageType() == typ {
  1041  				conv.Conv.MaxMsgSummaries[mindex] = msg.Summary()
  1042  				found = true
  1043  				break
  1044  			}
  1045  		}
  1046  		if !found {
  1047  			conv.Conv.MaxMsgSummaries = append(conv.Conv.MaxMsgSummaries, msg.Summary())
  1048  		}
  1049  	} else {
  1050  		i.Debug(ctx, "NewMessage: setting max messages from server payload")
  1051  		conv.Conv.MaxMsgSummaries = maxMsgs
  1052  	}
  1053  
  1054  	// If we are all up to date on the thread (and the sender is the current user),
  1055  	// mark this message as read too
  1056  	if conv.Conv.ReaderInfo.ReadMsgid == conv.Conv.ReaderInfo.MaxMsgid &&
  1057  		bytes.Equal(msg.ClientHeader.Sender.Bytes(), uid) {
  1058  		conv.Conv.ReaderInfo.ReadMsgid = msg.GetMessageID()
  1059  		conv.Conv.ReaderInfo.LastSendTime = msg.Ctime()
  1060  	}
  1061  	conv.Conv.ReaderInfo.MaxMsgid = msg.GetMessageID()
  1062  	conv.Conv.ReaderInfo.Mtime = gregor1.ToTime(time.Now())
  1063  	conv.Conv.Metadata.ActiveList = i.promoteWriter(ctx, msg.ClientHeader.Sender,
  1064  		conv.Conv.Metadata.ActiveList)
  1065  
  1066  	// If we are the sender, adjust the status.
  1067  	if bytes.Equal(msg.ClientHeader.Sender.Bytes(), uid) &&
  1068  		utils.GetConversationStatusBehavior(conv.Conv.Metadata.Status).SendingRemovesStatus {
  1069  		conv.Conv.Metadata.Status = chat1.ConversationStatus_UNFILED
  1070  	}
  1071  	// If we are a participant, adjust the status.
  1072  	if utils.GetConversationStatusBehavior(conv.Conv.Metadata.Status).ActivityRemovesStatus {
  1073  		conv.Conv.Metadata.Status = chat1.ConversationStatus_UNFILED
  1074  	}
  1075  	conv.Conv.Metadata.Version = vers.ToConvVers()
  1076  	if err := i.writeConv(ctx, uid, conv, false); err != nil {
  1077  		return err
  1078  	}
  1079  
  1080  	// if we have a conv at all, then we want to let any layout engine know about this
  1081  	// new message
  1082  	if conv.GetTopicType() == chat1.TopicType_CHAT {
  1083  		defer i.layoutNotifier.UpdateLayoutFromNewMessage(ctx, conv)
  1084  	}
  1085  	// Write out to disk
  1086  	iboxVers.InboxVersion = vers
  1087  	return i.writeDiskVersions(ctx, uid, iboxVers)
  1088  }
  1089  
  1090  func (i *Inbox) ReadMessage(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers,
  1091  	convID chat1.ConversationID, msgID chat1.MessageID) (err Error) {
  1092  	var ierr error
  1093  	defer i.Trace(ctx, &ierr, "ReadMessage")()
  1094  	defer func() { ierr = i.castInternalError(err) }()
  1095  	locks.Inbox.Lock()
  1096  	defer locks.Inbox.Unlock()
  1097  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
  1098  
  1099  	i.Debug(ctx, "ReadMessage: vers: %d convID: %s", vers, convID)
  1100  	iboxVers, err := i.readDiskVersions(ctx, uid, true)
  1101  	if err != nil {
  1102  		if _, ok := err.(MissError); ok {
  1103  			return nil
  1104  		}
  1105  		return err
  1106  	}
  1107  
  1108  	// Check inbox versions, make sure it makes sense (clear otherwise)
  1109  	var cont bool
  1110  	if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont {
  1111  		return err
  1112  	}
  1113  
  1114  	// Find conversation
  1115  	conv, found, err := i.getConv(ctx, uid, convID)
  1116  	if err != nil {
  1117  		return err
  1118  	}
  1119  	if !found {
  1120  		i.Debug(ctx, "ReadMessage: no conversation found: convID: %s", convID)
  1121  	} else {
  1122  		// Update conv
  1123  		i.Debug(ctx, "ReadMessage: updating mtime: readMsgID: %d msgID: %d", conv.Conv.ReaderInfo.ReadMsgid,
  1124  			msgID)
  1125  		conv.Conv.ReaderInfo.Mtime = gregor1.ToTime(time.Now())
  1126  		conv.Conv.ReaderInfo.ReadMsgid = msgID
  1127  		conv.Conv.Metadata.Version = vers.ToConvVers()
  1128  		if err := i.writeConv(ctx, uid, conv, false); err != nil {
  1129  			return err
  1130  		}
  1131  	}
  1132  
  1133  	// Write out to disk
  1134  	iboxVers.InboxVersion = vers
  1135  	return i.writeDiskVersions(ctx, uid, iboxVers)
  1136  }
  1137  
  1138  func (i *Inbox) SetStatus(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers,
  1139  	convID chat1.ConversationID, status chat1.ConversationStatus) (err Error) {
  1140  	var ierr error
  1141  	defer i.Trace(ctx, &ierr, "SetStatus")()
  1142  	defer func() { ierr = i.castInternalError(err) }()
  1143  	locks.Inbox.Lock()
  1144  	defer locks.Inbox.Unlock()
  1145  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
  1146  	defer i.layoutNotifier.UpdateLayout(ctx, chat1.InboxLayoutReselectMode_DEFAULT, "set status")
  1147  
  1148  	i.Debug(ctx, "SetStatus: vers: %d convID: %s", vers, convID)
  1149  	iboxVers, err := i.readDiskVersions(ctx, uid, true)
  1150  	if err != nil {
  1151  		if _, ok := err.(MissError); !ok {
  1152  			return nil
  1153  		}
  1154  		return err
  1155  	}
  1156  
  1157  	// Check inbox versions, make sure it makes sense (clear otherwise)
  1158  	var cont bool
  1159  	if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont {
  1160  		return err
  1161  	}
  1162  
  1163  	// Find conversation
  1164  	conv, found, err := i.getConv(ctx, uid, convID)
  1165  	if err != nil {
  1166  		return err
  1167  	}
  1168  	if !found {
  1169  		i.Debug(ctx, "SetStatus: no conversation found: convID: %s", convID)
  1170  	} else {
  1171  		conv.Conv.ReaderInfo.Mtime = gregor1.ToTime(time.Now())
  1172  		conv.Conv.Metadata.Status = status
  1173  		conv.Conv.Metadata.Version = vers.ToConvVers()
  1174  		if err := i.writeConv(ctx, uid, conv, false); err != nil {
  1175  			return err
  1176  		}
  1177  	}
  1178  
  1179  	// Write out to disk
  1180  	iboxVers.InboxVersion = vers
  1181  	return i.writeDiskVersions(ctx, uid, iboxVers)
  1182  }
  1183  
  1184  func (i *Inbox) SetAppNotificationSettings(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers,
  1185  	convID chat1.ConversationID, settings chat1.ConversationNotificationInfo) (err Error) {
  1186  	var ierr error
  1187  	defer i.Trace(ctx, &ierr, "SetAppNotificationSettings")()
  1188  	defer func() { ierr = i.castInternalError(err) }()
  1189  	locks.Inbox.Lock()
  1190  	defer locks.Inbox.Unlock()
  1191  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
  1192  
  1193  	i.Debug(ctx, "SetAppNotificationSettings: vers: %d convID: %s", vers, convID)
  1194  	iboxVers, err := i.readDiskVersions(ctx, uid, true)
  1195  	if err != nil {
  1196  		if _, ok := err.(MissError); !ok {
  1197  			return nil
  1198  		}
  1199  		return err
  1200  	}
  1201  	// Check inbox versions, make sure it makes sense (clear otherwise)
  1202  	var cont bool
  1203  	if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont {
  1204  		return err
  1205  	}
  1206  
  1207  	// Find conversation
  1208  	conv, found, err := i.getConv(ctx, uid, convID)
  1209  	if err != nil {
  1210  		return err
  1211  	}
  1212  	if !found {
  1213  		i.Debug(ctx, "SetAppNotificationSettings: no conversation found: convID: %s", convID)
  1214  	} else {
  1215  		for apptype, kindMap := range settings.Settings {
  1216  			for kind, enabled := range kindMap {
  1217  				conv.Conv.Notifications.Settings[apptype][kind] = enabled
  1218  			}
  1219  		}
  1220  		conv.Conv.Notifications.ChannelWide = settings.ChannelWide
  1221  		conv.Conv.Metadata.Version = vers.ToConvVers()
  1222  		if err := i.writeConv(ctx, uid, conv, false); err != nil {
  1223  			return err
  1224  		}
  1225  	}
  1226  
  1227  	// Write out to disk
  1228  	iboxVers.InboxVersion = vers
  1229  	return i.writeDiskVersions(ctx, uid, iboxVers)
  1230  }
  1231  
  1232  // Mark the expunge on the stored inbox
  1233  // The inbox Expunge tag is kept up to date for retention but not for delete-history.
  1234  // Does not delete any messages. Relies on separate server mechanism to delete clear max messages.
  1235  func (i *Inbox) Expunge(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers,
  1236  	convID chat1.ConversationID, expunge chat1.Expunge, maxMsgs []chat1.MessageSummary) (err Error) {
  1237  	var ierr error
  1238  	defer i.Trace(ctx, &ierr, "Expunge")()
  1239  	defer func() { ierr = i.castInternalError(err) }()
  1240  	locks.Inbox.Lock()
  1241  	defer locks.Inbox.Unlock()
  1242  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
  1243  
  1244  	i.Debug(ctx, "Expunge: vers: %d convID: %s", vers, convID)
  1245  	iboxVers, err := i.readDiskVersions(ctx, uid, true)
  1246  	if err != nil {
  1247  		if _, ok := err.(MissError); !ok {
  1248  			return nil
  1249  		}
  1250  		return err
  1251  	}
  1252  	// Check inbox versions, make sure it makes sense (clear otherwise)
  1253  	var cont bool
  1254  	if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont {
  1255  		return err
  1256  	}
  1257  
  1258  	// Find conversation
  1259  	conv, found, err := i.getConv(ctx, uid, convID)
  1260  	if err != nil {
  1261  		return err
  1262  	}
  1263  	if !found {
  1264  		i.Debug(ctx, "Expunge: no conversation found: convID: %s", convID)
  1265  	} else {
  1266  		conv.Conv.Expunge = expunge
  1267  		conv.Conv.Metadata.Version = vers.ToConvVers()
  1268  
  1269  		if len(maxMsgs) == 0 {
  1270  			// Expunge notifications should always come with max msgs.
  1271  			i.Debug(ctx,
  1272  				"Expunge: returning fake version mismatch error because of missing maxMsgs: vers: %d", vers)
  1273  			return NewVersionMismatchError(iboxVers.InboxVersion, vers)
  1274  		}
  1275  
  1276  		i.Debug(ctx, "Expunge: setting max messages from server payload")
  1277  		conv.Conv.MaxMsgSummaries = maxMsgs
  1278  		if err := i.writeConv(ctx, uid, conv, false); err != nil {
  1279  			return err
  1280  		}
  1281  	}
  1282  
  1283  	// Write out to disk
  1284  	iboxVers.InboxVersion = vers
  1285  	return i.writeDiskVersions(ctx, uid, iboxVers)
  1286  }
  1287  
  1288  func (i *Inbox) SubteamRename(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers,
  1289  	convIDs []chat1.ConversationID) (err Error) {
  1290  	var ierr error
  1291  	defer i.Trace(ctx, &ierr, "SubteamRename")()
  1292  	defer func() { ierr = i.castInternalError(err) }()
  1293  	locks.Inbox.Lock()
  1294  	defer locks.Inbox.Unlock()
  1295  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
  1296  	var layoutConvs []types.RemoteConversation
  1297  	defer func() {
  1298  		i.layoutNotifier.UpdateLayoutFromSubteamRename(ctx, layoutConvs)
  1299  	}()
  1300  
  1301  	i.Debug(ctx, "SubteamRename: vers: %d convIDs: %d", vers, len(convIDs))
  1302  	iboxVers, err := i.readDiskVersions(ctx, uid, true)
  1303  	if err != nil {
  1304  		if _, ok := err.(MissError); !ok {
  1305  			return nil
  1306  		}
  1307  		return err
  1308  	}
  1309  	// Check inbox versions, make sure it makes sense (clear otherwise)
  1310  	var cont bool
  1311  	if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont {
  1312  		return err
  1313  	}
  1314  
  1315  	// Update convs
  1316  	for _, convID := range convIDs {
  1317  		conv, found, err := i.getConv(ctx, uid, convID)
  1318  		if err != nil {
  1319  			return err
  1320  		}
  1321  		if !found {
  1322  			i.Debug(ctx, "SubteamRename: no conversation found: convID: %s", convID)
  1323  			continue
  1324  		}
  1325  		layoutConvs = append(layoutConvs, conv)
  1326  		conv.Conv.Metadata.Version = vers.ToConvVers()
  1327  		if err := i.writeConv(ctx, uid, conv, false); err != nil {
  1328  			return err
  1329  		}
  1330  	}
  1331  
  1332  	// Write out to disk
  1333  	iboxVers.InboxVersion = vers
  1334  	return i.writeDiskVersions(ctx, uid, iboxVers)
  1335  }
  1336  
  1337  func (i *Inbox) SetConvRetention(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers,
  1338  	convID chat1.ConversationID, policy chat1.RetentionPolicy) (err Error) {
  1339  	var ierr error
  1340  	defer i.Trace(ctx, &ierr, "SetConvRetention")()
  1341  	defer func() { ierr = i.castInternalError(err) }()
  1342  	locks.Inbox.Lock()
  1343  	defer locks.Inbox.Unlock()
  1344  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
  1345  
  1346  	i.Debug(ctx, "SetConvRetention: vers: %d convID: %s", vers, convID)
  1347  	iboxVers, err := i.readDiskVersions(ctx, uid, true)
  1348  	if err != nil {
  1349  		if _, ok := err.(MissError); !ok {
  1350  			return nil
  1351  		}
  1352  		return err
  1353  	}
  1354  	// Check inbox versions, make sure it makes sense (clear otherwise)
  1355  	var cont bool
  1356  	if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont {
  1357  		return err
  1358  	}
  1359  
  1360  	// Find conversation
  1361  	conv, found, err := i.getConv(ctx, uid, convID)
  1362  	if err != nil {
  1363  		return err
  1364  	}
  1365  	if !found {
  1366  		i.Debug(ctx, "SetConvRetention: no conversation found: convID: %s", convID)
  1367  	} else {
  1368  		conv.Conv.ConvRetention = &policy
  1369  		conv.Conv.Metadata.Version = vers.ToConvVers()
  1370  		if err := i.writeConv(ctx, uid, conv, false); err != nil {
  1371  			return err
  1372  		}
  1373  	}
  1374  
  1375  	// Write out to disk
  1376  	iboxVers.InboxVersion = vers
  1377  	return i.writeDiskVersions(ctx, uid, iboxVers)
  1378  }
  1379  
  1380  // Update any local conversations with this team ID.
  1381  func (i *Inbox) SetTeamRetention(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers,
  1382  	teamID keybase1.TeamID, policy chat1.RetentionPolicy) (res []chat1.ConversationID, err Error) {
  1383  	var ierr error
  1384  	defer i.Trace(ctx, &ierr, "SetTeamRetention")()
  1385  	defer func() { ierr = i.castInternalError(err) }()
  1386  	locks.Inbox.Lock()
  1387  	defer locks.Inbox.Unlock()
  1388  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
  1389  
  1390  	i.Debug(ctx, "SetTeamRetention: vers: %d teamID: %s", vers, teamID)
  1391  	iboxVers, err := i.readDiskVersions(ctx, uid, true)
  1392  	if err != nil {
  1393  		if _, ok := err.(MissError); !ok {
  1394  			return res, nil
  1395  		}
  1396  		return res, err
  1397  	}
  1398  	iboxIndex, err := i.readDiskIndex(ctx, uid, true)
  1399  	if err != nil {
  1400  		if _, ok := err.(MissError); !ok {
  1401  			return res, nil
  1402  		}
  1403  		return res, err
  1404  	}
  1405  	// Check inbox versions, make sure it makes sense (clear otherwise)
  1406  	var cont bool
  1407  	if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont {
  1408  		return res, err
  1409  	}
  1410  
  1411  	// Update conversations
  1412  	convs := i.getConvsForTeam(ctx, uid, teamID, iboxIndex)
  1413  	for _, conv := range convs {
  1414  		conv.Conv.TeamRetention = &policy
  1415  		conv.Conv.Metadata.Version = vers.ToConvVers()
  1416  		if err := i.writeConv(ctx, uid, conv, false); err != nil {
  1417  			return res, err
  1418  		}
  1419  		res = append(res, conv.Conv.GetConvID())
  1420  	}
  1421  
  1422  	// Write out to disk
  1423  	iboxVers.InboxVersion = vers
  1424  	err = i.writeDiskVersions(ctx, uid, iboxVers)
  1425  	return res, err
  1426  }
  1427  
  1428  func (i *Inbox) SetConvSettings(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers,
  1429  	convID chat1.ConversationID, convSettings *chat1.ConversationSettings) (err Error) {
  1430  	var ierr error
  1431  	defer i.Trace(ctx, &ierr, "SetConvSettings")()
  1432  	defer func() { ierr = i.castInternalError(err) }()
  1433  	locks.Inbox.Lock()
  1434  	defer locks.Inbox.Unlock()
  1435  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
  1436  
  1437  	i.Debug(ctx, "SetConvSettings: vers: %d convID: %s", vers, convID)
  1438  	iboxVers, err := i.readDiskVersions(ctx, uid, true)
  1439  	if err != nil {
  1440  		if _, ok := err.(MissError); !ok {
  1441  			return nil
  1442  		}
  1443  		return err
  1444  	}
  1445  	// Check inbox versions, make sure it makes sense (clear otherwise)
  1446  	var cont bool
  1447  	if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont {
  1448  		return err
  1449  	}
  1450  
  1451  	// Find conversation
  1452  	conv, found, err := i.getConv(ctx, uid, convID)
  1453  	if !found {
  1454  		i.Debug(ctx, "SetConvSettings: no conversation found: convID: %s", convID)
  1455  	} else {
  1456  		conv.Conv.ConvSettings = convSettings
  1457  		conv.Conv.Metadata.Version = vers.ToConvVers()
  1458  		if err := i.writeConv(ctx, uid, conv, false); err != nil {
  1459  			return err
  1460  		}
  1461  	}
  1462  
  1463  	// Write out to disk
  1464  	iboxVers.InboxVersion = vers
  1465  	return i.writeDiskVersions(ctx, uid, iboxVers)
  1466  }
  1467  
  1468  func (i *Inbox) UpgradeKBFSToImpteam(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers,
  1469  	convID chat1.ConversationID) (err Error) {
  1470  	var ierr error
  1471  	defer i.Trace(ctx, &ierr, "UpgradeKBFSToImpteam")()
  1472  	defer func() { ierr = i.castInternalError(err) }()
  1473  	locks.Inbox.Lock()
  1474  	defer locks.Inbox.Unlock()
  1475  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
  1476  
  1477  	i.Debug(ctx, "UpgradeKBFSToImpteam: vers: %d convID: %s", vers, convID)
  1478  	iboxVers, err := i.readDiskVersions(ctx, uid, true)
  1479  	if err != nil {
  1480  		if _, ok := err.(MissError); !ok {
  1481  			return nil
  1482  		}
  1483  		return err
  1484  	}
  1485  	// Check inbox versions, make sure it makes sense (clear otherwise)
  1486  	var cont bool
  1487  	if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont {
  1488  		return err
  1489  	}
  1490  
  1491  	// Find conversation
  1492  	conv, found, err := i.getConv(ctx, uid, convID)
  1493  	if !found {
  1494  		i.Debug(ctx, "UpgradeKBFSToImpteam: no conversation found: convID: %s", convID)
  1495  	} else {
  1496  		conv.Conv.Metadata.MembersType = chat1.ConversationMembersType_IMPTEAMUPGRADE
  1497  		if err := i.writeConv(ctx, uid, conv, false); err != nil {
  1498  			return err
  1499  		}
  1500  	}
  1501  
  1502  	// Write out to disk
  1503  	iboxVers.InboxVersion = vers
  1504  	return i.writeDiskVersions(ctx, uid, iboxVers)
  1505  }
  1506  
  1507  func (i *Inbox) TeamTypeChanged(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers,
  1508  	convID chat1.ConversationID, teamType chat1.TeamType, notifInfo *chat1.ConversationNotificationInfo) (err Error) {
  1509  	var ierr error
  1510  	defer i.Trace(ctx, &ierr, "TeamTypeChanged")()
  1511  	defer func() { ierr = i.castInternalError(err) }()
  1512  	locks.Inbox.Lock()
  1513  	defer locks.Inbox.Unlock()
  1514  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
  1515  	defer i.layoutNotifier.UpdateLayout(ctx, chat1.InboxLayoutReselectMode_DEFAULT, "team type")
  1516  
  1517  	i.Debug(ctx, "TeamTypeChanged: vers: %d convID: %s typ: %v", vers, convID, teamType)
  1518  	iboxVers, err := i.readDiskVersions(ctx, uid, true)
  1519  	if err != nil {
  1520  		if _, ok := err.(MissError); !ok {
  1521  			return nil
  1522  		}
  1523  		return err
  1524  	}
  1525  	// Check inbox versions, make sure it makes sense (clear otherwise)
  1526  	var cont bool
  1527  	if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont {
  1528  		return err
  1529  	}
  1530  
  1531  	// Find conversation
  1532  	conv, found, err := i.getConv(ctx, uid, convID)
  1533  	if !found {
  1534  		i.Debug(ctx, "TeamTypeChanged: no conversation found: convID: %s", convID)
  1535  	} else {
  1536  		conv.Conv.Notifications = notifInfo
  1537  		conv.Conv.Metadata.TeamType = teamType
  1538  		conv.Conv.Metadata.Version = vers.ToConvVers()
  1539  		if err := i.writeConv(ctx, uid, conv, false); err != nil {
  1540  			return err
  1541  		}
  1542  	}
  1543  
  1544  	// Write out to disk
  1545  	iboxVers.InboxVersion = vers
  1546  	return i.writeDiskVersions(ctx, uid, iboxVers)
  1547  }
  1548  
  1549  func (i *Inbox) TlfFinalize(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers,
  1550  	convIDs []chat1.ConversationID, finalizeInfo chat1.ConversationFinalizeInfo) (err Error) {
  1551  	var ierr error
  1552  	defer i.Trace(ctx, &ierr, "TlfFinalize")()
  1553  	defer func() { ierr = i.castInternalError(err) }()
  1554  	locks.Inbox.Lock()
  1555  	defer locks.Inbox.Unlock()
  1556  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
  1557  
  1558  	i.Debug(ctx, "TlfFinalize: vers: %d convIDs: %v finalizeInfo: %v", vers, convIDs, finalizeInfo)
  1559  	iboxVers, err := i.readDiskVersions(ctx, uid, true)
  1560  	if err != nil {
  1561  		if _, ok := err.(MissError); ok {
  1562  			return nil
  1563  		}
  1564  		return err
  1565  	}
  1566  
  1567  	// Check inbox versions, make sure it makes sense (clear otherwise)
  1568  	var cont bool
  1569  	if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont {
  1570  		return err
  1571  	}
  1572  
  1573  	for _, convID := range convIDs {
  1574  		// Find conversation
  1575  		conv, found, err := i.getConv(ctx, uid, convID)
  1576  		if err != nil {
  1577  			return err
  1578  		}
  1579  		if !found {
  1580  			i.Debug(ctx, "TlfFinalize: no conversation found: convID: %s", convID)
  1581  			continue
  1582  		}
  1583  		conv.Conv.Metadata.FinalizeInfo = &finalizeInfo
  1584  		conv.Conv.Metadata.Version = vers.ToConvVers()
  1585  		if err := i.writeConv(ctx, uid, conv, false); err != nil {
  1586  			return err
  1587  		}
  1588  	}
  1589  
  1590  	// Write out to disk
  1591  	iboxVers.InboxVersion = vers
  1592  	return i.writeDiskVersions(ctx, uid, iboxVers)
  1593  }
  1594  
  1595  func (i *Inbox) Version(ctx context.Context, uid gregor1.UID) (vers chat1.InboxVers, err Error) {
  1596  	var ierr error
  1597  	defer i.Trace(ctx, &ierr, "Version")()
  1598  	defer func() { ierr = i.castInternalError(err) }()
  1599  	locks.Inbox.Lock()
  1600  	defer locks.Inbox.Unlock()
  1601  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
  1602  	ibox, err := i.readDiskVersions(ctx, uid, true)
  1603  	if err != nil {
  1604  		if _, ok := err.(MissError); ok {
  1605  			return 0, nil
  1606  		}
  1607  		return 0, err
  1608  	}
  1609  	return ibox.InboxVersion, nil
  1610  }
  1611  
  1612  func (i *Inbox) ServerVersion(ctx context.Context, uid gregor1.UID) (vers int, err Error) {
  1613  	var ierr error
  1614  	defer i.Trace(ctx, &ierr, "ServerVersion")()
  1615  	defer func() { ierr = i.castInternalError(err) }()
  1616  	locks.Inbox.Lock()
  1617  	defer locks.Inbox.Unlock()
  1618  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
  1619  	ibox, err := i.readDiskVersions(ctx, uid, true)
  1620  	if err != nil {
  1621  		if _, ok := err.(MissError); ok {
  1622  			return 0, nil
  1623  		}
  1624  		return 0, err
  1625  	}
  1626  	vers = ibox.ServerVersion
  1627  	return vers, nil
  1628  }
  1629  
  1630  func (i *Inbox) topicNameChanged(ctx context.Context, oldConv, newConv chat1.Conversation) bool {
  1631  	oldMsg, oldErr := oldConv.GetMaxMessage(chat1.MessageType_METADATA)
  1632  	newMsg, newErr := newConv.GetMaxMessage(chat1.MessageType_METADATA)
  1633  	if oldErr != nil && newErr != nil {
  1634  		return false
  1635  	}
  1636  	if oldErr != newErr {
  1637  		return true
  1638  	}
  1639  	return oldMsg.GetMessageID() != newMsg.GetMessageID()
  1640  }
  1641  
  1642  func (i *Inbox) Sync(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, convs []chat1.Conversation) (res types.InboxSyncRes, err Error) {
  1643  	var ierr error
  1644  	defer i.Trace(ctx, &ierr, "Sync")()
  1645  	defer func() { ierr = i.castInternalError(err) }()
  1646  	locks.Inbox.Lock()
  1647  	defer locks.Inbox.Unlock()
  1648  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
  1649  	defer i.layoutNotifier.UpdateLayout(ctx, chat1.InboxLayoutReselectMode_DEFAULT, "sync")
  1650  
  1651  	iboxVers, err := i.readDiskVersions(ctx, uid, true)
  1652  	if err != nil {
  1653  		// Return MissError, since it should be unexpected if are calling this
  1654  		return res, err
  1655  	}
  1656  	iboxIndex, err := i.readDiskIndex(ctx, uid, true)
  1657  	if err != nil {
  1658  		// Return MissError, since it should be unexpected if are calling this
  1659  		return res, err
  1660  	}
  1661  
  1662  	// Sync inbox with new conversations
  1663  	oldVers := iboxVers.InboxVersion
  1664  	iboxVers.InboxVersion = vers
  1665  	convMap := make(map[chat1.ConvIDStr]chat1.Conversation)
  1666  	for _, conv := range convs {
  1667  		convMap[conv.GetConvID().ConvIDStr()] = conv
  1668  	}
  1669  	for _, convID := range iboxIndex.ConversationIDs {
  1670  		if newConv, ok := convMap[convID.ConvIDStr()]; ok {
  1671  			oldConv, err := i.readConv(ctx, uid, convID)
  1672  			if err != nil {
  1673  				if _, ok := err.(MissError); ok {
  1674  					// just keep going if we don't have it
  1675  					continue
  1676  				}
  1677  				return res, err
  1678  			}
  1679  			if oldConv.Conv.Metadata.TeamType != newConv.Metadata.TeamType {
  1680  				// Changing the team type might be hard for clients of the inbox system to process,
  1681  				// so call it out so they can know a hard update happened here.
  1682  				res.TeamTypeChanged = true
  1683  			}
  1684  			if oldConv.Conv.Metadata.MembersType != newConv.Metadata.MembersType {
  1685  				res.MembersTypeChanged = append(res.MembersTypeChanged,
  1686  					oldConv.GetConvID())
  1687  			}
  1688  			if oldConv.Conv.Expunge != newConv.Expunge {
  1689  				// The earliest point in non-deleted history has moved up.
  1690  				// Point it out so that convsource can get updated.
  1691  				res.Expunges = append(res.Expunges, types.InboxSyncResExpunge{
  1692  					ConvID:  newConv.Metadata.ConversationID,
  1693  					Expunge: newConv.Expunge,
  1694  				})
  1695  			}
  1696  			if i.topicNameChanged(ctx, oldConv.Conv, newConv) {
  1697  				res.TopicNameChanged = append(res.TopicNameChanged, newConv.GetConvID())
  1698  			}
  1699  			delete(convMap, oldConv.ConvIDStr)
  1700  			oldConv.Conv = newConv
  1701  			if err := i.writeConv(ctx, uid, oldConv, false); err != nil {
  1702  				return res, err
  1703  			}
  1704  		}
  1705  	}
  1706  	i.Debug(ctx, "Sync: adding %d new conversations", len(convMap))
  1707  	for _, conv := range convMap {
  1708  		if err := i.writeConv(ctx, uid, utils.RemoteConv(conv), false); err != nil {
  1709  			return res, err
  1710  		}
  1711  		iboxIndex.ConversationIDs = append(iboxIndex.ConversationIDs, conv.GetConvID())
  1712  	}
  1713  	if err = i.writeDiskIndex(ctx, uid, iboxIndex); err != nil {
  1714  		return res, err
  1715  	}
  1716  	i.Debug(ctx, "Sync: old vers: %v new vers: %v convs: %d", oldVers, iboxVers.InboxVersion, len(convs))
  1717  	if err = i.writeDiskVersions(ctx, uid, iboxVers); err != nil {
  1718  		return res, err
  1719  	}
  1720  
  1721  	// Filter the conversations for the result
  1722  	res.FilteredConvs = utils.ApplyInboxQuery(ctx, i.DebugLabeler, &chat1.GetInboxQuery{
  1723  		ConvIDs: utils.PluckConvIDs(convs),
  1724  	}, utils.RemoteConvs(convs))
  1725  
  1726  	return res, nil
  1727  }
  1728  
  1729  func (i *Inbox) MembershipUpdate(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers,
  1730  	userJoined []chat1.Conversation, userRemoved []chat1.ConversationMember,
  1731  	othersJoined []chat1.ConversationMember, othersRemoved []chat1.ConversationMember,
  1732  	userReset []chat1.ConversationMember, othersReset []chat1.ConversationMember,
  1733  	teamMemberRoleUpdate *chat1.TeamMemberRoleUpdate) (roleUpdates []chat1.ConversationID, err Error) {
  1734  	var ierr error
  1735  	defer i.Trace(ctx, &ierr, "MembershipUpdate")()
  1736  	defer func() { ierr = i.castInternalError(err) }()
  1737  	locks.Inbox.Lock()
  1738  	defer locks.Inbox.Unlock()
  1739  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
  1740  	layoutChanged := false
  1741  	defer func() {
  1742  		if layoutChanged {
  1743  			i.layoutNotifier.UpdateLayout(ctx, chat1.InboxLayoutReselectMode_DEFAULT, "membership")
  1744  		}
  1745  	}()
  1746  
  1747  	i.Debug(ctx, "MembershipUpdate: updating userJoined: %d userRemoved: %d othersJoined: %d othersRemoved: %d, teamMemberRoleUpdate: %+v",
  1748  		len(userJoined), len(userRemoved), len(othersJoined), len(othersRemoved), teamMemberRoleUpdate)
  1749  	iboxVers, err := i.readDiskVersions(ctx, uid, true)
  1750  	if err != nil {
  1751  		if _, ok := err.(MissError); ok {
  1752  			return nil, nil
  1753  		}
  1754  		return nil, err
  1755  	}
  1756  	iboxIndex, err := i.readDiskIndex(ctx, uid, true)
  1757  	if err != nil {
  1758  		if _, ok := err.(MissError); ok {
  1759  			return nil, nil
  1760  		}
  1761  		return nil, err
  1762  	}
  1763  	// Check inbox versions, make sure it makes sense (clear otherwise)
  1764  	var cont bool
  1765  	if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont {
  1766  		return nil, err
  1767  	}
  1768  
  1769  	// Process our own changes
  1770  	var ujids []chat1.ConversationID
  1771  	for _, uj := range userJoined {
  1772  		i.Debug(ctx, "MembershipUpdate: joined conv: %s", uj.GetConvID())
  1773  		conv := utils.RemoteConv(uj)
  1774  		if err := i.writeConv(ctx, uid, conv, true); err != nil {
  1775  			return nil, err
  1776  		}
  1777  		ujids = append(ujids, conv.GetConvID())
  1778  		layoutChanged = layoutChanged || uj.GetTopicType() == chat1.TopicType_CHAT
  1779  	}
  1780  	iboxIndex.mergeConvs(ujids)
  1781  	convIDs := iboxIndex.ConversationIDs
  1782  	removedMap := make(map[chat1.ConvIDStr]bool)
  1783  	for _, r := range userRemoved {
  1784  		i.Debug(ctx, "MembershipUpdate: removing user from: %s", r)
  1785  		removedMap[r.ConvID.ConvIDStr()] = true
  1786  		layoutChanged = layoutChanged || r.TopicType == chat1.TopicType_CHAT
  1787  	}
  1788  	resetMap := make(map[chat1.ConvIDStr]bool)
  1789  	for _, r := range userReset {
  1790  		i.Debug(ctx, "MembershipUpdate: user reset in: %s", r)
  1791  		resetMap[r.ConvID.ConvIDStr()] = true
  1792  	}
  1793  	iboxIndex.ConversationIDs = nil
  1794  	for _, convID := range convIDs {
  1795  		dirty := false
  1796  		conv, err := i.readConv(ctx, uid, convID)
  1797  		if err != nil {
  1798  			return nil, err
  1799  		}
  1800  		if teamMemberRoleUpdate != nil && conv.Conv.Metadata.IdTriple.Tlfid.Eq(teamMemberRoleUpdate.TlfID) {
  1801  			conv.Conv.ReaderInfo.UntrustedTeamRole = teamMemberRoleUpdate.Role
  1802  			conv.Conv.Metadata.LocalVersion++
  1803  			roleUpdates = append(roleUpdates, conv.GetConvID())
  1804  			dirty = true
  1805  		}
  1806  		if removedMap[conv.ConvIDStr] {
  1807  			conv.Conv.ReaderInfo.Status = chat1.ConversationMemberStatus_LEFT
  1808  			conv.Conv.Metadata.Version = vers.ToConvVers()
  1809  			newAllList := make([]gregor1.UID, 0, len(conv.Conv.Metadata.AllList))
  1810  			for _, u := range conv.Conv.Metadata.AllList {
  1811  				if !u.Eq(uid) {
  1812  					newAllList = append(newAllList, u)
  1813  				}
  1814  			}
  1815  			switch conv.GetMembersType() {
  1816  			case chat1.ConversationMembersType_TEAM:
  1817  			default:
  1818  				conv.Conv.Metadata.AllList = newAllList
  1819  			}
  1820  			dirty = true
  1821  		} else if resetMap[conv.ConvIDStr] {
  1822  			conv.Conv.ReaderInfo.Status = chat1.ConversationMemberStatus_RESET
  1823  			conv.Conv.Metadata.Version = vers.ToConvVers()
  1824  			switch conv.GetMembersType() {
  1825  			case chat1.ConversationMembersType_TEAM:
  1826  				// do nothing
  1827  			default:
  1828  				// Double check this user isn't already in here
  1829  				exists := false
  1830  				for _, u := range conv.Conv.Metadata.ResetList {
  1831  					if u.Eq(uid) {
  1832  						exists = true
  1833  						break
  1834  					}
  1835  				}
  1836  				if !exists {
  1837  					conv.Conv.Metadata.ResetList = append(conv.Conv.Metadata.ResetList, uid)
  1838  				}
  1839  			}
  1840  			dirty = true
  1841  		}
  1842  		if dirty {
  1843  			if err := i.writeConv(ctx, uid, conv, false); err != nil {
  1844  				return nil, err
  1845  			}
  1846  		}
  1847  		iboxIndex.ConversationIDs = append(iboxIndex.ConversationIDs, convID)
  1848  	}
  1849  
  1850  	// Update all lists with other people joining and leaving
  1851  	for _, oj := range othersJoined {
  1852  		cp, err := i.readConv(ctx, uid, oj.ConvID)
  1853  		if err != nil {
  1854  			continue
  1855  		}
  1856  		// Check reset list for this UID, if we find it remove it instead of adding to all list
  1857  		isReset := false
  1858  		var resetIndex int
  1859  		var r gregor1.UID
  1860  		for resetIndex, r = range cp.Conv.Metadata.ResetList {
  1861  			if r.Eq(oj.Uid) {
  1862  				isReset = true
  1863  				break
  1864  			}
  1865  		}
  1866  		if isReset {
  1867  			switch cp.Conv.GetMembersType() {
  1868  			case chat1.ConversationMembersType_TEAM:
  1869  			default:
  1870  				cp.Conv.Metadata.ResetList = append(cp.Conv.Metadata.ResetList[:resetIndex],
  1871  					cp.Conv.Metadata.ResetList[resetIndex+1:]...)
  1872  			}
  1873  		} else {
  1874  			// Double check this user isn't already in here
  1875  			exists := false
  1876  			for _, u := range cp.Conv.Metadata.AllList {
  1877  				if u.Eq(oj.Uid) {
  1878  					exists = true
  1879  					break
  1880  				}
  1881  			}
  1882  			if !exists {
  1883  				switch cp.Conv.GetMembersType() {
  1884  				case chat1.ConversationMembersType_TEAM:
  1885  				default:
  1886  					cp.Conv.Metadata.AllList = append(cp.Conv.Metadata.AllList, oj.Uid)
  1887  				}
  1888  			}
  1889  		}
  1890  		cp.Conv.Metadata.Version = vers.ToConvVers()
  1891  		if err := i.writeConv(ctx, uid, cp, false); err != nil {
  1892  			return nil, err
  1893  		}
  1894  	}
  1895  	for _, or := range othersRemoved {
  1896  		cp, err := i.readConv(ctx, uid, or.ConvID)
  1897  		if err != nil {
  1898  			continue
  1899  		}
  1900  		newAllList := make([]gregor1.UID, 0, len(cp.Conv.Metadata.AllList))
  1901  		for _, u := range cp.Conv.Metadata.AllList {
  1902  			if !u.Eq(or.Uid) {
  1903  				newAllList = append(newAllList, u)
  1904  			}
  1905  		}
  1906  		cp.Conv.Metadata.AllList = newAllList
  1907  		cp.Conv.Metadata.Version = vers.ToConvVers()
  1908  		if err := i.writeConv(ctx, uid, cp, false); err != nil {
  1909  			return nil, err
  1910  		}
  1911  	}
  1912  	for _, or := range othersReset {
  1913  		cp, err := i.readConv(ctx, uid, or.ConvID)
  1914  		if err != nil {
  1915  			continue
  1916  		}
  1917  		switch cp.Conv.GetMembersType() {
  1918  		case chat1.ConversationMembersType_TEAM:
  1919  		default:
  1920  			cp.Conv.Metadata.ResetList = append(cp.Conv.Metadata.ResetList, or.Uid)
  1921  		}
  1922  		cp.Conv.Metadata.Version = vers.ToConvVers()
  1923  		if err := i.writeConv(ctx, uid, cp, false); err != nil {
  1924  			return nil, err
  1925  		}
  1926  	}
  1927  	if err := i.writeDiskIndex(ctx, uid, iboxIndex); err != nil {
  1928  		return nil, err
  1929  	}
  1930  	iboxVers.InboxVersion = vers
  1931  	return roleUpdates, i.writeDiskVersions(ctx, uid, iboxVers)
  1932  }
  1933  
  1934  func (i *Inbox) ConversationsUpdate(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers,
  1935  	convUpdates []chat1.ConversationUpdate) (err Error) {
  1936  	var ierr error
  1937  	defer i.Trace(ctx, &ierr, "ConversationsUpdate")()
  1938  	defer func() { ierr = i.castInternalError(err) }()
  1939  	locks.Inbox.Lock()
  1940  	defer locks.Inbox.Unlock()
  1941  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
  1942  
  1943  	if len(convUpdates) == 0 {
  1944  		return nil
  1945  	}
  1946  
  1947  	layoutChanged := false
  1948  	defer func() {
  1949  		if layoutChanged {
  1950  			i.layoutNotifier.UpdateLayout(ctx, chat1.InboxLayoutReselectMode_DEFAULT, "existence")
  1951  		}
  1952  	}()
  1953  
  1954  	i.Debug(ctx, "ConversationsUpdate: updating %d convs", len(convUpdates))
  1955  	iboxVers, err := i.readDiskVersions(ctx, uid, true)
  1956  	if err != nil {
  1957  		if _, ok := err.(MissError); ok {
  1958  			return nil
  1959  		}
  1960  		return err
  1961  	}
  1962  	// Check inbox versions, make sure it makes sense (clear otherwise)
  1963  	var cont bool
  1964  	if vers, cont, err = i.handleVersion(ctx, iboxVers.InboxVersion, vers); !cont {
  1965  		return err
  1966  	}
  1967  
  1968  	// Process our own changes
  1969  	for _, u := range convUpdates {
  1970  		i.Debug(ctx, "ConversationsUpdate: changed conv: %v", u)
  1971  		oldConv, err := i.readConv(ctx, uid, u.ConvID)
  1972  		if err != nil {
  1973  			i.Debug(ctx, "ConversationsUpdate: skipping conv: %s err: %s", u.ConvID, err)
  1974  			continue
  1975  		}
  1976  		if oldConv.Conv.Metadata.Existence != u.Existence {
  1977  			layoutChanged = true
  1978  		}
  1979  		oldConv.Conv.Metadata.Existence = u.Existence
  1980  		if err := i.writeConv(ctx, uid, oldConv, false); err != nil {
  1981  			return err
  1982  		}
  1983  	}
  1984  
  1985  	iboxVers.InboxVersion = vers
  1986  	return i.writeDiskVersions(ctx, uid, iboxVers)
  1987  }
  1988  
  1989  func (i *Inbox) UpdateLocalMtime(ctx context.Context, uid gregor1.UID,
  1990  	convUpdates []chat1.LocalMtimeUpdate) (err Error) {
  1991  	if len(convUpdates) == 0 {
  1992  		return nil
  1993  	}
  1994  	var ierr error
  1995  	defer i.Trace(ctx, &ierr, "UpdateLocalMtime")()
  1996  	defer func() { ierr = i.castInternalError(err) }()
  1997  	locks.Inbox.Lock()
  1998  	defer locks.Inbox.Unlock()
  1999  	defer i.maybeNuke(ctx, func() Error { return err }, uid)
  2000  	var convs []types.RemoteConversation
  2001  	defer func() {
  2002  		for _, conv := range convs {
  2003  			i.layoutNotifier.UpdateLayoutFromNewMessage(ctx, conv)
  2004  		}
  2005  	}()
  2006  
  2007  	i.Debug(ctx, "UpdateLocalMtime: updating %d convs", len(convUpdates))
  2008  	iboxVers, err := i.readDiskVersions(ctx, uid, true)
  2009  	if err != nil {
  2010  		if _, ok := err.(MissError); ok {
  2011  			return nil
  2012  		}
  2013  		return err
  2014  	}
  2015  
  2016  	// Process our own changes
  2017  	for _, u := range convUpdates {
  2018  		i.Debug(ctx, "UpdateLocalMtime: applying conv update: %v", u)
  2019  		oldConv, err := i.readConv(ctx, uid, u.ConvID)
  2020  		if err != nil {
  2021  			i.Debug(ctx, "UpdateLocalMtime: skipping conv: %s err: %s", u.ConvID, err)
  2022  			continue
  2023  		}
  2024  		oldConv.LocalMtime = u.Mtime
  2025  		oldConv.Conv.Metadata.LocalVersion++
  2026  		if err := i.writeConv(ctx, uid, oldConv, false); err != nil {
  2027  			return err
  2028  		}
  2029  	}
  2030  	return i.writeDiskVersions(ctx, uid, iboxVers)
  2031  }
  2032  
  2033  type InboxVersionSource struct {
  2034  	globals.Contextified
  2035  }
  2036  
  2037  func NewInboxVersionSource(g *globals.Context) *InboxVersionSource {
  2038  	return &InboxVersionSource{
  2039  		Contextified: globals.NewContextified(g),
  2040  	}
  2041  }
  2042  
  2043  func (i *InboxVersionSource) GetInboxVersion(ctx context.Context, uid gregor1.UID) (chat1.InboxVers, error) {
  2044  	return NewInbox(i.G()).Version(ctx, uid)
  2045  }