|
|
- // Package cursor implements time-ordered item cursors for an event log.
- package cursor
-
- import (
- "errors"
- "fmt"
- "strconv"
- "strings"
- "time"
- )
-
- // A Source produces cursors based on a time index generator and a sequence
- // counter. A zero-valued Source is ready for use with defaults as described.
- type Source struct {
- // This function is called to produce the current time index.
- // If nil, it defaults to time.Now().UnixNano().
- TimeIndex func() int64
-
- // The current counter value used for sequence number generation. It is
- // incremented in-place each time a cursor is generated.
- Counter int64
- }
-
- func (s *Source) timeIndex() int64 {
- if s.TimeIndex == nil {
- return time.Now().UnixNano()
- }
- return s.TimeIndex()
- }
-
- func (s *Source) nextCounter() int64 {
- s.Counter++
- return s.Counter
- }
-
- // Cursor produces a fresh cursor from s at the current time index and counter.
- func (s *Source) Cursor() Cursor {
- return Cursor{
- timestamp: uint64(s.timeIndex()),
- sequence: uint16(s.nextCounter() & 0xffff),
- }
- }
-
- // A Cursor is a unique identifier for an item in a time-ordered event log.
- // It is safe to copy and compare cursors by value.
- type Cursor struct {
- timestamp uint64 // ns since Unix epoch
- sequence uint16 // sequence number
- }
-
- // Before reports whether c is prior to o in time ordering. This comparison
- // ignores sequence numbers.
- func (c Cursor) Before(o Cursor) bool { return c.timestamp < o.timestamp }
-
- // Diff returns the time duration between c and o. The duration is negative if
- // c is before o in time order.
- func (c Cursor) Diff(o Cursor) time.Duration {
- return time.Duration(c.timestamp) - time.Duration(o.timestamp)
- }
-
- // IsZero reports whether c is the zero cursor.
- func (c Cursor) IsZero() bool { return c == Cursor{} }
-
- // MarshalText implements the encoding.TextMarshaler interface.
- // A zero cursor marshals as "", otherwise the format used by the String method.
- func (c Cursor) MarshalText() ([]byte, error) {
- if c.IsZero() {
- return nil, nil
- }
- return []byte(c.String()), nil
- }
-
- // UnmarshalText implements the encoding.TextUnmarshaler interface.
- // An empty text unmarshals without error to a zero cursor.
- func (c *Cursor) UnmarshalText(data []byte) error {
- if len(data) == 0 {
- *c = Cursor{} // set zero
- return nil
- }
- ps := strings.SplitN(string(data), "-", 2)
- if len(ps) != 2 {
- return errors.New("invalid cursor format")
- }
- ts, err := strconv.ParseUint(ps[0], 16, 64)
- if err != nil {
- return fmt.Errorf("invalid timestamp: %w", err)
- }
- sn, err := strconv.ParseUint(ps[1], 16, 16)
- if err != nil {
- return fmt.Errorf("invalid sequence: %w", err)
- }
- c.timestamp = ts
- c.sequence = uint16(sn)
- return nil
- }
-
- // String returns a printable text representation of a cursor.
- func (c Cursor) String() string {
- return fmt.Sprintf("%016x-%04x", c.timestamp, c.sequence)
- }
|