Merge branch 'break-things-again'
This commit is contained in:
commit
8b87809ac1
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,3 +7,4 @@ gomuks
|
|||||||
coverage.out
|
coverage.out
|
||||||
coverage.html
|
coverage.html
|
||||||
deb/usr
|
deb/usr
|
||||||
|
*.prof
|
||||||
|
115
config/config.go
115
config/config.go
@ -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)
|
||||||
}
|
}
|
||||||
|
30
go.mod
30
go.mod
@ -1,33 +1,39 @@
|
|||||||
module maunium.net/go/gomuks
|
module maunium.net/go/gomuks
|
||||||
|
|
||||||
go 1.12
|
go 1.11
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/chroma v0.6.3
|
github.com/alecthomas/chroma v0.6.3
|
||||||
github.com/alecthomas/kong v0.1.16 // indirect
|
|
||||||
github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1 // indirect
|
github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1 // indirect
|
||||||
github.com/disintegration/imaging v1.6.0
|
github.com/disintegration/imaging v1.6.0
|
||||||
|
github.com/kr/pretty v0.1.0 // indirect
|
||||||
github.com/kyokomi/emoji v2.1.0+incompatible
|
github.com/kyokomi/emoji v2.1.0+incompatible
|
||||||
github.com/lithammer/fuzzysearch v1.0.2
|
github.com/lithammer/fuzzysearch v1.0.2
|
||||||
github.com/lucasb-eyer/go-colorful v1.0.2
|
github.com/lucasb-eyer/go-colorful v1.0.2
|
||||||
github.com/mattn/go-colorable v0.1.1 // indirect
|
github.com/mattn/go-isatty v0.0.8 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.7 // indirect
|
|
||||||
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/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
github.com/stretchr/testify v1.3.0
|
||||||
github.com/stretchr/objx v0.2.0 // indirect
|
|
||||||
go.etcd.io/bbolt v1.3.2
|
go.etcd.io/bbolt v1.3.2
|
||||||
golang.org/x/image v0.0.0-20190507092727-e4e5bf290fec
|
golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff
|
||||||
golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65
|
||||||
|
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||||
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 // indirect
|
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.20190512142959-897a8c5be1d9
|
maunium.net/go/mautrix v0.1.0-alpha.3.0.20190616114735-e5bf3141e88e
|
||||||
maunium.net/go/mauview v0.0.0-20190426104003-3e5387b8a125
|
maunium.net/go/mauview v0.0.0-20190606152754-de9e0a754a5d
|
||||||
maunium.net/go/tcell v0.0.0-20190426103942-24a060c2189b
|
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
|
||||||
|
)
|
||||||
|
68
go.sum
68
go.sum
@ -1,15 +1,19 @@
|
|||||||
|
github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08=
|
||||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||||
|
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
||||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
||||||
github.com/alecthomas/chroma v0.6.3 h1:8H1D0yddf0mvgvO4JDBKnzLd9ERmzzAijBxnZXGV/FA=
|
github.com/alecthomas/chroma v0.6.3 h1:8H1D0yddf0mvgvO4JDBKnzLd9ERmzzAijBxnZXGV/FA=
|
||||||
github.com/alecthomas/chroma v0.6.3/go.mod h1:quT2EpvJNqkuPi6DmBHB+E33FXBgBBPzyH5++Dn1LPc=
|
github.com/alecthomas/chroma v0.6.3/go.mod h1:quT2EpvJNqkuPi6DmBHB+E33FXBgBBPzyH5++Dn1LPc=
|
||||||
|
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
|
||||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
||||||
github.com/alecthomas/kong v0.1.15/go.mod h1:0m2VYms8rH0qbCqVB2gvGHk74bqLIq0HXjCs5bNbNQU=
|
github.com/alecthomas/kong v0.1.15/go.mod h1:0m2VYms8rH0qbCqVB2gvGHk74bqLIq0HXjCs5bNbNQU=
|
||||||
github.com/alecthomas/kong v0.1.16/go.mod h1:0m2VYms8rH0qbCqVB2gvGHk74bqLIq0HXjCs5bNbNQU=
|
|
||||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
||||||
|
github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1 h1:GDQdwm/gAcJcLAKQQZGOJ4knlw+7rfEQQcmwTbt4p5E=
|
||||||
github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
||||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
||||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/disintegration/imaging v1.6.0 h1:nVPXRUUQ36Z7MNf0O77UzgnOb1mkMMor7lmJMJXc/mA=
|
github.com/disintegration/imaging v1.6.0 h1:nVPXRUUQ36Z7MNf0O77UzgnOb1mkMMor7lmJMJXc/mA=
|
||||||
github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ=
|
github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ=
|
||||||
@ -17,6 +21,11 @@ github.com/dlclark/regexp2 v1.1.6 h1:CqB4MjHw0MFCDj+PHHjiESmHX+N7t0tJzKvC6M97BRg
|
|||||||
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kyokomi/emoji v2.1.0+incompatible h1:+DYU2RgpI6OHG4oQkM5KlqD3Wd3UPEsX8jamTo1Mp6o=
|
github.com/kyokomi/emoji v2.1.0+incompatible h1:+DYU2RgpI6OHG4oQkM5KlqD3Wd3UPEsX8jamTo1Mp6o=
|
||||||
github.com/kyokomi/emoji v2.1.0+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
|
github.com/kyokomi/emoji v2.1.0+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
|
||||||
github.com/lithammer/fuzzysearch v1.0.2 h1:AjCE2iwc5y+8K+h2nXVc0Pmrpjvu+JVqMgiZ0oakXDM=
|
github.com/lithammer/fuzzysearch v1.0.2 h1:AjCE2iwc5y+8K+h2nXVc0Pmrpjvu+JVqMgiZ0oakXDM=
|
||||||
@ -24,65 +33,68 @@ github.com/lithammer/fuzzysearch v1.0.2/go.mod h1:bvAJyokfCQ7Vknrd4Kgc+izmMrPj5C
|
|||||||
github.com/lucasb-eyer/go-colorful v1.0.2 h1:mCMFu6PgSozg9tDNMMK3g18oJBX7oYGrC09mS6CXfO4=
|
github.com/lucasb-eyer/go-colorful v1.0.2 h1:mCMFu6PgSozg9tDNMMK3g18oJBX7oYGrC09mS6CXfO4=
|
||||||
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
|
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
|
||||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
github.com/npat-efault/poller v2.0.0+incompatible h1:jtTdXWKgN5kDK41ts8hoY1rvTEi0K08MTB8/bRO9MqE=
|
||||||
|
github.com/npat-efault/poller v2.0.0+incompatible/go.mod h1:lni01B89P8PtVpwlAhdhK1niN5rPkDGGpGGgBJzpSgo=
|
||||||
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
||||||
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/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-20190313204849-f699dde9c340 h1:nOZbL5f2xmBAHWYrrHbHV1xatzZirN++oOQ3g83Ypgs=
|
github.com/rivo/uniseg v0.0.0-20190513083848-b9f5b9457d44 h1:XKCbzPvK4/BbMXoMJOkYP2ANxiAEO0HM1xn6psSbXxY=
|
||||||
github.com/rivo/uniseg v0.0.0-20190313204849-f699dde9c340/go.mod h1:SOLvOL4ybwgLJ6TYoX/rtaJ8EGOulH4XU7E9/TLrTCE=
|
github.com/rivo/uniseg v0.0.0-20190513083848-b9f5b9457d44/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sasha-s/go-deadlock v0.2.0 h1:lMqc+fUb7RrFS3gQLtoQsJ7/6TV/pAIFvBsqX73DK8Y=
|
github.com/sasha-s/go-deadlock v0.2.0 h1:lMqc+fUb7RrFS3gQLtoQsJ7/6TV/pAIFvBsqX73DK8Y=
|
||||||
github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10=
|
github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10=
|
||||||
|
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/zyedidia/clipboard v0.0.0-20180718195219-bd31d747117d h1:Lhqt2eo+rgM8aswvM7nTtAMVm8ARPWzkE9n6eZDOccY=
|
github.com/zyedidia/clipboard v0.0.0-20180718195219-bd31d747117d h1:Lhqt2eo+rgM8aswvM7nTtAMVm8ARPWzkE9n6eZDOccY=
|
||||||
github.com/zyedidia/clipboard v0.0.0-20180718195219-bd31d747117d/go.mod h1:WDk3p8GiZV9+xFWlSo8qreeoLhW6Ik692rqXk+cNeRY=
|
github.com/zyedidia/clipboard v0.0.0-20180718195219-bd31d747117d/go.mod h1:WDk3p8GiZV9+xFWlSo8qreeoLhW6Ik692rqXk+cNeRY=
|
||||||
|
github.com/zyedidia/poller v2.0.1-0.20170616160828-ab09682913b7+incompatible h1:8VIuqV713C9SmwvUGGpMhrK/5RdsRyp9N4YnPDPDe6c=
|
||||||
|
github.com/zyedidia/poller v2.0.1-0.20170616160828-ab09682913b7+incompatible/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
|
||||||
go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
|
go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
|
||||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||||
golang.org/x/image v0.0.0-20190507092727-e4e5bf290fec h1:arXJwtMuk5vqI1NHX0UTnNw977rYk5Sl4jQqHj+hun4=
|
golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff h1:+2zgJKVDVAz/BWSsuniCmU1kLCjL88Z8/kv39xCI9NQ=
|
||||||
golang.org/x/image v0.0.0-20190507092727-e4e5bf290fec/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5 h1:6M3SDHlHHDCx2PcQw3S4KsR170vGqDhJDOmpVd4Hjak=
|
|
||||||
golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4 h1:3i7qG/aA9NUAzdnJHfhgxSKSmxbAebomYR5IZgFbC5Y=
|
||||||
golang.org/x/sys v0.0.0-20190425145619-16072639606e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190425222832-ad9eeb80039a/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190511041617-99f201b6807e/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2 h1:MZF6J7CV6s/h0HBkfqebrYfKCVEo5iN+wzE4QhV3Evo=
|
||||||
gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2/go.mod h1:s1Sn2yZos05Qfs7NKt867Xe18emOmtsO3eAKbDaon0o=
|
gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2/go.mod h1:s1Sn2yZos05Qfs7NKt867Xe18emOmtsO3eAKbDaon0o=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
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.20190512142959-897a8c5be1d9 h1:bmXWacD7spGlj2DriogI5ahet6dzY2Q33Aa8bCyQdtI=
|
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.20190512142959-897a8c5be1d9/go.mod h1:cyZKXVQphK5gnbKMvRLXoxfKMySfAQJvn8ttR4x/23c=
|
maunium.net/go/mautrix v0.1.0-alpha.3.0.20190606153009-ca5d9535b6cc/go.mod h1:O+QWJP3H7BZEzIBSrECKpnpRnEKBwaoWVEu/yZwVwxg=
|
||||||
maunium.net/go/mauview v0.0.0-20190426104003-3e5387b8a125 h1:wSrf+ZYCavbnU21f3Q1fKytL2mJyCyi2+Dosbck780Q=
|
maunium.net/go/mautrix v0.1.0-alpha.3.0.20190607192515-d505052a02ac h1:r0X7mMPcc8eJaCaHdbW9ibfCLe3EruuqZIH2FM8oLIs=
|
||||||
maunium.net/go/mauview v0.0.0-20190426104003-3e5387b8a125/go.mod h1:TIbj5iET7pJSq4SpVxZ080mAe6dgoQxGN5oRHwnSXnI=
|
maunium.net/go/mautrix v0.1.0-alpha.3.0.20190607192515-d505052a02ac/go.mod h1:O+QWJP3H7BZEzIBSrECKpnpRnEKBwaoWVEu/yZwVwxg=
|
||||||
maunium.net/go/tcell v0.0.0-20190426103942-24a060c2189b h1:xncLOTadq4VzDKFUV7Jm3OSkehS2KAL88pfwowcAmRA=
|
maunium.net/go/mauview v0.0.0-20190606152754-de9e0a754a5d h1:H4wZ4vMVnOh5QFsb4xZtssgpv3DDEkBRzQ8iyEg2fX0=
|
||||||
maunium.net/go/tcell v0.0.0-20190426103942-24a060c2189b/go.mod h1:V4YmSYrOlCtlTM188iXR8VwWSo+ksAVawxQLXibeAyQ=
|
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/go.mod h1:8UOoBx9iuQZewMnaDHz9KQZtwFvl0TOA1f6hQhycgBw=
|
||||||
|
10
gomuks.go
10
gomuks.go
@ -58,8 +58,6 @@ func NewGomuks(uiProvider ifc.UIProvider, configDir, cacheDir string) *Gomuks {
|
|||||||
// Save saves the active session and message history.
|
// Save saves the active session and message history.
|
||||||
func (gmx *Gomuks) Save() {
|
func (gmx *Gomuks) Save() {
|
||||||
gmx.config.SaveAll()
|
gmx.config.SaveAll()
|
||||||
//debug.Print("Saving history...")
|
|
||||||
//gmx.ui.MainView().SaveAllHistory()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartAutosave calls Save() every minute until it receives a stop signal
|
// StartAutosave calls Save() every minute until it receives a stop signal
|
||||||
@ -70,7 +68,9 @@ func (gmx *Gomuks) StartAutosave() {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
|
if gmx.config.AuthCache.InitialSyncDone {
|
||||||
gmx.Save()
|
gmx.Save()
|
||||||
|
}
|
||||||
case val := <-gmx.stop:
|
case val := <-gmx.stop:
|
||||||
if val {
|
if val {
|
||||||
return
|
return
|
||||||
@ -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()
|
||||||
|
@ -27,5 +27,5 @@ type Gomuks interface {
|
|||||||
Config() *config.Config
|
Config() *config.Config
|
||||||
|
|
||||||
Start()
|
Start()
|
||||||
Stop()
|
Stop(save bool)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -43,14 +43,14 @@ 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)
|
||||||
|
Bump(room *rooms.Room)
|
||||||
|
|
||||||
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 +68,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)
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
107
matrix/matrix.go
107
matrix/matrix.go
@ -29,7 +29,9 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
dbg "runtime/debug"
|
||||||
|
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/mautrix/format"
|
"maunium.net/go/mautrix/format"
|
||||||
@ -204,6 +206,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)
|
||||||
@ -218,9 +223,22 @@ func (c *Container) OnLogin() {
|
|||||||
c.syncer.InitDoneCallback = func() {
|
c.syncer.InitDoneCallback = func() {
|
||||||
debug.Print("Initial sync done")
|
debug.Print("Initial sync done")
|
||||||
c.config.AuthCache.InitialSyncDone = true
|
c.config.AuthCache.InitialSyncDone = true
|
||||||
c.config.SaveAuthCache()
|
debug.Print("Updating title caches")
|
||||||
c.ui.MainView().InitialSyncDone()
|
for _, room := range c.config.Rooms.Map {
|
||||||
|
room.GetTitle()
|
||||||
|
}
|
||||||
|
debug.Print("Cleaning cached rooms from memory")
|
||||||
|
c.config.Rooms.ForceClean()
|
||||||
|
debug.Print("Saving all data")
|
||||||
|
c.config.SaveAll()
|
||||||
|
debug.Print("Adding rooms to UI")
|
||||||
|
c.ui.MainView().SetRooms(c.config.Rooms)
|
||||||
c.ui.Render()
|
c.ui.Render()
|
||||||
|
// The initial sync can be a bit heavy, so we force run the GC here
|
||||||
|
// after cleaning up rooms from memory above.
|
||||||
|
debug.Print("Running GC")
|
||||||
|
runtime.GC()
|
||||||
|
dbg.FreeOSMemory()
|
||||||
}
|
}
|
||||||
c.client.Syncer = c.syncer
|
c.client.Syncer = c.syncer
|
||||||
|
|
||||||
@ -274,8 +292,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()
|
||||||
@ -289,9 +309,24 @@ func (c *Container) SendPreferencesToMatrix() {
|
|||||||
|
|
||||||
// HandleMessage is the event handler for the m.room.message timeline event.
|
// HandleMessage is the event handler for the m.room.message timeline event.
|
||||||
func (c *Container) HandleMessage(source EventSource, evt *mautrix.Event) {
|
func (c *Container) HandleMessage(source EventSource, evt *mautrix.Event) {
|
||||||
if source&EventSourceLeave != 0 || source&EventSourceState != 0 {
|
room := c.GetOrCreateRoom(evt.RoomID)
|
||||||
|
if source&EventSourceLeave != 0 {
|
||||||
|
room.HasLeft = true
|
||||||
|
return
|
||||||
|
} else if source&EventSourceState != 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
room.LastReceivedMessage = time.Unix(evt.Timestamp/1000, evt.Timestamp%1000*1000)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
mainView := c.ui.MainView()
|
mainView := c.ui.MainView()
|
||||||
|
|
||||||
roomView := mainView.GetRoom(evt.RoomID)
|
roomView := mainView.GetRoom(evt.RoomID)
|
||||||
@ -300,23 +335,29 @@ func (c *Container) HandleMessage(source EventSource, evt *mautrix.Event) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := c.history.Append(roomView.MxRoom(), []*mautrix.Event{evt})
|
if !room.Loaded() {
|
||||||
if err != nil {
|
pushRules := c.PushRules().GetActions(room, evt).Should()
|
||||||
debug.Printf("Failed to add event %s to history: %v", evt.ID, err)
|
shouldNotify := pushRules.Notify || !pushRules.NotifySpecified
|
||||||
|
if !shouldNotify {
|
||||||
|
room.LastReceivedMessage = time.Unix(evt.Timestamp/1000, evt.Timestamp%1000*1000)
|
||||||
|
room.AddUnread(evt.ID, shouldNotify, pushRules.Highlight)
|
||||||
|
mainView.Bump(room)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
c.ui.Render()
|
c.ui.Render()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debug.Printf("Parsing event %s type %s %v from %s in %s failed (ParseEvent() returned nil).", evt.ID, evt.Type, evt.Content.Raw, evt.Sender, evt.RoomID)
|
debug.Printf("Parsing event %s type %s %v from %s in %s failed (ParseEvent() returned nil).", evt.ID, evt.Type.String(), evt.Content.Raw, evt.Sender, evt.RoomID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,6 +365,9 @@ func (c *Container) HandleMessage(source EventSource, evt *mautrix.Event) {
|
|||||||
func (c *Container) HandleMembership(source EventSource, evt *mautrix.Event) {
|
func (c *Container) HandleMembership(source EventSource, evt *mautrix.Event) {
|
||||||
isLeave := source&EventSourceLeave != 0
|
isLeave := source&EventSourceLeave != 0
|
||||||
isTimeline := source&EventSourceTimeline != 0
|
isTimeline := source&EventSourceTimeline != 0
|
||||||
|
if isLeave {
|
||||||
|
c.GetOrCreateRoom(evt.RoomID).HasLeft = true
|
||||||
|
}
|
||||||
isNonTimelineLeave := isLeave && !isTimeline
|
isNonTimelineLeave := isLeave && !isTimeline
|
||||||
if !c.config.AuthCache.InitialSyncDone && isNonTimelineLeave {
|
if !c.config.AuthCache.InitialSyncDone && isNonTimelineLeave {
|
||||||
return
|
return
|
||||||
@ -350,11 +394,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 +448,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)
|
||||||
@ -417,7 +470,7 @@ func (c *Container) parseDirectChatInfo(evt *mautrix.Event) map[*rooms.Room]bool
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
room := c.GetRoom(roomID)
|
room := c.GetOrCreateRoom(roomID)
|
||||||
if room != nil && !room.HasLeft {
|
if room != nil && !room.HasLeft {
|
||||||
directChats[room] = true
|
directChats[room] = true
|
||||||
}
|
}
|
||||||
@ -428,14 +481,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) {
|
||||||
@ -451,7 +506,7 @@ func (c *Container) HandlePushRules(source EventSource, evt *mautrix.Event) {
|
|||||||
|
|
||||||
// HandleTag is the event handler for the m.tag account data event.
|
// HandleTag is the event handler for the m.tag account data event.
|
||||||
func (c *Container) HandleTag(source EventSource, evt *mautrix.Event) {
|
func (c *Container) HandleTag(source EventSource, evt *mautrix.Event) {
|
||||||
room := c.config.GetRoom(evt.RoomID)
|
room := c.GetOrCreateRoom(evt.RoomID)
|
||||||
|
|
||||||
newTags := make([]rooms.RoomTag, len(evt.Content.RoomTags))
|
newTags := make([]rooms.RoomTag, len(evt.Content.RoomTags))
|
||||||
index := 0
|
index := 0
|
||||||
@ -466,14 +521,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 +604,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 +617,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 +627,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 +653,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 +673,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 +707,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package rooms
|
package rooms
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"compress/gzip"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@ -31,17 +32,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
gob.Register([]interface{}{})
|
|
||||||
gob.Register(map[string]interface{}{})
|
gob.Register(map[string]interface{}{})
|
||||||
|
gob.Register([]interface{}{})
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +62,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
|
||||||
@ -70,6 +73,7 @@ type Room struct {
|
|||||||
PrevBatch string
|
PrevBatch string
|
||||||
// The MXID of the user whose session this room was created for.
|
// The MXID of the user whose session this room was created for.
|
||||||
SessionUserID string
|
SessionUserID string
|
||||||
|
SessionMember *mautrix.Member
|
||||||
|
|
||||||
// The number of unread messages that were notified about.
|
// The number of unread messages that were notified about.
|
||||||
UnreadMessages []UnreadMessage
|
UnreadMessages []UnreadMessage
|
||||||
@ -79,19 +83,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 +108,143 @@ 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
|
||||||
|
// Pre/post un/load hooks
|
||||||
|
preUnload func() bool
|
||||||
|
preLoad func() bool
|
||||||
|
postUnload func()
|
||||||
|
postLoad func()
|
||||||
|
// Whether or not the room state has changed
|
||||||
|
changed bool
|
||||||
|
|
||||||
|
// 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
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (room *Room) Loaded() bool {
|
||||||
|
return room.state != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (room *Room) Load() {
|
||||||
|
room.cache.TouchNode(room)
|
||||||
|
if room.Loaded() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if room.preLoad != nil && !room.preLoad() {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
defer file.Close()
|
|
||||||
dec := gob.NewDecoder(file)
|
|
||||||
room.lock.Lock()
|
room.lock.Lock()
|
||||||
defer room.lock.Unlock()
|
room.load()
|
||||||
return dec.Decode(room)
|
room.lock.Unlock()
|
||||||
|
if room.postLoad != nil {
|
||||||
|
room.postLoad()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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, "from disk")
|
||||||
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)
|
||||||
|
}
|
||||||
|
room.changed = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (room *Room) Touch() {
|
||||||
|
room.cache.TouchNode(room)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (room *Room) Unload() bool {
|
||||||
|
if room.preUnload != nil && !room.preUnload() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
debug.Print("Unloading", room.ID)
|
||||||
|
room.Save()
|
||||||
|
room.state = nil
|
||||||
|
room.aliasesCache = nil
|
||||||
|
room.topicCache = ""
|
||||||
|
room.canonicalAliasCache = ""
|
||||||
|
room.firstMemberCache = nil
|
||||||
|
room.secondMemberCache = nil
|
||||||
|
if room.postUnload != nil {
|
||||||
|
room.postUnload()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (room *Room) SetPreUnload(fn func() bool) {
|
||||||
|
room.preUnload = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (room *Room) SetPreLoad(fn func() bool) {
|
||||||
|
room.preLoad = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (room *Room) SetPostUnload(fn func()) {
|
||||||
|
room.postUnload = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (room *Room) SetPostLoad(fn func()) {
|
||||||
|
room.postLoad = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (room *Room) Save() {
|
||||||
|
if !room.Loaded() {
|
||||||
|
debug.Print("Failed to save room", room.ID, "state: room not loaded")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !room.changed {
|
||||||
|
debug.Print("Not saving", room.ID, "as state hasn't changed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
debug.Print("Saving state for room", room.ID, "to disk")
|
||||||
|
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,62 +339,79 @@ 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]
|
room.changed = true
|
||||||
|
_, 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 = event.Content.Name
|
||||||
|
room.nameCacheSource = ExplicitRoomName
|
||||||
case mautrix.StateCanonicalAlias:
|
case mautrix.StateCanonicalAlias:
|
||||||
if room.nameCacheSource >= CanonicalAliasRoomName {
|
if room.nameCacheSource <= CanonicalAliasRoomName {
|
||||||
room.nameCache = ""
|
room.NameCache = event.Content.Alias
|
||||||
|
room.nameCacheSource = CanonicalAliasRoomName
|
||||||
}
|
}
|
||||||
room.canonicalAliasCache = ""
|
room.canonicalAliasCache = event.Content.Alias
|
||||||
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
|
userID := event.GetStateKey()
|
||||||
room.firstMemberCache = nil
|
if userID == room.SessionUserID {
|
||||||
if room.nameCacheSource >= MemberRoomName {
|
room.SessionMember = room.eventToMember(userID, &event.Content)
|
||||||
room.nameCache = ""
|
}
|
||||||
|
if room.memberCache != nil {
|
||||||
|
if event.Content.Membership == mautrix.MembershipLeave || event.Content.Membership == mautrix.MembershipBan {
|
||||||
|
delete(room.memberCache, userID)
|
||||||
|
} else if event.Content.Membership == mautrix.MembershipInvite || event.Content.Membership == mautrix.MembershipJoin {
|
||||||
|
member := room.eventToMember(userID, &event.Content)
|
||||||
|
existingMember, ok := room.memberCache[userID]
|
||||||
|
if ok {
|
||||||
|
*existingMember = *member
|
||||||
|
} else {
|
||||||
|
room.memberCache[userID] = member
|
||||||
|
room.updateNthMemberCache(userID, member)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if room.nameCacheSource <= MemberRoomName {
|
||||||
|
room.NameCache = ""
|
||||||
}
|
}
|
||||||
case mautrix.StateTopic:
|
case mautrix.StateTopic:
|
||||||
room.topicCache = ""
|
room.topicCache = event.Content.Topic
|
||||||
}
|
}
|
||||||
|
|
||||||
stateKey := ""
|
|
||||||
if event.StateKey != nil {
|
|
||||||
stateKey = *event.StateKey
|
|
||||||
}
|
|
||||||
if event.Type != mautrix.StateMember {
|
if event.Type != mautrix.StateMember {
|
||||||
debug.Printf("Updating state %s#%s for %s", event.Type, stateKey, room.ID)
|
debug.Printf("Updating state %s#%s for %s", event.Type.String(), event.GetStateKey(), room.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +459,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 +472,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 +487,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,27 +532,47 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (room *Room) eventToMember(userID string, content *mautrix.Content) *mautrix.Member {
|
||||||
|
member := &content.Member
|
||||||
|
member.Membership = content.Membership
|
||||||
|
if len(member.Displayname) == 0 {
|
||||||
|
member.Displayname = userID
|
||||||
|
}
|
||||||
|
return member
|
||||||
|
}
|
||||||
|
|
||||||
|
func (room *Room) updateNthMemberCache(userID string, member *mautrix.Member) {
|
||||||
|
if userID != room.SessionUserID {
|
||||||
|
if room.firstMemberCache == nil {
|
||||||
|
room.firstMemberCache = member
|
||||||
|
} else if room.secondMemberCache == nil {
|
||||||
|
room.secondMemberCache = member
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 := room.eventToMember(userID, &event.Content)
|
||||||
member.Membership = event.Content.Membership
|
|
||||||
if len(member.Displayname) == 0 {
|
|
||||||
member.Displayname = userID
|
|
||||||
}
|
|
||||||
if room.firstMemberCache == nil && userID != room.SessionUserID {
|
|
||||||
room.firstMemberCache = 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
|
||||||
|
room.updateNthMemberCache(userID, member)
|
||||||
|
}
|
||||||
|
if userID == room.SessionUserID {
|
||||||
|
room.SessionMember = member
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -425,18 +588,19 @@ 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 {
|
if userID == room.SessionUserID && room.SessionMember != nil {
|
||||||
room.createMemberCache()
|
return room.SessionMember
|
||||||
}
|
}
|
||||||
|
room.Load()
|
||||||
|
room.createMemberCache()
|
||||||
room.lock.RLock()
|
room.lock.RLock()
|
||||||
member, _ := room.memberCache[userID]
|
member, _ := room.memberCache[userID]
|
||||||
room.lock.RUnlock()
|
room.lock.RUnlock()
|
||||||
@ -449,9 +613,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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
319
matrix/rooms/roomcache.go
Normal file
319
matrix/rooms/roomcache.go
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
// 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(false)
|
||||||
|
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 {
|
||||||
|
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(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cache *RoomCache) ForceClean() {
|
||||||
|
cache.Lock()
|
||||||
|
cache.clean(true)
|
||||||
|
cache.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cache *RoomCache) clean(force bool) {
|
||||||
|
origSize := cache.size
|
||||||
|
maxTS := time.Now().Unix() - cache.maxAge
|
||||||
|
for cache.size > cache.maxSize {
|
||||||
|
if cache.tail.touch > maxTS && !force {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ok := cache.tail.Unload()
|
||||||
|
node := cache.tail
|
||||||
|
cache.llPop(node)
|
||||||
|
if !ok {
|
||||||
|
debug.Print("Unload returned false, pushing node back")
|
||||||
|
cache.llPush(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
ok := node.Unload()
|
||||||
|
if !ok {
|
||||||
|
debug.Print("Unload returned false, pushing node back")
|
||||||
|
cache.llPush(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cache *RoomCache) newRoom(roomID string) *Room {
|
||||||
|
node := NewRoom(roomID, cache)
|
||||||
|
cache.Map[node.ID] = node
|
||||||
|
return node
|
||||||
|
}
|
@ -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",
|
||||||
|
@ -108,6 +108,8 @@ func NewCommandProcessor(parent *MainView) *CommandProcessor {
|
|||||||
"rainbow": cmdRainbow,
|
"rainbow": cmdRainbow,
|
||||||
"invite": cmdInvite,
|
"invite": cmdInvite,
|
||||||
"hprof": cmdHeapProfile,
|
"hprof": cmdHeapProfile,
|
||||||
|
"cprof": cmdCPUProfile,
|
||||||
|
"trace": cmdTrace,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,10 +19,15 @@ package ui
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
dbg "runtime/debug"
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
|
"runtime/trace"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/lucasb-eyer/go-colorful"
|
"github.com/lucasb-eyer/go-colorful"
|
||||||
@ -72,40 +77,80 @@ var rainbow = GradientTable{
|
|||||||
|
|
||||||
func cmdHeapProfile(cmd *Command) {
|
func cmdHeapProfile(cmd *Command) {
|
||||||
runtime.GC()
|
runtime.GC()
|
||||||
memProfile, err := os.Create("gomuks.prof")
|
dbg.FreeOSMemory()
|
||||||
|
memProfile, err := os.Create("gomuks.heap.prof")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Print(err)
|
debug.Print("Failed to open gomuks.heap.prof:", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
defer memProfile.Close()
|
defer func() {
|
||||||
|
err := memProfile.Close()
|
||||||
|
if err != nil {
|
||||||
|
debug.Print("Failed to close gomuks.heap.prof:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
if err := pprof.WriteHeapProfile(memProfile); err != nil {
|
if err := pprof.WriteHeapProfile(memProfile); err != nil {
|
||||||
debug.Print(err)
|
debug.Print("Heap profile error:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runTimedProfile(cmd *Command, start func(writer io.Writer) error, stop func(), task, file string) {
|
||||||
|
if len(cmd.Args) == 0 {
|
||||||
|
cmd.Reply("Usage: /%s <seconds>", cmd.Command)
|
||||||
|
} else if dur, err := strconv.Atoi(cmd.Args[0]); err != nil || dur < 0 {
|
||||||
|
cmd.Reply("Usage: /%s <seconds>", cmd.Command)
|
||||||
|
} else if cpuProfile, err := os.Create(file); err != nil {
|
||||||
|
debug.Printf("Failed to open %s: %v", file, err)
|
||||||
|
} else if err = start(cpuProfile); err != nil {
|
||||||
|
_ = cpuProfile.Close()
|
||||||
|
debug.Print(task, "error:", err)
|
||||||
|
} else {
|
||||||
|
cmd.Reply("Started %s for %d seconds", task, dur)
|
||||||
|
go func() {
|
||||||
|
time.Sleep(time.Duration(dur) * time.Second)
|
||||||
|
stop()
|
||||||
|
cmd.Reply("%s finished.", task)
|
||||||
|
|
||||||
|
err := cpuProfile.Close()
|
||||||
|
if err != nil {
|
||||||
|
debug.Print("Failed to close gomuks.cpu.prof:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdCPUProfile(cmd *Command) {
|
||||||
|
runTimedProfile(cmd, pprof.StartCPUProfile, pprof.StopCPUProfile, "CPU profiling", "gomuks.cpu.prof")
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdTrace(cmd *Command) {
|
||||||
|
runTimedProfile(cmd, trace.Start, trace.Stop, "Call tracing", "gomuks.trace")
|
||||||
|
}
|
||||||
|
|
||||||
// TODO this command definitely belongs in a plugin once we have a plugin system.
|
// TODO this command definitely belongs in a plugin once we have a plugin system.
|
||||||
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) {
|
||||||
|
@ -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,
|
||||||
@ -86,6 +89,23 @@ func NewMessageView(parent *RoomView) *MessageView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (view *MessageView) Unload() {
|
||||||
|
debug.Print("Unloading message view", view.parent.Room.ID)
|
||||||
|
view.messagesLock.Lock()
|
||||||
|
view.msgBufferLock.Lock()
|
||||||
|
view.messageIDLock.Lock()
|
||||||
|
view.messageIDs = make(map[string]*messages.UIMessage)
|
||||||
|
view.msgBuffer = make([]*messages.UIMessage, 0)
|
||||||
|
view.messages = make([]*messages.UIMessage, 0)
|
||||||
|
view.initialHistoryLoaded = false
|
||||||
|
view.ScrollOffset = 0
|
||||||
|
view._widestSender = 5
|
||||||
|
view.prevMsgCount = -1
|
||||||
|
view.messagesLock.Unlock()
|
||||||
|
view.msgBufferLock.Unlock()
|
||||||
|
view.messageIDLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
func (view *MessageView) updateWidestSender(sender string) {
|
func (view *MessageView) updateWidestSender(sender string) {
|
||||||
if len(sender) > int(view._widestSender) {
|
if len(sender) > int(view._widestSender) {
|
||||||
if len(sender) > view.MaxSenderWidth {
|
if len(sender) > view.MaxSenderWidth {
|
||||||
@ -108,20 +128,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.GetReplaceID()); oldMsg != nil {
|
||||||
direction = IgnoreMessage
|
direction = IgnoreMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +156,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 +179,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 +196,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 +209,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 +223,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 +271,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 +311,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 +330,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 +348,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 +394,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 +527,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 +535,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 +591,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
|
||||||
|
@ -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,78 @@ 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.ReplyTo = nil
|
||||||
return clone
|
clone.Renderer = clone.Renderer.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, msg)
|
||||||
|
msg.CalculateReplyBuffer(preferences, width)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 +274,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()
|
||||||
|
}
|
||||||
|
@ -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) {}
|
||||||
|
@ -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())
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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"
|
|
@ -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,17 @@ 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 {
|
msg.ReplyTo = replyToMsg.Clone()
|
||||||
replyToRoom = matrix.GetRoom(evt.Content.RelatesTo.InReplyTo.RoomID)
|
} else if replyToEvt, _ := matrix.GetEvent(room, evt.Content.GetReplyTo()); replyToEvt != nil {
|
||||||
}
|
if replyToMsg := directParseEvent(matrix, room, replyToEvt); replyToMsg != nil {
|
||||||
|
msg.ReplyTo = replyToMsg
|
||||||
if replyToMsg := getCachedEvent(mainView, replyToRoom.ID, evt.Content.GetReplyTo()); replyToMsg != nil {
|
|
||||||
replyToMsg = replyToMsg.Clone()
|
|
||||||
replyToMsg.SetReplyTo(nil)
|
|
||||||
msg.SetReplyTo(replyToMsg)
|
|
||||||
} else if replyToEvt, _ := matrix.GetEvent(replyToRoom, evt.Content.GetReplyTo()); replyToEvt != nil {
|
|
||||||
if replyToMsg := directParseEvent(matrix, replyToRoom, replyToEvt); replyToMsg != nil {
|
|
||||||
msg.SetReplyTo(replyToMsg)
|
|
||||||
} else {
|
} else {
|
||||||
// TODO add unrenderable reply header
|
// TODO add unrenderable reply header
|
||||||
}
|
}
|
||||||
@ -70,15 +63,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 +86,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 +119,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 +217,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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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) {}
|
||||||
|
@ -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.
|
||||||
@ -415,8 +423,10 @@ func (list *RoomList) OnMouseEvent(event mauview.MouseEvent) bool {
|
|||||||
switch event.Buttons() {
|
switch event.Buttons() {
|
||||||
case tcell.WheelUp:
|
case tcell.WheelUp:
|
||||||
list.AddScrollOffset(-WheelScrollOffsetDiff)
|
list.AddScrollOffset(-WheelScrollOffsetDiff)
|
||||||
|
return true
|
||||||
case tcell.WheelDown:
|
case tcell.WheelDown:
|
||||||
list.AddScrollOffset(WheelScrollOffsetDiff)
|
list.AddScrollOffset(WheelScrollOffsetDiff)
|
||||||
|
return true
|
||||||
case tcell.Button1:
|
case tcell.Button1:
|
||||||
x, y := event.Position()
|
x, y := event.Position()
|
||||||
return list.clickRoom(y, x, event.Modifiers() == tcell.ModCtrl)
|
return list.clickRoom(y, x, event.Modifiers() == tcell.ModCtrl)
|
||||||
@ -478,7 +488,8 @@ func (list *RoomList) clickRoom(line, column int, mod bool) bool {
|
|||||||
if trl.maxShown < 10 {
|
if trl.maxShown < 10 {
|
||||||
trl.maxShown = 10
|
trl.maxShown = 10
|
||||||
}
|
}
|
||||||
break
|
list.RUnlock()
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Tag footer
|
// Tag footer
|
||||||
|
@ -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
|
||||||
@ -91,6 +93,14 @@ func NewRoomView(parent *MainView, room *rooms.Room) *RoomView {
|
|||||||
config: parent.config,
|
config: parent.config,
|
||||||
}
|
}
|
||||||
view.content = NewMessageView(view)
|
view.content = NewMessageView(view)
|
||||||
|
view.Room.SetPreUnload(func() bool {
|
||||||
|
if view.parent.currentRoom == view {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
view.content.Unload()
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
view.Room.SetPostLoad(view.loadTyping)
|
||||||
|
|
||||||
view.input.
|
view.input.
|
||||||
SetBackgroundColor(tcell.ColorDefault).
|
SetBackgroundColor(tcell.ColorDefault).
|
||||||
@ -99,7 +109,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)
|
||||||
|
|
||||||
@ -269,14 +278,20 @@ func (view *RoomView) SetCompletions(completions []string) {
|
|||||||
view.completions.time = time.Now()
|
view.completions.time = time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) SetTyping(users []string) {
|
func (view *RoomView) loadTyping() {
|
||||||
for index, user := range users {
|
for index, user := range view.typing {
|
||||||
member := view.Room.GetMember(user)
|
member := view.Room.GetMember(user)
|
||||||
if member != nil {
|
if member != nil {
|
||||||
users[index] = member.Displayname
|
view.typing[index] = member.Displayname
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (view *RoomView) SetTyping(users []string) {
|
||||||
view.typing = users
|
view.typing = users
|
||||||
|
if view.Room.Loaded() {
|
||||||
|
view.loadTyping()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type completion struct {
|
type completion struct {
|
||||||
@ -385,11 +400,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 +416,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 +431,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 +455,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 {
|
||||||
|
@ -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})
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
@ -388,9 +387,14 @@ func sendNotification(room *rooms.Room, sender, text string, critical, sound boo
|
|||||||
notification.Send(sender, text, critical, sound)
|
notification.Send(sender, text, critical, sound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) NotifyMessage(room *rooms.Room, message ifc.Message, should pushrules.PushActionArrayShould) {
|
func (view *MainView) Bump(room *rooms.Room) {
|
||||||
view.roomList.Bump(room)
|
view.roomList.Bump(room)
|
||||||
if message.SenderID() == view.config.UserID {
|
}
|
||||||
|
|
||||||
|
func (view *MainView) NotifyMessage(room *rooms.Room, message ifc.Message, should pushrules.PushActionArrayShould) {
|
||||||
|
view.Bump(room)
|
||||||
|
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 +424,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)
|
||||||
|
Loading…
Reference in New Issue
Block a user