Unbreak things

This commit is contained in:
Tulir Asokan 2019-06-15 01:11:51 +03:00
parent a4ac699c93
commit a55ea42d7f
26 changed files with 985 additions and 568 deletions

View File

@ -53,15 +53,19 @@ type Config struct {
AccessToken string `yaml:"access_token"` AccessToken string `yaml:"access_token"`
HS string `yaml:"homeserver"` HS string `yaml:"homeserver"`
RoomCacheSize int `yaml:"room_cache_size"`
RoomCacheAge int64 `yaml:"room_cache_age"`
Dir string `yaml:"-"` Dir string `yaml:"-"`
CacheDir string `yaml:"cache_dir"` CacheDir string `yaml:"cache_dir"`
HistoryPath string `yaml:"history_path"` HistoryPath string `yaml:"history_path"`
RoomListPath string `yaml:"room_list_path"`
MediaDir string `yaml:"media_dir"` MediaDir string `yaml:"media_dir"`
StateDir string `yaml:"state_dir"` StateDir string `yaml:"state_dir"`
Preferences UserPreferences `yaml:"-"` Preferences UserPreferences `yaml:"-"`
AuthCache AuthCache `yaml:"-"` AuthCache AuthCache `yaml:"-"`
Rooms map[string]*rooms.Room `yaml:"-"` Rooms *rooms.RoomCache `yaml:"-"`
PushRules *pushrules.PushRuleset `yaml:"-"` PushRules *pushrules.PushRuleset `yaml:"-"`
nosave bool nosave bool
@ -73,33 +77,36 @@ func NewConfig(configDir, cacheDir string) *Config {
Dir: configDir, Dir: configDir,
CacheDir: cacheDir, CacheDir: cacheDir,
HistoryPath: filepath.Join(cacheDir, "history.db"), HistoryPath: filepath.Join(cacheDir, "history.db"),
RoomListPath: filepath.Join(cacheDir, "rooms.gob.gz"),
StateDir: filepath.Join(cacheDir, "state"), StateDir: filepath.Join(cacheDir, "state"),
MediaDir: filepath.Join(cacheDir, "media"), MediaDir: filepath.Join(cacheDir, "media"),
Rooms: make(map[string]*rooms.Room), RoomCacheSize: 32,
RoomCacheAge: 1 * 60,
} }
} }
// Clear clears the session cache and removes all history. // Clear clears the session cache and removes all history.
func (config *Config) Clear() { func (config *Config) Clear() {
os.Remove(config.HistoryPath) _ = os.Remove(config.HistoryPath)
os.RemoveAll(config.StateDir) _ = os.Remove(config.RoomListPath)
os.RemoveAll(config.MediaDir) _ = os.RemoveAll(config.StateDir)
os.RemoveAll(config.CacheDir) _ = os.RemoveAll(config.MediaDir)
_ = os.RemoveAll(config.CacheDir)
config.nosave = true config.nosave = true
} }
func (config *Config) CreateCacheDirs() { func (config *Config) CreateCacheDirs() {
os.MkdirAll(config.CacheDir, 0700) _ = os.MkdirAll(config.CacheDir, 0700)
os.MkdirAll(config.StateDir, 0700) _ = os.MkdirAll(config.StateDir, 0700)
os.MkdirAll(config.MediaDir, 0700) _ = os.MkdirAll(config.MediaDir, 0700)
} }
func (config *Config) DeleteSession() { func (config *Config) DeleteSession() {
config.AuthCache.NextBatch = "" config.AuthCache.NextBatch = ""
config.AuthCache.InitialSyncDone = false config.AuthCache.InitialSyncDone = false
config.AccessToken = "" config.AccessToken = ""
config.Rooms = make(map[string]*rooms.Room) config.Rooms = rooms.NewRoomCache(config.RoomListPath, config.StateDir, config.RoomCacheSize, config.RoomCacheAge, config.GetUserID)
config.PushRules = nil config.PushRules = nil
config.Clear() config.Clear()
@ -109,10 +116,14 @@ func (config *Config) DeleteSession() {
func (config *Config) LoadAll() { func (config *Config) LoadAll() {
config.Load() config.Load()
config.Rooms = rooms.NewRoomCache(config.RoomListPath, config.StateDir, config.RoomCacheSize, config.RoomCacheAge, config.GetUserID)
config.LoadAuthCache() config.LoadAuthCache()
config.LoadPushRules() config.LoadPushRules()
config.LoadPreferences() config.LoadPreferences()
config.LoadRooms() err := config.Rooms.LoadList()
if err != nil {
panic(err)
}
} }
// Load loads the config from config.yaml in the directory given to the config struct. // Load loads the config from config.yaml in the directory given to the config struct.
@ -126,7 +137,11 @@ func (config *Config) SaveAll() {
config.SaveAuthCache() config.SaveAuthCache()
config.SavePushRules() config.SavePushRules()
config.SavePreferences() config.SavePreferences()
config.SaveRooms() err := config.Rooms.SaveList()
if err != nil {
panic(err)
}
config.Rooms.SaveLoadedRooms()
} }
// Save saves this config to config.yaml in the directory given to the config struct. // Save saves this config to config.yaml in the directory given to the config struct.
@ -161,48 +176,13 @@ func (config *Config) SavePushRules() {
config.save("push rules", config.CacheDir, "pushrules.json", &config.PushRules) config.save("push rules", config.CacheDir, "pushrules.json", &config.PushRules)
} }
func (config *Config) LoadRooms() { func (config *Config) load(name, dir, file string, target interface{}) {
os.MkdirAll(config.StateDir, 0700) err := os.MkdirAll(dir, 0700)
roomFiles, err := ioutil.ReadDir(config.StateDir)
if err != nil { if err != nil {
debug.Print("Failed to list rooms state caches in", config.StateDir) debug.Print("Failed to create", dir)
panic(err) panic(err)
} }
for _, roomFile := range roomFiles {
if roomFile.IsDir() || !strings.HasSuffix(roomFile.Name(), ".gmxstate") {
continue
}
path := filepath.Join(config.StateDir, roomFile.Name())
room := &rooms.Room{}
err = room.Load(path)
if err != nil {
debug.Printf("Failed to load room state cache from %s: %v", path, err)
continue
}
config.Rooms[room.ID] = room
}
}
func (config *Config) SaveRooms() {
if config.nosave {
return
}
os.MkdirAll(config.StateDir, 0700)
for _, room := range config.Rooms {
path := config.getRoomCachePath(room)
err := room.Save(path)
if err != nil {
debug.Printf("Failed to save room state cache to file %s: %v", path, err)
}
}
}
func (config *Config) load(name, dir, file string, target interface{}) {
os.MkdirAll(dir, 0700)
path := filepath.Join(dir, file) path := filepath.Join(dir, file)
data, err := ioutil.ReadFile(path) data, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
@ -229,9 +209,12 @@ func (config *Config) save(name, dir, file string, source interface{}) {
return return
} }
os.MkdirAll(dir, 0700) err := os.MkdirAll(dir, 0700)
if err != nil {
debug.Print("Failed to create", dir)
panic(err)
}
var data []byte var data []byte
var err error
if strings.HasSuffix(file, ".yaml") { if strings.HasSuffix(file, ".yaml") {
data, err = yaml.Marshal(source) data, err = yaml.Marshal(source)
} else { } else {
@ -272,30 +255,14 @@ func (config *Config) LoadNextBatch(_ string) string {
return config.AuthCache.NextBatch return config.AuthCache.NextBatch
} }
func (config *Config) GetRoom(roomID string) *rooms.Room {
room, _ := config.Rooms[roomID]
if room == nil {
room = rooms.NewRoom(roomID, config.UserID)
config.Rooms[room.ID] = room
}
return room
}
func (config *Config) getRoomCachePath(room *rooms.Room) string {
return filepath.Join(config.StateDir, room.ID+".gmxstate")
}
func (config *Config) PutRoom(room *rooms.Room) {
config.Rooms[room.ID] = room
room.Save(config.getRoomCachePath(room))
}
func (config *Config) SaveRoom(room *mautrix.Room) { func (config *Config) SaveRoom(room *mautrix.Room) {
gmxRoom := config.GetRoom(room.ID) panic("SaveRoom is not supported")
gmxRoom.Room = room
gmxRoom.Save(config.getRoomCachePath(gmxRoom))
} }
func (config *Config) LoadRoom(roomID string) *mautrix.Room { func (config *Config) LoadRoom(roomID string) *mautrix.Room {
return config.GetRoom(roomID).Room panic("LoadRoom is not supported")
}
func (config *Config) GetRoom(roomID string) *rooms.Room {
return config.Rooms.GetOrCreate(roomID)
} }

9
go.mod
View File

@ -14,6 +14,7 @@ require (
github.com/mattn/go-runewidth v0.0.4 github.com/mattn/go-runewidth v0.0.4
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/pkg/errors v0.8.1
github.com/sasha-s/go-deadlock v0.2.0 github.com/sasha-s/go-deadlock v0.2.0
github.com/stretchr/testify v1.3.0 github.com/stretchr/testify v1.3.0
go.etcd.io/bbolt v1.3.2 go.etcd.io/bbolt v1.3.2
@ -24,9 +25,15 @@ require (
gopkg.in/russross/blackfriday.v2 v2.0.1 gopkg.in/russross/blackfriday.v2 v2.0.1
gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2 gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2
gopkg.in/yaml.v2 v2.2.2 gopkg.in/yaml.v2 v2.2.2
maunium.net/go/mautrix v0.1.0-alpha.3.0.20190606153009-ca5d9535b6cc maunium.net/go/mautrix v0.1.0-alpha.3.0.20190607192515-d505052a02ac
maunium.net/go/mauview v0.0.0-20190606152754-de9e0a754a5d maunium.net/go/mauview v0.0.0-20190606152754-de9e0a754a5d
maunium.net/go/tcell v0.0.0-20190606152714-9a88fc07b3ed maunium.net/go/tcell v0.0.0-20190606152714-9a88fc07b3ed
) )
replace gopkg.in/russross/blackfriday.v2 => github.com/russross/blackfriday/v2 v2.0.1 replace gopkg.in/russross/blackfriday.v2 => github.com/russross/blackfriday/v2 v2.0.1
replace (
maunium.net/go/mautrix => ../mautrix-go
maunium.net/go/mauview => ../../Go/mauview
maunium.net/go/tcell => ../../Go/tcell
)

4
go.sum
View File

@ -44,6 +44,8 @@ github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.0.0-20190513083848-b9f5b9457d44 h1:XKCbzPvK4/BbMXoMJOkYP2ANxiAEO0HM1xn6psSbXxY= github.com/rivo/uniseg v0.0.0-20190513083848-b9f5b9457d44 h1:XKCbzPvK4/BbMXoMJOkYP2ANxiAEO0HM1xn6psSbXxY=
@ -90,6 +92,8 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
maunium.net/go/mautrix v0.1.0-alpha.3.0.20190606153009-ca5d9535b6cc h1:G7Nse6r/XaCu+p7yc/3m/nfFuOFZZ87Hb3AOX4INOEk= maunium.net/go/mautrix v0.1.0-alpha.3.0.20190606153009-ca5d9535b6cc h1:G7Nse6r/XaCu+p7yc/3m/nfFuOFZZ87Hb3AOX4INOEk=
maunium.net/go/mautrix v0.1.0-alpha.3.0.20190606153009-ca5d9535b6cc/go.mod h1:O+QWJP3H7BZEzIBSrECKpnpRnEKBwaoWVEu/yZwVwxg= maunium.net/go/mautrix v0.1.0-alpha.3.0.20190606153009-ca5d9535b6cc/go.mod h1:O+QWJP3H7BZEzIBSrECKpnpRnEKBwaoWVEu/yZwVwxg=
maunium.net/go/mautrix v0.1.0-alpha.3.0.20190607192515-d505052a02ac h1:r0X7mMPcc8eJaCaHdbW9ibfCLe3EruuqZIH2FM8oLIs=
maunium.net/go/mautrix v0.1.0-alpha.3.0.20190607192515-d505052a02ac/go.mod h1:O+QWJP3H7BZEzIBSrECKpnpRnEKBwaoWVEu/yZwVwxg=
maunium.net/go/mauview v0.0.0-20190606152754-de9e0a754a5d h1:H4wZ4vMVnOh5QFsb4xZtssgpv3DDEkBRzQ8iyEg2fX0= maunium.net/go/mauview v0.0.0-20190606152754-de9e0a754a5d h1:H4wZ4vMVnOh5QFsb4xZtssgpv3DDEkBRzQ8iyEg2fX0=
maunium.net/go/mauview v0.0.0-20190606152754-de9e0a754a5d/go.mod h1:GL+akv58wNFzzX4IKLvryKx0F/AcYKHql35DiBzBc/w= maunium.net/go/mauview v0.0.0-20190606152754-de9e0a754a5d/go.mod h1:GL+akv58wNFzzX4IKLvryKx0F/AcYKHql35DiBzBc/w=
maunium.net/go/tcell v0.0.0-20190606152714-9a88fc07b3ed h1:sAcUrUZG2LFWBTkTtLKPQvHPHFM5d6huAhr5ZZuxtbQ= maunium.net/go/tcell v0.0.0-20190606152714-9a88fc07b3ed h1:sAcUrUZG2LFWBTkTtLKPQvHPHFM5d6huAhr5ZZuxtbQ=

View File

@ -81,13 +81,15 @@ func (gmx *Gomuks) StartAutosave() {
// Stop stops the Matrix syncer, the tview app and the autosave goroutine, // Stop stops the Matrix syncer, the tview app and the autosave goroutine,
// then saves everything and calls os.Exit(0). // then saves everything and calls os.Exit(0).
func (gmx *Gomuks) Stop() { func (gmx *Gomuks) Stop(save bool) {
debug.Print("Disconnecting from Matrix...") debug.Print("Disconnecting from Matrix...")
gmx.matrix.Stop() gmx.matrix.Stop()
debug.Print("Cleaning up UI...") debug.Print("Cleaning up UI...")
gmx.ui.Stop() gmx.ui.Stop()
gmx.stop <- true gmx.stop <- true
if save {
gmx.Save() gmx.Save()
}
os.Exit(0) os.Exit(0)
} }
@ -102,7 +104,7 @@ func (gmx *Gomuks) Start() {
signal.Notify(c, os.Interrupt, syscall.SIGTERM) signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() { go func() {
<-c <-c
gmx.Stop() gmx.Stop(true)
}() }()
go gmx.StartAutosave() go gmx.StartAutosave()

View File

@ -27,5 +27,5 @@ type Gomuks interface {
Config() *config.Config Config() *config.Config
Start() Start()
Stop() Stop(save bool)
} }

View File

@ -45,6 +45,7 @@ type MatrixContainer interface {
GetHistory(room *rooms.Room, limit int) ([]*mautrix.Event, error) GetHistory(room *rooms.Room, limit int) ([]*mautrix.Event, error)
GetEvent(room *rooms.Room, eventID string) (*mautrix.Event, error) GetEvent(room *rooms.Room, eventID string) (*mautrix.Event, error)
GetRoom(roomID string) *rooms.Room GetRoom(roomID string) *rooms.Room
GetOrCreateRoom(roomID string) *rooms.Room
Download(mxcURL string) ([]byte, string, string, error) Download(mxcURL string) ([]byte, string, string, error)
GetDownloadURL(homeserver, fileID string) string GetDownloadURL(homeserver, fileID string) string

View File

@ -43,14 +43,13 @@ type MainView interface {
GetRoom(roomID string) RoomView GetRoom(roomID string) RoomView
AddRoom(room *rooms.Room) AddRoom(room *rooms.Room)
RemoveRoom(room *rooms.Room) RemoveRoom(room *rooms.Room)
SetRooms(rooms map[string]*rooms.Room) SetRooms(rooms *rooms.RoomCache)
UpdateTags(room *rooms.Room) UpdateTags(room *rooms.Room)
SetTyping(roomID string, users []string) SetTyping(roomID string, users []string)
NotifyMessage(room *rooms.Room, message Message, should pushrules.PushActionArrayShould) NotifyMessage(room *rooms.Room, message Message, should pushrules.PushActionArrayShould)
InitialSyncDone()
} }
type RoomView interface { type RoomView interface {
@ -68,13 +67,10 @@ type RoomView interface {
type Message interface { type Message interface {
ID() string ID() string
TxnID() string Time() time.Time
SenderID() string
Timestamp() time.Time
NotificationSenderName() string NotificationSenderName() string
NotificationContent() string NotificationContent() string
SetState(state mautrix.OutgoingEventState)
SetIsHighlight(highlight bool) SetIsHighlight(highlight bool)
SetID(id string) SetID(id string)
} }

View File

@ -18,6 +18,7 @@ package matrix
import ( import (
"bytes" "bytes"
"compress/gzip"
"encoding/binary" "encoding/binary"
"encoding/gob" "encoding/gob"
@ -28,6 +29,10 @@ import (
"maunium.net/go/mautrix" "maunium.net/go/mautrix"
) )
func init() {
gob.Register(&mautrix.Event{})
}
type HistoryManager struct { type HistoryManager struct {
sync.Mutex sync.Mutex
@ -226,13 +231,27 @@ func btoi(b []byte) uint64 {
func marshalEvent(event *mautrix.Event) ([]byte, error) { func marshalEvent(event *mautrix.Event) ([]byte, error) {
var buf bytes.Buffer var buf bytes.Buffer
err := gob.NewEncoder(&buf).Encode(event) enc := gzip.NewWriter(&buf)
return buf.Bytes(), err if err := gob.NewEncoder(enc).Encode(event); err != nil {
_ = enc.Close()
return nil, err
} else if err := enc.Close(); err != nil {
return nil, err
}
return buf.Bytes(), nil
} }
func unmarshalEvent(data []byte) (*mautrix.Event, error) { func unmarshalEvent(data []byte) (*mautrix.Event, error) {
event := &mautrix.Event{} event := &mautrix.Event{}
return event, gob.NewDecoder(bytes.NewReader(data)).Decode(event) if cmpReader, err := gzip.NewReader(bytes.NewReader(data)); err != nil {
return nil, err
} else if err := gob.NewDecoder(cmpReader).Decode(event); err != nil {
_ = cmpReader.Close()
return nil, err
} else if err := cmpReader.Close(); err != nil {
return nil, err
}
return event, nil
} }
func put(streams, eventIDs *bolt.Bucket, event *mautrix.Event, key uint64) error { func put(streams, eventIDs *bolt.Bucket, event *mautrix.Event, key uint64) error {

View File

@ -204,6 +204,9 @@ func (c *Container) OnLogin() {
debug.Print("Initializing syncer") debug.Print("Initializing syncer")
c.syncer = NewGomuksSyncer(c.config) c.syncer = NewGomuksSyncer(c.config)
c.syncer.OnEventType(mautrix.EventMessage, c.HandleMessage) c.syncer.OnEventType(mautrix.EventMessage, c.HandleMessage)
// Just pass encrypted events as messages, they'll show up with an encryption unsupported message.
c.syncer.OnEventType(mautrix.EventEncrypted, c.HandleMessage)
c.syncer.OnEventType(mautrix.EventSticker, c.HandleMessage)
c.syncer.OnEventType(mautrix.StateAliases, c.HandleMessage) c.syncer.OnEventType(mautrix.StateAliases, c.HandleMessage)
c.syncer.OnEventType(mautrix.StateCanonicalAlias, c.HandleMessage) c.syncer.OnEventType(mautrix.StateCanonicalAlias, c.HandleMessage)
c.syncer.OnEventType(mautrix.StateTopic, c.HandleMessage) c.syncer.OnEventType(mautrix.StateTopic, c.HandleMessage)
@ -219,7 +222,7 @@ func (c *Container) OnLogin() {
debug.Print("Initial sync done") debug.Print("Initial sync done")
c.config.AuthCache.InitialSyncDone = true c.config.AuthCache.InitialSyncDone = true
c.config.SaveAuthCache() c.config.SaveAuthCache()
c.ui.MainView().InitialSyncDone() c.ui.MainView().SetRooms(c.config.Rooms)
c.ui.Render() c.ui.Render()
} }
c.client.Syncer = c.syncer c.client.Syncer = c.syncer
@ -274,8 +277,10 @@ func (c *Container) HandlePreferences(source EventSource, evt *mautrix.Event) {
return return
} }
debug.Print("Updated preferences:", orig, "->", c.config.Preferences) debug.Print("Updated preferences:", orig, "->", c.config.Preferences)
if c.config.AuthCache.InitialSyncDone {
c.ui.HandleNewPreferences() c.ui.HandleNewPreferences()
} }
}
func (c *Container) SendPreferencesToMatrix() { func (c *Container) SendPreferencesToMatrix() {
defer debug.Recover() defer debug.Recover()
@ -292,6 +297,17 @@ func (c *Container) HandleMessage(source EventSource, evt *mautrix.Event) {
if source&EventSourceLeave != 0 || source&EventSourceState != 0 { if source&EventSourceLeave != 0 || source&EventSourceState != 0 {
return return
} }
room := c.GetOrCreateRoom(evt.RoomID)
err := c.history.Append(room, []*mautrix.Event{evt})
if err != nil {
debug.Printf("Failed to add event %s to history: %v", evt.ID, err)
}
if !c.config.AuthCache.InitialSyncDone {
return
}
mainView := c.ui.MainView() mainView := c.ui.MainView()
roomView := mainView.GetRoom(evt.RoomID) roomView := mainView.GetRoom(evt.RoomID)
@ -300,16 +316,11 @@ func (c *Container) HandleMessage(source EventSource, evt *mautrix.Event) {
return return
} }
err := c.history.Append(roomView.MxRoom(), []*mautrix.Event{evt})
if err != nil {
debug.Printf("Failed to add event %s to history: %v", evt.ID, err)
}
// TODO switch to roomView.AddEvent // TODO switch to roomView.AddEvent
message := roomView.ParseEvent(evt) message := roomView.ParseEvent(evt)
if message != nil { if message != nil {
roomView.AddMessage(message) roomView.AddMessage(message)
roomView.MxRoom().LastReceivedMessage = message.Timestamp() roomView.MxRoom().LastReceivedMessage = message.Time()
if c.syncer.FirstSyncDone { if c.syncer.FirstSyncDone {
pushRules := c.PushRules().GetActions(roomView.MxRoom(), evt).Should() pushRules := c.PushRules().GetActions(roomView.MxRoom(), evt).Should()
mainView.NotifyMessage(roomView.MxRoom(), message, pushRules) mainView.NotifyMessage(roomView.MxRoom(), message, pushRules)
@ -350,11 +361,16 @@ func (c *Container) processOwnMembershipChange(evt *mautrix.Event) {
room := c.GetRoom(evt.RoomID) room := c.GetRoom(evt.RoomID)
switch membership { switch membership {
case "join": case "join":
if c.config.AuthCache.InitialSyncDone {
c.ui.MainView().AddRoom(room) c.ui.MainView().AddRoom(room)
}
room.HasLeft = false room.HasLeft = false
case "leave": case "leave":
if c.config.AuthCache.InitialSyncDone {
c.ui.MainView().RemoveRoom(room) c.ui.MainView().RemoveRoom(room)
}
room.HasLeft = true room.HasLeft = true
room.Unload()
case "invite": case "invite":
// TODO handle // TODO handle
debug.Printf("%s invited the user to %s", evt.Sender, evt.RoomID) debug.Printf("%s invited the user to %s", evt.Sender, evt.RoomID)
@ -399,9 +415,13 @@ func (c *Container) HandleReadReceipt(source EventSource, evt *mautrix.Event) {
} }
room := c.GetRoom(evt.RoomID) room := c.GetRoom(evt.RoomID)
if room != nil {
room.MarkRead(lastReadEvent) room.MarkRead(lastReadEvent)
if c.config.AuthCache.InitialSyncDone {
c.ui.Render() c.ui.Render()
} }
}
}
func (c *Container) parseDirectChatInfo(evt *mautrix.Event) map[*rooms.Room]bool { func (c *Container) parseDirectChatInfo(evt *mautrix.Event) map[*rooms.Room]bool {
directChats := make(map[*rooms.Room]bool) directChats := make(map[*rooms.Room]bool)
@ -428,14 +448,16 @@ func (c *Container) parseDirectChatInfo(evt *mautrix.Event) map[*rooms.Room]bool
func (c *Container) HandleDirectChatInfo(source EventSource, evt *mautrix.Event) { func (c *Container) HandleDirectChatInfo(source EventSource, evt *mautrix.Event) {
directChats := c.parseDirectChatInfo(evt) directChats := c.parseDirectChatInfo(evt)
for _, room := range c.config.Rooms { for _, room := range c.config.Rooms.Map {
shouldBeDirect := directChats[room] shouldBeDirect := directChats[room]
if shouldBeDirect != room.IsDirect { if shouldBeDirect != room.IsDirect {
room.IsDirect = shouldBeDirect room.IsDirect = shouldBeDirect
if c.config.AuthCache.InitialSyncDone {
c.ui.MainView().UpdateTags(room) c.ui.MainView().UpdateTags(room)
} }
} }
} }
}
// HandlePushRules is the event handler for the m.push_rules account data event. // HandlePushRules is the event handler for the m.push_rules account data event.
func (c *Container) HandlePushRules(source EventSource, evt *mautrix.Event) { func (c *Container) HandlePushRules(source EventSource, evt *mautrix.Event) {
@ -466,14 +488,19 @@ func (c *Container) HandleTag(source EventSource, evt *mautrix.Event) {
} }
index++ index++
} }
mainView := c.ui.MainView()
room.RawTags = newTags room.RawTags = newTags
if c.config.AuthCache.InitialSyncDone {
mainView := c.ui.MainView()
mainView.UpdateTags(room) mainView.UpdateTags(room)
} }
}
// HandleTyping is the event handler for the m.typing event. // HandleTyping is the event handler for the m.typing event.
func (c *Container) HandleTyping(source EventSource, evt *mautrix.Event) { func (c *Container) HandleTyping(source EventSource, evt *mautrix.Event) {
if !c.config.AuthCache.InitialSyncDone {
return
}
c.ui.MainView().SetTyping(evt.RoomID, evt.Content.TypingUserIDs) c.ui.MainView().SetTyping(evt.RoomID, evt.Content.TypingUserIDs)
} }
@ -544,7 +571,7 @@ func (c *Container) CreateRoom(req *mautrix.ReqCreateRoom) (*rooms.Room, error)
if err != nil { if err != nil {
return nil, err return nil, err
} }
room := c.GetRoom(resp.RoomID) room := c.GetOrCreateRoom(resp.RoomID)
return room, nil return room, nil
} }
@ -557,7 +584,6 @@ func (c *Container) JoinRoom(roomID, server string) (*rooms.Room, error) {
room := c.GetRoom(resp.RoomID) room := c.GetRoom(resp.RoomID)
room.HasLeft = false room.HasLeft = false
return room, nil return room, nil
} }
@ -568,8 +594,9 @@ func (c *Container) LeaveRoom(roomID string) error {
return err return err
} }
room := c.GetRoom(roomID) node := c.GetOrCreateRoom(roomID)
room.HasLeft = true node.HasLeft = true
node.Unload()
return nil return nil
} }
@ -593,9 +620,9 @@ func (c *Container) GetHistory(room *rooms.Room, limit int) ([]*mautrix.Event, e
return nil, err return nil, err
} }
} }
room.PrevBatch = resp.End
c.config.PutRoom(room)
debug.Printf("Loaded %d events for %s from server from %s to %s", len(resp.Chunk), room.ID, resp.Start, resp.End) debug.Printf("Loaded %d events for %s from server from %s to %s", len(resp.Chunk), room.ID, resp.Start, resp.End)
room.PrevBatch = resp.End
c.config.Rooms.Put(room)
return resp.Chunk, nil return resp.Chunk, nil
} }
@ -613,9 +640,14 @@ func (c *Container) GetEvent(room *rooms.Room, eventID string) (*mautrix.Event,
return event, nil return event, nil
} }
// GetOrCreateRoom gets the room instance stored in the session.
func (c *Container) GetOrCreateRoom(roomID string) *rooms.Room {
return c.config.Rooms.GetOrCreate(roomID)
}
// GetRoom gets the room instance stored in the session. // GetRoom gets the room instance stored in the session.
func (c *Container) GetRoom(roomID string) *rooms.Room { func (c *Container) GetRoom(roomID string) *rooms.Room {
return c.config.GetRoom(roomID) return c.config.Rooms.Get(roomID)
} }
var mxcRegex = regexp.MustCompile("mxc://(.+)/(.+)") var mxcRegex = regexp.MustCompile("mxc://(.+)/(.+)")
@ -642,7 +674,7 @@ func (c *Container) Download(mxcURL string) (data []byte, hs, id string, err err
} }
} }
data, err = c.download(hs, id, cacheFile) //FIXME data, err = c.download(hs, id, cacheFile)
return return
} }

