github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/internal/eventlog/cursor/cursor.go (about)

     1  // Package cursor implements time-ordered item cursors for an event log.
     2  package cursor
     3  
     4  import (
     5  	"errors"
     6  	"fmt"
     7  	"strconv"
     8  	"strings"
     9  	"time"
    10  )
    11  
    12  // A Source produces cursors based on a time index generator and a sequence
    13  // counter. A zero-valued Source is ready for use with defaults as described.
    14  type Source struct {
    15  	// This function is called to produce the current time index.
    16  	// If nil, it defaults to time.Now().UnixNano().
    17  	TimeIndex func() int64
    18  
    19  	// The current counter value used for sequence number generation.  It is
    20  	// incremented in-place each time a cursor is generated.
    21  	Counter int64
    22  }
    23  
    24  func (s *Source) timeIndex() int64 {
    25  	if s.TimeIndex == nil {
    26  		return time.Now().UnixNano()
    27  	}
    28  	return s.TimeIndex()
    29  }
    30  
    31  func (s *Source) nextCounter() int64 {
    32  	s.Counter++
    33  	return s.Counter
    34  }
    35  
    36  // Cursor produces a fresh cursor from s at the current time index and counter.
    37  func (s *Source) Cursor() Cursor {
    38  	return Cursor{
    39  		timestamp: uint64(s.timeIndex()),
    40  		sequence:  uint16(s.nextCounter() & 0xffff),
    41  	}
    42  }
    43  
    44  // A Cursor is a unique identifier for an item in a time-ordered event log.
    45  // It is safe to copy and compare cursors by value.
    46  type Cursor struct {
    47  	timestamp uint64 // ns since Unix epoch
    48  	sequence  uint16 // sequence number
    49  }
    50  
    51  // Before reports whether c is prior to o in time ordering. This comparison
    52  // ignores sequence numbers.
    53  func (c Cursor) Before(o Cursor) bool { return c.timestamp < o.timestamp }
    54  
    55  // Diff returns the time duration between c and o. The duration is negative if
    56  // c is before o in time order.
    57  func (c Cursor) Diff(o Cursor) time.Duration {
    58  	return time.Duration(c.timestamp) - time.Duration(o.timestamp)
    59  }
    60  
    61  // IsZero reports whether c is the zero cursor.
    62  func (c Cursor) IsZero() bool { return c == Cursor{} }
    63  
    64  // MarshalText implements the encoding.TextMarshaler interface.
    65  // A zero cursor marshals as "", otherwise the format used by the String method.
    66  func (c Cursor) MarshalText() ([]byte, error) {
    67  	if c.IsZero() {
    68  		return nil, nil
    69  	}
    70  	return []byte(c.String()), nil
    71  }
    72  
    73  // UnmarshalText implements the encoding.TextUnmarshaler interface.
    74  // An empty text unmarshals without error to a zero cursor.
    75  func (c *Cursor) UnmarshalText(data []byte) error {
    76  	if len(data) == 0 {
    77  		*c = Cursor{} // set zero
    78  		return nil
    79  	}
    80  	ps := strings.SplitN(string(data), "-", 2)
    81  	if len(ps) != 2 {
    82  		return errors.New("invalid cursor format")
    83  	}
    84  	ts, err := strconv.ParseUint(ps[0], 16, 64)
    85  	if err != nil {
    86  		return fmt.Errorf("invalid timestamp: %w", err)
    87  	}
    88  	sn, err := strconv.ParseUint(ps[1], 16, 16)
    89  	if err != nil {
    90  		return fmt.Errorf("invalid sequence: %w", err)
    91  	}
    92  	c.timestamp = ts
    93  	c.sequence = uint16(sn)
    94  	return nil
    95  }
    96  
    97  // String returns a printable text representation of a cursor.
    98  func (c Cursor) String() string {
    99  	return fmt.Sprintf("%016x-%04x", c.timestamp, c.sequence)
   100  }