github.com/status-im/status-go@v1.1.0/db/history.go (about) 1 package db 2 3 import ( 4 "encoding/binary" 5 "encoding/json" 6 "errors" 7 "time" 8 9 "github.com/syndtr/goleveldb/leveldb/util" 10 11 "github.com/status-im/status-go/eth-node/types" 12 ) 13 14 var ( 15 // ErrEmptyKey returned if key is not expected to be empty. 16 ErrEmptyKey = errors.New("TopicHistoryKey is empty") 17 ) 18 19 // DB is a common interface for DB operations. 20 type DB interface { 21 Get([]byte) ([]byte, error) 22 Put([]byte, []byte) error 23 Delete([]byte) error 24 Range([]byte, []byte) *util.Range 25 NewIterator(*util.Range) NamespaceIterator 26 } 27 28 // TopicHistoryKey defines bytes that are used as unique key for TopicHistory. 29 // first 4 bytes are types.TopicType bytes 30 // next 8 bytes are time.Duration encoded in big endian notation. 31 type TopicHistoryKey [12]byte 32 33 // LoadTopicHistoryFromKey unmarshalls key into topic and duration and loads value of topic history 34 // from given database. 35 func LoadTopicHistoryFromKey(db DB, key TopicHistoryKey) (th TopicHistory, err error) { 36 if (key == TopicHistoryKey{}) { 37 return th, ErrEmptyKey 38 } 39 topic := types.TopicType{} 40 copy(topic[:], key[:4]) 41 duration := binary.BigEndian.Uint64(key[4:]) 42 th = TopicHistory{db: db, Topic: topic, Duration: time.Duration(duration)} 43 return th, th.Load() 44 } 45 46 // TopicHistory stores necessary information. 47 type TopicHistory struct { 48 db DB 49 // whisper topic 50 Topic types.TopicType 51 52 Duration time.Duration 53 // Timestamp that was used for the first request with this topic. 54 // Used to identify overlapping ranges. 55 First time.Time 56 // Timestamp of the last synced envelope. 57 Current time.Time 58 End time.Time 59 60 RequestID types.Hash 61 } 62 63 // Key returns unique identifier for this TopicHistory. 64 func (t TopicHistory) Key() TopicHistoryKey { 65 key := TopicHistoryKey{} 66 copy(key[:], t.Topic[:]) 67 binary.BigEndian.PutUint64(key[4:], uint64(t.Duration)) 68 return key 69 } 70 71 // Value marshalls TopicHistory into bytes. 72 func (t TopicHistory) Value() ([]byte, error) { 73 return json.Marshal(t) 74 } 75 76 // Load TopicHistory from db using key and unmarshalls it. 77 func (t *TopicHistory) Load() error { 78 key := t.Key() 79 if (key == TopicHistoryKey{}) { 80 return errors.New("key is empty") 81 } 82 value, err := t.db.Get(key[:]) 83 if err != nil { 84 return err 85 } 86 return json.Unmarshal(value, t) 87 } 88 89 // Save persists TopicHistory on disk. 90 func (t TopicHistory) Save() error { 91 key := t.Key() 92 val, err := t.Value() 93 if err != nil { 94 return err 95 } 96 return t.db.Put(key[:], val) 97 } 98 99 // Delete removes topic history from database. 100 func (t TopicHistory) Delete() error { 101 key := t.Key() 102 return t.db.Delete(key[:]) 103 } 104 105 // SameRange returns true if topic has same range, which means: 106 // true if Current is zero and Duration is the same 107 // and true if Current is the same 108 func (t TopicHistory) SameRange(other TopicHistory) bool { 109 zero := time.Time{} 110 if t.Current == zero && other.Current == zero { 111 return t.Duration == other.Duration 112 } 113 return t.Current == other.Current 114 } 115 116 // Pending returns true if this topic was requested from a mail server. 117 func (t TopicHistory) Pending() bool { 118 return t.RequestID != types.Hash{} 119 } 120 121 // HistoryRequest is kept in the database while request is in the progress. 122 // Stores necessary information to identify topics with associated ranges included in the request. 123 type HistoryRequest struct { 124 requestDB DB 125 topicDB DB 126 127 histories []TopicHistory 128 129 // Generated ID 130 ID types.Hash 131 // List of the topics 132 TopicHistoryKeys []TopicHistoryKey 133 } 134 135 // AddHistory adds instance to internal list of instance and add instance key to the list 136 // which will be persisted on disk. 137 func (req *HistoryRequest) AddHistory(history TopicHistory) { 138 req.histories = append(req.histories, history) 139 req.TopicHistoryKeys = append(req.TopicHistoryKeys, history.Key()) 140 } 141 142 // Histories returns internal lsit of topic histories. 143 func (req *HistoryRequest) Histories() []TopicHistory { 144 // TODO Lazy load from database on first access 145 return req.histories 146 } 147 148 // Value returns content of HistoryRequest as bytes. 149 func (req HistoryRequest) Value() ([]byte, error) { 150 return json.Marshal(req) 151 } 152 153 // Save persists all attached histories and request itself on the disk. 154 func (req HistoryRequest) Save() error { 155 for i := range req.histories { 156 th := &req.histories[i] 157 th.RequestID = req.ID 158 if err := th.Save(); err != nil { 159 return err 160 } 161 } 162 val, err := req.Value() 163 if err != nil { 164 return err 165 } 166 return req.requestDB.Put(req.ID.Bytes(), val) 167 } 168 169 // Replace saves request with new ID and all data attached to the old one. 170 func (req HistoryRequest) Replace(id types.Hash) error { 171 if (req.ID != types.Hash{}) { 172 if err := req.Delete(); err != nil { 173 return err 174 } 175 } 176 req.ID = id 177 return req.Save() 178 } 179 180 // Delete HistoryRequest from store and update every topic. 181 func (req HistoryRequest) Delete() error { 182 return req.requestDB.Delete(req.ID.Bytes()) 183 } 184 185 // Load reads request and topic histories content from disk and unmarshalls them. 186 func (req *HistoryRequest) Load() error { 187 val, err := req.requestDB.Get(req.ID.Bytes()) 188 if err != nil { 189 return err 190 } 191 return req.RawUnmarshall(val) 192 } 193 194 func (req *HistoryRequest) loadHistories() error { 195 for _, hk := range req.TopicHistoryKeys { 196 th, err := LoadTopicHistoryFromKey(req.topicDB, hk) 197 if err != nil { 198 return err 199 } 200 req.histories = append(req.histories, th) 201 } 202 return nil 203 } 204 205 // RawUnmarshall unmarshall given bytes into the structure. 206 // Used in range queries to unmarshall content of the iter.Value directly into request struct. 207 func (req *HistoryRequest) RawUnmarshall(val []byte) error { 208 err := json.Unmarshal(val, req) 209 if err != nil { 210 return err 211 } 212 return req.loadHistories() 213 } 214 215 // Includes checks if TopicHistory is included into the request. 216 func (req *HistoryRequest) Includes(history TopicHistory) bool { 217 key := history.Key() 218 for i := range req.TopicHistoryKeys { 219 if key == req.TopicHistoryKeys[i] { 220 return true 221 } 222 } 223 return false 224 }