View File

@ -17,6 +17,7 @@
package rooms package rooms
import ( import (
"compress/gzip"
"encoding/gob" "encoding/gob"
"fmt" "fmt"
"os" "os"
@ -31,17 +32,20 @@ import (
) )
func init() { func init() {
gob.Register([]interface{}{})
gob.Register(map[string]interface{}{}) gob.Register(map[string]interface{}{})
gob.Register([]interface{}{})
gob.Register(&Room{})
gob.Register(0)
} }
type RoomNameSource int type RoomNameSource int
const ( const (
ExplicitRoomName RoomNameSource = iota UnknownRoomName RoomNameSource = iota
CanonicalAliasRoomName
AliasRoomName
MemberRoomName MemberRoomName
AliasRoomName
CanonicalAliasRoomName
ExplicitRoomName
) )
// RoomTag is a tag given to a specific room. // RoomTag is a tag given to a specific room.
@ -60,7 +64,8 @@ type UnreadMessage struct {
// Room represents a single Matrix room. // Room represents a single Matrix room.
type Room struct { type Room struct {
*mautrix.Room // The room ID.
ID string
// Whether or not the user has left the room. // Whether or not the user has left the room.
HasLeft bool HasLeft bool
@ -79,19 +84,22 @@ type Room struct {
// Whether or not this room is marked as a direct chat. // Whether or not this room is marked as a direct chat.
IsDirect bool IsDirect bool
// List of tags given to this room // List of tags given to this room.
RawTags []RoomTag RawTags []RoomTag
// Timestamp of previously received actual message. // Timestamp of previously received actual message.
LastReceivedMessage time.Time LastReceivedMessage time.Time
// Room state cache.
state map[mautrix.EventType]map[string]*mautrix.Event
// MXID -> Member cache calculated from membership events. // MXID -> Member cache calculated from membership events.
memberCache map[string]*mautrix.Member memberCache map[string]*mautrix.Member
// The first non-SessionUserID member in the room. Calculated at // The first two non-SessionUserID members in the room. Calculated at
// the same time as memberCache. // the same time as memberCache.
firstMemberCache *mautrix.Member firstMemberCache *mautrix.Member
secondMemberCache *mautrix.Member
// The name of the room. Calculated from the state event name, // The name of the room. Calculated from the state event name,
// canonical_alias or alias or the member cache. // canonical_alias or alias or the member cache.
nameCache string NameCache string
// The event type from which the name cache was calculated from. // The event type from which the name cache was calculated from.
nameCacheSource RoomNameSource nameCacheSource RoomNameSource
// The topic of the room. Directly fetched from the m.room.topic state event. // The topic of the room. Directly fetched from the m.room.topic state event.
@ -101,31 +109,98 @@ type Room struct {
// The list of aliases. Directly fetched from the m.room.aliases state event. // The list of aliases. Directly fetched from the m.room.aliases state event.
aliasesCache []string aliasesCache []string
// Path for state store file.
path string
// Room cache object
cache *RoomCache
// Lock for state and other room stuff.
lock sync.RWMutex lock sync.RWMutex
// Room state cache linked list.
prev *Room
next *Room
touch int64
} }
func (room *Room) Load(path string) error { func debugPrintError(fn func() error, message string) {
file, err := os.OpenFile(path, os.O_RDONLY, 0600) if err := fn(); err != nil {
if err != nil { debug.Printf("%s: %v", message, err)
return err
} }
defer file.Close() }
dec := gob.NewDecoder(file)
func (room *Room) Loaded() bool {
return room.state != nil
}
func (room *Room) Load() {
if room.Loaded() {
return
}
room.cache.TouchNode(room)
room.lock.Lock() room.lock.Lock()
defer room.lock.Unlock() room.load()
return dec.Decode(room) room.lock.Unlock()
} }
func (room *Room) Save(path string) error { func (room *Room) load() {
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0600) if room.Loaded() {
if err != nil { return
return err
} }
defer file.Close() debug.Print("Loading state for room", room.ID)
enc := gob.NewEncoder(file) room.state = make(map[mautrix.EventType]map[string]*mautrix.Event)
file, err := os.OpenFile(room.path, os.O_RDONLY, 0600)
if err != nil {
if !os.IsNotExist(err) {
debug.Print("Failed to open room state file for reading:", err)
} else {
debug.Print("Room state file for", room.ID, "does not exist")
}
return
}
defer debugPrintError(file.Close, "Failed to close room state file after reading")
cmpReader, err := gzip.NewReader(file)
if err != nil {
debug.Print("Failed to open room state gzip reader:", err)
return
}
defer debugPrintError(cmpReader.Close, "Failed to close room state gzip reader")
dec := gob.NewDecoder(cmpReader)
if err = dec.Decode(&room.state); err != nil {
debug.Print("Failed to decode room state:", err)
}
}
func (room *Room) Unload() {
debug.Print("Unloading", room.ID)
room.Save()
room.state = nil
room.aliasesCache = nil
room.topicCache = ""
room.canonicalAliasCache = ""
room.firstMemberCache = nil
room.secondMemberCache = nil
}
func (room *Room) Save() {
if !room.Loaded() {
debug.Print("Failed to save room state: room not loaded")
return
}
debug.Print("Saving state for room", room.ID)
file, err := os.OpenFile(room.path, os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
debug.Print("Failed to open room state file for writing:", err)
return
}
defer debugPrintError(file.Close, "Failed to close room state file after writing")
cmpWriter := gzip.NewWriter(file)
defer debugPrintError(cmpWriter.Close, "Failed to close room state gzip writer")
enc := gob.NewEncoder(cmpWriter)
room.lock.RLock() room.lock.RLock()
defer room.lock.RUnlock() defer room.lock.RUnlock()
return enc.Encode(room) if err := enc.Encode(&room.state); err != nil {
debug.Print("Failed to encode room state:", err)
}
} }
// MarkRead clears the new message statuses on this room. // MarkRead clears the new message statuses on this room.
@ -220,30 +295,32 @@ func (room *Room) Tags() []RoomTag {
// UpdateState updates the room's current state with the given Event. This will clobber events based // UpdateState updates the room's current state with the given Event. This will clobber events based
// on the type/state_key combination. // on the type/state_key combination.
func (room *Room) UpdateState(event *mautrix.Event) { func (room *Room) UpdateState(event *mautrix.Event) {
room.Load()
room.lock.Lock() room.lock.Lock()
defer room.lock.Unlock() defer room.lock.Unlock()
_, exists := room.State[event.Type] _, exists := room.state[event.Type]
if !exists { if !exists {
room.State[event.Type] = make(map[string]*mautrix.Event) room.state[event.Type] = make(map[string]*mautrix.Event)
} }
switch event.Type { switch event.Type {
case mautrix.StateRoomName: case mautrix.StateRoomName:
room.nameCache = "" room.NameCache = ""
case mautrix.StateCanonicalAlias: case mautrix.StateCanonicalAlias:
if room.nameCacheSource >= CanonicalAliasRoomName { if room.nameCacheSource <= CanonicalAliasRoomName {
room.nameCache = "" room.NameCache = ""
} }
room.canonicalAliasCache = "" room.canonicalAliasCache = ""
case mautrix.StateAliases: case mautrix.StateAliases:
if room.nameCacheSource >= AliasRoomName { if room.nameCacheSource <= AliasRoomName {
room.nameCache = "" room.NameCache = ""
} }
room.aliasesCache = nil room.aliasesCache = nil
case mautrix.StateMember: case mautrix.StateMember:
room.memberCache = nil room.memberCache = nil
room.firstMemberCache = nil room.firstMemberCache = nil
if room.nameCacheSource >= MemberRoomName { room.secondMemberCache = nil
room.nameCache = "" if room.nameCacheSource <= MemberRoomName {
room.NameCache = ""
} }
case mautrix.StateTopic: case mautrix.StateTopic:
room.topicCache = "" room.topicCache = ""
@ -258,24 +335,25 @@ func (room *Room) UpdateState(event *mautrix.Event) {
} }
if event.StateKey == nil { if event.StateKey == nil {
room.State[event.Type][""] = event room.state[event.Type][""] = event
} else { } else {
room.State[event.Type][*event.StateKey] = event room.state[event.Type][*event.StateKey] = event
} }
} }
// GetStateEvent returns the state event for the given type/state_key combo, or nil. // GetStateEvent returns the state event for the given type/state_key combo, or nil.
func (room *Room) GetStateEvent(eventType mautrix.EventType, stateKey string) *mautrix.Event { func (room *Room) GetStateEvent(eventType mautrix.EventType, stateKey string) *mautrix.Event {
room.Load()
room.lock.RLock() room.lock.RLock()
defer room.lock.RUnlock() defer room.lock.RUnlock()
stateEventMap, _ := room.State[eventType] stateEventMap, _ := room.state[eventType]
event, _ := stateEventMap[stateKey] event, _ := stateEventMap[stateKey]
return event return event
} }
// getStateEvents returns the state events for the given type. // getStateEvents returns the state events for the given type.
func (room *Room) getStateEvents(eventType mautrix.EventType) map[string]*mautrix.Event { func (room *Room) getStateEvents(eventType mautrix.EventType) map[string]*mautrix.Event {
stateEventMap, _ := room.State[eventType] stateEventMap, _ := room.state[eventType]
return stateEventMap return stateEventMap
} }
@ -323,7 +401,7 @@ func (room *Room) GetAliases() []string {
func (room *Room) updateNameFromNameEvent() { func (room *Room) updateNameFromNameEvent() {
nameEvt := room.GetStateEvent(mautrix.StateRoomName, "") nameEvt := room.GetStateEvent(mautrix.StateRoomName, "")
if nameEvt != nil { if nameEvt != nil {
room.nameCache = nameEvt.Content.Name room.NameCache = nameEvt.Content.Name
} }
} }
@ -336,7 +414,7 @@ func (room *Room) updateNameFromAliases() {
aliases := room.GetAliases() aliases := room.GetAliases()
if len(aliases) > 0 { if len(aliases) > 0 {
sort.Sort(sort.StringSlice(aliases)) sort.Sort(sort.StringSlice(aliases))
room.nameCache = aliases[0] room.NameCache = aliases[0]
} }
} }
@ -351,33 +429,40 @@ func (room *Room) updateNameFromAliases() {
func (room *Room) updateNameFromMembers() { func (room *Room) updateNameFromMembers() {
members := room.GetMembers() members := room.GetMembers()
if len(members) <= 1 { if len(members) <= 1 {
room.nameCache = "Empty room" room.NameCache = "Empty room"
} else if room.firstMemberCache == nil { } else if room.firstMemberCache == nil {
room.nameCache = "Room" room.NameCache = "Room"
} else if len(members) == 2 { } else if len(members) == 2 {
room.nameCache = room.firstMemberCache.Displayname room.NameCache = room.firstMemberCache.Displayname
} else if len(members) == 3 && room.secondMemberCache != nil {
room.NameCache = fmt.Sprintf("%s and %s", room.firstMemberCache.Displayname, room.secondMemberCache.Displayname)
} else { } else {
firstMember := room.firstMemberCache.Displayname members := room.firstMemberCache.Displayname
room.nameCache = fmt.Sprintf("%s and %d others", firstMember, len(members)-2) count := len(members) - 2
if room.secondMemberCache != nil {
members += ", " + room.secondMemberCache.Displayname
count--
}
room.NameCache = fmt.Sprintf("%s and %d others", members, count)
} }
} }
// updateNameCache updates the room display name based on the room state in the order // updateNameCache updates the room display name based on the room state in the order
// specified in spec section 11.2.2.5. // specified in spec section 11.2.2.5.
func (room *Room) updateNameCache() { func (room *Room) updateNameCache() {
if len(room.nameCache) == 0 { if len(room.NameCache) == 0 {
room.updateNameFromNameEvent() room.updateNameFromNameEvent()
room.nameCacheSource = ExplicitRoomName room.nameCacheSource = ExplicitRoomName
} }
if len(room.nameCache) == 0 { if len(room.NameCache) == 0 {
room.nameCache = room.GetCanonicalAlias() room.NameCache = room.GetCanonicalAlias()
room.nameCacheSource = CanonicalAliasRoomName room.nameCacheSource = CanonicalAliasRoomName
} }
if len(room.nameCache) == 0 { if len(room.NameCache) == 0 {
room.updateNameFromAliases() room.updateNameFromAliases()
room.nameCacheSource = AliasRoomName room.nameCacheSource = AliasRoomName
} }
if len(room.nameCache) == 0 { if len(room.NameCache) == 0 {
room.updateNameFromMembers() room.updateNameFromMembers()
room.nameCacheSource = MemberRoomName room.nameCacheSource = MemberRoomName
} }
@ -389,15 +474,19 @@ func (room *Room) updateNameCache() {
// If the cache is empty, it is updated first. // If the cache is empty, it is updated first.
func (room *Room) GetTitle() string { func (room *Room) GetTitle() string {
room.updateNameCache() room.updateNameCache()
return room.nameCache return room.NameCache
} }
// createMemberCache caches all member events into a easily processable MXID -> *Member map. // createMemberCache caches all member events into a easily processable MXID -> *Member map.
func (room *Room) createMemberCache() map[string]*mautrix.Member { func (room *Room) createMemberCache() map[string]*mautrix.Member {
if len(room.memberCache) > 0 {
return room.memberCache
}
cache := make(map[string]*mautrix.Member) cache := make(map[string]*mautrix.Member)
room.lock.RLock() room.lock.RLock()
events := room.getStateEvents(mautrix.StateMember) events := room.getStateEvents(mautrix.StateMember)
room.firstMemberCache = nil room.firstMemberCache = nil
room.secondMemberCache = nil
if events != nil { if events != nil {
for userID, event := range events { for userID, event := range events {
member := &event.Content.Member member := &event.Content.Member
@ -405,8 +494,12 @@ func (room *Room) createMemberCache() map[string]*mautrix.Member {
if len(member.Displayname) == 0 { if len(member.Displayname) == 0 {
member.Displayname = userID member.Displayname = userID
} }
if room.firstMemberCache == nil && userID != room.SessionUserID { if userID != room.SessionUserID {
if room.firstMemberCache == nil {
room.firstMemberCache = member room.firstMemberCache = member
} else if room.secondMemberCache == nil {
room.secondMemberCache = member
}
} }
if member.Membership == mautrix.MembershipJoin || member.Membership == mautrix.MembershipInvite { if member.Membership == mautrix.MembershipJoin || member.Membership == mautrix.MembershipInvite {
cache[userID] = member cache[userID] = member
@ -425,18 +518,16 @@ func (room *Room) createMemberCache() map[string]*mautrix.Member {
// The members are returned from the cache. // The members are returned from the cache.
// If the cache is empty, it is updated first. // If the cache is empty, it is updated first.
func (room *Room) GetMembers() map[string]*mautrix.Member { func (room *Room) GetMembers() map[string]*mautrix.Member {
if len(room.memberCache) == 0 || room.firstMemberCache == nil { room.Load()
room.createMemberCache() room.createMemberCache()
}
return room.memberCache return room.memberCache
} }
// GetMember returns the member with the given MXID. // GetMember returns the member with the given MXID.
// If the member doesn't exist, nil is returned. // If the member doesn't exist, nil is returned.
func (room *Room) GetMember(userID string) *mautrix.Member { func (room *Room) GetMember(userID string) *mautrix.Member {
if len(room.memberCache) == 0 { room.Load()
room.createMemberCache() room.createMemberCache()
}
room.lock.RLock() room.lock.RLock()
member, _ := room.memberCache[userID] member, _ := room.memberCache[userID]
room.lock.RUnlock() room.lock.RUnlock()
@ -449,9 +540,13 @@ func (room *Room) GetSessionOwner() string {
} }
// NewRoom creates a new Room with the given ID // NewRoom creates a new Room with the given ID
func NewRoom(roomID, owner string) *Room { func NewRoom(roomID string, cache *RoomCache) *Room {
return &Room{ return &Room{
Room: mautrix.NewRoom(roomID), ID: roomID,
SessionUserID: owner, state: make(map[mautrix.EventType]map[string]*mautrix.Event),
path: cache.roomPath(roomID),
cache: cache,
SessionUserID: cache.getOwner(),
} }
} }

305
matrix/rooms/roomcache.go Normal file
View File

@ -0,0 +1,305 @@
// gomuks - A terminal Matrix client written in Go.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package rooms
import (
"compress/gzip"
"encoding/gob"
"os"
"path/filepath"
"time"
"github.com/pkg/errors"
sync "github.com/sasha-s/go-deadlock"
"maunium.net/go/gomuks/debug"
)
// RoomCache contains room state info in a hashmap and linked list.
type RoomCache struct {
sync.Mutex
listPath string
directory string
maxSize int
maxAge int64
getOwner func() string
Map map[string]*Room
head *Room
tail *Room
size int
}
func NewRoomCache(listPath, directory string, maxSize int, maxAge int64, getOwner func() string) *RoomCache {
return &RoomCache{
listPath: listPath,
directory: directory,
maxSize: maxSize,
maxAge: maxAge,
getOwner: getOwner,
Map: make(map[string]*Room),
}
}
func (cache *RoomCache) LoadList() error {
cache.Lock()
defer cache.Unlock()
// Open room list file
file, err := os.OpenFile(cache.listPath, os.O_RDONLY, 0600)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return errors.Wrap(err, "failed to open room list file for reading")
}
defer debugPrintError(file.Close, "Failed to close room list file after reading")
// Open gzip reader for room list file
cmpReader, err := gzip.NewReader(file)
if err != nil {
return errors.Wrap(err, "failed to read gzip room list")
}
defer debugPrintError(cmpReader.Close, "Failed to close room list gzip reader")
// Open gob decoder for gzip reader
dec := gob.NewDecoder(cmpReader)
// Read number of items in list
var size int
err = dec.Decode(&size)
if err != nil {
return errors.Wrap(err, "failed to read size of room list")
}
// Read list
cache.Map = make(map[string]*Room, size)
for i := 0; i < size; i++ {
room := &Room{}
err = dec.Decode(room)
if err != nil {
debug.Printf("Failed to decode %dth room list entry: %v", i+1, err)
continue
}
room.path = cache.roomPath(room.ID)
room.cache = cache
cache.Map[room.ID] = room
}
return nil
}
func (cache *RoomCache) SaveLoadedRooms() {
cache.Lock()
defer cache.Unlock()
cache.clean()
for node := cache.head; node != nil; node = node.prev {
node.Save()
}
}
func (cache *RoomCache) SaveList() error {
cache.Lock()
defer cache.Unlock()
debug.Print("Saving room list...")
// Open room list file
file, err := os.OpenFile(cache.listPath, os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return errors.Wrap(err, "failed to open room list file for writing")
}
defer debugPrintError(file.Close, "Failed to close room list file after writing")
// Open gzip writer for room list file
cmpWriter := gzip.NewWriter(file)
defer debugPrintError(cmpWriter.Close, "Failed to close room list gzip writer")
// Open gob encoder for gzip writer
enc := gob.NewEncoder(cmpWriter)
// Write number of items in list
err = enc.Encode(len(cache.Map))
if err != nil {
return errors.Wrap(err, "failed to write size of room list")
}
// Write list
for _, node := range cache.Map {
err = enc.Encode(node)
if err != nil {
debug.Printf("Failed to encode room list entry of %s: %v", node.ID, err)
}
}
debug.Print("Room list saved to", cache.listPath, len(cache.Map), cache.size)
return nil
}
func (cache *RoomCache) Touch(roomID string) {
cache.Lock()
node, ok := cache.Map[roomID]
if !ok || node == nil {
cache.Unlock()
return
}
cache.touch(node)
cache.Unlock()
}
func (cache *RoomCache) TouchNode(node *Room) {
cache.Lock()
cache.touch(node)
cache.Unlock()
}
func (cache *RoomCache) touch(node *Room) {
if node == cache.head {
return
}
debug.Print("Touching", node.ID)
cache.llPop(node)
cache.llPush(node)
node.touch = time.Now().Unix()
}
func (cache *RoomCache) Get(roomID string) *Room {
cache.Lock()
node := cache.get(roomID)
cache.Unlock()
return node
}
func (cache *RoomCache) GetOrCreate(roomID string) *Room {
cache.Lock()
node := cache.get(roomID)
if node == nil {
node = cache.newRoom(roomID)
cache.llPush(node)
}
cache.Unlock()
return node
}
func (cache *RoomCache) get(roomID string) *Room {
node, ok := cache.Map[roomID]
if ok && node != nil && node.Loaded() {
cache.touch(node)
return node
}
return nil
}
func (cache *RoomCache) Put(room *Room) {
cache.Lock()
node := cache.get(room.ID)
if node != nil {
cache.touch(node)
} else {
cache.Map[room.ID] = room
if room.Loaded() {
cache.llPush(room)
}
node = room
}
cache.Unlock()
node.Save()
}
func (cache *RoomCache) roomPath(roomID string) string {
return filepath.Join(cache.directory, roomID+".gob.gz")
}
func (cache *RoomCache) Load(roomID string) *Room {
cache.Lock()
defer cache.Unlock()
node, ok := cache.Map[roomID]
if ok {
return node
}
node = NewRoom(roomID, cache)
node.Load()
return node
}
func (cache *RoomCache) llPop(node *Room) {
if node.prev == nil && node.next == nil {
return
}
if node.prev != nil {
node.prev.next = node.next
}
if node.next != nil {
node.next.prev = node.prev
}
if node == cache.tail {
cache.tail = node.next
}
if node == cache.head {
cache.head = node.prev
}
node.next = nil
node.prev = nil
cache.size--
}
func (cache *RoomCache) llPush(node *Room) {
if node.next != nil || node.prev != nil {
debug.PrintStack()
debug.Print("Tried to llPush node that is already in stack")
return
}
if node == cache.head {
return
}
if cache.head != nil {
cache.head.next = node
}
node.prev = cache.head
node.next = nil
cache.head = node
if cache.tail == nil {
cache.tail = node
}
cache.size++
cache.clean()
}
func (cache *RoomCache) clean() {
origSize := cache.size
maxTS := time.Now().Unix() - cache.maxAge
for cache.size > cache.maxSize {
if cache.tail.touch > maxTS {
break
}
cache.tail.Unload()
cache.llPop(cache.tail)
}
if cleaned := origSize - cache.size; cleaned > 0 {
debug.Print("Cleaned", cleaned, "rooms")
}
}
func (cache *RoomCache) Unload(node *Room) {
cache.Lock()
defer cache.Unlock()
cache.llPop(node)
node.Unload()
}
func (cache *RoomCache) newRoom(roomID string) *Room {
node := NewRoom(roomID, cache)
cache.Map[node.ID] = node
return node
}

View File

@ -107,8 +107,6 @@ func NewGomuksSyncer(session SyncerSession) *GomuksSyncer {
// ProcessResponse processes a Matrix sync response. // ProcessResponse processes a Matrix sync response.
func (s *GomuksSyncer) ProcessResponse(res *mautrix.RespSync, since string) (err error) { func (s *GomuksSyncer) ProcessResponse(res *mautrix.RespSync, since string) (err error) {
debug.Print("Received sync response") debug.Print("Received sync response")
// dat, _ := json.MarshalIndent(res, "", " ")
// debug.Print(string(dat))
s.processSyncEvents(nil, res.Presence.Events, EventSourcePresence) s.processSyncEvents(nil, res.Presence.Events, EventSourcePresence)
s.processSyncEvents(nil, res.AccountData.Events, EventSourceAccountData) s.processSyncEvents(nil, res.AccountData.Events, EventSourceAccountData)
@ -215,6 +213,10 @@ func (s *GomuksSyncer) GetFilterJSON(userID string) json.RawMessage {
Timeline: mautrix.FilterPart{ Timeline: mautrix.FilterPart{
Types: []string{ Types: []string{
"m.room.message", "m.room.message",
"m.room.encrypted",
"m.sticker",
"m.reaction",
"m.room.member", "m.room.member",
"m.room.name", "m.room.name",
"m.room.topic", "m.room.topic",

View File

@ -86,26 +86,26 @@ func cmdHeapProfile(cmd *Command) {
func cmdRainbow(cmd *Command) { func cmdRainbow(cmd *Command) {
text := strings.Join(cmd.Args, " ") text := strings.Join(cmd.Args, " ")
var html strings.Builder var html strings.Builder
fmt.Fprint(&html, "**🌈** ") _, _ = fmt.Fprint(&html, "**🌈** ")
for i, char := range text { for i, char := range text {
if unicode.IsSpace(char) { if unicode.IsSpace(char) {
html.WriteRune(char) html.WriteRune(char)
continue continue
} }
color := rainbow.GetInterpolatedColorFor(float64(i) / float64(len(text))).Hex() color := rainbow.GetInterpolatedColorFor(float64(i) / float64(len(text))).Hex()
fmt.Fprintf(&html, "<font color=\"%s\">%c</font>", color, char) _, _ = fmt.Fprintf(&html, "<font color=\"%s\">%c</font>", color, char)
} }
go cmd.Room.SendMessage("m.text", html.String()) go cmd.Room.SendMessage("m.text", html.String())
cmd.UI.Render() cmd.UI.Render()
} }
func cmdQuit(cmd *Command) { func cmdQuit(cmd *Command) {
cmd.Gomuks.Stop() cmd.Gomuks.Stop(true)
} }
func cmdClearCache(cmd *Command) { func cmdClearCache(cmd *Command) {
cmd.Config.Clear() cmd.Config.Clear()
cmd.Gomuks.Stop() cmd.Gomuks.Stop(false)
} }
func cmdUnknownCommand(cmd *Command) { func cmdUnknownCommand(cmd *Command) {

View File

@ -25,6 +25,7 @@ import (
"github.com/mattn/go-runewidth" "github.com/mattn/go-runewidth"
sync "github.com/sasha-s/go-deadlock" sync "github.com/sasha-s/go-deadlock"
"maunium.net/go/mautrix"
"maunium.net/go/mauview" "maunium.net/go/mauview"
"maunium.net/go/tcell" "maunium.net/go/tcell"
@ -58,11 +59,13 @@ type MessageView struct {
prevPrefs config.UserPreferences prevPrefs config.UserPreferences
messageIDLock sync.RWMutex messageIDLock sync.RWMutex
messageIDs map[string]messages.UIMessage messageIDs map[string]*messages.UIMessage
messagesLock sync.RWMutex messagesLock sync.RWMutex
messages []messages.UIMessage messages []*messages.UIMessage
msgBufferLock sync.RWMutex msgBufferLock sync.RWMutex
msgBuffer []messages.UIMessage msgBuffer []*messages.UIMessage
initialHistoryLoaded bool
} }
func NewMessageView(parent *RoomView) *MessageView { func NewMessageView(parent *RoomView) *MessageView {
@ -74,9 +77,9 @@ func NewMessageView(parent *RoomView) *MessageView {
TimestampWidth: len(messages.TimeFormat), TimestampWidth: len(messages.TimeFormat),
ScrollOffset: 0, ScrollOffset: 0,
messages: make([]messages.UIMessage, 0), messages: make([]*messages.UIMessage, 0),
messageIDs: make(map[string]messages.UIMessage), messageIDs: make(map[string]*messages.UIMessage),
msgBuffer: make([]messages.UIMessage, 0), msgBuffer: make([]*messages.UIMessage, 0),
_width: 80, _width: 80,
_widestSender: 5, _widestSender: 5,
@ -108,20 +111,22 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction MessageDir
if ifcMessage == nil { if ifcMessage == nil {
return return
} }
message, ok := ifcMessage.(messages.UIMessage) message, ok := ifcMessage.(*messages.UIMessage)
if !ok { if !ok || message == nil {
debug.Print("[Warning] Passed non-UIMessage ifc.Message object to AddMessage().") debug.Print("[Warning] Passed non-UIMessage ifc.Message object to AddMessage().")
debug.PrintStack() debug.PrintStack()
return return
} }
var oldMsg messages.UIMessage var oldMsg *messages.UIMessage
if oldMsg = view.getMessageByID(message.ID()); oldMsg != nil { if oldMsg = view.getMessageByID(message.EventID); oldMsg != nil {
view.replaceMessage(oldMsg, message) view.replaceMessage(oldMsg, message)
direction = IgnoreMessage direction = IgnoreMessage
} else if oldMsg = view.getMessageByID(message.TxnID()); oldMsg != nil { } else if oldMsg = view.getMessageByID(message.TxnID); oldMsg != nil {
view.replaceMessage(oldMsg, message) view.replaceMessage(oldMsg, message)
view.deleteMessageID(message.TxnID()) view.deleteMessageID(message.TxnID)
direction = IgnoreMessage
} else if oldMsg = view.getMessageByID(message.Relation.EventID); oldMsg != nil {
direction = IgnoreMessage direction = IgnoreMessage
} }
@ -134,7 +139,7 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction MessageDir
} }
message.CalculateBuffer(view.config.Preferences, width) message.CalculateBuffer(view.config.Preferences, width)
makeDateChange := func() messages.UIMessage { makeDateChange := func() *messages.UIMessage {
dateChange := messages.NewDateChangeMessage( dateChange := messages.NewDateChangeMessage(
fmt.Sprintf("Date changed to %s", message.FormatDate())) fmt.Sprintf("Date changed to %s", message.FormatDate()))
dateChange.CalculateBuffer(view.config.Preferences, width) dateChange.CalculateBuffer(view.config.Preferences, width)
@ -157,9 +162,9 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction MessageDir
} else if direction == PrependMessage { } else if direction == PrependMessage {
view.messagesLock.Lock() view.messagesLock.Lock()
if len(view.messages) > 0 && !view.messages[0].SameDate(message) { if len(view.messages) > 0 && !view.messages[0].SameDate(message) {
view.messages = append([]messages.UIMessage{message, makeDateChange()}, view.messages...) view.messages = append([]*messages.UIMessage{message, makeDateChange()}, view.messages...)
} else { } else {
view.messages = append([]messages.UIMessage{message}, view.messages...) view.messages = append([]*messages.UIMessage{message}, view.messages...)
} }
view.messagesLock.Unlock() view.messagesLock.Unlock()
} else if oldMsg != nil { } else if oldMsg != nil {
@ -174,7 +179,7 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction MessageDir
} }
} }
func (view *MessageView) replaceMessage(original messages.UIMessage, new messages.UIMessage) { func (view *MessageView) replaceMessage(original *messages.UIMessage, new *messages.UIMessage) {
if len(new.ID()) > 0 { if len(new.ID()) > 0 {
view.setMessageID(new) view.setMessageID(new)
} }
@ -187,7 +192,10 @@ func (view *MessageView) replaceMessage(original messages.UIMessage, new message
view.messagesLock.Unlock() view.messagesLock.Unlock()
} }
func (view *MessageView) getMessageByID(id string) messages.UIMessage { func (view *MessageView) getMessageByID(id string) *messages.UIMessage {
if id == "" {
return nil
}
view.messageIDLock.RLock() view.messageIDLock.RLock()
defer view.messageIDLock.RUnlock() defer view.messageIDLock.RUnlock()
msg, ok := view.messageIDs[id] msg, ok := view.messageIDs[id]
@ -198,31 +206,37 @@ func (view *MessageView) getMessageByID(id string) messages.UIMessage {
} }
func (view *MessageView) deleteMessageID(id string) { func (view *MessageView) deleteMessageID(id string) {
if id == "" {
return
}
view.messageIDLock.Lock() view.messageIDLock.Lock()
delete(view.messageIDs, id) delete(view.messageIDs, id)
view.messageIDLock.Unlock() view.messageIDLock.Unlock()
} }
func (view *MessageView) setMessageID(message messages.UIMessage) { func (view *MessageView) setMessageID(message *messages.UIMessage) {
if message.ID() == "" {
return
}
view.messageIDLock.Lock() view.messageIDLock.Lock()
view.messageIDs[message.ID()] = message view.messageIDs[message.ID()] = message
view.messageIDLock.Unlock() view.messageIDLock.Unlock()
} }
func (view *MessageView) appendBuffer(message messages.UIMessage) { func (view *MessageView) appendBuffer(message *messages.UIMessage) {
view.msgBufferLock.Lock() view.msgBufferLock.Lock()
view.appendBufferUnlocked(message) view.appendBufferUnlocked(message)
view.msgBufferLock.Unlock() view.msgBufferLock.Unlock()
} }
func (view *MessageView) appendBufferUnlocked(message messages.UIMessage) { func (view *MessageView) appendBufferUnlocked(message *messages.UIMessage) {
for i := 0; i < message.Height(); i++ { for i := 0; i < message.Height(); i++ {
view.msgBuffer = append(view.msgBuffer, message) view.msgBuffer = append(view.msgBuffer, message)
} }
view.prevMsgCount++ view.prevMsgCount++
} }
func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages.UIMessage) { func (view *MessageView) replaceBuffer(original *messages.UIMessage, new *messages.UIMessage) {
start := -1 start := -1
end := -1 end := -1
view.msgBufferLock.RLock() view.msgBufferLock.RLock()
@ -240,7 +254,7 @@ func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages
if start == -1 { if start == -1 {
debug.Print("Called replaceBuffer() with message that was not in the buffer:", original) debug.Print("Called replaceBuffer() with message that was not in the buffer:", original)
debug.PrintStack() //debug.PrintStack()
view.appendBuffer(new) view.appendBuffer(new)
return return
} }
@ -280,7 +294,7 @@ func (view *MessageView) recalculateBuffers() {
if !prefs.BareMessageView { if !prefs.BareMessageView {
width -= view.TimestampWidth + TimestampSenderGap + view.widestSender() + SenderMessageGap width -= view.TimestampWidth + TimestampSenderGap + view.widestSender() + SenderMessageGap
} }
view.msgBuffer = []messages.UIMessage{} view.msgBuffer = []*messages.UIMessage{}
view.prevMsgCount = 0 view.prevMsgCount = 0
for i, message := range view.messages { for i, message := range view.messages {
if message == nil { if message == nil {
@ -299,17 +313,17 @@ func (view *MessageView) recalculateBuffers() {
view.prevPrefs = prefs view.prevPrefs = prefs
} }
func (view *MessageView) handleMessageClick(message messages.UIMessage) bool { func (view *MessageView) handleMessageClick(message *messages.UIMessage) bool {
switch message := message.(type) { switch msg := message.Renderer.(type) {
case *messages.ImageMessage: case *messages.ImageMessage:
open.Open(message.Path()) open.Open(msg.Path())
case messages.UIMessage: default:
debug.Print("Message clicked:", message) debug.Print("Message clicked:", message)
} }
return false return false
} }
func (view *MessageView) handleUsernameClick(message messages.UIMessage, prevMessage messages.UIMessage) bool { func (view *MessageView) handleUsernameClick(message *messages.UIMessage, prevMessage *messages.UIMessage) bool {
if prevMessage != nil && prevMessage.Sender() == message.Sender() { if prevMessage != nil && prevMessage.Sender() == message.Sender() {
return false return false
} }
@ -317,7 +331,7 @@ func (view *MessageView) handleUsernameClick(message messages.UIMessage, prevMes
if len(message.Sender()) == 0 { if len(message.Sender()) == 0 {
return false return false
} }
sender := fmt.Sprintf("[%s](https://matrix.to/#/%s)", message.Sender(), message.SenderID()) sender := fmt.Sprintf("[%s](https://matrix.to/#/%s)", message.Sender(), message.SenderID)
cursorPos := view.parent.input.GetCursorOffset() cursorPos := view.parent.input.GetCursorOffset()
text := view.parent.input.GetText() text := view.parent.input.GetText()
@ -363,7 +377,7 @@ func (view *MessageView) OnMouseEvent(event mauview.MouseEvent) bool {
view.msgBufferLock.RLock() view.msgBufferLock.RLock()
message := view.msgBuffer[line] message := view.msgBuffer[line]
var prevMessage messages.UIMessage var prevMessage *messages.UIMessage
if y != 0 && line > 0 { if y != 0 && line > 0 {
prevMessage = view.msgBuffer[line-1] prevMessage = view.msgBuffer[line-1]
} }
@ -496,7 +510,7 @@ func (view *MessageView) getIndexOffset(screen mauview.Screen, height, messageX
func (view *MessageView) CapturePlaintext(height int) string { func (view *MessageView) CapturePlaintext(height int) string {
var buf strings.Builder var buf strings.Builder
indexOffset := view.TotalHeight() - view.ScrollOffset - height indexOffset := view.TotalHeight() - view.ScrollOffset - height
var prevMessage messages.UIMessage var prevMessage *messages.UIMessage
view.msgBufferLock.RLock() view.msgBufferLock.RLock()
for line := 0; line < height; line++ { for line := 0; line < height; line++ {
index := indexOffset + line index := indexOffset + line
@ -504,14 +518,13 @@ func (view *MessageView) CapturePlaintext(height int) string {
continue continue
} }
meta := view.msgBuffer[index] message := view.msgBuffer[index]
message, ok := meta.(messages.UIMessage) if message != prevMessage {
if ok && message != prevMessage {
var sender string var sender string
if len(message.Sender()) > 0 { if len(message.Sender()) > 0 {
sender = fmt.Sprintf(" <%s>", message.Sender()) sender = fmt.Sprintf(" <%s>", message.Sender())
} else if message.Type() == "m.emote" { } else if message.Type == mautrix.MsgEmote {
sender = fmt.Sprintf(" * %s", message.RealSender()) sender = fmt.Sprintf(" * %s", message.SenderName)
} }
fmt.Fprintf(&buf, "%s%s %s\n", message.FormatTime(), sender, message.PlainText()) fmt.Fprintf(&buf, "%s%s %s\n", message.FormatTime(), sender, message.PlainText())
prevMessage = message prevMessage = message
@ -561,7 +574,7 @@ func (view *MessageView) Draw(screen mauview.Screen) {
} }
} }
var prevMsg messages.UIMessage var prevMsg *messages.UIMessage
view.msgBufferLock.RLock() view.msgBufferLock.RLock()
for line := viewStart; line < height && indexOffset+line < len(view.msgBuffer); line++ { for line := viewStart; line < height && indexOffset+line < len(view.msgBuffer); line++ {
index := indexOffset + line index := indexOffset + line

View File

@ -27,44 +27,60 @@ import (
"maunium.net/go/tcell" "maunium.net/go/tcell"
"maunium.net/go/gomuks/interface" "maunium.net/go/gomuks/interface"
"maunium.net/go/gomuks/ui/messages/tstring"
"maunium.net/go/gomuks/ui/widget" "maunium.net/go/gomuks/ui/widget"
) )
type BaseMessage struct { type MessageRenderer interface {
MsgID string Draw(screen mauview.Screen)
MsgTxnID string NotificationContent() string
MsgType mautrix.MessageType PlainText() string
MsgSenderID string CalculateBuffer(prefs config.UserPreferences, width int, msg *UIMessage)
MsgSender string RegisterMatrix(matrix ifc.MatrixContainer)
MsgSenderColor tcell.Color Height() int
MsgTimestamp time.Time Clone() MessageRenderer
MsgState mautrix.OutgoingEventState String() string
MsgIsHighlight bool
MsgIsService bool
MsgSource json.RawMessage
ReplyTo UIMessage
buffer []tstring.TString
} }
func newBaseMessage(event *mautrix.Event, displayname string) BaseMessage { type UIMessage struct {
EventID string
TxnID string
Relation mautrix.RelatesTo
Type mautrix.MessageType
SenderID string
SenderName string
DefaultSenderColor tcell.Color
Timestamp time.Time
State mautrix.OutgoingEventState
IsHighlight bool
IsService bool
Source json.RawMessage
ReplyTo *UIMessage
Renderer MessageRenderer
}
const DateFormat = "January _2, 2006"
const TimeFormat = "15:04:05"
func newUIMessage(event *mautrix.Event, displayname string, renderer MessageRenderer) *UIMessage {
msgtype := event.Content.MsgType msgtype := event.Content.MsgType
if len(msgtype) == 0 { if len(msgtype) == 0 {
msgtype = mautrix.MessageType(event.Type.String()) msgtype = mautrix.MessageType(event.Type.String())
} }
return BaseMessage{ return &UIMessage{
MsgSenderID: event.Sender, SenderID: event.Sender,
MsgSender: displayname, SenderName: displayname,
MsgTimestamp: unixToTime(event.Timestamp), Timestamp: unixToTime(event.Timestamp),
MsgSenderColor: widget.GetHashColor(event.Sender), DefaultSenderColor: widget.GetHashColor(event.Sender),
MsgType: msgtype, Type: msgtype,
MsgID: event.ID, EventID: event.ID,
MsgTxnID: event.Unsigned.TransactionID, TxnID: event.Unsigned.TransactionID,
MsgState: event.Unsigned.OutgoingState, Relation: *event.Content.GetRelatesTo(),
MsgIsHighlight: false, State: event.Unsigned.OutgoingState,
MsgIsService: false, IsHighlight: false,
MsgSource: event.Content.VeryRaw, IsService: false,
Source: event.Content.VeryRaw,
Renderer: renderer,
} }
} }
@ -76,44 +92,38 @@ func unixToTime(unix int64) time.Time {
return timestamp return timestamp
} }
func (msg *BaseMessage) RegisterMatrix(matrix ifc.MatrixContainer) {}
// Sender gets the string that should be displayed as the sender of this message. // Sender gets the string that should be displayed as the sender of this message.
// //
// If the message is being sent, the sender is "Sending...". // If the message is being sent, the sender is "Sending...".
// If sending has failed, the sender is "Error". // If sending has failed, the sender is "Error".
// If the message is an emote, the sender is blank. // If the message is an emote, the sender is blank.
// In any other case, the sender is the display name of the user who sent the message. // In any other case, the sender is the display name of the user who sent the message.
func (msg *BaseMessage) Sender() string { func (msg *UIMessage) Sender() string {
switch msg.MsgState { switch msg.State {
case mautrix.EventStateLocalEcho: case mautrix.EventStateLocalEcho:
return "Sending..." return "Sending..."
case mautrix.EventStateSendFail: case mautrix.EventStateSendFail:
return "Error" return "Error"
} }
switch msg.MsgType { switch msg.Type {
case "m.emote": case "m.emote":
// Emotes don't show a separate sender, it's included in the buffer. // Emotes don't show a separate sender, it's included in the buffer.
return "" return ""
default: default:
return msg.MsgSender return msg.SenderName
} }
} }
func (msg *BaseMessage) SenderID() string { func (msg *UIMessage) NotificationSenderName() string {
return msg.MsgSenderID return msg.SenderName
} }
func (msg *BaseMessage) RealSender() string { func (msg *UIMessage) NotificationContent() string {
return msg.MsgSender return msg.Renderer.NotificationContent()
} }
func (msg *BaseMessage) NotificationSenderName() string { func (msg *UIMessage) getStateSpecificColor() tcell.Color {
return msg.MsgSender switch msg.State {
}
func (msg *BaseMessage) getStateSpecificColor() tcell.Color {
switch msg.MsgState {
case mautrix.EventStateLocalEcho: case mautrix.EventStateLocalEcho:
return tcell.ColorGray return tcell.ColorGray
case mautrix.EventStateSendFail: case mautrix.EventStateSendFail:
@ -132,31 +142,31 @@ func (msg *BaseMessage) getStateSpecificColor() tcell.Color {
// //
// In any other case, the color is whatever is specified in the Message struct. // In any other case, the color is whatever is specified in the Message struct.
// Usually that means it is the hash-based color of the sender (see ui/widget/color.go) // Usually that means it is the hash-based color of the sender (see ui/widget/color.go)
func (msg *BaseMessage) SenderColor() tcell.Color { func (msg *UIMessage) SenderColor() tcell.Color {
stateColor := msg.getStateSpecificColor() stateColor := msg.getStateSpecificColor()
switch { switch {
case stateColor != tcell.ColorDefault: case stateColor != tcell.ColorDefault:
return stateColor return stateColor
case msg.MsgType == "m.room.member": case msg.Type == "m.room.member":
return widget.GetHashColor(msg.MsgSender) return widget.GetHashColor(msg.SenderName)
case msg.MsgIsService: case msg.IsService:
return tcell.ColorGray return tcell.ColorGray
default: default:
return msg.MsgSenderColor return msg.DefaultSenderColor
} }
} }
// TextColor returns the color the actual content of the message should be shown in. // TextColor returns the color the actual content of the message should be shown in.
func (msg *BaseMessage) TextColor() tcell.Color { func (msg *UIMessage) TextColor() tcell.Color {
stateColor := msg.getStateSpecificColor() stateColor := msg.getStateSpecificColor()
switch { switch {
case stateColor != tcell.ColorDefault: case stateColor != tcell.ColorDefault:
return stateColor return stateColor
case msg.MsgIsService, msg.MsgType == "m.notice": case msg.IsService, msg.Type == "m.notice":
return tcell.ColorGray return tcell.ColorGray
case msg.MsgIsHighlight: case msg.IsHighlight:
return tcell.ColorYellow return tcell.ColorYellow
case msg.MsgType == "m.room.member": case msg.Type == "m.room.member":
return tcell.ColorGreen return tcell.ColorGreen
default: default:
return tcell.ColorDefault return tcell.ColorDefault
@ -169,14 +179,14 @@ func (msg *BaseMessage) TextColor() tcell.Color {
// gray and red respectively. // gray and red respectively.
// //
// However, other messages are the default color instead of a color stored in the struct. // However, other messages are the default color instead of a color stored in the struct.
func (msg *BaseMessage) TimestampColor() tcell.Color { func (msg *UIMessage) TimestampColor() tcell.Color {
if msg.MsgIsService { if msg.IsService {
return tcell.ColorGray return tcell.ColorGray
} }
return msg.getStateSpecificColor() return msg.getStateSpecificColor()
} }
func (msg *BaseMessage) ReplyHeight() int { func (msg *UIMessage) ReplyHeight() int {
if msg.ReplyTo != nil { if msg.ReplyTo != nil {
return 1 + msg.ReplyTo.Height() return 1 + msg.ReplyTo.Height()
} }
@ -184,102 +194,76 @@ func (msg *BaseMessage) ReplyHeight() int {
} }
// Height returns the number of rows in the computed buffer (see Buffer()). // Height returns the number of rows in the computed buffer (see Buffer()).
func (msg *BaseMessage) Height() int { func (msg *UIMessage) Height() int {
return msg.ReplyHeight() + len(msg.buffer) return msg.ReplyHeight() + msg.Renderer.Height()
} }
// Timestamp returns the full timestamp when the message was sent. func (msg *UIMessage) Time() time.Time {
func (msg *BaseMessage) Timestamp() time.Time { return msg.Timestamp
return msg.MsgTimestamp
} }
// FormatTime returns the formatted time when the message was sent. // FormatTime returns the formatted time when the message was sent.
func (msg *BaseMessage) FormatTime() string { func (msg *UIMessage) FormatTime() string {
return msg.MsgTimestamp.Format(TimeFormat) return msg.Timestamp.Format(TimeFormat)
} }
// FormatDate returns the formatted date when the message was sent. // FormatDate returns the formatted date when the message was sent.
func (msg *BaseMessage) FormatDate() string { func (msg *UIMessage) FormatDate() string {
return msg.MsgTimestamp.Format(DateFormat) return msg.Timestamp.Format(DateFormat)
} }
func (msg *BaseMessage) SameDate(message UIMessage) bool { func (msg *UIMessage) SameDate(message *UIMessage) bool {
year1, month1, day1 := msg.Timestamp().Date() year1, month1, day1 := msg.Timestamp.Date()
year2, month2, day2 := message.Timestamp().Date() year2, month2, day2 := message.Timestamp.Date()
return day1 == day2 && month1 == month2 && year1 == year2 return day1 == day2 && month1 == month2 && year1 == year2
} }
func (msg *BaseMessage) ID() string { func (msg *UIMessage) ID() string {
if len(msg.MsgID) == 0 { if len(msg.EventID) == 0 {
return msg.MsgTxnID return msg.TxnID
} }
return msg.MsgID return msg.EventID
} }
func (msg *BaseMessage) SetID(id string) { func (msg *UIMessage) SetID(id string) {
msg.MsgID = id msg.EventID = id
} }
func (msg *BaseMessage) TxnID() string { func (msg *UIMessage) SetIsHighlight(isHighlight bool) {
return msg.MsgTxnID // TODO Textmessage cache needs to be cleared
msg.IsHighlight = isHighlight
} }
func (msg *BaseMessage) Type() mautrix.MessageType { func (msg *UIMessage) Draw(screen mauview.Screen) {
return msg.MsgType
}
func (msg *BaseMessage) State() mautrix.OutgoingEventState {
return msg.MsgState
}
func (msg *BaseMessage) SetState(state mautrix.OutgoingEventState) {
msg.MsgState = state
}
func (msg *BaseMessage) IsHighlight() bool {
return msg.MsgIsHighlight
}
func (msg *BaseMessage) SetIsHighlight(isHighlight bool) {
msg.MsgIsHighlight = isHighlight
}
func (msg *BaseMessage) Source() json.RawMessage {
return msg.MsgSource
}
func (msg *BaseMessage) SetReplyTo(event UIMessage) {
msg.ReplyTo = event
}
func (msg *BaseMessage) Draw(screen mauview.Screen) {
screen = msg.DrawReply(screen) screen = msg.DrawReply(screen)
for y, line := range msg.buffer { msg.Renderer.Draw(screen)
line.Draw(screen, 0, y)
}
} }
func (msg *BaseMessage) clone() BaseMessage { func (msg *UIMessage) Clone() *UIMessage {
clone := *msg clone := *msg
clone.buffer = nil clone.Renderer = clone.Renderer.Clone()
return clone return &clone
} }
func (msg *BaseMessage) CalculateReplyBuffer(preferences config.UserPreferences, width int) { func (msg *UIMessage) CalculateReplyBuffer(preferences config.UserPreferences, width int) {
if msg.ReplyTo == nil { if msg.ReplyTo == nil {
return return
} }
msg.ReplyTo.CalculateBuffer(preferences, width-1) msg.ReplyTo.CalculateBuffer(preferences, width-1)
} }
func (msg *BaseMessage) DrawReply(screen mauview.Screen) mauview.Screen { func (msg *UIMessage) CalculateBuffer(preferences config.UserPreferences, width int) {
msg.Renderer.CalculateBuffer(preferences, width-1, msg)
}
func (msg *UIMessage) DrawReply(screen mauview.Screen) mauview.Screen {
if msg.ReplyTo == nil { if msg.ReplyTo == nil {
return screen return screen
} }
width, height := screen.Size() width, height := screen.Size()
replyHeight := msg.ReplyTo.Height() replyHeight := msg.ReplyTo.Height()
widget.WriteLineSimpleColor(screen, "In reply to", 1, 0, tcell.ColorGreen) widget.WriteLineSimpleColor(screen, "In reply to", 1, 0, tcell.ColorGreen)
widget.WriteLineSimpleColor(screen, msg.ReplyTo.RealSender(), 13, 0, msg.ReplyTo.SenderColor()) widget.WriteLineSimpleColor(screen, msg.ReplyTo.SenderName, 13, 0, msg.ReplyTo.SenderColor())
for y := 0; y < 1+replyHeight; y++ { for y := 0; y < 1+replyHeight; y++ {
screen.SetCell(0, y, tcell.StyleDefault, '▊') screen.SetCell(0, y, tcell.StyleDefault, '▊')
} }
@ -288,16 +272,21 @@ func (msg *BaseMessage) DrawReply(screen mauview.Screen) mauview.Screen {
return mauview.NewProxyScreen(screen, 0, replyHeight+1, width, height-replyHeight-1) return mauview.NewProxyScreen(screen, 0, replyHeight+1, width, height-replyHeight-1)
} }
func (msg *BaseMessage) String() string { func (msg *UIMessage) String() string {
return fmt.Sprintf(`&messages.BaseMessage{ return fmt.Sprintf(`&messages.UIMessage{
ID="%s", TxnID="%s", ID="%s", TxnID="%s",
Type="%s", Timestamp=%s, Type="%s", Timestamp=%s,
Sender={ID="%s", Name="%s", Color=#%X}, Sender={ID="%s", Name="%s", Color=#%X},
IsService=%t, IsHighlight=%t, IsService=%t, IsHighlight=%t,
Renderer=%s,
}`, }`,
msg.MsgID, msg.MsgTxnID, msg.EventID, msg.TxnID,
msg.MsgType, msg.MsgTimestamp.String(), msg.Type, msg.Timestamp.String(),
msg.MsgSenderID, msg.MsgSender, msg.MsgSenderColor.Hex(), msg.SenderID, msg.SenderName, msg.DefaultSenderColor.Hex(),
msg.MsgIsService, msg.MsgIsHighlight, msg.IsService, msg.IsHighlight, msg.Renderer.String(),
) )
} }
func (msg *UIMessage) PlainText() string {
return msg.Renderer.PlainText()
}

View File

@ -17,9 +17,12 @@
package messages package messages
import ( import (
"fmt"
"time" "time"
ifc "maunium.net/go/gomuks/interface"
"maunium.net/go/mautrix" "maunium.net/go/mautrix"
"maunium.net/go/mauview"
"maunium.net/go/tcell" "maunium.net/go/tcell"
"maunium.net/go/gomuks/config" "maunium.net/go/gomuks/config"
@ -27,55 +30,63 @@ import (
) )
type ExpandedTextMessage struct { type ExpandedTextMessage struct {
BaseMessage Text tstring.TString
MsgText tstring.TString buffer []tstring.TString
} }
// NewExpandedTextMessage creates a new ExpandedTextMessage object with the provided values and the default state. // NewExpandedTextMessage creates a new ExpandedTextMessage object with the provided values and the default state.
func NewExpandedTextMessage(event *mautrix.Event, displayname string, text tstring.TString) UIMessage { func NewExpandedTextMessage(event *mautrix.Event, displayname string, text tstring.TString) *UIMessage {
return &ExpandedTextMessage{ return newUIMessage(event, displayname, &ExpandedTextMessage{
BaseMessage: newBaseMessage(event, displayname), Text: text,
MsgText: text, })
}
} }
func NewDateChangeMessage(text string) UIMessage { func NewDateChangeMessage(text string) *UIMessage {
midnight := time.Now() midnight := time.Now()
midnight = time.Date(midnight.Year(), midnight.Month(), midnight.Day(), midnight = time.Date(midnight.Year(), midnight.Month(), midnight.Day(),
0, 0, 0, 0, 0, 0, 0, 0,
midnight.Location()) midnight.Location())
return &ExpandedTextMessage{ return &UIMessage{
BaseMessage: BaseMessage{ SenderID: "*",
MsgSenderID: "*", SenderName: "*",
MsgSender: "*", Timestamp: midnight,
MsgTimestamp: midnight, IsService: true,
MsgIsService: true, Renderer: &ExpandedTextMessage{
Text: tstring.NewColorTString(text, tcell.ColorGreen),
}, },
MsgText: tstring.NewColorTString(text, tcell.ColorGreen),
} }
} }
func (msg *ExpandedTextMessage) Clone() MessageRenderer {
func (msg *ExpandedTextMessage) Clone() UIMessage {
return &ExpandedTextMessage{ return &ExpandedTextMessage{
BaseMessage: msg.BaseMessage.clone(), Text: msg.Text.Clone(),
MsgText: msg.MsgText.Clone(),
} }
} }
func (msg *ExpandedTextMessage) GenerateText() tstring.TString {
return msg.MsgText
}
func (msg *ExpandedTextMessage) NotificationContent() string { func (msg *ExpandedTextMessage) NotificationContent() string {
return msg.MsgText.String() return msg.Text.String()
} }
func (msg *ExpandedTextMessage) PlainText() string { func (msg *ExpandedTextMessage) PlainText() string {
return msg.MsgText.String() return msg.Text.String()
} }
func (msg *ExpandedTextMessage) CalculateBuffer(prefs config.UserPreferences, width int) { func (msg *ExpandedTextMessage) String() string {
msg.CalculateReplyBuffer(prefs, width) return fmt.Sprintf(`&messages.ExpandedTextMessage{Text="%s"}`, msg.Text.String())
msg.calculateBufferWithText(prefs, msg.MsgText, width)
} }
func (msg *ExpandedTextMessage) CalculateBuffer(prefs config.UserPreferences, width int, uiMsg *UIMessage) {
msg.buffer = calculateBufferWithText(prefs, msg.Text, width, uiMsg)
}
func (msg *ExpandedTextMessage) Height() int {
return len(msg.buffer)
}
func (msg *ExpandedTextMessage) Draw(screen mauview.Screen) {
for y, line := range msg.buffer {
line.Draw(screen, 0, y)
}
}
func (msg *ExpandedTextMessage) RegisterMatrix(matrix ifc.MatrixContainer) {}

View File

@ -17,9 +17,7 @@
package messages package messages
import ( import (
"fmt" ifc "maunium.net/go/gomuks/interface"
"strings"
"maunium.net/go/mautrix" "maunium.net/go/mautrix"
"maunium.net/go/mauview" "maunium.net/go/mauview"
"maunium.net/go/tcell" "maunium.net/go/tcell"
@ -29,30 +27,27 @@ import (
) )
type HTMLMessage struct { type HTMLMessage struct {
BaseMessage
Root html.Entity Root html.Entity
FocusedBg tcell.Color FocusedBg tcell.Color
focused bool focused bool
} }
func NewHTMLMessage(event *mautrix.Event, displayname string, root html.Entity) UIMessage { func NewHTMLMessage(event *mautrix.Event, displayname string, root html.Entity) *UIMessage {
return &HTMLMessage{ return newUIMessage(event, displayname, &HTMLMessage{
BaseMessage: newBaseMessage(event, displayname),
Root: root, Root: root,
} })
} }
func (hw *HTMLMessage) Clone() UIMessage { func (hw *HTMLMessage) RegisterMatrix(matrix ifc.MatrixContainer) {}
func (hw *HTMLMessage) Clone() MessageRenderer {
return &HTMLMessage{ return &HTMLMessage{
BaseMessage: hw.BaseMessage.clone(),
Root: hw.Root.Clone(), Root: hw.Root.Clone(),
FocusedBg: hw.FocusedBg, FocusedBg: hw.FocusedBg,
} }
} }
func (hw *HTMLMessage) Draw(screen mauview.Screen) { func (hw *HTMLMessage) Draw(screen mauview.Screen) {
screen = hw.DrawReply(screen)
if hw.focused { if hw.focused {
screen.SetStyle(tcell.StyleDefault.Background(hw.FocusedBg)) screen.SetStyle(tcell.StyleDefault.Background(hw.FocusedBg))
} }
@ -80,18 +75,17 @@ func (hw *HTMLMessage) OnPasteEvent(event mauview.PasteEvent) bool {
return false return false
} }
func (hw *HTMLMessage) CalculateBuffer(preferences config.UserPreferences, width int) { func (hw *HTMLMessage) CalculateBuffer(preferences config.UserPreferences, width int, msg *UIMessage) {
if width < 2 { if width < 2 {
return return
} }
hw.CalculateReplyBuffer(preferences, width)
// TODO account for bare messages in initial startX // TODO account for bare messages in initial startX
startX := 0 startX := 0
hw.Root.CalculateBuffer(width, startX, preferences.BareMessageView) hw.Root.CalculateBuffer(width, startX, preferences.BareMessageView)
} }
func (hw *HTMLMessage) Height() int { func (hw *HTMLMessage) Height() int {
return hw.ReplyHeight() + hw.Root.Height() return hw.Root.Height()
} }
func (hw *HTMLMessage) PlainText() string { func (hw *HTMLMessage) PlainText() string {
@ -103,8 +97,5 @@ func (hw *HTMLMessage) NotificationContent() string {
} }
func (hw *HTMLMessage) String() string { func (hw *HTMLMessage) String() string {
return fmt.Sprintf("&messages.HTMLMessage{\n" + return hw.Root.String()
" Base=%s,\n" +
" Root=||\n%s\n" +
"}", strings.Replace(hw.BaseMessage.String(), "\n", "\n ", -1), hw.Root.String())
} }

View File

@ -22,6 +22,7 @@ import (
"image/color" "image/color"
"maunium.net/go/mautrix" "maunium.net/go/mautrix"
"maunium.net/go/mauview"
"maunium.net/go/tcell" "maunium.net/go/tcell"
"maunium.net/go/gomuks/config" "maunium.net/go/gomuks/config"
@ -32,32 +33,30 @@ import (
) )
type ImageMessage struct { type ImageMessage struct {
BaseMessage
Body string Body string
Homeserver string Homeserver string
FileID string FileID string
data []byte data []byte
buffer []tstring.TString
matrix ifc.MatrixContainer matrix ifc.MatrixContainer
} }
// NewImageMessage creates a new ImageMessage object with the provided values and the default state. // NewImageMessage creates a new ImageMessage object with the provided values and the default state.
func NewImageMessage(matrix ifc.MatrixContainer, event *mautrix.Event, displayname string, body, homeserver, fileID string, data []byte) UIMessage { func NewImageMessage(matrix ifc.MatrixContainer, event *mautrix.Event, displayname string, body, homeserver, fileID string, data []byte) *UIMessage {
return &ImageMessage{ return newUIMessage(event, displayname, &ImageMessage{
newBaseMessage(event, displayname), Body: body,
body, Homeserver: homeserver,
homeserver, FileID: fileID,
fileID, data: data,
data, matrix: matrix,
matrix, })
}
} }
func (msg *ImageMessage) Clone() UIMessage { func (msg *ImageMessage) Clone() MessageRenderer {
data := make([]byte, len(msg.data)) data := make([]byte, len(msg.data))
copy(data, msg.data) copy(data, msg.data)
return &ImageMessage{ return &ImageMessage{
BaseMessage: msg.BaseMessage.clone(),
Body: msg.Body, Body: msg.Body,
Homeserver: msg.Homeserver, Homeserver: msg.Homeserver,
FileID: msg.FileID, FileID: msg.FileID,
@ -70,7 +69,7 @@ func (msg *ImageMessage) RegisterMatrix(matrix ifc.MatrixContainer) {
msg.matrix = matrix msg.matrix = matrix
if len(msg.data) == 0 { if len(msg.data) == 0 {
go msg.updateData() //FIXME go msg.updateData()
} }
} }
@ -82,6 +81,10 @@ func (msg *ImageMessage) PlainText() string {
return fmt.Sprintf("%s: %s", msg.Body, msg.matrix.GetDownloadURL(msg.Homeserver, msg.FileID)) return fmt.Sprintf("%s: %s", msg.Body, msg.matrix.GetDownloadURL(msg.Homeserver, msg.FileID))
} }
func (msg *ImageMessage) String() string {
return fmt.Sprintf(`&messages.ImageMessage{Body="%s", Homeserver="%s", FileID="%s"}`, msg.Body, msg.Homeserver, msg.FileID)
}
func (msg *ImageMessage) updateData() { func (msg *ImageMessage) updateData() {
defer debug.Recover() defer debug.Recover()
debug.Print("Loading image:", msg.Homeserver, msg.FileID) debug.Print("Loading image:", msg.Homeserver, msg.FileID)
@ -101,14 +104,13 @@ func (msg *ImageMessage) Path() string {
// CalculateBuffer generates the internal buffer for this message that consists // CalculateBuffer generates the internal buffer for this message that consists
// of the text of this message split into lines at most as wide as the width // of the text of this message split into lines at most as wide as the width
// parameter. // parameter.
func (msg *ImageMessage) CalculateBuffer(prefs config.UserPreferences, width int) { func (msg *ImageMessage) CalculateBuffer(prefs config.UserPreferences, width int, uiMsg *UIMessage) {
if width < 2 { if width < 2 {
return return
} }
msg.CalculateReplyBuffer(prefs, width)
if prefs.BareMessageView || prefs.DisableImages { if prefs.BareMessageView || prefs.DisableImages {
msg.calculateBufferWithText(prefs, tstring.NewTString(msg.PlainText()), width) msg.buffer = calculateBufferWithText(prefs, tstring.NewTString(msg.PlainText()), width, uiMsg)
return return
} }
@ -121,3 +123,13 @@ func (msg *ImageMessage) CalculateBuffer(prefs config.UserPreferences, width int
msg.buffer = image.Render() msg.buffer = image.Render()
} }
func (msg *ImageMessage) Height() int {
return len(msg.buffer)
}
func (msg *ImageMessage) Draw(screen mauview.Screen) {
for y, line := range msg.buffer {
line.Draw(screen, 0, y)
}
}

View File

@ -1,53 +0,0 @@
// gomuks - A terminal Matrix client written in Go.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package messages
import (
"maunium.net/go/gomuks/config"
"maunium.net/go/gomuks/interface"
"maunium.net/go/mautrix"
"maunium.net/go/mauview"
"maunium.net/go/tcell"
)
// UIMessage is a wrapper for the content and metadata of a Matrix message intended to be displayed.
type UIMessage interface {
ifc.Message
Type() mautrix.MessageType
Sender() string
SenderColor() tcell.Color
TextColor() tcell.Color
TimestampColor() tcell.Color
FormatTime() string
FormatDate() string
SameDate(message UIMessage) bool
SetReplyTo(message UIMessage)
CalculateBuffer(preferences config.UserPreferences, width int)
Draw(screen mauview.Screen)
Height() int
PlainText() string
Clone() UIMessage
RealSender() string
RegisterMatrix(matrix ifc.MatrixContainer)
}
const DateFormat = "January _2, 2006"
const TimeFormat = "15:04:05"

View File

@ -31,10 +31,10 @@ import (
"maunium.net/go/gomuks/ui/widget" "maunium.net/go/gomuks/ui/widget"
) )
func getCachedEvent(mainView ifc.MainView, roomID, eventID string) UIMessage { func getCachedEvent(mainView ifc.MainView, roomID, eventID string) *UIMessage {
if roomView := mainView.GetRoom(roomID); roomView != nil { if roomView := mainView.GetRoom(roomID); roomView != nil {
if replyToIfcMsg := roomView.GetEvent(eventID); replyToIfcMsg != nil { if replyToIfcMsg := roomView.GetEvent(eventID); replyToIfcMsg != nil {
if replyToMsg, ok := replyToIfcMsg.(UIMessage); ok && replyToMsg != nil { if replyToMsg, ok := replyToIfcMsg.(*UIMessage); ok && replyToMsg != nil {
return replyToMsg return replyToMsg
} }
} }
@ -42,24 +42,19 @@ func getCachedEvent(mainView ifc.MainView, roomID, eventID string) UIMessage {
return nil return nil
} }
func ParseEvent(matrix ifc.MatrixContainer, mainView ifc.MainView, room *rooms.Room, evt *mautrix.Event) UIMessage { func ParseEvent(matrix ifc.MatrixContainer, mainView ifc.MainView, room *rooms.Room, evt *mautrix.Event) *UIMessage {
msg := directParseEvent(matrix, room, evt) msg := directParseEvent(matrix, room, evt)
if msg == nil { if msg == nil {
return nil return nil
} }
if len(evt.Content.GetReplyTo()) > 0 { if len(evt.Content.GetReplyTo()) > 0 {
replyToRoom := room if replyToMsg := getCachedEvent(mainView, room.ID, evt.Content.GetReplyTo()); replyToMsg != nil {
if len(evt.Content.RelatesTo.InReplyTo.RoomID) > 0 {
replyToRoom = matrix.GetRoom(evt.Content.RelatesTo.InReplyTo.RoomID)
}
if replyToMsg := getCachedEvent(mainView, replyToRoom.ID, evt.Content.GetReplyTo()); replyToMsg != nil {
replyToMsg = replyToMsg.Clone() replyToMsg = replyToMsg.Clone()
replyToMsg.SetReplyTo(nil) replyToMsg.ReplyTo = nil
msg.SetReplyTo(replyToMsg) msg.ReplyTo = replyToMsg
} else if replyToEvt, _ := matrix.GetEvent(replyToRoom, evt.Content.GetReplyTo()); replyToEvt != nil { } else if replyToEvt, _ := matrix.GetEvent(room, evt.Content.GetReplyTo()); replyToEvt != nil {
if replyToMsg := directParseEvent(matrix, replyToRoom, replyToEvt); replyToMsg != nil { if replyToMsg := directParseEvent(matrix, room, replyToEvt); replyToMsg != nil {
msg.SetReplyTo(replyToMsg) msg.ReplyTo = replyToMsg
} else { } else {
// TODO add unrenderable reply header // TODO add unrenderable reply header
} }
@ -70,15 +65,22 @@ func ParseEvent(matrix ifc.MatrixContainer, mainView ifc.MainView, room *rooms.R
return msg return msg
} }
func directParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) UIMessage { func directParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) *UIMessage {
displayname := evt.Sender
member := room.GetMember(evt.Sender)
if member != nil {
displayname = member.Displayname
}
switch evt.Type { switch evt.Type {
case mautrix.EventSticker: case mautrix.EventSticker:
evt.Content.MsgType = mautrix.MsgImage evt.Content.MsgType = mautrix.MsgImage
fallthrough fallthrough
case mautrix.EventMessage: case mautrix.EventMessage:
return ParseMessage(matrix, room, evt) return ParseMessage(matrix, room, evt, displayname)
case mautrix.EventEncrypted:
return NewExpandedTextMessage(evt, displayname, tstring.NewStyleTString("Encrypted messages are not yet supported", tcell.StyleDefault.Italic(true)))
case mautrix.StateTopic, mautrix.StateRoomName, mautrix.StateAliases, mautrix.StateCanonicalAlias: case mautrix.StateTopic, mautrix.StateRoomName, mautrix.StateAliases, mautrix.StateCanonicalAlias:
return ParseStateEvent(matrix, room, evt) return ParseStateEvent(evt, displayname)
case mautrix.StateMember: case mautrix.StateMember:
return ParseMembershipEvent(room, evt) return ParseMembershipEvent(room, evt)
} }
@ -86,12 +88,7 @@ func directParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix
return nil return nil
} }
func ParseStateEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) UIMessage { func ParseStateEvent(evt *mautrix.Event, displayname string) *UIMessage {
displayname := evt.Sender
member := room.GetMember(evt.Sender)
if member != nil {
displayname = member.Displayname
}
text := tstring.NewColorTString(displayname, widget.GetHashColor(evt.Sender)) text := tstring.NewColorTString(displayname, widget.GetHashColor(evt.Sender))
switch evt.Type { switch evt.Type {
case mautrix.StateTopic: case mautrix.StateTopic:
@ -124,15 +121,13 @@ func ParseStateEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.
return NewExpandedTextMessage(evt, displayname, text) return NewExpandedTextMessage(evt, displayname, text)
} }
func ParseMessage(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) UIMessage { func ParseMessage(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event, displayname string) *UIMessage {
displayname := evt.Sender
member := room.GetMember(evt.Sender)
if member != nil {
displayname = member.Displayname
}
if len(evt.Content.GetReplyTo()) > 0 { if len(evt.Content.GetReplyTo()) > 0 {
evt.Content.RemoveReplyFallback() evt.Content.RemoveReplyFallback()
} }
if evt.Content.GetRelatesTo().Type == mautrix.RelReplace && evt.Content.NewContent != nil {
evt.Content = *evt.Content.NewContent
}
switch evt.Content.MsgType { switch evt.Content.MsgType {
case "m.text", "m.notice", "m.emote": case "m.text", "m.notice", "m.emote":
if evt.Content.Format == mautrix.FormatHTML { if evt.Content.Format == mautrix.FormatHTML {
@ -224,7 +219,7 @@ func getMembershipEventContent(room *rooms.Room, evt *mautrix.Event) (sender str
return return
} }
func ParseMembershipEvent(room *rooms.Room, evt *mautrix.Event) UIMessage { func ParseMembershipEvent(room *rooms.Room, evt *mautrix.Event) *UIMessage {
displayname, text := getMembershipEventContent(room, evt) displayname, text := getMembershipEventContent(room, evt)
if len(text) == 0 { if len(text) == 0 {
return nil return nil

View File

@ -52,12 +52,12 @@ func matchBoundaryPattern(bare bool, extract tstring.TString) tstring.TString {
// CalculateBuffer generates the internal buffer for this message that consists // CalculateBuffer generates the internal buffer for this message that consists
// of the text of this message split into lines at most as wide as the width // of the text of this message split into lines at most as wide as the width
// parameter. // parameter.
func (msg *BaseMessage) calculateBufferWithText(prefs config.UserPreferences, text tstring.TString, width int) { func calculateBufferWithText(prefs config.UserPreferences, text tstring.TString, width int, msg *UIMessage) []tstring.TString {
if width < 2 { if width < 2 {
return return nil
} }
msg.buffer = []tstring.TString{} var buffer []tstring.TString
if prefs.BareMessageView { if prefs.BareMessageView {
newText := tstring.NewTString(msg.FormatTime()) newText := tstring.NewTString(msg.FormatTime())
@ -74,7 +74,7 @@ func (msg *BaseMessage) calculateBufferWithText(prefs config.UserPreferences, te
newlines := 0 newlines := 0
for _, str := range forcedLinebreaks { for _, str := range forcedLinebreaks {
if len(str) == 0 && newlines < 1 { if len(str) == 0 && newlines < 1 {
msg.buffer = append(msg.buffer, tstring.TString{}) buffer = append(buffer, tstring.TString{})
newlines++ newlines++
} else { } else {
newlines = 0 newlines = 0
@ -88,8 +88,9 @@ func (msg *BaseMessage) calculateBufferWithText(prefs config.UserPreferences, te
} }
extract = matchBoundaryPattern(prefs.BareMessageView, extract) extract = matchBoundaryPattern(prefs.BareMessageView, extract)
} }
msg.buffer = append(msg.buffer, extract) buffer = append(buffer, extract)
str = str[len(extract):] str = str[len(extract):]
} }
} }
return buffer
} }

View File

@ -20,72 +20,82 @@ import (
"fmt" "fmt"
"time" "time"
ifc "maunium.net/go/gomuks/interface"
"maunium.net/go/mautrix" "maunium.net/go/mautrix"
"maunium.net/go/mauview"
"maunium.net/go/gomuks/config" "maunium.net/go/gomuks/config"
"maunium.net/go/gomuks/ui/messages/tstring" "maunium.net/go/gomuks/ui/messages/tstring"
) )
type TextMessage struct { type TextMessage struct {
BaseMessage
cache tstring.TString cache tstring.TString
MsgText string buffer []tstring.TString
Text string
} }
// NewTextMessage creates a new UITextMessage object with the provided values and the default state. // NewTextMessage creates a new UITextMessage object with the provided values and the default state.
func NewTextMessage(event *mautrix.Event, displayname string, text string) UIMessage { func NewTextMessage(event *mautrix.Event, displayname string, text string) *UIMessage {
return &TextMessage{ return newUIMessage(event, displayname, &TextMessage{
BaseMessage: newBaseMessage(event, displayname), Text: text,
MsgText: text, })
}
} }
func NewServiceMessage(text string) UIMessage { func NewServiceMessage(text string) *UIMessage {
return &TextMessage{ return &UIMessage{
BaseMessage: BaseMessage{ SenderID: "*",
MsgSenderID: "*", SenderName: "*",
MsgSender: "*", Timestamp: time.Now(),
MsgTimestamp: time.Now(), IsService: true,
MsgIsService: true, Renderer: &TextMessage{
Text: text,
}, },
MsgText: text,
} }
} }
func (msg *TextMessage) Clone() UIMessage { func (msg *TextMessage) Clone() MessageRenderer {
return &TextMessage{ return &TextMessage{
BaseMessage: msg.BaseMessage.clone(), Text: msg.Text,
MsgText: msg.MsgText,
} }
} }
func (msg *TextMessage) getCache() tstring.TString { func (msg *TextMessage) getCache(uiMsg *UIMessage) tstring.TString {
if msg.cache == nil { if msg.cache == nil {
switch msg.MsgType { switch uiMsg.Type {
case "m.emote": case "m.emote":
msg.cache = tstring.NewColorTString(fmt.Sprintf("* %s %s", msg.MsgSender, msg.MsgText), msg.TextColor()) msg.cache = tstring.NewColorTString(fmt.Sprintf("* %s %s", uiMsg.SenderName, msg.Text), uiMsg.TextColor())
msg.cache.Colorize(0, len(msg.MsgSender)+2, msg.SenderColor()) msg.cache.Colorize(0, len(uiMsg.SenderName)+2, uiMsg.SenderColor())
default: default:
msg.cache = tstring.NewColorTString(msg.MsgText, msg.TextColor()) msg.cache = tstring.NewColorTString(msg.Text, uiMsg.TextColor())
} }
} }
return msg.cache return msg.cache
} }
func (msg *TextMessage) SetIsHighlight(isHighlight bool) {
msg.BaseMessage.SetIsHighlight(isHighlight)
msg.cache = nil
}
func (msg *TextMessage) NotificationContent() string { func (msg *TextMessage) NotificationContent() string {
return msg.MsgText return msg.Text
} }
func (msg *TextMessage) PlainText() string { func (msg *TextMessage) PlainText() string {
return msg.MsgText return msg.Text
} }
func (msg *TextMessage) CalculateBuffer(prefs config.UserPreferences, width int) { func (msg *TextMessage) String() string {
msg.CalculateReplyBuffer(prefs, width) return fmt.Sprintf(`&messages.TextMessage{Text="%s"}`, msg.Text)
msg.calculateBufferWithText(prefs, msg.getCache(), width)
} }
func (msg *TextMessage) CalculateBuffer(prefs config.UserPreferences, width int, uiMsg *UIMessage) {
msg.buffer = calculateBufferWithText(prefs, msg.getCache(uiMsg), width, uiMsg)
}
func (msg *TextMessage) Height() int {
return len(msg.buffer)
}
func (msg *TextMessage) Draw(screen mauview.Screen) {
for y, line := range msg.buffer {
line.Draw(screen, 0, y)
}
}
func (msg *TextMessage) RegisterMatrix(matrix ifc.MatrixContainer) {}

View File

@ -236,6 +236,10 @@ func (list *RoomList) AddScrollOffset(offset int) {
func (list *RoomList) First() (string, *rooms.Room) { func (list *RoomList) First() (string, *rooms.Room) {
list.RLock() list.RLock()
defer list.RUnlock() defer list.RUnlock()
return list.first()
}
func (list *RoomList) first() (string, *rooms.Room) {
for _, tag := range list.tags { for _, tag := range list.tags {
trl := list.items[tag] trl := list.items[tag]
if trl.HasVisibleRooms() { if trl.HasVisibleRooms() {
@ -248,6 +252,10 @@ func (list *RoomList) First() (string, *rooms.Room) {
func (list *RoomList) Last() (string, *rooms.Room) { func (list *RoomList) Last() (string, *rooms.Room) {
list.RLock() list.RLock()
defer list.RUnlock() defer list.RUnlock()
return list.last()
}
func (list *RoomList) last() (string, *rooms.Room) {
for tagIndex := len(list.tags) - 1; tagIndex >= 0; tagIndex-- { for tagIndex := len(list.tags) - 1; tagIndex >= 0; tagIndex-- {
tag := list.tags[tagIndex] tag := list.tags[tagIndex]
trl := list.items[tag] trl := list.items[tag]
@ -273,7 +281,7 @@ func (list *RoomList) Previous() (string, *rooms.Room) {
if len(list.items) == 0 { if len(list.items) == 0 {
return "", nil return "", nil
} else if list.selected == nil { } else if list.selected == nil {
return list.First() return list.first()
} }
trl := list.items[list.selectedTag] trl := list.items[list.selectedTag]
@ -295,11 +303,11 @@ func (list *RoomList) Previous() (string, *rooms.Room) {
return prevTag, prevTRL.LastVisible() return prevTag, prevTRL.LastVisible()
} }
} }
return list.Last() return list.last()
} else if index >= 0 { } else if index >= 0 {
return list.selectedTag, trl.Visible()[index+1].Room return list.selectedTag, trl.Visible()[index+1].Room
} }
return list.First() return list.first()
} }
func (list *RoomList) Next() (string, *rooms.Room) { func (list *RoomList) Next() (string, *rooms.Room) {
@ -308,7 +316,7 @@ func (list *RoomList) Next() (string, *rooms.Room) {
if len(list.items) == 0 { if len(list.items) == 0 {
return "", nil return "", nil
} else if list.selected == nil { } else if list.selected == nil {
return list.First() return list.first()
} }
trl := list.items[list.selectedTag] trl := list.items[list.selectedTag]
@ -330,11 +338,11 @@ func (list *RoomList) Next() (string, *rooms.Room) {
return nextTag, nextTRL.FirstVisible() return nextTag, nextTRL.FirstVisible()
} }
} }
return list.First() return list.first()
} else if index > 0 { } else if index > 0 {
return list.selectedTag, trl.Visible()[index-1].Room return list.selectedTag, trl.Visible()[index-1].Room
} }
return list.Last() return list.last()
} }
// NextWithActivity Returns next room with activity. // NextWithActivity Returns next room with activity.

View File

@ -57,6 +57,8 @@ type RoomView struct {
ulBorderScreen *mauview.ProxyScreen ulBorderScreen *mauview.ProxyScreen
ulScreen *mauview.ProxyScreen ulScreen *mauview.ProxyScreen
userListLoaded bool
prevScreen mauview.Screen prevScreen mauview.Screen
parent *MainView parent *MainView
@ -99,7 +101,6 @@ func NewRoomView(parent *MainView, room *rooms.Room) *RoomView {
SetTabCompleteFunc(view.InputTabComplete) SetTabCompleteFunc(view.InputTabComplete)
view.topic. view.topic.
SetText(strings.Replace(room.GetTopic(), "\n", " ", -1)).
SetTextColor(tcell.ColorWhite). SetTextColor(tcell.ColorWhite).
SetBackgroundColor(tcell.ColorDarkGreen) SetBackgroundColor(tcell.ColorDarkGreen)
@ -385,11 +386,11 @@ func (view *RoomView) SendMessage(msgtype mautrix.MessageType, text string) {
text = emoji.Sprint(text) text = emoji.Sprint(text)
} }
evt := view.parent.matrix.PrepareMarkdownMessage(view.Room.ID, msgtype, text) evt := view.parent.matrix.PrepareMarkdownMessage(view.Room.ID, msgtype, text)
msg := view.ParseEvent(evt) msg := view.parseEvent(evt)
view.AddMessage(msg) view.AddMessage(msg)
eventID, err := view.parent.matrix.SendEvent(evt) eventID, err := view.parent.matrix.SendEvent(evt)
if err != nil { if err != nil {
msg.SetState(mautrix.EventStateSendFail) msg.State = mautrix.EventStateSendFail
// Show shorter version if available // Show shorter version if available
if httpErr, ok := err.(mautrix.HTTPError); ok { if httpErr, ok := err.(mautrix.HTTPError); ok {
err = httpErr err = httpErr
@ -401,7 +402,10 @@ func (view *RoomView) SendMessage(msgtype mautrix.MessageType, text string) {
view.parent.parent.Render() view.parent.parent.Render()
} else { } else {
debug.Print("Event ID received:", eventID) debug.Print("Event ID received:", eventID)
//view.MessageView().UpdateMessageID(msg, eventID) msg.EventID = eventID
msg.State = mautrix.EventStateDefault
view.MessageView().setMessageID(msg)
view.parent.parent.Render()
} }
} }
@ -413,12 +417,20 @@ func (view *RoomView) MxRoom() *rooms.Room {
return view.Room return view.Room
} }
func (view *RoomView) Update() {
view.topic.SetText(strings.Replace(view.Room.GetTopic(), "\n", " ", -1))
if !view.userListLoaded {
view.UpdateUserList()
}
}
func (view *RoomView) UpdateUserList() { func (view *RoomView) UpdateUserList() {
pls := &mautrix.PowerLevels{} pls := &mautrix.PowerLevels{}
if plEvent := view.Room.GetStateEvent(mautrix.StatePowerLevels, ""); plEvent != nil { if plEvent := view.Room.GetStateEvent(mautrix.StatePowerLevels, ""); plEvent != nil {
pls = plEvent.Content.GetPowerLevels() pls = plEvent.Content.GetPowerLevels()
} }
view.userList.Update(view.Room.GetMembers(), pls) view.userList.Update(view.Room.GetMembers(), pls)
view.userListLoaded = true
} }
func (view *RoomView) AddServiceMessage(text string) { func (view *RoomView) AddServiceMessage(text string) {
@ -429,10 +441,18 @@ func (view *RoomView) AddMessage(message ifc.Message) {
view.content.AddMessage(message, AppendMessage) view.content.AddMessage(message, AppendMessage)
} }
func (view *RoomView) ParseEvent(evt *mautrix.Event) ifc.Message { func (view *RoomView) parseEvent(evt *mautrix.Event) *messages.UIMessage {
return messages.ParseEvent(view.parent.matrix, view.parent, view.Room, evt) return messages.ParseEvent(view.parent.matrix, view.parent, view.Room, evt)
} }
func (view *RoomView) ParseEvent(evt *mautrix.Event) ifc.Message {
msg := view.parseEvent(evt)
if msg == nil {
return nil
}
return msg
}
func (view *RoomView) GetEvent(eventID string) ifc.Message { func (view *RoomView) GetEvent(eventID string) ifc.Message {
message, ok := view.content.messageIDs[eventID] message, ok := view.content.messageIDs[eventID]
if !ok { if !ok {

View File

@ -74,7 +74,7 @@ func (ui *GomuksUI) NewLoginView() mauview.Component {
view.username.SetText(ui.gmx.Config().UserID) view.username.SetText(ui.gmx.Config().UserID)
view.password.SetMaskCharacter('*') view.password.SetMaskCharacter('*')
view.quitButton.SetOnClick(ui.gmx.Stop).SetBackgroundColor(tcell.ColorDarkCyan) view.quitButton.SetOnClick(func() { ui.gmx.Stop(true) }).SetBackgroundColor(tcell.ColorDarkCyan)
view.loginButton.SetOnClick(view.Login).SetBackgroundColor(tcell.ColorDarkCyan) view.loginButton.SetOnClick(view.Login).SetBackgroundColor(tcell.ColorDarkCyan)
view.SetColumns([]int{1, 10, 1, 9, 1, 9, 1, 10, 1}) view.SetColumns([]int{1, 10, 1, 9, 1, 9, 1, 10, 1})

View File

@ -26,6 +26,7 @@ import (
sync "github.com/sasha-s/go-deadlock" sync "github.com/sasha-s/go-deadlock"
"maunium.net/go/gomuks/ui/messages"
"maunium.net/go/mauview" "maunium.net/go/mauview"
"maunium.net/go/tcell" "maunium.net/go/tcell"
@ -256,6 +257,7 @@ func (view *MainView) switchRoom(tag string, room *rooms.Room, lock bool) {
if room == nil { if room == nil {
return return
} }
room.Load()
roomView, ok := view.getRoomView(room.ID, lock) roomView, ok := view.getRoomView(room.ID, lock)
if !ok { if !ok {
@ -263,12 +265,15 @@ func (view *MainView) switchRoom(tag string, room *rooms.Room, lock bool) {
debug.Print(tag, room) debug.Print(tag, room)
return return
} }
roomView.Update()
view.roomView.SetInnerComponent(roomView) view.roomView.SetInnerComponent(roomView)
view.currentRoom = roomView view.currentRoom = roomView
view.MarkRead(roomView) view.MarkRead(roomView)
view.roomList.SetSelected(tag, room) view.roomList.SetSelected(tag, room)
view.parent.Render() view.parent.Render()
if len(roomView.MessageView().messages) == 0 {
if msgView := roomView.MessageView(); len(msgView.messages) < 20 && !msgView.initialHistoryLoaded {
msgView.initialHistoryLoaded = true
go view.LoadHistory(room.ID) go view.LoadHistory(room.ID)
} }
} }
@ -278,12 +283,6 @@ func (view *MainView) addRoomPage(room *rooms.Room) *RoomView {
roomView := NewRoomView(view, room). roomView := NewRoomView(view, room).
SetInputChangedFunc(view.InputChanged) SetInputChangedFunc(view.InputChanged)
view.rooms[room.ID] = roomView view.rooms[room.ID] = roomView
roomView.UpdateUserList()
// TODO make sure this works
if len(roomView.MessageView().messages) == 0 {
go view.LoadHistory(room.ID)
}
return roomView return roomView
} }
return nil return nil
@ -292,7 +291,7 @@ func (view *MainView) addRoomPage(room *rooms.Room) *RoomView {
func (view *MainView) GetRoom(roomID string) ifc.RoomView { func (view *MainView) GetRoom(roomID string) ifc.RoomView {
room, ok := view.getRoomView(roomID, true) room, ok := view.getRoomView(roomID, true)
if !ok { if !ok {
return view.addRoom(view.matrix.GetRoom(roomID)) return view.addRoom(view.matrix.GetOrCreateRoom(roomID))
} }
return room return room
} }
@ -348,11 +347,11 @@ func (view *MainView) addRoom(room *rooms.Room) *RoomView {
return roomView return roomView
} }
func (view *MainView) SetRooms(rooms map[string]*rooms.Room) { func (view *MainView) SetRooms(rooms *rooms.RoomCache) {
view.roomList.Clear() view.roomList.Clear()
view.roomsLock.Lock() view.roomsLock.Lock()
view.rooms = make(map[string]*RoomView) view.rooms = make(map[string]*RoomView)
for _, room := range rooms { for _, room := range rooms.Map {
if room.HasLeft { if room.HasLeft {
continue continue
} }
@ -390,7 +389,8 @@ func sendNotification(room *rooms.Room, sender, text string, critical, sound boo
func (view *MainView) NotifyMessage(room *rooms.Room, message ifc.Message, should pushrules.PushActionArrayShould) { func (view *MainView) NotifyMessage(room *rooms.Room, message ifc.Message, should pushrules.PushActionArrayShould) {
view.roomList.Bump(room) view.roomList.Bump(room)
if message.SenderID() == view.config.UserID { uiMsg, ok := message.(*messages.UIMessage)
if ok && uiMsg.SenderID == view.config.UserID {
return return
} }
// Whether or not the room where the message came is the currently shown room. // Whether or not the room where the message came is the currently shown room.
@ -420,16 +420,6 @@ func (view *MainView) NotifyMessage(room *rooms.Room, message ifc.Message, shoul
message.SetIsHighlight(should.Highlight) message.SetIsHighlight(should.Highlight)
} }
func (view *MainView) InitialSyncDone() {
view.roomList.Clear()
view.roomsLock.RLock()
for _, room := range view.rooms {
view.roomList.Add(room.Room)
room.UpdateUserList()
}
view.roomsLock.RUnlock()
}
func (view *MainView) LoadHistory(roomID string) { func (view *MainView) LoadHistory(roomID string) {
defer debug.Recover() defer debug.Recover()
roomView, ok := view.getRoomView(roomID, true) roomView, ok := view.getRoomView(roomID, true)