Collapse and log join, part and quit, closes #27, log nick and topic changes, move state into irc package
This commit is contained in:
parent
edd4d6eadb
commit
ead3b37cf9
37 changed files with 1980 additions and 969 deletions
|
@ -278,19 +278,36 @@ func (s *BoltStore) RemoveOpenDM(user *storage.User, server, nick string) error
|
|||
})
|
||||
}
|
||||
|
||||
func (s *BoltStore) logMessage(tx *bolt.Tx, message *storage.Message) error {
|
||||
b, err := tx.Bucket(bucketMessages).CreateBucketIfNotExists([]byte(message.Server + ":" + message.To))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := message.Marshal(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.Put([]byte(message.ID), data)
|
||||
}
|
||||
|
||||
func (s *BoltStore) LogMessage(message *storage.Message) error {
|
||||
return s.db.Batch(func(tx *bolt.Tx) error {
|
||||
b, err := tx.Bucket(bucketMessages).CreateBucketIfNotExists([]byte(message.Server + ":" + message.To))
|
||||
if err != nil {
|
||||
return err
|
||||
return s.logMessage(tx, message)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *BoltStore) LogMessages(messages []*storage.Message) error {
|
||||
return s.db.Batch(func(tx *bolt.Tx) error {
|
||||
for _, message := range messages {
|
||||
err := s.logMessage(tx, message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
data, err := message.Marshal(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.Put([]byte(message.ID), data)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,206 +0,0 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type ChannelStore struct {
|
||||
users map[string]map[string][]*ChannelStoreUser
|
||||
userLock sync.Mutex
|
||||
|
||||
topic map[string]map[string]string
|
||||
topicLock sync.Mutex
|
||||
}
|
||||
|
||||
const userModePrefixes = "~&@%+"
|
||||
const userModeChars = "qaohv"
|
||||
|
||||
type ChannelStoreUser struct {
|
||||
nick string
|
||||
modes string
|
||||
prefix string
|
||||
}
|
||||
|
||||
func NewChannelStoreUser(nick string) *ChannelStoreUser {
|
||||
user := &ChannelStoreUser{nick: nick}
|
||||
|
||||
if i := strings.IndexAny(nick, userModePrefixes); i == 0 {
|
||||
i = strings.Index(userModePrefixes, string(nick[0]))
|
||||
user.modes = string(userModeChars[i])
|
||||
user.nick = nick[1:]
|
||||
user.updatePrefix()
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
func (c *ChannelStoreUser) String() string {
|
||||
return c.prefix + c.nick
|
||||
}
|
||||
|
||||
func (c *ChannelStoreUser) addModes(modes string) {
|
||||
for _, mode := range modes {
|
||||
if strings.Contains(c.modes, string(mode)) {
|
||||
continue
|
||||
}
|
||||
c.modes += string(mode)
|
||||
}
|
||||
c.updatePrefix()
|
||||
}
|
||||
|
||||
func (c *ChannelStoreUser) removeModes(modes string) {
|
||||
for _, mode := range modes {
|
||||
c.modes = strings.Replace(c.modes, string(mode), "", 1)
|
||||
}
|
||||
c.updatePrefix()
|
||||
}
|
||||
|
||||
func (c *ChannelStoreUser) updatePrefix() {
|
||||
for i, mode := range userModeChars {
|
||||
if strings.Contains(c.modes, string(mode)) {
|
||||
c.prefix = string(userModePrefixes[i])
|
||||
return
|
||||
}
|
||||
}
|
||||
c.prefix = ""
|
||||
}
|
||||
|
||||
func NewChannelStore() *ChannelStore {
|
||||
return &ChannelStore{
|
||||
users: make(map[string]map[string][]*ChannelStoreUser),
|
||||
topic: make(map[string]map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ChannelStore) GetUsers(server, channel string) []string {
|
||||
c.userLock.Lock()
|
||||
|
||||
users := make([]string, len(c.users[server][channel]))
|
||||
for i, user := range c.users[server][channel] {
|
||||
users[i] = user.String()
|
||||
}
|
||||
|
||||
c.userLock.Unlock()
|
||||
|
||||
return users
|
||||
}
|
||||
|
||||
func (c *ChannelStore) SetUsers(users []string, server, channel string) {
|
||||
c.userLock.Lock()
|
||||
|
||||
if _, ok := c.users[server]; !ok {
|
||||
c.users[server] = make(map[string][]*ChannelStoreUser)
|
||||
}
|
||||
|
||||
c.users[server][channel] = make([]*ChannelStoreUser, len(users))
|
||||
for i, nick := range users {
|
||||
c.users[server][channel][i] = NewChannelStoreUser(nick)
|
||||
}
|
||||
|
||||
c.userLock.Unlock()
|
||||
}
|
||||
|
||||
func (c *ChannelStore) AddUser(user, server, channel string) {
|
||||
c.userLock.Lock()
|
||||
|
||||
if _, ok := c.users[server]; !ok {
|
||||
c.users[server] = make(map[string][]*ChannelStoreUser)
|
||||
}
|
||||
|
||||
if users, ok := c.users[server][channel]; ok {
|
||||
for _, u := range users {
|
||||
if u.nick == user {
|
||||
c.userLock.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.users[server][channel] = append(users, NewChannelStoreUser(user))
|
||||
} else {
|
||||
c.users[server][channel] = []*ChannelStoreUser{NewChannelStoreUser(user)}
|
||||
}
|
||||
|
||||
c.userLock.Unlock()
|
||||
}
|
||||
|
||||
func (c *ChannelStore) RemoveUser(user, server, channel string) {
|
||||
c.userLock.Lock()
|
||||
c.removeUser(user, server, channel)
|
||||
c.userLock.Unlock()
|
||||
}
|
||||
|
||||
func (c *ChannelStore) RemoveUserAll(user, server string) {
|
||||
c.userLock.Lock()
|
||||
|
||||
for channel := range c.users[server] {
|
||||
c.removeUser(user, server, channel)
|
||||
}
|
||||
|
||||
c.userLock.Unlock()
|
||||
}
|
||||
|
||||
func (c *ChannelStore) RenameUser(oldNick, newNick, server string) {
|
||||
c.userLock.Lock()
|
||||
c.renameAll(server, oldNick, newNick)
|
||||
c.userLock.Unlock()
|
||||
}
|
||||
|
||||
func (c *ChannelStore) SetMode(server, channel, user, add, remove string) {
|
||||
c.userLock.Lock()
|
||||
|
||||
for _, u := range c.users[server][channel] {
|
||||
if u.nick == user {
|
||||
u.addModes(add)
|
||||
u.removeModes(remove)
|
||||
|
||||
c.userLock.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.userLock.Unlock()
|
||||
}
|
||||
|
||||
func (c *ChannelStore) GetTopic(server, channel string) string {
|
||||
c.topicLock.Lock()
|
||||
topic := c.topic[server][channel]
|
||||
c.topicLock.Unlock()
|
||||
return topic
|
||||
}
|
||||
|
||||
func (c *ChannelStore) SetTopic(topic, server, channel string) {
|
||||
c.topicLock.Lock()
|
||||
|
||||
if _, ok := c.topic[server]; !ok {
|
||||
c.topic[server] = make(map[string]string)
|
||||
}
|
||||
|
||||
c.topic[server][channel] = topic
|
||||
c.topicLock.Unlock()
|
||||
}
|
||||
|
||||
func (c *ChannelStore) rename(server, channel, oldNick, newNick string) {
|
||||
for _, user := range c.users[server][channel] {
|
||||
if user.nick == oldNick {
|
||||
user.nick = newNick
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ChannelStore) renameAll(server, oldNick, newNick string) {
|
||||
for channel := range c.users[server] {
|
||||
c.rename(server, channel, oldNick, newNick)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ChannelStore) removeUser(user, server, channel string) {
|
||||
for i, u := range c.users[server][channel] {
|
||||
if u.nick == user {
|
||||
users := c.users[server][channel]
|
||||
c.users[server][channel] = append(users[:i], users[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetSetUsers(t *testing.T) {
|
||||
channelStore := NewChannelStore()
|
||||
users := []string{"a", "b"}
|
||||
channelStore.SetUsers(users, "srv", "#chan")
|
||||
assert.Equal(t, users, channelStore.GetUsers("srv", "#chan"))
|
||||
channelStore.SetUsers(users, "srv", "#chan")
|
||||
assert.Equal(t, users, channelStore.GetUsers("srv", "#chan"))
|
||||
}
|
||||
|
||||
func TestAddRemoveUser(t *testing.T) {
|
||||
channelStore := NewChannelStore()
|
||||
channelStore.AddUser("user", "srv", "#chan")
|
||||
channelStore.AddUser("user", "srv", "#chan")
|
||||
assert.Len(t, channelStore.GetUsers("srv", "#chan"), 1)
|
||||
channelStore.AddUser("user2", "srv", "#chan")
|
||||
assert.Equal(t, []string{"user", "user2"}, channelStore.GetUsers("srv", "#chan"))
|
||||
channelStore.RemoveUser("user", "srv", "#chan")
|
||||
assert.Equal(t, []string{"user2"}, channelStore.GetUsers("srv", "#chan"))
|
||||
}
|
||||
|
||||
func TestRemoveUserAll(t *testing.T) {
|
||||
channelStore := NewChannelStore()
|
||||
channelStore.AddUser("user", "srv", "#chan1")
|
||||
channelStore.AddUser("user", "srv", "#chan2")
|
||||
channelStore.RemoveUserAll("user", "srv")
|
||||
assert.Empty(t, channelStore.GetUsers("srv", "#chan1"))
|
||||
assert.Empty(t, channelStore.GetUsers("srv", "#chan2"))
|
||||
}
|
||||
|
||||
func TestRenameUser(t *testing.T) {
|
||||
channelStore := NewChannelStore()
|
||||
channelStore.AddUser("user", "srv", "#chan1")
|
||||
channelStore.AddUser("user", "srv", "#chan2")
|
||||
channelStore.RenameUser("user", "new", "srv")
|
||||
assert.Equal(t, []string{"new"}, channelStore.GetUsers("srv", "#chan1"))
|
||||
assert.Equal(t, []string{"new"}, channelStore.GetUsers("srv", "#chan2"))
|
||||
|
||||
channelStore.AddUser("@gotop", "srv", "#chan3")
|
||||
channelStore.RenameUser("gotop", "stillgotit", "srv")
|
||||
assert.Equal(t, []string{"@stillgotit"}, channelStore.GetUsers("srv", "#chan3"))
|
||||
}
|
||||
|
||||
func TestMode(t *testing.T) {
|
||||
channelStore := NewChannelStore()
|
||||
channelStore.AddUser("+user", "srv", "#chan")
|
||||
channelStore.SetMode("srv", "#chan", "user", "o", "v")
|
||||
assert.Equal(t, []string{"@user"}, channelStore.GetUsers("srv", "#chan"))
|
||||
channelStore.SetMode("srv", "#chan", "user", "v", "")
|
||||
assert.Equal(t, []string{"@user"}, channelStore.GetUsers("srv", "#chan"))
|
||||
channelStore.SetMode("srv", "#chan", "user", "", "o")
|
||||
assert.Equal(t, []string{"+user"}, channelStore.GetUsers("srv", "#chan"))
|
||||
channelStore.SetMode("srv", "#chan", "user", "q", "")
|
||||
assert.Equal(t, []string{"~user"}, channelStore.GetUsers("srv", "#chan"))
|
||||
}
|
||||
|
||||
func TestTopic(t *testing.T) {
|
||||
channelStore := NewChannelStore()
|
||||
assert.Equal(t, "", channelStore.GetTopic("srv", "#chan"))
|
||||
channelStore.SetTopic("the topic", "srv", "#chan")
|
||||
assert.Equal(t, "the topic", channelStore.GetTopic("srv", "#chan"))
|
||||
}
|
||||
|
||||
func TestChannelUserMode(t *testing.T) {
|
||||
user := NewChannelStoreUser("&test")
|
||||
assert.Equal(t, "test", user.nick)
|
||||
assert.Equal(t, "a", string(user.modes[0]))
|
||||
assert.Equal(t, "&test", user.String())
|
||||
|
||||
user.removeModes("a")
|
||||
assert.Equal(t, "test", user.String())
|
||||
user.addModes("o")
|
||||
assert.Equal(t, "@test", user.String())
|
||||
user.addModes("q")
|
||||
assert.Equal(t, "~test", user.String())
|
||||
user.addModes("v")
|
||||
assert.Equal(t, "~test", user.String())
|
||||
user.removeModes("qo")
|
||||
assert.Equal(t, "+test", user.String())
|
||||
user.removeModes("v")
|
||||
assert.Equal(t, "test", user.String())
|
||||
}
|
|
@ -7,7 +7,12 @@ import (
|
|||
"github.com/khlieng/dispatch/pkg/session"
|
||||
)
|
||||
|
||||
var Path directory
|
||||
var (
|
||||
Path directory
|
||||
|
||||
GetMessageStore MessageStoreCreator
|
||||
GetMessageSearchProvider MessageSearchProviderCreator
|
||||
)
|
||||
|
||||
func Initialize(root, dataRoot, configRoot string) {
|
||||
if root != DefaultDirectory() {
|
||||
|
@ -52,13 +57,18 @@ type SessionStore interface {
|
|||
|
||||
type MessageStore interface {
|
||||
LogMessage(message *Message) error
|
||||
LogMessages(messages []*Message) error
|
||||
GetMessages(server, channel string, count int, fromID string) ([]Message, bool, error)
|
||||
GetMessagesByID(server, channel string, ids []string) ([]Message, error)
|
||||
Close()
|
||||
}
|
||||
|
||||
type MessageStoreCreator func(*User) (MessageStore, error)
|
||||
|
||||
type MessageSearchProvider interface {
|
||||
SearchMessages(server, channel, q string) ([]string, error)
|
||||
Index(id string, message *Message) error
|
||||
Close()
|
||||
}
|
||||
|
||||
type MessageSearchProviderCreator func(*User) (MessageSearchProvider, error)
|
||||
|
|
|
@ -25,7 +25,6 @@ struct Server {
|
|||
struct Channel {
|
||||
Server string
|
||||
Name string
|
||||
Topic string
|
||||
}
|
||||
|
||||
struct Message {
|
||||
|
@ -33,4 +32,11 @@ struct Message {
|
|||
From string
|
||||
Content string
|
||||
Time int64
|
||||
Events []Event
|
||||
}
|
||||
|
||||
struct Event {
|
||||
Type string
|
||||
Params []string
|
||||
Time int64
|
||||
}
|
||||
|
|
|
@ -791,21 +791,6 @@ func (d *Channel) Size() (s uint64) {
|
|||
}
|
||||
s += l
|
||||
}
|
||||
{
|
||||
l := uint64(len(d.Topic))
|
||||
|
||||
{
|
||||
|
||||
t := l
|
||||
for t >= 0x80 {
|
||||
t >>= 7
|
||||
s++
|
||||
}
|
||||
s++
|
||||
|
||||
}
|
||||
s += l
|
||||
}
|
||||
return
|
||||
}
|
||||
func (d *Channel) Marshal(buf []byte) ([]byte, error) {
|
||||
|
@ -857,25 +842,6 @@ func (d *Channel) Marshal(buf []byte) ([]byte, error) {
|
|||
copy(buf[i+0:], d.Name)
|
||||
i += l
|
||||
}
|
||||
{
|
||||
l := uint64(len(d.Topic))
|
||||
|
||||
{
|
||||
|
||||
t := uint64(l)
|
||||
|
||||
for t >= 0x80 {
|
||||
buf[i+0] = byte(t) | 0x80
|
||||
t >>= 7
|
||||
i++
|
||||
}
|
||||
buf[i+0] = byte(t)
|
||||
i++
|
||||
|
||||
}
|
||||
copy(buf[i+0:], d.Topic)
|
||||
i += l
|
||||
}
|
||||
return buf[:i+0], nil
|
||||
}
|
||||
|
||||
|
@ -922,26 +888,6 @@ func (d *Channel) Unmarshal(buf []byte) (uint64, error) {
|
|||
d.Name = string(buf[i+0 : i+0+l])
|
||||
i += l
|
||||
}
|
||||
{
|
||||
l := uint64(0)
|
||||
|
||||
{
|
||||
|
||||
bs := uint8(7)
|
||||
t := uint64(buf[i+0] & 0x7F)
|
||||
for buf[i+0]&0x80 == 0x80 {
|
||||
i++
|
||||
t |= uint64(buf[i+0]&0x7F) << bs
|
||||
bs += 7
|
||||
}
|
||||
i++
|
||||
|
||||
l = t
|
||||
|
||||
}
|
||||
d.Topic = string(buf[i+0 : i+0+l])
|
||||
i += l
|
||||
}
|
||||
return i + 0, nil
|
||||
}
|
||||
|
||||
|
@ -992,6 +938,29 @@ func (d *Message) Size() (s uint64) {
|
|||
}
|
||||
s += l
|
||||
}
|
||||
{
|
||||
l := uint64(len(d.Events))
|
||||
|
||||
{
|
||||
|
||||
t := l
|
||||
for t >= 0x80 {
|
||||
t >>= 7
|
||||
s++
|
||||
}
|
||||
s++
|
||||
|
||||
}
|
||||
|
||||
for k0 := range d.Events {
|
||||
|
||||
{
|
||||
s += d.Events[k0].Size()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
s += 8
|
||||
return
|
||||
}
|
||||
|
@ -1068,6 +1037,34 @@ func (d *Message) Marshal(buf []byte) ([]byte, error) {
|
|||
*(*int64)(unsafe.Pointer(&buf[i+0])) = d.Time
|
||||
|
||||
}
|
||||
{
|
||||
l := uint64(len(d.Events))
|
||||
|
||||
{
|
||||
|
||||
t := uint64(l)
|
||||
|
||||
for t >= 0x80 {
|
||||
buf[i+8] = byte(t) | 0x80
|
||||
t >>= 7
|
||||
i++
|
||||
}
|
||||
buf[i+8] = byte(t)
|
||||
i++
|
||||
|
||||
}
|
||||
for k0 := range d.Events {
|
||||
|
||||
{
|
||||
nbuf, err := d.Events[k0].Marshal(buf[i+8:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i += uint64(len(nbuf))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return buf[:i+8], nil
|
||||
}
|
||||
|
||||
|
@ -1138,6 +1135,251 @@ func (d *Message) Unmarshal(buf []byte) (uint64, error) {
|
|||
|
||||
d.Time = *(*int64)(unsafe.Pointer(&buf[i+0]))
|
||||
|
||||
}
|
||||
{
|
||||
l := uint64(0)
|
||||
|
||||
{
|
||||
|
||||
bs := uint8(7)
|
||||
t := uint64(buf[i+8] & 0x7F)
|
||||
for buf[i+8]&0x80 == 0x80 {
|
||||
i++
|
||||
t |= uint64(buf[i+8]&0x7F) << bs
|
||||
bs += 7
|
||||
}
|
||||
i++
|
||||
|
||||
l = t
|
||||
|
||||
}
|
||||
if uint64(cap(d.Events)) >= l {
|
||||
d.Events = d.Events[:l]
|
||||
} else {
|
||||
d.Events = make([]Event, l)
|
||||
}
|
||||
for k0 := range d.Events {
|
||||
|
||||
{
|
||||
ni, err := d.Events[k0].Unmarshal(buf[i+8:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += ni
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return i + 8, nil
|
||||
}
|
||||
|
||||
func (d *Event) Size() (s uint64) {
|
||||
|
||||
{
|
||||
l := uint64(len(d.Type))
|
||||
|
||||
{
|
||||
|
||||
t := l
|
||||
for t >= 0x80 {
|
||||
t >>= 7
|
||||
s++
|
||||
}
|
||||
s++
|
||||
|
||||
}
|
||||
s += l
|
||||
}
|
||||
{
|
||||
l := uint64(len(d.Params))
|
||||
|
||||
{
|
||||
|
||||
t := l
|
||||
for t >= 0x80 {
|
||||
t >>= 7
|
||||
s++
|
||||
}
|
||||
s++
|
||||
|
||||
}
|
||||
|
||||
for k0 := range d.Params {
|
||||
|
||||
{
|
||||
l := uint64(len(d.Params[k0]))
|
||||
|
||||
{
|
||||
|
||||
t := l
|
||||
for t >= 0x80 {
|
||||
t >>= 7
|
||||
s++
|
||||
}
|
||||
s++
|
||||
|
||||
}
|
||||
s += l
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
s += 8
|
||||
return
|
||||
}
|
||||
func (d *Event) Marshal(buf []byte) ([]byte, error) {
|
||||
size := d.Size()
|
||||
{
|
||||
if uint64(cap(buf)) >= size {
|
||||
buf = buf[:size]
|
||||
} else {
|
||||
buf = make([]byte, size)
|
||||
}
|
||||
}
|
||||
i := uint64(0)
|
||||
|
||||
{
|
||||
l := uint64(len(d.Type))
|
||||
|
||||
{
|
||||
|
||||
t := uint64(l)
|
||||
|
||||
for t >= 0x80 {
|
||||
buf[i+0] = byte(t) | 0x80
|
||||
t >>= 7
|
||||
i++
|
||||
}
|
||||
buf[i+0] = byte(t)
|
||||
i++
|
||||
|
||||
}
|
||||
copy(buf[i+0:], d.Type)
|
||||
i += l
|
||||
}
|
||||
{
|
||||
l := uint64(len(d.Params))
|
||||
|
||||
{
|
||||
|
||||
t := uint64(l)
|
||||
|
||||
for t >= 0x80 {
|
||||
buf[i+0] = byte(t) | 0x80
|
||||
t >>= 7
|
||||
i++
|
||||
}
|
||||
buf[i+0] = byte(t)
|
||||
i++
|
||||
|
||||
}
|
||||
for k0 := range d.Params {
|
||||
|
||||
{
|
||||
l := uint64(len(d.Params[k0]))
|
||||
|
||||
{
|
||||
|
||||
t := uint64(l)
|
||||
|
||||
for t >= 0x80 {
|
||||
buf[i+0] = byte(t) | 0x80
|
||||
t >>= 7
|
||||
i++
|
||||
}
|
||||
buf[i+0] = byte(t)
|
||||
i++
|
||||
|
||||
}
|
||||
copy(buf[i+0:], d.Params[k0])
|
||||
i += l
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
*(*int64)(unsafe.Pointer(&buf[i+0])) = d.Time
|
||||
|
||||
}
|
||||
return buf[:i+8], nil
|
||||
}
|
||||
|
||||
func (d *Event) Unmarshal(buf []byte) (uint64, error) {
|
||||
i := uint64(0)
|
||||
|
||||
{
|
||||
l := uint64(0)
|
||||
|
||||
{
|
||||
|
||||
bs := uint8(7)
|
||||
t := uint64(buf[i+0] & 0x7F)
|
||||
for buf[i+0]&0x80 == 0x80 {
|
||||
i++
|
||||
t |= uint64(buf[i+0]&0x7F) << bs
|
||||
bs += 7
|
||||
}
|
||||
i++
|
||||
|
||||
l = t
|
||||
|
||||
}
|
||||
d.Type = string(buf[i+0 : i+0+l])
|
||||
i += l
|
||||
}
|
||||
{
|
||||
l := uint64(0)
|
||||
|
||||
{
|
||||
|
||||
bs := uint8(7)
|
||||
t := uint64(buf[i+0] & 0x7F)
|
||||
for buf[i+0]&0x80 == 0x80 {
|
||||
i++
|
||||
t |= uint64(buf[i+0]&0x7F) << bs
|
||||
bs += 7
|
||||
}
|
||||
i++
|
||||
|
||||
l = t
|
||||
|
||||
}
|
||||
if uint64(cap(d.Params)) >= l {
|
||||
d.Params = d.Params[:l]
|
||||
} else {
|
||||
d.Params = make([]string, l)
|
||||
}
|
||||
for k0 := range d.Params {
|
||||
|
||||
{
|
||||
l := uint64(0)
|
||||
|
||||
{
|
||||
|
||||
bs := uint8(7)
|
||||
t := uint64(buf[i+0] & 0x7F)
|
||||
for buf[i+0]&0x80 == 0x80 {
|
||||
i++
|
||||
t |= uint64(buf[i+0]&0x7F) << bs
|
||||
bs += 7
|
||||
}
|
||||
i++
|
||||
|
||||
l = t
|
||||
|
||||
}
|
||||
d.Params[k0] = string(buf[i+0 : i+0+l])
|
||||
i += l
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
d.Time = *(*int64)(unsafe.Pointer(&buf[i+0]))
|
||||
|
||||
}
|
||||
return i + 8, nil
|
||||
}
|
||||
|
|
173
storage/user.go
173
storage/user.go
|
@ -5,6 +5,8 @@ import (
|
|||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/kjk/betterguid"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
|
@ -15,6 +17,7 @@ type User struct {
|
|||
store Store
|
||||
messageLog MessageStore
|
||||
messageIndex MessageSearchProvider
|
||||
lastMessages map[string]map[string]*Message
|
||||
clientSettings *ClientSettings
|
||||
lastIP []byte
|
||||
certificate *tls.Certificate
|
||||
|
@ -25,6 +28,7 @@ func NewUser(store Store) (*User, error) {
|
|||
user := &User{
|
||||
store: store,
|
||||
clientSettings: DefaultClientSettings(),
|
||||
lastMessages: map[string]map[string]*Message{},
|
||||
}
|
||||
|
||||
err := store.SaveUser(user)
|
||||
|
@ -32,11 +36,19 @@ func NewUser(store Store) (*User, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
user.messageLog, err = GetMessageStore(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.messageIndex, err = GetMessageSearchProvider(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = os.MkdirAll(Path.User(user.Username), 0700)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = os.Mkdir(Path.Downloads(user.Username), 0700)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -53,20 +65,35 @@ func LoadUsers(store Store) ([]*User, error) {
|
|||
|
||||
for _, user := range users {
|
||||
user.store = store
|
||||
user.messageLog, err = GetMessageStore(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.messageIndex, err = GetMessageSearchProvider(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.lastMessages = map[string]map[string]*Message{}
|
||||
user.loadCertificate()
|
||||
|
||||
channels, err := user.GetChannels()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, channel := range channels {
|
||||
messages, _, err := user.GetLastMessages(channel.Server, channel.Name, 1)
|
||||
if err == nil && len(messages) == 1 {
|
||||
user.lastMessages[channel.Server] = map[string]*Message{
|
||||
channel.Name: &messages[0],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (u *User) SetMessageStore(store MessageStore) {
|
||||
u.messageLog = store
|
||||
}
|
||||
|
||||
func (u *User) SetMessageSearchProvider(search MessageSearchProvider) {
|
||||
u.messageIndex = search
|
||||
}
|
||||
|
||||
func (u *User) Remove() {
|
||||
u.store.DeleteUser(u)
|
||||
if u.messageLog != nil {
|
||||
|
@ -178,7 +205,6 @@ func (u *User) SetServerName(name, address string) error {
|
|||
return u.AddServer(server)
|
||||
}
|
||||
|
||||
// TODO: Remove topic from disk schema
|
||||
type Channel struct {
|
||||
Server string
|
||||
Name string
|
||||
|
@ -215,33 +241,128 @@ func (u *User) RemoveOpenDM(server, nick string) error {
|
|||
}
|
||||
|
||||
type Message struct {
|
||||
ID string `json:"-" bleve:"-"`
|
||||
Server string `json:"-" bleve:"server"`
|
||||
From string `bleve:"-"`
|
||||
To string `json:"-" bleve:"to"`
|
||||
Content string `bleve:"content"`
|
||||
Time int64 `bleve:"-"`
|
||||
ID string `json:"-" bleve:"-"`
|
||||
Server string `json:"-" bleve:"server"`
|
||||
From string `bleve:"-"`
|
||||
To string `json:"-" bleve:"to"`
|
||||
Content string `bleve:"content"`
|
||||
Time int64 `bleve:"-"`
|
||||
Events []Event `bleve:"-"`
|
||||
}
|
||||
|
||||
func (m Message) Type() string {
|
||||
return "message"
|
||||
}
|
||||
|
||||
func (u *User) LogMessage(id, server, from, to, content string) error {
|
||||
message := &Message{
|
||||
ID: id,
|
||||
Server: server,
|
||||
From: from,
|
||||
To: to,
|
||||
Content: content,
|
||||
Time: time.Now().Unix(),
|
||||
func (u *User) LogMessage(msg *Message) error {
|
||||
if msg.Time == 0 {
|
||||
msg.Time = time.Now().Unix()
|
||||
}
|
||||
|
||||
err := u.messageLog.LogMessage(message)
|
||||
if msg.ID == "" {
|
||||
msg.ID = betterguid.New()
|
||||
}
|
||||
|
||||
if msg.To == "" {
|
||||
msg.To = msg.From
|
||||
}
|
||||
|
||||
u.setLastMessage(msg.Server, msg.To, msg)
|
||||
|
||||
err := u.messageLog.LogMessage(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return u.messageIndex.Index(id, message)
|
||||
return u.messageIndex.Index(msg.ID, msg)
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
Type string
|
||||
Params []string
|
||||
Time int64
|
||||
}
|
||||
|
||||
func (u *User) LogEvent(server, name string, params []string, channels ...string) error {
|
||||
now := time.Now().Unix()
|
||||
event := Event{
|
||||
Type: name,
|
||||
Params: params,
|
||||
Time: now,
|
||||
}
|
||||
|
||||
for _, channel := range channels {
|
||||
lastMessage := u.getLastMessage(server, channel)
|
||||
|
||||
if lastMessage != nil && shouldCollapse(lastMessage, event) {
|
||||
lastMessage.Events = append(lastMessage.Events, event)
|
||||
u.setLastMessage(server, channel, lastMessage)
|
||||
|
||||
err := u.messageLog.LogMessage(lastMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
msg := &Message{
|
||||
ID: betterguid.New(),
|
||||
Server: server,
|
||||
To: channel,
|
||||
Time: now,
|
||||
Events: []Event{event},
|
||||
}
|
||||
u.setLastMessage(server, channel, msg)
|
||||
|
||||
err := u.messageLog.LogMessage(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var collapsed = []string{"join", "part", "quit"}
|
||||
|
||||
func shouldCollapse(msg *Message, event Event) bool {
|
||||
matches := 0
|
||||
if len(msg.Events) > 0 {
|
||||
for _, collapseType := range collapsed {
|
||||
if msg.Events[0].Type == collapseType {
|
||||
matches++
|
||||
}
|
||||
if event.Type == collapseType {
|
||||
matches++
|
||||
}
|
||||
}
|
||||
}
|
||||
return matches == 2
|
||||
}
|
||||
|
||||
func (u *User) getLastMessage(server, channel string) *Message {
|
||||
u.lock.Lock()
|
||||
defer u.lock.Unlock()
|
||||
|
||||
if _, ok := u.lastMessages[server]; !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
last := u.lastMessages[server][channel]
|
||||
if last != nil {
|
||||
msg := *last
|
||||
return &msg
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) setLastMessage(server, channel string, msg *Message) {
|
||||
u.lock.Lock()
|
||||
|
||||
if _, ok := u.lastMessages[server]; !ok {
|
||||
u.lastMessages[server] = map[string]*Message{}
|
||||
}
|
||||
|
||||
u.lastMessages[server][channel] = msg
|
||||
u.lock.Unlock()
|
||||
}
|
||||
|
||||
func (u *User) GetMessages(server, channel string, count int, fromID string) ([]Message, bool, error) {
|
||||
|
|
|
@ -24,6 +24,13 @@ func TestUser(t *testing.T) {
|
|||
db, err := boltdb.New(storage.Path.Database())
|
||||
assert.Nil(t, err)
|
||||
|
||||
storage.GetMessageStore = func(_ *storage.User) (storage.MessageStore, error) {
|
||||
return db, nil
|
||||
}
|
||||
storage.GetMessageSearchProvider = func(_ *storage.User) (storage.MessageSearchProvider, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
user, err := storage.NewUser(db)
|
||||
assert.Nil(t, err)
|
||||
|
||||
|
@ -124,17 +131,18 @@ func TestMessages(t *testing.T) {
|
|||
db, err := boltdb.New(storage.Path.Database())
|
||||
assert.Nil(t, err)
|
||||
|
||||
storage.GetMessageStore = func(_ *storage.User) (storage.MessageStore, error) {
|
||||
return db, nil
|
||||
}
|
||||
storage.GetMessageSearchProvider = func(user *storage.User) (storage.MessageSearchProvider, error) {
|
||||
return bleve.New(storage.Path.Index(user.Username))
|
||||
}
|
||||
|
||||
user, err := storage.NewUser(db)
|
||||
assert.Nil(t, err)
|
||||
|
||||
os.MkdirAll(storage.Path.User(user.Username), 0700)
|
||||
|
||||
search, err := bleve.New(storage.Path.Index(user.Username))
|
||||
assert.Nil(t, err)
|
||||
|
||||
user.SetMessageStore(db)
|
||||
user.SetMessageSearchProvider(search)
|
||||
|
||||
messages, hasMore, err := user.GetMessages("irc.freenode.net", "#go-nuts", 10, "6")
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, hasMore)
|
||||
|
@ -153,7 +161,13 @@ func TestMessages(t *testing.T) {
|
|||
for i := 0; i < 5; i++ {
|
||||
id := betterguid.New()
|
||||
ids = append(ids, id)
|
||||
err = user.LogMessage(id, "irc.freenode.net", "nick", "#go-nuts", "message"+strconv.Itoa(i))
|
||||
err = user.LogMessage(&storage.Message{
|
||||
ID: id,
|
||||
Server: "irc.freenode.net",
|
||||
From: "nick",
|
||||
To: "#go-nuts",
|
||||
Content: "message" + strconv.Itoa(i),
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
|
@ -196,5 +210,42 @@ func TestMessages(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
assert.True(t, len(messages) > 0)
|
||||
|
||||
user.LogEvent("irc.freenode.net", "join", []string{"bob"}, "#go-nuts")
|
||||
messages, hasMore, err = user.GetLastMessages("irc.freenode.net", "#go-nuts", 1)
|
||||
assert.Zero(t, messages[0].Content)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, hasMore)
|
||||
assert.Len(t, messages[0].Events, 1)
|
||||
assert.Equal(t, "join", messages[0].Events[0].Type)
|
||||
assert.NotZero(t, messages[0].Events[0].Time)
|
||||
|
||||
user.LogEvent("irc.freenode.net", "part", []string{"bob"}, "#go-nuts")
|
||||
messages, hasMore, err = user.GetLastMessages("irc.freenode.net", "#go-nuts", 1)
|
||||
assert.Zero(t, messages[0].Content)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, hasMore)
|
||||
assert.Len(t, messages[0].Events, 2)
|
||||
assert.Equal(t, "part", messages[0].Events[1].Type)
|
||||
assert.NotZero(t, messages[0].Events[0].Time)
|
||||
|
||||
user.LogEvent("irc.freenode.net", "nick", []string{"bob", "rob"}, "#go-nuts")
|
||||
messages, hasMore, err = user.GetLastMessages("irc.freenode.net", "#go-nuts", 1)
|
||||
assert.Zero(t, messages[0].Content)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, hasMore)
|
||||
assert.Len(t, messages[0].Events, 1)
|
||||
assert.Equal(t, "nick", messages[0].Events[0].Type)
|
||||
assert.NotZero(t, messages[0].Events[0].Time)
|
||||
|
||||
user.LogEvent("irc.freenode.net", "quit", []string{"rob", "bored"}, "#go-nuts")
|
||||
messages, hasMore, err = user.GetLastMessages("irc.freenode.net", "#go-nuts", 1)
|
||||
assert.Zero(t, messages[0].Content)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, hasMore)
|
||||
assert.Len(t, messages[0].Events, 1)
|
||||
assert.Equal(t, "quit", messages[0].Events[0].Type)
|
||||
assert.Equal(t, []string{"rob", "bored"}, messages[0].Events[0].Params)
|
||||
assert.NotZero(t, messages[0].Events[0].Time)
|
||||
|
||||
db.Close()
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue