2018-03-21 22:29:58 +01:00
// gomuks - A terminal Matrix client written in Go.
2020-04-19 17:10:14 +02:00
// Copyright (C) 2020 Tulir Asokan
2018-03-21 22:29:58 +01:00
//
// This program is free software: you can redistribute it and/or modify
2019-01-17 13:13:25 +01:00
// it under the terms of the GNU Affero General Public License as published by
2018-03-21 22:29:58 +01:00
// 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
2019-01-17 13:13:25 +01:00
// GNU Affero General Public License for more details.
2018-03-21 22:29:58 +01:00
//
2019-01-17 13:13:25 +01:00
// 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/>.
2018-03-21 22:29:58 +01:00
2018-11-13 23:00:35 +01:00
// Based on https://github.com/matrix-org/mautrix/blob/master/sync.go
2018-03-21 22:29:58 +01:00
2018-03-18 20:24:03 +01:00
package matrix
2018-03-18 16:34:42 +01:00
import (
2020-04-19 14:57:49 +02:00
"sync"
2018-03-18 16:34:42 +01:00
"time"
2019-01-11 22:28:47 +01:00
"maunium.net/go/mautrix"
2020-04-16 18:27:35 +02:00
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
2019-01-17 13:13:25 +01:00
"maunium.net/go/gomuks/debug"
2022-04-15 11:53:09 +02:00
ifc "maunium.net/go/gomuks/interface"
2019-01-17 13:13:25 +01:00
"maunium.net/go/gomuks/matrix/rooms"
2018-03-18 16:34:42 +01:00
)
type GomuksSyncer struct {
2020-04-19 17:06:45 +02:00
rooms * rooms . RoomCache
2021-04-29 22:52:27 +02:00
globalListeners [ ] mautrix . SyncHandler
listeners map [ event . Type ] [ ] mautrix . EventHandler // event type to listeners array
2020-04-19 17:06:45 +02:00
FirstSyncDone bool
InitDoneCallback func ( )
FirstDoneCallback func ( )
Progress ifc . SyncingModal
2018-03-18 16:34:42 +01:00
}
// NewGomuksSyncer returns an instantiated GomuksSyncer
2020-04-19 17:06:45 +02:00
func NewGomuksSyncer ( rooms * rooms . RoomCache ) * GomuksSyncer {
2018-03-18 16:34:42 +01:00
return & GomuksSyncer {
2020-04-26 23:38:04 +02:00
rooms : rooms ,
2021-04-29 22:52:27 +02:00
globalListeners : [ ] mautrix . SyncHandler { } ,
listeners : make ( map [ event . Type ] [ ] mautrix . EventHandler ) ,
2020-04-26 23:38:04 +02:00
FirstSyncDone : false ,
Progress : StubSyncingModal { } ,
2018-03-18 16:34:42 +01:00
}
}
2018-03-21 22:29:58 +01:00
// ProcessResponse processes a Matrix sync response.
2018-11-13 23:00:35 +01:00
func ( s * GomuksSyncer ) ProcessResponse ( res * mautrix . RespSync , since string ) ( err error ) {
2020-04-19 14:57:49 +02:00
if since == "" {
2020-04-19 17:06:45 +02:00
s . rooms . DisableUnloading ( )
2020-04-19 14:57:49 +02:00
}
2018-11-13 23:00:35 +01:00
debug . Print ( "Received sync response" )
2020-04-26 23:38:04 +02:00
s . Progress . SetMessage ( "Processing sync response" )
2020-04-19 17:06:45 +02:00
steps := len ( res . Rooms . Join ) + len ( res . Rooms . Invite ) + len ( res . Rooms . Leave )
2020-04-26 23:38:04 +02:00
s . Progress . SetSteps ( steps + 2 + len ( s . globalListeners ) )
2018-03-23 22:39:17 +01:00
2020-04-19 14:57:49 +02:00
wait := & sync . WaitGroup { }
2020-04-19 17:06:45 +02:00
callback := func ( ) {
wait . Done ( )
s . Progress . Step ( )
}
2020-04-26 23:38:04 +02:00
wait . Add ( len ( s . globalListeners ) )
2020-04-27 23:58:26 +02:00
s . notifyGlobalListeners ( res , since , callback )
2020-04-26 23:38:04 +02:00
wait . Wait ( )
2020-06-17 12:27:22 +02:00
s . processSyncEvents ( nil , res . Presence . Events , mautrix . EventSourcePresence )
2020-04-26 23:38:04 +02:00
s . Progress . Step ( )
2020-06-17 12:27:22 +02:00
s . processSyncEvents ( nil , res . AccountData . Events , mautrix . EventSourceAccountData )
2020-04-26 23:38:04 +02:00
s . Progress . Step ( )
wait . Add ( steps )
2020-04-19 17:06:45 +02:00
2018-03-18 16:34:42 +01:00
for roomID , roomData := range res . Rooms . Join {
2020-04-19 17:06:45 +02:00
go s . processJoinedRoom ( roomID , roomData , callback )
2018-03-18 16:34:42 +01:00
}
2018-03-23 22:39:17 +01:00
2018-03-18 16:34:42 +01:00
for roomID , roomData := range res . Rooms . Invite {
2020-04-19 17:06:45 +02:00
go s . processInvitedRoom ( roomID , roomData , callback )
2018-03-18 16:34:42 +01:00
}
2018-03-23 22:39:17 +01:00
2018-03-18 16:34:42 +01:00
for roomID , roomData := range res . Rooms . Leave {
2020-04-19 17:06:45 +02:00
go s . processLeftRoom ( roomID , roomData , callback )
2018-03-18 16:34:42 +01:00
}
2018-03-23 22:39:17 +01:00
2020-04-19 14:57:49 +02:00
wait . Wait ( )
2020-04-19 17:56:36 +02:00
s . Progress . SetMessage ( "Finishing sync" )
2020-04-19 14:57:49 +02:00
2018-04-24 16:12:08 +02:00
if since == "" && s . InitDoneCallback != nil {
s . InitDoneCallback ( )
2020-04-19 17:06:45 +02:00
s . rooms . EnableUnloading ( )
}
if ! s . FirstSyncDone && s . FirstDoneCallback != nil {
s . FirstDoneCallback ( )
2018-04-24 01:11:32 +02:00
}
2018-04-01 08:53:00 +02:00
s . FirstSyncDone = true
2018-03-18 16:34:42 +01:00
return
}
2020-04-27 23:58:26 +02:00
func ( s * GomuksSyncer ) notifyGlobalListeners ( res * mautrix . RespSync , since string , callback func ( ) ) {
2020-04-26 23:38:04 +02:00
for _ , listener := range s . globalListeners {
2021-04-29 22:52:27 +02:00
go func ( listener mautrix . SyncHandler ) {
2020-04-27 23:58:26 +02:00
listener ( res , since )
2020-04-26 23:38:04 +02:00
callback ( )
} ( listener )
}
}
2020-04-19 17:06:45 +02:00
func ( s * GomuksSyncer ) processJoinedRoom ( roomID id . RoomID , roomData mautrix . SyncJoinedRoom , callback func ( ) ) {
2020-04-19 14:57:49 +02:00
defer debug . Recover ( )
2020-04-19 17:06:45 +02:00
room := s . rooms . GetOrCreate ( roomID )
2020-04-19 14:57:49 +02:00
room . UpdateSummary ( roomData . Summary )
2020-06-17 12:27:22 +02:00
s . processSyncEvents ( room , roomData . State . Events , mautrix . EventSourceJoin | mautrix . EventSourceState )
s . processSyncEvents ( room , roomData . Timeline . Events , mautrix . EventSourceJoin | mautrix . EventSourceTimeline )
s . processSyncEvents ( room , roomData . Ephemeral . Events , mautrix . EventSourceJoin | mautrix . EventSourceEphemeral )
s . processSyncEvents ( room , roomData . AccountData . Events , mautrix . EventSourceJoin | mautrix . EventSourceAccountData )
2020-04-19 14:57:49 +02:00
if len ( room . PrevBatch ) == 0 {
room . PrevBatch = roomData . Timeline . PrevBatch
}
room . LastPrevBatch = roomData . Timeline . PrevBatch
2020-04-19 17:06:45 +02:00
callback ( )
2020-04-19 14:57:49 +02:00
}
2020-04-19 17:06:45 +02:00
func ( s * GomuksSyncer ) processInvitedRoom ( roomID id . RoomID , roomData mautrix . SyncInvitedRoom , callback func ( ) ) {
2020-04-19 14:57:49 +02:00
defer debug . Recover ( )
2020-04-19 17:06:45 +02:00
room := s . rooms . GetOrCreate ( roomID )
2020-04-19 14:57:49 +02:00
room . UpdateSummary ( roomData . Summary )
2020-06-17 12:27:22 +02:00
s . processSyncEvents ( room , roomData . State . Events , mautrix . EventSourceInvite | mautrix . EventSourceState )
2020-04-19 17:06:45 +02:00
callback ( )
2020-04-19 14:57:49 +02:00
}
2020-04-19 17:06:45 +02:00
func ( s * GomuksSyncer ) processLeftRoom ( roomID id . RoomID , roomData mautrix . SyncLeftRoom , callback func ( ) ) {
2020-04-19 14:57:49 +02:00
defer debug . Recover ( )
2020-04-19 17:06:45 +02:00
room := s . rooms . GetOrCreate ( roomID )
2020-04-19 14:57:49 +02:00
room . HasLeft = true
room . UpdateSummary ( roomData . Summary )
2020-06-17 12:27:22 +02:00
s . processSyncEvents ( room , roomData . State . Events , mautrix . EventSourceLeave | mautrix . EventSourceState )
s . processSyncEvents ( room , roomData . Timeline . Events , mautrix . EventSourceLeave | mautrix . EventSourceTimeline )
2020-04-19 14:57:49 +02:00
if len ( room . PrevBatch ) == 0 {
room . PrevBatch = roomData . Timeline . PrevBatch
}
room . LastPrevBatch = roomData . Timeline . PrevBatch
2020-04-19 17:06:45 +02:00
callback ( )
2020-04-19 14:57:49 +02:00
}
2020-06-17 12:27:22 +02:00
func ( s * GomuksSyncer ) processSyncEvents ( room * rooms . Room , events [ ] * event . Event , source mautrix . EventSource ) {
2020-04-16 18:27:35 +02:00
for _ , evt := range events {
s . processSyncEvent ( room , evt , source )
2018-03-23 22:39:17 +01:00
}
}
2020-06-17 12:27:22 +02:00
func ( s * GomuksSyncer ) processSyncEvent ( room * rooms . Room , evt * event . Event , source mautrix . EventSource ) {
2020-04-19 14:00:49 +02:00
if room != nil {
evt . RoomID = room . ID
2020-02-19 00:25:11 +01:00
}
2020-04-16 18:27:35 +02:00
// Ensure the type class is correct. It's safe to mutate since it's not a pointer.
// Listeners are keyed by type structs, which means only the correct class will pass.
switch {
case evt . StateKey != nil :
evt . Type . Class = event . StateEventType
2020-06-17 12:27:22 +02:00
case source == mautrix . EventSourcePresence , source & mautrix . EventSourceEphemeral != 0 :
2020-04-16 18:27:35 +02:00
evt . Type . Class = event . EphemeralEventType
2020-06-17 12:27:22 +02:00
case source & mautrix . EventSourceAccountData != 0 :
2020-04-16 18:27:35 +02:00
evt . Type . Class = event . AccountDataEventType
2020-06-17 12:27:22 +02:00
case source == mautrix . EventSourceToDevice :
2020-04-16 18:27:35 +02:00
evt . Type . Class = event . ToDeviceEventType
default :
evt . Type . Class = event . MessageEventType
}
2020-04-19 14:00:49 +02:00
err := evt . Content . ParseRaw ( evt . Type )
if err != nil {
debug . Printf ( "Failed to unmarshal content of event %s (type %s) by %s in %s: %v\n%s" , evt . ID , evt . Type . Repr ( ) , evt . Sender , evt . RoomID , err , string ( evt . Content . VeryRaw ) )
// TODO might be good to let these pass to allow handling invalid events too
return
}
if room != nil && evt . Type . IsState ( ) {
room . UpdateState ( evt )
2018-03-23 22:39:17 +01:00
}
2020-04-16 18:27:35 +02:00
s . notifyListeners ( source , evt )
2018-03-23 22:39:17 +01:00
}
2018-03-18 16:34:42 +01:00
// OnEventType allows callers to be notified when there are new events for the given event type.
// There are no duplicate checks.
2021-04-29 22:52:27 +02:00
func ( s * GomuksSyncer ) OnEventType ( eventType event . Type , callback mautrix . EventHandler ) {
2018-03-18 16:34:42 +01:00
_ , exists := s . listeners [ eventType ]
if ! exists {
2021-04-29 22:52:27 +02:00
s . listeners [ eventType ] = [ ] mautrix . EventHandler { }
2018-03-18 16:34:42 +01:00
}
s . listeners [ eventType ] = append ( s . listeners [ eventType ] , callback )
}
2021-04-29 22:52:27 +02:00
func ( s * GomuksSyncer ) OnSync ( callback mautrix . SyncHandler ) {
2020-04-26 23:38:04 +02:00
s . globalListeners = append ( s . globalListeners , callback )
}
2020-06-17 12:27:22 +02:00
func ( s * GomuksSyncer ) notifyListeners ( source mautrix . EventSource , evt * event . Event ) {
2020-04-16 18:27:35 +02:00
listeners , exists := s . listeners [ evt . Type ]
2018-03-18 16:34:42 +01:00
if ! exists {
return
}
for _ , fn := range listeners {
2020-04-16 18:27:35 +02:00
fn ( source , evt )
2018-03-18 16:34:42 +01:00
}
}
// OnFailedSync always returns a 10 second wait period between failed /syncs, never a fatal error.
2018-11-13 23:00:35 +01:00
func ( s * GomuksSyncer ) OnFailedSync ( res * mautrix . RespSync , err error ) ( time . Duration , error ) {
debug . Printf ( "Sync failed: %v" , err )
2018-03-18 16:34:42 +01:00
return 10 * time . Second , nil
}
// GetFilterJSON returns a filter with a timeline limit of 50.
2020-04-19 14:00:49 +02:00
func ( s * GomuksSyncer ) GetFilterJSON ( _ id . UserID ) * mautrix . Filter {
2020-11-29 01:05:11 +01:00
stateEvents := [ ] event . Type {
event . StateMember ,
event . StateRoomName ,
event . StateTopic ,
event . StateCanonicalAlias ,
event . StatePowerLevels ,
event . StateTombstone ,
event . StateEncryption ,
}
messageEvents := [ ] event . Type {
event . EventMessage ,
event . EventRedaction ,
event . EventEncrypted ,
event . EventSticker ,
event . EventReaction ,
}
2020-04-19 14:00:49 +02:00
return & mautrix . Filter {
2018-11-13 23:00:35 +01:00
Room : mautrix . RoomFilter {
2018-05-10 19:56:46 +02:00
IncludeLeave : false ,
2018-11-13 23:00:35 +01:00
State : mautrix . FilterPart {
2020-02-21 23:03:57 +01:00
LazyLoadMembers : true ,
2021-03-11 21:53:13 +01:00
Types : stateEvents ,
2018-03-24 12:27:13 +01:00
} ,
2018-11-13 23:00:35 +01:00
Timeline : mautrix . FilterPart {
2020-02-21 23:03:57 +01:00
LazyLoadMembers : true ,
2021-03-11 21:53:13 +01:00
Types : append ( messageEvents , stateEvents ... ) ,
Limit : 50 ,
2018-03-24 12:27:13 +01:00
} ,
2018-11-13 23:00:35 +01:00
Ephemeral : mautrix . FilterPart {
2020-04-16 18:27:35 +02:00
Types : [ ] event . Type { event . EphemeralEventTyping , event . EphemeralEventReceipt } ,
2018-03-24 12:27:13 +01:00
} ,
2018-11-13 23:00:35 +01:00
AccountData : mautrix . FilterPart {
2020-04-16 18:27:35 +02:00
Types : [ ] event . Type { event . AccountDataRoomTags } ,
2018-05-10 19:56:46 +02:00
} ,
} ,
2018-11-13 23:00:35 +01:00
AccountData : mautrix . FilterPart {
2020-04-16 18:27:35 +02:00
Types : [ ] event . Type { event . AccountDataPushRules , event . AccountDataDirectChats , AccountDataGomuksPreferences } ,
2018-03-24 12:27:13 +01:00
} ,
2018-11-13 23:00:35 +01:00
Presence : mautrix . FilterPart {
2020-04-16 18:27:35 +02:00
NotTypes : [ ] event . Type { event . NewEventType ( "*" ) } ,
2018-03-24 12:27:13 +01:00
} ,
2018-05-10 19:56:46 +02:00
}
2018-03-18 16:34:42 +01:00
}