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

     1  package pager
     2  
     3  import (
     4  	"github.com/keybase/client/go/protocol/chat1"
     5  	"github.com/keybase/client/go/protocol/gregor1"
     6  	"github.com/keybase/go-codec/codec"
     7  )
     8  
     9  // inboxPagerFields is the info that gives a total ordering on the inbox
    10  type InboxPagerFields struct {
    11  	Mtime  gregor1.Time         `codec:"M"`
    12  	ConvID chat1.ConversationID `codec:"C"`
    13  }
    14  
    15  func (i InboxPagerFields) GetMtime() gregor1.Time {
    16  	return i.Mtime
    17  }
    18  
    19  func (i InboxPagerFields) GetConvID() chat1.ConversationID {
    20  	return i.ConvID
    21  }
    22  
    23  // pager provides the getPage and makePage functions for implementing
    24  // paging in the chat1 protocol
    25  type Pager struct {
    26  	codec codec.Handle
    27  }
    28  
    29  func NewPager() Pager {
    30  	mh := codec.MsgpackHandle{WriteExt: true}
    31  	return Pager{
    32  		codec: &mh,
    33  	}
    34  }
    35  
    36  func (p Pager) encode(input interface{}) ([]byte, error) {
    37  	var data []byte
    38  	enc := codec.NewEncoderBytes(&data, p.codec)
    39  	if err := enc.Encode(input); err != nil {
    40  		return nil, err
    41  	}
    42  	return data, nil
    43  }
    44  
    45  func (p Pager) decode(data []byte, res interface{}) error {
    46  	dec := codec.NewDecoderBytes(data, p.codec)
    47  	err := dec.Decode(res)
    48  	return err
    49  }
    50  
    51  func (p Pager) GetPage(getcond func(bool) string, page *chat1.Pagination,
    52  	pivot interface{}) (string, bool, error) {
    53  
    54  	var dat []byte
    55  	var cond string
    56  	var prev bool
    57  
    58  	// Set the query condition depending on what direction we are looking
    59  	if len(page.Next) > 0 {
    60  		cond = getcond(false)
    61  		dat = page.Next
    62  	} else if len(page.Previous) > 0 {
    63  		cond = getcond(true)
    64  		dat = page.Previous
    65  		prev = true
    66  	} else {
    67  		return "", false, nil
    68  	}
    69  
    70  	if err := p.decode(dat, pivot); err != nil {
    71  		return "", false, err
    72  	}
    73  
    74  	return cond, prev, nil
    75  }
    76  
    77  func (p Pager) MakePage(length, reqed int, next interface{}, prev interface{}) (*chat1.Pagination, error) {
    78  	prevEncoded, err := p.encode(prev)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  	nextEncoded, err := p.encode(next)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	return &chat1.Pagination{
    88  		Num:      length,
    89  		Next:     nextEncoded,
    90  		Previous: prevEncoded,
    91  		Last:     (length < reqed) || length == 0,
    92  	}, nil
    93  }
    94  
    95  // inboxPager provides a convenient interface to pager for the inbox
    96  // use case
    97  type InboxPager struct {
    98  	Pager
    99  }
   100  
   101  type InboxEntry interface {
   102  	GetMtime() gregor1.Time
   103  	GetConvID() chat1.ConversationID
   104  }
   105  
   106  func NewInboxPager() InboxPager {
   107  	return InboxPager{Pager: NewPager()}
   108  }
   109  
   110  func (p InboxPager) MakePage(res []InboxEntry, reqed int) (*chat1.Pagination, error) {
   111  	if len(res) == 0 {
   112  		return &chat1.Pagination{Num: 0, Last: true}, nil
   113  	}
   114  
   115  	// Get first and last message IDs to encode in the result
   116  	prevPF := InboxPagerFields{Mtime: res[0].GetMtime(), ConvID: res[0].GetConvID()}
   117  	nextPF := InboxPagerFields{
   118  		Mtime:  res[len(res)-1].GetMtime(),
   119  		ConvID: res[len(res)-1].GetConvID(),
   120  	}
   121  
   122  	return p.Pager.MakePage(len(res), reqed, nextPF, prevPF)
   123  }
   124  
   125  // threadPager provides a convenient interface to pager for the thread use case
   126  type ThreadPager struct {
   127  	Pager
   128  }
   129  
   130  type Message interface {
   131  	GetMessageID() chat1.MessageID
   132  }
   133  
   134  func NewThreadPager() ThreadPager {
   135  	return ThreadPager{Pager: NewPager()}
   136  }
   137  
   138  func (p ThreadPager) MakePage(res []Message, reqed int, maxDeletedUpto chat1.MessageID) (*chat1.Pagination, error) {
   139  	if len(res) == 0 {
   140  		return &chat1.Pagination{Num: 0, Last: true}, nil
   141  	}
   142  
   143  	// Get first and last message IDs to encode in the result
   144  	prevMsgID := res[0].GetMessageID()
   145  	nextMsgID := res[len(res)-1].GetMessageID()
   146  
   147  	page, err := p.Pager.MakePage(len(res), reqed, nextMsgID, prevMsgID)
   148  	if err != nil {
   149  		return page, err
   150  	}
   151  	if prevMsgID.Min(nextMsgID) <= maxDeletedUpto {
   152  		// If any message is prior to the nukepoint, say this is the last page.
   153  		page.Last = true
   154  	}
   155  	return page, nil
   156  }
   157  
   158  func (p ThreadPager) MakeIndex(msg Message) ([]byte, error) {
   159  	return p.encode(msg.GetMessageID())
   160  }
   161  
   162  func (p ThreadPager) MakeIndexByID(msgID chat1.MessageID) ([]byte, error) {
   163  	return p.encode(msgID)
   164  }