status-im/status-go

View on GitHub
db/history.go

Summary

Maintainability
A
0 mins
Test Coverage
C
71%
package db

import (
    "encoding/binary"
    "encoding/json"
    "errors"
    "time"

    "github.com/syndtr/goleveldb/leveldb/util"

    "github.com/status-im/status-go/eth-node/types"
)

var (
    // ErrEmptyKey returned if key is not expected to be empty.
    ErrEmptyKey = errors.New("TopicHistoryKey is empty")
)

// DB is a common interface for DB operations.
type DB interface {
    Get([]byte) ([]byte, error)
    Put([]byte, []byte) error
    Delete([]byte) error
    Range([]byte, []byte) *util.Range
    NewIterator(*util.Range) NamespaceIterator
}

// TopicHistoryKey defines bytes that are used as unique key for TopicHistory.
// first 4 bytes are types.TopicType bytes
// next 8 bytes are time.Duration encoded in big endian notation.
type TopicHistoryKey [12]byte

// LoadTopicHistoryFromKey unmarshalls key into topic and duration and loads value of topic history
// from given database.
func LoadTopicHistoryFromKey(db DB, key TopicHistoryKey) (th TopicHistory, err error) {
    if (key == TopicHistoryKey{}) {
        return th, ErrEmptyKey
    }
    topic := types.TopicType{}
    copy(topic[:], key[:4])
    duration := binary.BigEndian.Uint64(key[4:])
    th = TopicHistory{db: db, Topic: topic, Duration: time.Duration(duration)}
    return th, th.Load()
}

// TopicHistory stores necessary information.
type TopicHistory struct {
    db DB
    // whisper topic
    Topic types.TopicType

    Duration time.Duration
    // Timestamp that was used for the first request with this topic.
    // Used to identify overlapping ranges.
    First time.Time
    // Timestamp of the last synced envelope.
    Current time.Time
    End     time.Time

    RequestID types.Hash
}

// Key returns unique identifier for this TopicHistory.
func (t TopicHistory) Key() TopicHistoryKey {
    key := TopicHistoryKey{}
    copy(key[:], t.Topic[:])
    binary.BigEndian.PutUint64(key[4:], uint64(t.Duration))
    return key
}

// Value marshalls TopicHistory into bytes.
func (t TopicHistory) Value() ([]byte, error) {
    return json.Marshal(t)
}

// Load TopicHistory from db using key and unmarshalls it.
func (t *TopicHistory) Load() error {
    key := t.Key()
    if (key == TopicHistoryKey{}) {
        return errors.New("key is empty")
    }
    value, err := t.db.Get(key[:])
    if err != nil {
        return err
    }
    return json.Unmarshal(value, t)
}

// Save persists TopicHistory on disk.
func (t TopicHistory) Save() error {
    key := t.Key()
    val, err := t.Value()
    if err != nil {
        return err
    }
    return t.db.Put(key[:], val)
}

// Delete removes topic history from database.
func (t TopicHistory) Delete() error {
    key := t.Key()
    return t.db.Delete(key[:])
}

// SameRange returns true if topic has same range, which means:
// true if Current is zero and Duration is the same
// and true if Current is the same
func (t TopicHistory) SameRange(other TopicHistory) bool {
    zero := time.Time{}
    if t.Current == zero && other.Current == zero {
        return t.Duration == other.Duration
    }
    return t.Current == other.Current
}

// Pending returns true if this topic was requested from a mail server.
func (t TopicHistory) Pending() bool {
    return t.RequestID != types.Hash{}
}

// HistoryRequest is kept in the database while request is in the progress.
// Stores necessary information to identify topics with associated ranges included in the request.
type HistoryRequest struct {
    requestDB DB
    topicDB   DB

    histories []TopicHistory

    // Generated ID
    ID types.Hash
    // List of the topics
    TopicHistoryKeys []TopicHistoryKey
}

// AddHistory adds instance to internal list of instance and add instance key to the list
// which will be persisted on disk.
func (req *HistoryRequest) AddHistory(history TopicHistory) {
    req.histories = append(req.histories, history)
    req.TopicHistoryKeys = append(req.TopicHistoryKeys, history.Key())
}

// Histories returns internal lsit of topic histories.
func (req *HistoryRequest) Histories() []TopicHistory {
    // TODO Lazy load from database on first access
    return req.histories
}

// Value returns content of HistoryRequest as bytes.
func (req HistoryRequest) Value() ([]byte, error) {
    return json.Marshal(req)
}

// Save persists all attached histories and request itself on the disk.
func (req HistoryRequest) Save() error {
    for i := range req.histories {
        th := &req.histories[i]
        th.RequestID = req.ID
        if err := th.Save(); err != nil {
            return err
        }
    }
    val, err := req.Value()
    if err != nil {
        return err
    }
    return req.requestDB.Put(req.ID.Bytes(), val)
}

// Replace saves request with new ID and all data attached to the old one.
func (req HistoryRequest) Replace(id types.Hash) error {
    if (req.ID != types.Hash{}) {
        if err := req.Delete(); err != nil {
            return err
        }
    }
    req.ID = id
    return req.Save()
}

// Delete HistoryRequest from store and update every topic.
func (req HistoryRequest) Delete() error {
    return req.requestDB.Delete(req.ID.Bytes())
}

// Load reads request and topic histories content from disk and unmarshalls them.
func (req *HistoryRequest) Load() error {
    val, err := req.requestDB.Get(req.ID.Bytes())
    if err != nil {
        return err
    }
    return req.RawUnmarshall(val)
}

func (req *HistoryRequest) loadHistories() error {
    for _, hk := range req.TopicHistoryKeys {
        th, err := LoadTopicHistoryFromKey(req.topicDB, hk)
        if err != nil {
            return err
        }
        req.histories = append(req.histories, th)
    }
    return nil
}

// RawUnmarshall unmarshall given bytes into the structure.
// Used in range queries to unmarshall content of the iter.Value directly into request struct.
func (req *HistoryRequest) RawUnmarshall(val []byte) error {
    err := json.Unmarshal(val, req)
    if err != nil {
        return err
    }
    return req.loadHistories()
}

// Includes checks if TopicHistory is included into the request.
func (req *HistoryRequest) Includes(history TopicHistory) bool {
    key := history.Key()
    for i := range req.TopicHistoryKeys {
        if key == req.TopicHistoryKeys[i] {
            return true
        }
    }
    return false
}