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

     1  package storage
     2  
     3  import (
     4  	"github.com/keybase/client/go/protocol/chat1"
     5  	"github.com/keybase/client/go/protocol/gregor1"
     6  	context "golang.org/x/net/context"
     7  )
     8  
     9  // For a given conversation, purge all ephemeral messages from
    10  // purgeInfo.MinUnexplodedID to the present, updating bookkeeping for the next
    11  // time we need to purge this conv.
    12  func (s *Storage) EphemeralPurge(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID, purgeInfo *chat1.EphemeralPurgeInfo) (newPurgeInfo *chat1.EphemeralPurgeInfo, explodedMsgs []chat1.MessageUnboxed, err Error) {
    13  	var ierr error
    14  	defer s.Trace(ctx, &ierr, "EphemeralPurge")()
    15  	defer func() { ierr = s.castInternalError(err) }()
    16  	lock := locks.StorageLockTab.AcquireOnName(ctx, s.G(), convID.String())
    17  	defer lock.Release(ctx)
    18  
    19  	if purgeInfo == nil {
    20  		return nil, nil, nil
    21  	}
    22  
    23  	// Fetch secret key
    24  	key, ierr := GetSecretBoxKey(ctx, s.G().ExternalG())
    25  	if ierr != nil {
    26  		return nil, nil, MiscError{Msg: "unable to get secret key: " + ierr.Error()}
    27  	}
    28  
    29  	ctx, err = s.engine.Init(ctx, key, convID, uid)
    30  	if err != nil {
    31  		return nil, nil, err
    32  	}
    33  
    34  	maxMsgID, err := s.idtracker.getMaxMessageID(ctx, convID, uid)
    35  	if err != nil {
    36  		return nil, nil, err
    37  	}
    38  
    39  	// We don't care about holes.
    40  	maxHoles := int(maxMsgID-purgeInfo.MinUnexplodedID) + 1
    41  	var target int
    42  	if purgeInfo.MinUnexplodedID == 0 {
    43  		target = 0 // we need to traverse the whole conversation
    44  	} else {
    45  		target = maxHoles
    46  	}
    47  	rc := NewHoleyResultCollector(maxHoles, NewSimpleResultCollector(target, false))
    48  	err = s.engine.ReadMessages(ctx, rc, convID, uid, maxMsgID, 0)
    49  	switch err.(type) {
    50  	case nil:
    51  		// ok
    52  		if len(rc.Result()) == 0 {
    53  			ierr := s.G().EphemeralTracker.InactivatePurgeInfo(ctx, convID, uid)
    54  			if ierr != nil {
    55  				return nil, nil, NewInternalError(ctx, s.DebugLabeler, "EphemeralTracker unable to InactivatePurgeInfo: %v", ierr)
    56  			}
    57  			return nil, nil, nil
    58  		}
    59  	case MissError:
    60  		// We don't have these messages in cache, so don't retry this
    61  		// conversation until further notice.
    62  		ierr := s.G().EphemeralTracker.InactivatePurgeInfo(ctx, convID, uid)
    63  		if ierr != nil {
    64  			return nil, nil, NewInternalError(ctx, s.DebugLabeler, "EphemeralTracker unable to InactivatePurgeInfo: %v", ierr)
    65  		}
    66  		return nil, nil, nil
    67  	default:
    68  		return nil, nil, err
    69  	}
    70  	newPurgeInfo, explodedMsgs, err = s.ephemeralPurgeHelper(ctx, convID, uid, rc.Result())
    71  	if err != nil {
    72  		return nil, nil, err
    73  	}
    74  	ierr = s.G().EphemeralTracker.SetPurgeInfo(ctx, convID, uid, newPurgeInfo)
    75  	if ierr != nil {
    76  		return nil, nil, NewInternalError(ctx, s.DebugLabeler, "EphemeralTracker unable to SetPurgeInfo: %v", ierr)
    77  	}
    78  	return newPurgeInfo, explodedMsgs, err
    79  }
    80  
    81  func (s *Storage) explodeExpiredMessages(ctx context.Context, convID chat1.ConversationID,
    82  	uid gregor1.UID, msgs []chat1.MessageUnboxed) (explodedMsgs []chat1.MessageUnboxed, err Error) {
    83  	purgeInfo, explodedMsgs, err := s.ephemeralPurgeHelper(ctx, convID, uid, msgs)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	// We may only be merging in some subset of messages, we only update if the
    88  	// info we get is more restrictive that what we have already
    89  	ierr := s.G().EphemeralTracker.MaybeUpdatePurgeInfo(ctx, convID, uid, purgeInfo)
    90  	if ierr != nil {
    91  		return nil, NewInternalError(ctx, s.DebugLabeler, "EphemeralTracker unable to MaybeUpdatePurgeInfo: %v", ierr)
    92  	}
    93  	return explodedMsgs, nil
    94  }
    95  
    96  // Before adding or removing messages from storage, nuke any expired ones and
    97  // give info for our bookkeeping for the next time we have to purge.
    98  // requires msgs to be sorted by descending message ID
    99  func (s *Storage) ephemeralPurgeHelper(ctx context.Context, convID chat1.ConversationID,
   100  	uid gregor1.UID, msgs []chat1.MessageUnboxed) (purgeInfo *chat1.EphemeralPurgeInfo, explodedMsgs []chat1.MessageUnboxed, err Error) {
   101  
   102  	if len(msgs) == 0 {
   103  		return nil, nil, nil
   104  	}
   105  
   106  	nextPurgeTime := gregor1.Time(0)
   107  	minUnexplodedID := msgs[0].GetMessageID()
   108  	var allAssets []chat1.Asset
   109  	var allPurged []chat1.MessageUnboxed
   110  	var hasExploding bool
   111  	for i, msg := range msgs {
   112  		if !msg.IsValid() {
   113  			continue
   114  		}
   115  		mvalid := msg.Valid()
   116  		if mvalid.IsEphemeral() {
   117  			if !mvalid.IsEphemeralExpired(s.clock.Now()) {
   118  				hasExploding = true
   119  				// Keep track of the minimum ephemeral message that is not yet
   120  				// exploded.
   121  				if msg.GetMessageID() < minUnexplodedID {
   122  					minUnexplodedID = msg.GetMessageID()
   123  				}
   124  				// Keep track of the next time we'll have purge this conv.
   125  				if nextPurgeTime == 0 || mvalid.Etime() < nextPurgeTime {
   126  					nextPurgeTime = mvalid.Etime()
   127  				}
   128  			} else if mvalid.MessageBody.IsNil() {
   129  				// do nothing
   130  			} else {
   131  				msgPurged, assets := s.purgeMessage(mvalid)
   132  				allAssets = append(allAssets, assets...)
   133  				explodedMsgs = append(explodedMsgs, msgPurged)
   134  				allPurged = append(allPurged, msg)
   135  				msgs[i] = msgPurged
   136  			}
   137  		}
   138  	}
   139  
   140  	// queue asset deletions in the background
   141  	if s.assetDeleter != nil {
   142  		s.assetDeleter.DeleteAssets(ctx, uid, convID, allAssets)
   143  	}
   144  	// queue search index update in the background
   145  	go func() {
   146  		err := s.G().Indexer.Remove(ctx, convID, allPurged)
   147  		if err != nil {
   148  			s.Debug(ctx, "Error removing from indexer: %+v", err)
   149  		}
   150  	}()
   151  
   152  	if err = s.engine.WriteMessages(ctx, convID, uid, explodedMsgs); err != nil {
   153  		s.Debug(ctx, "write messages failed: %v", err)
   154  		return nil, nil, err
   155  	}
   156  
   157  	return &chat1.EphemeralPurgeInfo{
   158  		ConvID:          convID,
   159  		MinUnexplodedID: minUnexplodedID,
   160  		NextPurgeTime:   nextPurgeTime,
   161  		IsActive:        hasExploding,
   162  	}, explodedMsgs, nil
   163  }