package model import ( "encoding/json" "sort" "sync" "time" ) // Timeline encapsulates a collection of ordered Messages, and a mechanism to access them // in a threadsafe manner. type Timeline struct { Messages []Message SignedGroupID []byte lock sync.Mutex } // Message is a local representation of a given message sent over a group chat channel. type Message struct { Timestamp time.Time Received time.Time PeerID string Message string Signature []byte PreviousMessageSig []byte } // MessageBaseSize is a rough estimate of the base number of bytes the struct uses before strings are populated const MessageBaseSize = 104 func compareSignatures(a []byte, b []byte) bool { if len(a) != len(b) { return false } for i := range a { if a[i] != b[i] { return false } } return true } // GetMessages returns a copy of the entire timeline func (t *Timeline) GetMessages() []Message { t.lock.Lock() messages := make([]Message, len(t.Messages)) copy(messages[:], t.Messages[:]) t.lock.Unlock() return messages } // GetCopy returns a duplicate of the Timeline func (t *Timeline) GetCopy() *Timeline { t.lock.Lock() defer t.lock.Unlock() bytes, _ := json.Marshal(t) newt := &Timeline{} json.Unmarshal(bytes, newt) return newt } // SetMessages sets the Messages of this timeline. Only to be used in loading/initialization func (t *Timeline) SetMessages(messages []Message) { t.lock.Lock() defer t.lock.Unlock() t.Messages = messages } // Len gets the length of the timeline func (t *Timeline) Len() int { return len(t.Messages) } // Swap swaps 2 Messages on the timeline. func (t *Timeline) Swap(i, j int) { t.Messages[i], t.Messages[j] = t.Messages[j], t.Messages[i] } // Less checks 2 Messages (i and j) in the timeline and returns true if i occurred before j, else false func (t *Timeline) Less(i, j int) bool { if t.Messages[i].Timestamp.Before(t.Messages[j].Timestamp) { return true } // Short circuit false if j is before i, signature checks will give a wrong order in this case. if t.Messages[j].Timestamp.Before(t.Messages[i].Timestamp) { return false } if compareSignatures(t.Messages[i].PreviousMessageSig, t.SignedGroupID) { return true } if compareSignatures(t.Messages[i].Signature, t.Messages[j].PreviousMessageSig) { return true } return false } // Sort sorts the timeline in a canonical order. // TODO: There is almost definitely a more efficient way of doing things that involve not calling this method on every timeline load. func (t *Timeline) Sort() { t.lock.Lock() defer t.lock.Unlock() sort.Sort(t) } // Insert inserts a message into the timeline in a thread safe way. func (t *Timeline) Insert(mi *Message) bool { t.lock.Lock() defer t.lock.Unlock() for _, m := range t.Messages { // If the message already exists, then we don't add it if compareSignatures(m.Signature, mi.Signature) { return true } } t.Messages = append(t.Messages, *mi) sort.Sort(t) return false }