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 (
2019-03-26 21:09:10 +01:00
"fmt"
2020-04-19 14:57:49 +02:00
"sync"
2018-03-18 16:34:42 +01:00
"time"
2020-04-19 17:06:45 +02:00
ifc "maunium.net/go/gomuks/interface"
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"
"maunium.net/go/gomuks/matrix/rooms"
2018-03-18 16:34:42 +01:00
)
2018-04-30 21:28:29 +02:00
type EventSource int
const (
2018-05-22 16:24:47 +02:00
EventSourcePresence EventSource = 1 << iota
2018-04-30 21:28:29 +02:00
EventSourceJoin
EventSourceInvite
EventSourceLeave
2018-05-15 14:39:40 +02:00
EventSourceAccountData
EventSourceTimeline
EventSourceState
EventSourceEphemeral
2020-04-16 18:27:35 +02:00
EventSourceToDevice
2018-04-30 21:28:29 +02:00
)
2019-03-26 21:09:10 +01:00
func ( es EventSource ) String ( ) string {
2019-03-26 22:26:00 +01:00
switch {
case es == EventSourcePresence :
2019-03-26 21:09:10 +01:00
return "presence"
2019-03-26 22:26:00 +01:00
case es == EventSourceAccountData :
return "user account data"
case es & EventSourceJoin != 0 :
2019-03-26 21:09:10 +01:00
es -= EventSourceJoin
switch es {
case EventSourceState :
return "joined state"
case EventSourceTimeline :
return "joined timeline"
case EventSourceEphemeral :
return "room ephemeral (joined)"
case EventSourceAccountData :
return "room account data (joined)"
}
2019-03-26 22:26:00 +01:00
case es & EventSourceInvite != 0 :
2019-03-26 21:09:10 +01:00
es -= EventSourceInvite
switch es {
case EventSourceState :
return "invited state"
}
2019-03-26 22:26:00 +01:00
case es & EventSourceLeave != 0 :
2019-03-26 21:09:10 +01:00
es -= EventSourceLeave
switch es {
case EventSourceState :
return "left state"
case EventSourceTimeline :
return "left timeline"
}
}
return fmt . Sprintf ( "unknown (%d)" , es )
}
2020-04-16 18:27:35 +02:00
type EventHandler func ( source EventSource , event * event . Event )
2020-04-27 23:58:26 +02:00
type SyncHandler func ( resp * mautrix . RespSync , since string )
2018-04-30 21:28:29 +02:00
2018-03-18 16:34:42 +01:00
type GomuksSyncer struct {
2020-04-19 17:06:45 +02:00
rooms * rooms . RoomCache
2020-04-26 23:38:04 +02:00
globalListeners [ ] SyncHandler
2020-04-19 17:06:45 +02:00
listeners map [ event . Type ] [ ] EventHandler // event type to listeners array
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 ,
globalListeners : [ ] SyncHandler { } ,
listeners : make ( map [ event . Type ] [ ] EventHandler ) ,
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 ( )
s . processSyncEvents ( nil , res . Presence . Events , EventSourcePresence )
s . Progress . Step ( )
s . processSyncEvents ( nil , res . AccountData . Events , EventSourceAccountData )
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 {
go func ( listener 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 )
s . processSyncEvents ( room , roomData . State . Events , EventSourceJoin | EventSourceState )
s . processSyncEvents ( room , roomData . Timeline . Events , EventSourceJoin | EventSourceTimeline )
s . processSyncEvents ( room , roomData . Ephemeral . Events , EventSourceJoin | EventSourceEphemeral )
s . processSyncEvents ( room , roomData . AccountData . Events , EventSourceJoin | EventSourceAccountData )
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 )
s . processSyncEvents ( room , roomData . State . Events , EventSourceInvite | 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 )
s . processSyncEvents ( room , roomData . State . Events , EventSourceLeave | EventSourceState )
s . processSyncEvents ( room , roomData . Timeline . Events , EventSourceLeave | EventSourceTimeline )
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 14:00:49 +02:00
func ( s * GomuksSyncer ) processSyncEvents ( room * rooms . Room , events [ ] * event . Event , source 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-04-19 14:00:49 +02:00
func ( s * GomuksSyncer ) processSyncEvent ( room * rooms . Room , evt * event . Event , source EventSource ) {
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-04-19 14:00:49 +02:00
case source == EventSourcePresence , source & EventSourceEphemeral != 0 :
2020-04-16 18:27:35 +02:00
evt . Type . Class = event . EphemeralEventType
2020-04-19 14:00:49 +02:00
case source & EventSourceAccountData != 0 :
2020-04-16 18:27:35 +02:00
evt . Type . Class = event . AccountDataEventType
case source == EventSourceToDevice :
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.
2020-04-16 18:27:35 +02:00
func ( s * GomuksSyncer ) OnEventType ( eventType event . Type , callback EventHandler ) {
2018-03-18 16:34:42 +01:00
_ , exists := s . listeners [ eventType ]
if ! exists {
2018-04-30 21:28:29 +02:00
s . listeners [ eventType ] = [ ] EventHandler { }
2018-03-18 16:34:42 +01:00
}
s . listeners [ eventType ] = append ( s . listeners [ eventType ] , callback )
}
2020-04-26 23:38:04 +02:00
func ( s * GomuksSyncer ) OnSync ( callback SyncHandler ) {
s . globalListeners = append ( s . globalListeners , callback )
}
2020-04-16 18:27:35 +02:00
func ( s * GomuksSyncer ) notifyListeners ( source EventSource , evt * event . Event ) {
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 {
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 ,
2020-04-16 18:27:35 +02:00
Types : [ ] event . Type {
event . StateMember ,
event . StateRoomName ,
event . StateTopic ,
event . StateCanonicalAlias ,
event . StatePowerLevels ,
event . StateTombstone ,
2020-04-27 23:58:26 +02:00
event . StateEncryption ,
2018-05-10 19:56:46 +02:00
} ,
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 ,
2020-04-16 18:27:35 +02:00
Types : [ ] event . Type {
event . EventMessage ,
event . EventRedaction ,
event . EventEncrypted ,
event . EventSticker ,
event . EventReaction ,
2019-06-15 00:11:51 +02:00
2020-04-16 18:27:35 +02:00
event . StateMember ,
event . StateRoomName ,
event . StateTopic ,
event . StateCanonicalAlias ,
event . StatePowerLevels ,
event . StateTombstone ,
2019-01-11 22:28:47 +01:00
} ,
2020-04-16 18:27:35 +02:00
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
}