From bc7e2d9a1c871e3fbce932f9695fc24083bc2cc4 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 27 Apr 2019 15:02:21 +0300 Subject: Add locks and other sync stuff --- README.md | 2 +- debug/debug.go | 2 +- go.mod | 18 ++-- go.sum | 27 ++++++ matrix/matrix.go | 4 +- matrix/pushrules/ruleset.go | 5 +- matrix/rooms/room.go | 61 +++++++------ matrix/sync.go | 2 + ui/message-view.go | 213 ++++++++++++++++++++++++++++++-------------- ui/room-list.go | 46 ++++++++-- ui/tag-room-list.go | 2 +- ui/view-main.go | 111 ++++++++++++++--------- 12 files changed, 336 insertions(+), 157 deletions(-) diff --git a/README.md b/README.md index 553b779..2673952 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ precompiled executables. For now, you can either download a CI build from [dl.maunium.net/programs/gomuks](https://dl.maunium.net/programs/gomuks) or compile from source: -0. Install [Go](https://golang.org/) 1.11 or higher +0. Install [Go](https://golang.org/) 1.12 or higher 1. Clone the repo: `git clone https://github.com/tulir/gomuks.git && cd gomuks` 2. Build: `go build` diff --git a/debug/debug.go b/debug/debug.go index b7d97bb..573cff7 100644 --- a/debug/debug.go +++ b/debug/debug.go @@ -95,7 +95,7 @@ func PrettyPanic(panic interface{}) { var buf bytes.Buffer fmt.Fprintln(&buf, panic) buf.Write(debug.Stack()) - err := ioutil.WriteFile(traceFile, buf.Bytes(), 0644) + err := ioutil.WriteFile(traceFile, buf.Bytes(), 0640) if err != nil { fmt.Println("Saving the stack trace to", traceFile, "failed:") diff --git a/go.mod b/go.mod index 6130d63..0437fe2 100644 --- a/go.mod +++ b/go.mod @@ -4,22 +4,26 @@ go 1.12 require ( 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/disintegration/imaging v1.6.0 github.com/kyokomi/emoji v2.1.0+incompatible github.com/lithammer/fuzzysearch v1.0.2 - github.com/lucasb-eyer/go-colorful v1.0.1 + 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.7 // indirect github.com/mattn/go-runewidth v0.0.4 github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect github.com/russross/blackfriday/v2 v2.0.1 github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + github.com/stretchr/objx v0.2.0 // indirect go.etcd.io/bbolt v1.3.2 - golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5 // indirect - golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f - golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 - golang.org/x/sys v0.0.0-20190410170021-cc4d4f50624c // indirect + golang.org/x/image v0.0.0-20190424155947-59b11bec70c7 + golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6 + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2 // indirect gopkg.in/yaml.v2 v2.2.2 maunium.net/go/mautrix v0.1.0-alpha.3.0.20190410194750-53c7c9d954c8 - maunium.net/go/mauview v0.0.0-20190406150001-ad4a4e562f9e - maunium.net/go/tcell v0.0.0-20190406145848-d520315b0ddb + maunium.net/go/mauview v0.0.0-20190426104003-3e5387b8a125 + maunium.net/go/tcell v0.0.0-20190426103942-24a060c2189b ) diff --git a/go.sum b/go.sum index 23aa9e9..a0d3853 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,15 @@ +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/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= 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/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.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-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/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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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= @@ -20,8 +24,13 @@ github.com/lithammer/fuzzysearch v1.0.2 h1:AjCE2iwc5y+8K+h2nXVc0Pmrpjvu+JVqMgiZ0 github.com/lithammer/fuzzysearch v1.0.2/go.mod h1:bvAJyokfCQ7Vknrd4Kgc+izmMrPj5CiBAu2t6rK1Kak= github.com/lucasb-eyer/go-colorful v1.0.1 h1:nKJRBvZWPzvkwB4sY8A3U4zgqLf2Y9c02yzPsbXu/5c= github.com/lucasb-eyer/go-colorful v1.0.1/go.mod h1:tLy1nWSoU0DGtxQyNRrUmb6PUiB7usbds6gd97XTXwA= +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/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.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/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/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= @@ -35,8 +44,11 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD 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/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +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 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +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/go.mod h1:WDk3p8GiZV9+xFWlSo8qreeoLhW6Ik692rqXk+cNeRY= go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= @@ -44,23 +56,38 @@ 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-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d/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-20190321063152-3fc05d484e9f h1:FO4MZ3N56GnxbqxGKqh+YTzUWQ2sDwtFQEZgLOxh9Jc= golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190424155947-59b11bec70c7 h1:OIchhgIITCxOyycwe8n9adxV37/WqbetxrpXNm7oPqI= +golang.org/x/image v0.0.0-20190424155947-59b11bec70c7/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190326090315-15845e8f865b/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6 h1:FP8hkuE6yUEaJnK7O2eTuejKWwW+Rhfj80dQ2JcKxCU= +golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/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-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-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190410170021-cc4d4f50624c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190425145619-16072639606e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 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/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-20190425222832-ad9eeb80039a/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/matrix/matrix.go b/matrix/matrix.go index ef0e349..b87042e 100644 --- a/matrix/matrix.go +++ b/matrix/matrix.go @@ -178,8 +178,10 @@ func (c *Container) UpdatePushRules() { resp, err := pushrules.GetPushRules(c.client) if err != nil { debug.Print("Failed to fetch push rules:", err) + c.config.PushRules = &pushrules.PushRuleset{} + } else { + c.config.PushRules = resp } - c.config.PushRules = resp c.config.SavePushRules() } diff --git a/matrix/pushrules/ruleset.go b/matrix/pushrules/ruleset.go index 33036c4..7ad931a 100644 --- a/matrix/pushrules/ruleset.go +++ b/matrix/pushrules/ruleset.go @@ -75,7 +75,7 @@ func (rs *PushRuleset) MarshalJSON() ([]byte, error) { // DefaultPushActions is the value returned if none of the rule // collections in a Ruleset match the event given to GetActions() -var DefaultPushActions = make(PushActionArray, 0) +var DefaultPushActions = PushActionArray{&PushAction{Action: ActionDontNotify}} // GetActions matches the given event against all of the push rule // collections in this push ruleset in the order of priority as @@ -85,6 +85,9 @@ func (rs *PushRuleset) GetActions(room Room, event *mautrix.Event) (match PushAc arrays := []PushRuleCollection{rs.Override, rs.Content, rs.Room, rs.Sender, rs.Underride} // Loop until one of the push rule collections matches the room/event combo. for _, pra := range arrays { + if pra == nil { + continue + } if match = pra.GetActions(room, event); match != nil { // Match found, return it. return diff --git a/matrix/rooms/room.go b/matrix/rooms/room.go index 53156a3..6334036 100644 --- a/matrix/rooms/room.go +++ b/matrix/rooms/room.go @@ -57,7 +57,6 @@ type UnreadMessage struct { Highlight bool } - // Room represents a single Matrix room. type Room struct { *mautrix.Room @@ -101,26 +100,7 @@ type Room struct { // The list of aliases. Directly fetched from the m.room.aliases state event. aliasesCache []string - // fetchHistoryLock is used to make sure multiple goroutines don't fetch - // history for this room at the same time. - fetchHistoryLock *sync.Mutex -} - -// LockHistory locks the history fetching mutex. -// If the mutex is nil, it will be created. -func (room *Room) LockHistory() { - if room.fetchHistoryLock == nil { - room.fetchHistoryLock = &sync.Mutex{} - } - room.fetchHistoryLock.Lock() -} - -// UnlockHistory unlocks the history fetching mutex. -// If the mutex is nil, this does nothing. -func (room *Room) UnlockHistory() { - if room.fetchHistoryLock != nil { - room.fetchHistoryLock.Unlock() - } + lock sync.RWMutex } func (room *Room) Load(path string) error { @@ -130,6 +110,8 @@ func (room *Room) Load(path string) error { } defer file.Close() dec := gob.NewDecoder(file) + room.lock.Lock() + defer room.lock.Unlock() return dec.Decode(room) } @@ -140,11 +122,15 @@ func (room *Room) Save(path string) error { } defer file.Close() enc := gob.NewEncoder(file) + room.lock.RLock() + defer room.lock.RUnlock() return enc.Encode(room) } // MarkRead clears the new message statuses on this room. func (room *Room) MarkRead(eventID string) bool { + room.lock.Lock() + defer room.lock.Unlock() if room.lastMarkedRead == eventID { return false } @@ -164,6 +150,8 @@ func (room *Room) MarkRead(eventID string) bool { } func (room *Room) UnreadCount() int { + room.lock.Lock() + defer room.lock.Unlock() if room.unreadCountCache == nil { room.unreadCountCache = new(int) for _, unreadMessage := range room.UnreadMessages { @@ -176,6 +164,8 @@ func (room *Room) UnreadCount() int { } func (room *Room) Highlighted() bool { + room.lock.Lock() + defer room.lock.Unlock() if room.highlightCache == nil { room.highlightCache = new(bool) for _, unreadMessage := range room.UnreadMessages { @@ -193,6 +183,8 @@ func (room *Room) HasNewMessages() bool { } func (room *Room) AddUnread(eventID string, counted, highlight bool) { + room.lock.Lock() + defer room.lock.Unlock() room.UnreadMessages = append(room.UnreadMessages, UnreadMessage{ EventID: eventID, Counted: counted, @@ -213,6 +205,8 @@ func (room *Room) AddUnread(eventID string, counted, highlight bool) { } func (room *Room) Tags() []RoomTag { + room.lock.RLock() + defer room.lock.RUnlock() if len(room.RawTags) == 0 { if room.IsDirect { return []RoomTag{{"net.maunium.gomuks.fake.direct", "0.5"}} @@ -225,6 +219,8 @@ func (room *Room) Tags() []RoomTag { // UpdateState updates the room's current state with the given Event. This will clobber events based // on the type/state_key combination. func (room *Room) UpdateState(event *mautrix.Event) { + room.lock.Lock() + defer room.lock.Unlock() _, exists := room.State[event.Type] if !exists { room.State[event.Type] = make(map[string]*mautrix.Event) @@ -269,13 +265,15 @@ func (room *Room) UpdateState(event *mautrix.Event) { // 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 { + room.lock.RLock() + defer room.lock.RUnlock() stateEventMap, _ := room.State[eventType] event, _ := stateEventMap[stateKey] return event } -// GetStateEvents returns the state events for the given type. -func (room *Room) GetStateEvents(eventType mautrix.EventType) map[string]*mautrix.Event { +// getStateEvents returns the state events for the given type. +func (room *Room) getStateEvents(eventType mautrix.EventType) map[string]*mautrix.Event { stateEventMap, _ := room.State[eventType] return stateEventMap } @@ -309,11 +307,13 @@ func (room *Room) GetCanonicalAlias() string { // GetAliases returns the list of aliases that point to this room. func (room *Room) GetAliases() []string { if room.aliasesCache == nil { - aliasEvents := room.GetStateEvents(mautrix.StateAliases) + room.lock.RLock() + aliasEvents := room.getStateEvents(mautrix.StateAliases) room.aliasesCache = []string{} for _, event := range aliasEvents { room.aliasesCache = append(room.aliasesCache, event.Content.Aliases...) } + room.lock.RUnlock() } return room.aliasesCache } @@ -394,7 +394,8 @@ func (room *Room) GetTitle() string { // createMemberCache caches all member events into a easily processable MXID -> *Member map. func (room *Room) createMemberCache() map[string]*mautrix.Member { cache := make(map[string]*mautrix.Member) - events := room.GetStateEvents(mautrix.StateMember) + room.lock.RLock() + events := room.getStateEvents(mautrix.StateMember) room.firstMemberCache = nil if events != nil { for userID, event := range events { @@ -411,7 +412,10 @@ func (room *Room) createMemberCache() map[string]*mautrix.Member { } } } + room.lock.RUnlock() + room.lock.Lock() room.memberCache = cache + room.lock.Unlock() return cache } @@ -432,7 +436,9 @@ func (room *Room) GetMember(userID string) *mautrix.Member { if len(room.memberCache) == 0 { room.createMemberCache() } + room.lock.RLock() member, _ := room.memberCache[userID] + room.lock.RUnlock() return member } @@ -444,8 +450,7 @@ func (room *Room) GetSessionOwner() string { // NewRoom creates a new Room with the given ID func NewRoom(roomID, owner string) *Room { return &Room{ - Room: mautrix.NewRoom(roomID), - fetchHistoryLock: &sync.Mutex{}, - SessionUserID: owner, + Room: mautrix.NewRoom(roomID), + SessionUserID: owner, } } diff --git a/matrix/sync.go b/matrix/sync.go index e610b89..7f9c902 100644 --- a/matrix/sync.go +++ b/matrix/sync.go @@ -107,6 +107,8 @@ func NewGomuksSyncer(session SyncerSession) *GomuksSyncer { // ProcessResponse processes a Matrix sync response. func (s *GomuksSyncer) ProcessResponse(res *mautrix.RespSync, since string) (err error) { debug.Print("Received sync response") +// dat, _ := json.MarshalIndent(res, "", " ") +// debug.Print(string(dat)) s.processSyncEvents(nil, res.Presence.Events, EventSourcePresence) s.processSyncEvents(nil, res.AccountData.Events, EventSourceAccountData) diff --git a/ui/message-view.go b/ui/message-view.go index 1d9519e..43c757f 100644 --- a/ui/message-view.go +++ b/ui/message-view.go @@ -20,6 +20,8 @@ import ( "fmt" "math" "strings" + "sync" + "sync/atomic" "github.com/mattn/go-runewidth" @@ -43,20 +45,24 @@ type MessageView struct { DateFormat string TimestampFormat string TimestampWidth int - LoadingMessages bool - widestSender int - width int - height int - prevWidth int - prevHeight int - prevMsgCount int - prevPrefs config.UserPreferences - - messageIDs map[string]messages.UIMessage - messages []messages.UIMessage - - msgBuffer []messages.UIMessage + // Used for locking + loadingMessages int32 + + _widestSender uint32 + _width uint32 + _height uint32 + _prevWidth uint32 + _prevHeight uint32 + prevMsgCount int + prevPrefs config.UserPreferences + + messageIDLock sync.RWMutex + messageIDs map[string]messages.UIMessage + messagesLock sync.RWMutex + messages []messages.UIMessage + msgBufferLock sync.RWMutex + msgBuffer []messages.UIMessage } func NewMessageView(parent *RoomView) *MessageView { @@ -72,19 +78,20 @@ func NewMessageView(parent *RoomView) *MessageView { messageIDs: make(map[string]messages.UIMessage), msgBuffer: make([]messages.UIMessage, 0), - width: 80, - widestSender: 5, - prevWidth: -1, - prevHeight: -1, - prevMsgCount: -1, + _width: 80, + _widestSender: 5, + _prevWidth: 0, + _prevHeight: 0, + prevMsgCount: -1, } } func (view *MessageView) updateWidestSender(sender string) { - if len(sender) > view.widestSender { - view.widestSender = len(sender) - if view.widestSender > view.MaxSenderWidth { - view.widestSender = view.MaxSenderWidth + if len(sender) > int(view._widestSender) { + if len(sender) > view.MaxSenderWidth { + atomic.StoreUint32(&view._widestSender, uint32(view.MaxSenderWidth)) + } else { + atomic.StoreUint32(&view._widestSender, uint32(len(sender))) } } } @@ -109,22 +116,21 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction MessageDir } var oldMsg messages.UIMessage - var messageExists bool - if oldMsg, messageExists = view.messageIDs[message.ID()]; messageExists { + if oldMsg = view.getMessageByID(message.ID()); oldMsg != nil { view.replaceMessage(oldMsg, message) direction = IgnoreMessage - } else if oldMsg, messageExists = view.messageIDs[message.TxnID()]; messageExists { + } else if oldMsg = view.getMessageByID(message.TxnID()); oldMsg != nil { view.replaceMessage(oldMsg, message) - delete(view.messageIDs, message.TxnID()) + view.deleteMessageID(message.TxnID()) direction = IgnoreMessage } view.updateWidestSender(message.Sender()) - width := view.width + width := view.width() bare := view.config.Preferences.BareMessageView if !bare { - width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap + width -= view.TimestampWidth + TimestampSenderGap + view.widestSender() + SenderMessageGap } message.CalculateBuffer(view.config.Preferences, width) @@ -140,18 +146,22 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction MessageDir if view.ScrollOffset > 0 { view.ScrollOffset += message.Height() } + view.messagesLock.Lock() if len(view.messages) > 0 && !view.messages[len(view.messages)-1].SameDate(message) { view.messages = append(view.messages, makeDateChange(), message) } else { view.messages = append(view.messages, message) } + view.messagesLock.Unlock() view.appendBuffer(message) } else if direction == PrependMessage { + view.messagesLock.Lock() if len(view.messages) > 0 && !view.messages[0].SameDate(message) { view.messages = append([]messages.UIMessage{message, makeDateChange()}, view.messages...) } else { view.messages = append([]messages.UIMessage{message}, view.messages...) } + view.messagesLock.Unlock() } else if oldMsg != nil { view.replaceBuffer(oldMsg, message) } else { @@ -160,31 +170,62 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction MessageDir } if len(message.ID()) > 0 { - view.messageIDs[message.ID()] = message - } -} - -func (view *MessageView) appendBuffer(message messages.UIMessage) { - for i := 0; i < message.Height(); i++ { - view.msgBuffer = append(view.msgBuffer, message) + view.setMessageID(message) } - view.prevMsgCount++ } func (view *MessageView) replaceMessage(original messages.UIMessage, new messages.UIMessage) { if len(new.ID()) > 0 { - view.messageIDs[new.ID()] = new + view.setMessageID(new) } + view.messagesLock.Lock() for index, msg := range view.messages { if msg == original { view.messages[index] = new } } + view.messagesLock.Unlock() +} + +func (view *MessageView) getMessageByID(id string) messages.UIMessage { + view.messageIDLock.RLock() + defer view.messageIDLock.RUnlock() + msg, ok := view.messageIDs[id] + if !ok { + return nil + } + return msg +} + +func (view *MessageView) deleteMessageID(id string) { + view.messageIDLock.Lock() + delete(view.messageIDs, id) + view.messageIDLock.Unlock() +} + +func (view *MessageView) setMessageID(message messages.UIMessage) { + view.messageIDLock.Lock() + view.messageIDs[message.ID()] = message + view.messageIDLock.Unlock() +} + +func (view *MessageView) appendBuffer(message messages.UIMessage) { + view.msgBufferLock.Lock() + view.appendBufferUnlocked(message) + view.msgBufferLock.Unlock() +} + +func (view *MessageView) appendBufferUnlocked(message messages.UIMessage) { + for i := 0; i < message.Height(); i++ { + view.msgBuffer = append(view.msgBuffer, message) + } + view.prevMsgCount++ } func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages.UIMessage) { start := -1 end := -1 + view.msgBufferLock.RLock() for index, meta := range view.msgBuffer { if meta == original { if start == -1 { @@ -195,6 +236,7 @@ func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages break } } + view.msgBufferLock.RUnlock() if start == -1 { debug.Print("Called replaceBuffer() with message that was not in the buffer:", original) @@ -208,9 +250,10 @@ func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages } if new.Height() == 0 { - new.CalculateBuffer(view.prevPrefs, view.prevWidth) + new.CalculateBuffer(view.prevPrefs, view.prevWidth()) } + view.msgBufferLock.Lock() if new.Height() != end-start { metaBuffer := view.msgBuffer[0:start] for i := 0; i < new.Height(); i++ { @@ -222,17 +265,20 @@ func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages view.msgBuffer[i] = new } } + view.msgBufferLock.Unlock() } func (view *MessageView) recalculateBuffers() { prefs := view.config.Preferences - recalculateMessageBuffers := view.width != view.prevWidth || + recalculateMessageBuffers := view.width() != view.prevWidth() || view.prevPrefs.BareMessageView != prefs.BareMessageView || view.prevPrefs.DisableImages != prefs.DisableImages + view.messagesLock.RLock() + view.msgBufferLock.Lock() if recalculateMessageBuffers || len(view.messages) != view.prevMsgCount { - width := view.width + width := view.width() if !prefs.BareMessageView { - width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap + width -= view.TimestampWidth + TimestampSenderGap + view.widestSender() + SenderMessageGap } view.msgBuffer = []messages.UIMessage{} view.prevMsgCount = 0 @@ -244,11 +290,12 @@ func (view *MessageView) recalculateBuffers() { if recalculateMessageBuffers { message.CalculateBuffer(prefs, width) } - view.appendBuffer(message) + view.appendBufferUnlocked(message) } } - view.prevHeight = view.height - view.prevWidth = view.width + view.msgBufferLock.Unlock() + view.messagesLock.RUnlock() + view.updatePrevSize() view.prevPrefs = prefs } @@ -309,19 +356,21 @@ func (view *MessageView) OnMouseEvent(event mauview.MouseEvent) bool { return true case tcell.Button1: x, y := event.Position() - line := view.TotalHeight() - view.ScrollOffset - view.height + y + line := view.TotalHeight() - view.ScrollOffset - view.Height() + y if line < 0 || line >= view.TotalHeight() { return false } + view.msgBufferLock.RLock() message := view.msgBuffer[line] var prevMessage messages.UIMessage if y != 0 && line > 0 { prevMessage = view.msgBuffer[line-1] } + view.msgBufferLock.RUnlock() usernameX := view.TimestampWidth + TimestampSenderGap - messageX := usernameX + view.widestSender + SenderMessageGap + messageX := usernameX + view.widestSender() + SenderMessageGap if x >= messageX { return view.handleMessageClick(message) @@ -336,30 +385,59 @@ const PaddingAtTop = 5 func (view *MessageView) AddScrollOffset(diff int) { totalHeight := view.TotalHeight() - if diff >= 0 && view.ScrollOffset+diff >= totalHeight-view.height+PaddingAtTop { - view.ScrollOffset = totalHeight - view.height + PaddingAtTop + height := view.Height() + if diff >= 0 && view.ScrollOffset+diff >= totalHeight-height+PaddingAtTop { + view.ScrollOffset = totalHeight - height + PaddingAtTop } else { view.ScrollOffset += diff } - if view.ScrollOffset > totalHeight-view.height+PaddingAtTop { - view.ScrollOffset = totalHeight - view.height + PaddingAtTop + if view.ScrollOffset > totalHeight-height+PaddingAtTop { + view.ScrollOffset = totalHeight - height + PaddingAtTop } if view.ScrollOffset < 0 { view.ScrollOffset = 0 } } +func (view *MessageView) setSize(width, height int) { + atomic.StoreUint32(&view._width, uint32(width)) + atomic.StoreUint32(&view._height, uint32(height)) +} + +func (view *MessageView) updatePrevSize() { + atomic.StoreUint32(&view._prevWidth, atomic.LoadUint32(&view._width)) + atomic.StoreUint32(&view._prevHeight, atomic.LoadUint32(&view._height)) +} + +func (view *MessageView) prevHeight() int { + return int(atomic.LoadUint32(&view._prevHeight)) +} + +func (view *MessageView) prevWidth() int { + return int(atomic.LoadUint32(&view._prevWidth)) +} + +func (view *MessageView) widestSender() int { + return int(atomic.LoadUint32(&view._widestSender)) +} + func (view *MessageView) Height() int { - return view.height + return int(atomic.LoadUint32(&view._height)) +} + +func (view *MessageView) width() int { + return int(atomic.LoadUint32(&view._width)) } func (view *MessageView) TotalHeight() int { + view.msgBufferLock.RLock() + defer view.msgBufferLock.RUnlock() return len(view.msgBuffer) } func (view *MessageView) IsAtTop() bool { - return view.ScrollOffset >= len(view.msgBuffer)-view.height+PaddingAtTop + return view.ScrollOffset >= view.TotalHeight()-view.Height()+PaddingAtTop } const ( @@ -407,7 +485,7 @@ func (view *MessageView) getIndexOffset(screen mauview.Screen, height, messageX indexOffset = view.TotalHeight() - view.ScrollOffset - height if indexOffset <= -PaddingAtTop { message := "Scroll up to load more messages." - if view.LoadingMessages { + if atomic.LoadInt32(&view.loadingMessages) == 1 { message = "Loading more messages..." } widget.WriteLineSimpleColor(screen, message, messageX, 0, tcell.ColorGreen) @@ -419,6 +497,7 @@ func (view *MessageView) CapturePlaintext(height int) string { var buf strings.Builder indexOffset := view.TotalHeight() - view.ScrollOffset - height var prevMessage messages.UIMessage + view.msgBufferLock.RLock() for line := 0; line < height; line++ { index := indexOffset + line if index < 0 { @@ -438,27 +517,29 @@ func (view *MessageView) CapturePlaintext(height int) string { prevMessage = message } } + view.msgBufferLock.RUnlock() return buf.String() } func (view *MessageView) Draw(screen mauview.Screen) { - view.width, view.height = screen.Size() + view.setSize(screen.Size()) view.recalculateBuffers() + height := view.Height() if view.TotalHeight() == 0 { - widget.WriteLineSimple(screen, "It's quite empty in here.", 0, view.height) + widget.WriteLineSimple(screen, "It's quite empty in here.", 0, height) return } usernameX := view.TimestampWidth + TimestampSenderGap - messageX := usernameX + view.widestSender + SenderMessageGap + messageX := usernameX + view.widestSender() + SenderMessageGap bareMode := view.config.Preferences.BareMessageView if bareMode { messageX = 0 } - indexOffset := view.getIndexOffset(screen, view.height, messageX) + indexOffset := view.getIndexOffset(screen, height, messageX) viewStart := 0 if indexOffset < 0 { @@ -466,13 +547,13 @@ func (view *MessageView) Draw(screen mauview.Screen) { } if !bareMode { - separatorX := usernameX + view.widestSender + SenderSeparatorGap - scrollBarHeight, scrollBarPos := view.calculateScrollBar(view.height) + separatorX := usernameX + view.widestSender() + SenderSeparatorGap + scrollBarHeight, scrollBarPos := view.calculateScrollBar(height) - for line := viewStart; line < view.height; line++ { + for line := viewStart; line < height; line++ { showScrollbar := line-viewStart >= scrollBarPos-scrollBarHeight && line-viewStart < scrollBarPos - isTop := line == viewStart && view.ScrollOffset+view.height >= view.TotalHeight() - isBottom := line == view.height-1 && view.ScrollOffset == 0 + isTop := line == viewStart && view.ScrollOffset+height >= view.TotalHeight() + isBottom := line == height-1 && view.ScrollOffset == 0 borderChar, borderStyle := getScrollbarStyle(showScrollbar, isTop, isBottom) @@ -481,7 +562,8 @@ func (view *MessageView) Draw(screen mauview.Screen) { } var prevMsg messages.UIMessage - for line := viewStart; line < view.height && indexOffset+line < view.TotalHeight(); line++ { + view.msgBufferLock.RLock() + for line := viewStart; line < height && indexOffset+line < len(view.msgBuffer); line++ { index := indexOffset + line msg := view.msgBuffer[index] @@ -493,7 +575,7 @@ func (view *MessageView) Draw(screen mauview.Screen) { //if !bareMode && (prevMsg == nil || meta.Sender() != prevMsg.Sender()) { widget.WriteLineColor( screen, mauview.AlignRight, msg.Sender(), - usernameX, line, view.widestSender, + usernameX, line, view.widestSender(), msg.SenderColor()) //} prevMsg = msg @@ -502,7 +584,8 @@ func (view *MessageView) Draw(screen mauview.Screen) { for i := index - 1; i >= 0 && view.msgBuffer[i] == msg; i-- { line-- } - msg.Draw(mauview.NewProxyScreen(screen, messageX, line, view.width-messageX, msg.Height())) + msg.Draw(mauview.NewProxyScreen(screen, messageX, line, view.width()-messageX, msg.Height())) line += msg.Height() - 1 } + view.msgBufferLock.RUnlock() } diff --git a/ui/room-list.go b/ui/room-list.go index c8e43a6..47ac182 100644 --- a/ui/room-list.go +++ b/ui/room-list.go @@ -20,6 +20,7 @@ import ( "math" "regexp" "strings" + "sync" "maunium.net/go/mauview" "maunium.net/go/tcell" @@ -29,6 +30,8 @@ import ( ) type RoomList struct { + sync.RWMutex + parent *MainView // The list of tags in display order. @@ -71,6 +74,8 @@ func NewRoomList(parent *MainView) *RoomList { } func (list *RoomList) Contains(roomID string) bool { + list.RLock() + defer list.RUnlock() for _, trl := range list.items { for _, room := range trl.All() { if room.ID == roomID { @@ -88,8 +93,8 @@ func (list *RoomList) Add(room *rooms.Room) { } } -func (list *RoomList) CheckTag(tag string) { - index := list.IndexTag(tag) +func (list *RoomList) checkTag(tag string) { + index := list.indexTag(tag) trl, ok := list.items[tag] @@ -107,6 +112,8 @@ func (list *RoomList) CheckTag(tag string) { } func (list *RoomList) AddToTag(tag rooms.RoomTag, room *rooms.Room) { + list.Lock() + defer list.Unlock() trl, ok := list.items[tag.Tag] if !ok { list.items[tag.Tag] = NewTagRoomList(list, tag.Tag, NewDefaultOrderedRoom(room)) @@ -114,7 +121,7 @@ func (list *RoomList) AddToTag(tag rooms.RoomTag, room *rooms.Room) { } trl.Insert(tag.Order, room) - list.CheckTag(tag.Tag) + list.checkTag(tag.Tag) } func (list *RoomList) Remove(room *rooms.Room) { @@ -124,6 +131,8 @@ func (list *RoomList) Remove(room *rooms.Room) { } func (list *RoomList) RemoveFromTag(tag string, room *rooms.Room) { + list.Lock() + defer list.Unlock() trl, ok := list.items[tag] if !ok { return @@ -158,10 +167,12 @@ func (list *RoomList) RemoveFromTag(tag string, room *rooms.Room) { list.selectedTag = "" } } - list.CheckTag(tag) + list.checkTag(tag) } func (list *RoomList) Bump(room *rooms.Room) { + list.RLock() + defer list.RUnlock() for _, tag := range room.Tags() { trl, ok := list.items[tag.Tag] if !ok { @@ -172,6 +183,8 @@ func (list *RoomList) Bump(room *rooms.Room) { } func (list *RoomList) Clear() { + list.Lock() + defer list.Unlock() list.items = make(map[string]*TagRoomList) list.tags = []string{"m.favourite", "net.maunium.gomuks.fake.direct", "", "m.lowpriority"} for _, tag := range list.tags { @@ -220,6 +233,8 @@ func (list *RoomList) AddScrollOffset(offset int) { } func (list *RoomList) First() (string, *rooms.Room) { + list.RLock() + defer list.RUnlock() for _, tag := range list.tags { trl := list.items[tag] if trl.HasVisibleRooms() { @@ -230,6 +245,8 @@ func (list *RoomList) First() (string, *rooms.Room) { } func (list *RoomList) Last() (string, *rooms.Room) { + list.RLock() + defer list.RUnlock() for tagIndex := len(list.tags) - 1; tagIndex >= 0; tagIndex-- { tag := list.tags[tagIndex] trl := list.items[tag] @@ -240,7 +257,7 @@ func (list *RoomList) Last() (string, *rooms.Room) { return "", nil } -func (list *RoomList) IndexTag(tag string) int { +func (list *RoomList) indexTag(tag string) int { for index, entry := range list.tags { if tag == entry { return index @@ -250,6 +267,8 @@ func (list *RoomList) IndexTag(tag string) int { } func (list *RoomList) Previous() (string, *rooms.Room) { + list.RLock() + defer list.RUnlock() if len(list.items) == 0 { return "", nil } else if list.selected == nil { @@ -266,7 +285,7 @@ func (list *RoomList) Previous() (string, *rooms.Room) { } if index == trl.Length()-1 { - tagIndex := list.IndexTag(list.selectedTag) + tagIndex := list.indexTag(list.selectedTag) tagIndex-- for ; tagIndex >= 0; tagIndex-- { prevTag := list.tags[tagIndex] @@ -283,6 +302,8 @@ func (list *RoomList) Previous() (string, *rooms.Room) { } func (list *RoomList) Next() (string, *rooms.Room) { + list.RLock() + defer list.RUnlock() if len(list.items) == 0 { return "", nil } else if list.selected == nil { @@ -299,7 +320,7 @@ func (list *RoomList) Next() (string, *rooms.Room) { } if index == 0 { - tagIndex := list.IndexTag(list.selectedTag) + tagIndex := list.indexTag(list.selectedTag) tagIndex++ for ; tagIndex < len(list.tags); tagIndex++ { nextTag := list.tags[tagIndex] @@ -325,6 +346,8 @@ func (list *RoomList) Next() (string, *rooms.Room) { // // TODO: Sorting. Now just finds first room with new messages. func (list *RoomList) NextWithActivity() (string, *rooms.Room) { + list.RLock() + defer list.RUnlock() for tag, trl := range list.items { for _, room := range trl.All() { if room.HasNewMessages() { @@ -337,7 +360,7 @@ func (list *RoomList) NextWithActivity() (string, *rooms.Room) { } func (list *RoomList) index(tag string, room *rooms.Room) int { - tagIndex := list.IndexTag(tag) + tagIndex := list.indexTag(tag) if tagIndex == -1 { return -1 } @@ -358,6 +381,7 @@ func (list *RoomList) index(tag string, room *rooms.Room) int { if tagIndex > 0 { for i := 0; i < tagIndex; i++ { prevTag := list.tags[i] + prevTRL := list.items[prevTag] localIndex += prevTRL.RenderHeight() } @@ -367,9 +391,11 @@ func (list *RoomList) index(tag string, room *rooms.Room) int { } func (list *RoomList) ContentHeight() (height int) { + list.RLock() for _, tag := range list.tags { height += list.items[tag].RenderHeight() } + list.RUnlock() return } @@ -410,6 +436,8 @@ func (list *RoomList) clickRoom(line, column int, mod bool) bool { if line < 0 { return false } + list.RLock() + defer list.RUnlock() for _, tag := range list.tags { trl := list.items[tag] if line--; line == -1 { @@ -484,6 +512,7 @@ func (list *RoomList) Draw(screen mauview.Screen) { y -= list.scrollOffset // Draw the list items. + list.RLock() for _, tag := range list.tags { trl := list.items[tag] tagDisplayName := list.GetTagDisplayName(tag) @@ -501,4 +530,5 @@ func (list *RoomList) Draw(screen mauview.Screen) { break } } + list.RUnlock() } diff --git a/ui/tag-room-list.go b/ui/tag-room-list.go index c965f10..dfc7cb2 100644 --- a/ui/tag-room-list.go +++ b/ui/tag-room-list.go @@ -277,7 +277,7 @@ func (trl *TagRoomList) Draw(screen mauview.Screen) { screen.SetCell(width-1, 0, tcell.StyleDefault, '▼') y := 1 - for i := trl.Length() - 1; i >= 0; i-- { + for i := len(items) - 1; i >= 0; i-- { if y >= height { return } diff --git a/ui/view-main.go b/ui/view-main.go index bda53ad..2d2afdf 100644 --- a/ui/view-main.go +++ b/ui/view-main.go @@ -20,6 +20,8 @@ import ( "bufio" "fmt" "os" + "sync" + "sync/atomic" "time" "unicode" @@ -42,6 +44,7 @@ type MainView struct { roomView *mauview.Box currentRoom *RoomView rooms map[string]*RoomView + roomsLock sync.RWMutex cmdProcessor *CommandProcessor focused mauview.Focusable @@ -245,13 +248,17 @@ func (view *MainView) Blur() { } func (view *MainView) SwitchRoom(tag string, room *rooms.Room) { + view.switchRoom(tag, room, true) +} + +func (view *MainView) switchRoom(tag string, room *rooms.Room, lock bool) { if room == nil { return } - roomView := view.rooms[room.ID] - if roomView == nil { - debug.Print("Tried to switch to non-nil room with nil roomView!") + roomView, ok := view.getRoomView(room.ID, lock) + if !ok { + debug.Print("Tried to switch to room with nonexistent roomView!") debug.Print(tag, room) return } @@ -265,7 +272,7 @@ func (view *MainView) SwitchRoom(tag string, room *rooms.Room) { } } -func (view *MainView) addRoomPage(room *rooms.Room) { +func (view *MainView) addRoomPage(room *rooms.Room) *RoomView { if _, ok := view.rooms[room.ID]; !ok { roomView := NewRoomView(view, room). SetInputChangedFunc(view.InputChanged) @@ -276,53 +283,73 @@ func (view *MainView) addRoomPage(room *rooms.Room) { if len(roomView.MessageView().messages) == 0 { go view.LoadHistory(room.ID) } + return roomView } + return nil } func (view *MainView) GetRoom(roomID string) ifc.RoomView { - room, ok := view.rooms[roomID] + room, ok := view.getRoomView(roomID, true) if !ok { - view.AddRoom(view.matrix.GetRoom(roomID)) - room, ok := view.rooms[roomID] - if !ok { - return nil - } - return room + return view.addRoom(view.matrix.GetRoom(roomID)) } return room } -func (view *MainView) AddRoom(room *rooms.Room) { - if view.roomList.Contains(room.ID) { - debug.Print("Add aborted (room exists)", room.ID, room.GetTitle()) - return - } - debug.Print("Adding", room.ID, room.GetTitle()) - view.roomList.Add(room) - view.addRoomPage(room) - if !view.roomList.HasSelected() { - view.SwitchRoom(view.roomList.First()) +func (view *MainView) getRoomView(roomID string, lock bool) (room *RoomView, ok bool) { + if lock { + view.roomsLock.RLock() + room, ok = view.rooms[roomID] + view.roomsLock.RUnlock() + } else { + room, ok = view.rooms[roomID] } + return room, ok +} + +func (view *MainView) AddRoom(room *rooms.Room) { + view.addRoom(room) } func (view *MainView) RemoveRoom(room *rooms.Room) { - roomView := view.GetRoom(room.ID) - if roomView == nil { + view.roomsLock.Lock() + _, ok := view.getRoomView(room.ID, false) + if !ok { + view.roomsLock.Unlock() debug.Print("Remove aborted (not found)", room.ID, room.GetTitle()) return } debug.Print("Removing", room.ID, room.GetTitle()) view.roomList.Remove(room) - view.SwitchRoom(view.roomList.Selected()) - + t, r := view.roomList.Selected() + view.switchRoom(t, r, false) delete(view.rooms, room.ID) + view.roomsLock.Unlock() view.parent.Render() } +func (view *MainView) addRoom(room *rooms.Room) *RoomView { + if view.roomList.Contains(room.ID) { + debug.Print("Add aborted (room exists)", room.ID, room.GetTitle()) + return nil + } + debug.Print("Adding", room.ID, room.GetTitle()) + view.roomList.Add(room) + view.roomsLock.Lock() + roomView := view.addRoomPage(room) + if !view.roomList.HasSelected() { + t, r := view.roomList.First() + view.switchRoom(t, r, false) + } + view.roomsLock.Unlock() + return roomView +} + func (view *MainView) SetRooms(rooms map[string]*rooms.Room) { view.roomList.Clear() + view.roomsLock.Lock() view.rooms = make(map[string]*RoomView) for _, room := range rooms { if room.HasLeft { @@ -331,7 +358,9 @@ func (view *MainView) SetRooms(rooms map[string]*rooms.Room) { view.roomList.Add(room) view.addRoomPage(room) } - view.SwitchRoom(view.roomList.First()) + t, r := view.roomList.First() + view.switchRoom(t, r, false) + view.roomsLock.Unlock() } func (view *MainView) UpdateTags(room *rooms.Room) { @@ -342,8 +371,8 @@ func (view *MainView) UpdateTags(room *rooms.Room) { view.roomList.Add(room) } -func (view *MainView) SetTyping(room string, users []string) { - roomView, ok := view.rooms[room] +func (view *MainView) SetTyping(roomID string, users []string) { + roomView, ok := view.getRoomView(roomID, true) if ok { roomView.SetTyping(users) view.parent.Render() @@ -392,33 +421,27 @@ func (view *MainView) NotifyMessage(room *rooms.Room, message ifc.Message, shoul 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(room string) { +func (view *MainView) LoadHistory(roomID string) { defer debug.Recover() - roomView := view.rooms[room] + roomView, ok := view.getRoomView(roomID, true) + if !ok { + return + } msgView := roomView.MessageView() - batch := roomView.Room.PrevBatch - lockTime := time.Now().Unix() + 1 - - roomView.Room.LockHistory() - msgView.LoadingMessages = true - defer func() { - roomView.Room.UnlockHistory() - msgView.LoadingMessages = false - }() - - // There's no clean way to try to lock a mutex, so we just check if we still - // want to continue after we get the lock. This function should always be ran - // in a goroutine, so the blocking doesn't matter. - if time.Now().Unix() >= lockTime || batch != roomView.Room.PrevBatch { + if !atomic.CompareAndSwapInt32(&msgView.loadingMessages, 0, 1) { + // Locked return } + defer atomic.StoreInt32(&msgView.loadingMessages, 0) history, err := view.matrix.GetHistory(roomView.Room, 50) if err != nil { -- cgit v1.2.3