mirror of
https://mau.dev/mautrix/go.git
synced 2026-03-14 22:35:52 +01:00
bridgev2: implement re-ID'ing portals properly
This commit is contained in:
parent
c53c0c1860
commit
28d81a2b60
5 changed files with 211 additions and 21 deletions
|
|
@ -89,7 +89,11 @@ const (
|
|||
name_set=$11, avatar_set=$12, topic_set=$13, in_space=$14, metadata=$15
|
||||
WHERE bridge_id=$1 AND id=$2 AND receiver=$3
|
||||
`
|
||||
reIDPortalQuery = `UPDATE portal SET id=$3 WHERE bridge_id=$1 AND id=$2`
|
||||
deletePortalQuery = `
|
||||
DELETE FROM portal
|
||||
WHERE bridge_id=$1 AND id=$2 AND receiver=$3
|
||||
`
|
||||
reIDPortalQuery = `UPDATE portal SET id=$4, receiver=$5 WHERE bridge_id=$1 AND id=$2 AND receiver=$3`
|
||||
)
|
||||
|
||||
func (pq *PortalQuery) GetByID(ctx context.Context, key networkid.PortalKey) (*Portal, error) {
|
||||
|
|
@ -108,8 +112,8 @@ func (pq *PortalQuery) GetChildren(ctx context.Context, parentID networkid.Porta
|
|||
return pq.QueryMany(ctx, getChildPortalsQuery, pq.BridgeID, parentID)
|
||||
}
|
||||
|
||||
func (pq *PortalQuery) ReID(ctx context.Context, oldID, newID networkid.PortalID) error {
|
||||
return pq.Exec(ctx, reIDPortalQuery, pq.BridgeID, oldID, newID)
|
||||
func (pq *PortalQuery) ReID(ctx context.Context, oldID, newID networkid.PortalKey) error {
|
||||
return pq.Exec(ctx, reIDPortalQuery, pq.BridgeID, oldID.ID, oldID.Receiver, newID.ID, newID.Receiver)
|
||||
}
|
||||
|
||||
func (pq *PortalQuery) Insert(ctx context.Context, p *Portal) error {
|
||||
|
|
@ -122,6 +126,10 @@ func (pq *PortalQuery) Update(ctx context.Context, p *Portal) error {
|
|||
return pq.Exec(ctx, updatePortalQuery, p.sqlVariables()...)
|
||||
}
|
||||
|
||||
func (pq *PortalQuery) Delete(ctx context.Context, key networkid.PortalKey) error {
|
||||
return pq.Exec(ctx, deletePortalQuery, pq.BridgeID, key.ID, key.Receiver)
|
||||
}
|
||||
|
||||
func (p *Portal) Scan(row dbutil.Scannable) (*Portal, error) {
|
||||
var mxid, parentID sql.NullString
|
||||
var avatarHash string
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/appservice"
|
||||
"maunium.net/go/mautrix/bridgev2"
|
||||
|
|
@ -216,7 +218,34 @@ func (as *ASIntent) CreateRoom(ctx context.Context, req *mautrix.ReqCreateRoom)
|
|||
return resp.RoomID, nil
|
||||
}
|
||||
|
||||
func (as *ASIntent) DeleteRoom(ctx context.Context, roomID id.RoomID) error {
|
||||
// TODO implement non-beeper delete
|
||||
return as.Matrix.BeeperDeleteRoom(ctx, roomID)
|
||||
func (as *ASIntent) DeleteRoom(ctx context.Context, roomID id.RoomID, puppetsOnly bool) error {
|
||||
if as.Connector.SpecVersions.Supports(mautrix.BeeperFeatureRoomYeeting) {
|
||||
return as.Matrix.BeeperDeleteRoom(ctx, roomID)
|
||||
}
|
||||
members, err := as.Matrix.JoinedMembers(ctx, roomID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get portal members for cleanup: %w", err)
|
||||
}
|
||||
for member := range members.Joined {
|
||||
if member == as.Matrix.UserID {
|
||||
continue
|
||||
}
|
||||
_, isGhost := as.Connector.ParseGhostMXID(member)
|
||||
if isGhost {
|
||||
_, err = as.Connector.AS.Intent(member).LeaveRoom(ctx, roomID)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Stringer("user_id", member).Msg("Failed to leave room while cleaning up portal")
|
||||
}
|
||||
} else if !puppetsOnly {
|
||||
_, err = as.Matrix.KickUser(ctx, roomID, &mautrix.ReqKickUser{UserID: member, Reason: "Deleting portal"})
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Stringer("user_id", member).Msg("Failed to kick user while cleaning up portal")
|
||||
}
|
||||
}
|
||||
}
|
||||
_, err = as.Matrix.LeaveRoom(ctx, roomID)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to leave room while cleaning up portal")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ type MatrixAPI interface {
|
|||
SetExtraProfileMeta(ctx context.Context, data any) error
|
||||
|
||||
CreateRoom(ctx context.Context, req *mautrix.ReqCreateRoom) (id.RoomID, error)
|
||||
DeleteRoom(ctx context.Context, roomID id.RoomID) error
|
||||
DeleteRoom(ctx context.Context, roomID id.RoomID, puppetsOnly bool) error
|
||||
InviteUser(ctx context.Context, roomID id.RoomID, userID id.UserID) error
|
||||
EnsureJoined(ctx context.Context, roomID id.RoomID) error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -778,7 +778,7 @@ func (portal *Portal) handleRemoteEvent(source *UserLogin, evt RemoteEvent) {
|
|||
if !ok || !mcp.ShouldCreatePortal() {
|
||||
return
|
||||
}
|
||||
err := portal.CreateMatrixRoom(ctx, source)
|
||||
err := portal.CreateMatrixRoom(ctx, source, nil)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to create portal to handle event")
|
||||
// TODO error
|
||||
|
|
@ -1441,7 +1441,7 @@ func (portal *Portal) SyncParticipants(ctx context.Context, members []networkid.
|
|||
return expectedUserIDs, extraFunctionalMembers, nil
|
||||
}
|
||||
|
||||
func (portal *Portal) UpdateInfo(ctx context.Context, info *PortalInfo, sender *Ghost, ts time.Time) {
|
||||
func (portal *Portal) UpdateInfo(ctx context.Context, info *PortalInfo, source *UserLogin, sender *Ghost, ts time.Time) {
|
||||
changed := false
|
||||
if info.Name != nil {
|
||||
changed = portal.UpdateName(ctx, *info.Name, sender, ts) || changed
|
||||
|
|
@ -1452,12 +1452,13 @@ func (portal *Portal) UpdateInfo(ctx context.Context, info *PortalInfo, sender *
|
|||
if info.Avatar != nil {
|
||||
changed = portal.UpdateAvatar(ctx, info.Avatar, sender, ts) || changed
|
||||
}
|
||||
//if info.Members != nil && portal.MXID != "" {
|
||||
// _, err := portal.SyncParticipants(ctx, info.Members, source)
|
||||
// if err != nil {
|
||||
// zerolog.Ctx(ctx).Err(err).Msg("Failed to sync room members")
|
||||
// }
|
||||
//}
|
||||
if info.Members != nil && portal.MXID != "" && source != nil {
|
||||
_, _, err := portal.SyncParticipants(ctx, info.Members, source)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to sync room members")
|
||||
}
|
||||
// TODO detect changes to functional members list?
|
||||
}
|
||||
if changed {
|
||||
portal.UpdateBridgeInfo(ctx)
|
||||
err := portal.Save(ctx)
|
||||
|
|
@ -1467,7 +1468,7 @@ func (portal *Portal) UpdateInfo(ctx context.Context, info *PortalInfo, sender *
|
|||
}
|
||||
}
|
||||
|
||||
func (portal *Portal) CreateMatrixRoom(ctx context.Context, source *UserLogin) error {
|
||||
func (portal *Portal) CreateMatrixRoom(ctx context.Context, source *UserLogin, info *PortalInfo) error {
|
||||
portal.roomCreateLock.Lock()
|
||||
defer portal.roomCreateLock.Unlock()
|
||||
if portal.MXID != "" {
|
||||
|
|
@ -1479,12 +1480,15 @@ func (portal *Portal) CreateMatrixRoom(ctx context.Context, source *UserLogin) e
|
|||
ctx = log.WithContext(ctx)
|
||||
log.Info().Msg("Creating Matrix room")
|
||||
|
||||
info, err := source.Client.GetChatInfo(ctx, portal)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to update portal info for creation")
|
||||
return err
|
||||
var err error
|
||||
if info == nil {
|
||||
info, err = source.Client.GetChatInfo(ctx, portal)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to update portal info for creation")
|
||||
return err
|
||||
}
|
||||
}
|
||||
portal.UpdateInfo(ctx, info, nil, time.Time{})
|
||||
portal.UpdateInfo(ctx, info, source, nil, time.Time{})
|
||||
initialMembers, extraFunctionalMembers, err := portal.SyncParticipants(ctx, info.Members, source)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to process participant list for portal creation")
|
||||
|
|
@ -1583,6 +1587,34 @@ func (portal *Portal) CreateMatrixRoom(ctx context.Context, source *UserLogin) e
|
|||
return nil
|
||||
}
|
||||
|
||||
func (portal *Portal) Delete(ctx context.Context) error {
|
||||
err := portal.Bridge.DB.Portal.Delete(ctx, portal.PortalKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
portal.Bridge.cacheLock.Lock()
|
||||
defer portal.Bridge.cacheLock.Unlock()
|
||||
portal.unlockedDeleteCache()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (portal *Portal) unlockedDelete(ctx context.Context) error {
|
||||
// TODO delete child portals?
|
||||
err := portal.Bridge.DB.Portal.Delete(ctx, portal.PortalKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
portal.unlockedDeleteCache()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (portal *Portal) unlockedDeleteCache() {
|
||||
delete(portal.Bridge.portalsByKey, portal.PortalKey)
|
||||
if portal.MXID != "" {
|
||||
delete(portal.Bridge.portalsByMXID, portal.MXID)
|
||||
}
|
||||
}
|
||||
|
||||
func (portal *Portal) Save(ctx context.Context) error {
|
||||
return portal.Bridge.DB.Portal.Update(ctx, portal.Portal)
|
||||
}
|
||||
|
|
|
|||
121
bridgev2/portalreid.go
Normal file
121
bridgev2/portalreid.go
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
// Copyright (c) 2024 Tulir Asokan
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package bridgev2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||
"maunium.net/go/mautrix/event"
|
||||
)
|
||||
|
||||
type ReIDResult int
|
||||
|
||||
const (
|
||||
ReIDResultError ReIDResult = iota
|
||||
ReIDResultNoOp
|
||||
ReIDResultSourceDeleted
|
||||
ReIDResultSourceReIDd
|
||||
ReIDResultTargetDeletedAndSourceReIDd
|
||||
ReIDResultSourceTombstonedIntoTarget
|
||||
)
|
||||
|
||||
func (br *Bridge) ReIDPortal(ctx context.Context, source, target networkid.PortalKey) (ReIDResult, *Portal, error) {
|
||||
log := zerolog.Ctx(ctx)
|
||||
log.Debug().Msg("Re-ID'ing portal")
|
||||
defer func() {
|
||||
log.Debug().Msg("Finished handling portal re-ID")
|
||||
}()
|
||||
br.cacheLock.Lock()
|
||||
defer br.cacheLock.Unlock()
|
||||
sourcePortal, err := br.unlockedGetPortalByID(ctx, source, true)
|
||||
if err != nil {
|
||||
return ReIDResultError, nil, fmt.Errorf("failed to get source portal: %w", err)
|
||||
} else if sourcePortal == nil {
|
||||
log.Debug().Msg("Source portal not found, re-ID is no-op")
|
||||
return ReIDResultNoOp, nil, nil
|
||||
}
|
||||
sourcePortal.roomCreateLock.Lock()
|
||||
defer sourcePortal.roomCreateLock.Unlock()
|
||||
if sourcePortal.MXID == "" {
|
||||
log.Info().Msg("Source portal doesn't have Matrix room, deleting row")
|
||||
err = sourcePortal.unlockedDelete(ctx)
|
||||
if err != nil {
|
||||
return ReIDResultError, nil, fmt.Errorf("failed to delete source portal: %w", err)
|
||||
}
|
||||
return ReIDResultSourceDeleted, nil, nil
|
||||
}
|
||||
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
||||
return c.Stringer("source_portal_mxid", sourcePortal.MXID)
|
||||
})
|
||||
targetPortal, err := br.unlockedGetPortalByID(ctx, target, true)
|
||||
if err != nil {
|
||||
return ReIDResultError, nil, fmt.Errorf("failed to get target portal: %w", err)
|
||||
}
|
||||
if targetPortal == nil {
|
||||
log.Info().Msg("Target portal doesn't exist, re-ID'ing source portal")
|
||||
err = sourcePortal.unlockedReID(ctx, target)
|
||||
if err != nil {
|
||||
return ReIDResultError, nil, fmt.Errorf("failed to re-ID source portal: %w", err)
|
||||
}
|
||||
return ReIDResultSourceReIDd, sourcePortal, nil
|
||||
}
|
||||
targetPortal.roomCreateLock.Lock()
|
||||
defer targetPortal.roomCreateLock.Unlock()
|
||||
if targetPortal.MXID == "" {
|
||||
log.Info().Msg("Target portal row exists, but doesn't have a Matrix room. Deleting target portal row and re-ID'ing source portal")
|
||||
err = targetPortal.unlockedDelete(ctx)
|
||||
if err != nil {
|
||||
return ReIDResultError, nil, fmt.Errorf("failed to delete target portal: %w", err)
|
||||
}
|
||||
err = sourcePortal.unlockedReID(ctx, target)
|
||||
if err != nil {
|
||||
return ReIDResultError, nil, fmt.Errorf("failed to re-ID source portal after deleting target: %w", err)
|
||||
}
|
||||
return ReIDResultTargetDeletedAndSourceReIDd, sourcePortal, nil
|
||||
} else {
|
||||
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
||||
return c.Stringer("target_portal_mxid", targetPortal.MXID)
|
||||
})
|
||||
log.Info().Msg("Both target and source portals have Matrix rooms, tombstoning source portal")
|
||||
err = sourcePortal.unlockedDelete(ctx)
|
||||
if err != nil {
|
||||
return ReIDResultError, nil, fmt.Errorf("failed to delete source portal row: %w", err)
|
||||
}
|
||||
go func() {
|
||||
_, err := br.Bot.SendState(ctx, sourcePortal.MXID, event.StateTombstone, "", &event.Content{
|
||||
Parsed: &event.TombstoneEventContent{
|
||||
Body: fmt.Sprintf("This room has been merged"),
|
||||
ReplacementRoom: targetPortal.MXID,
|
||||
},
|
||||
}, time.Now())
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to send tombstone to source portal room")
|
||||
}
|
||||
err = br.Bot.DeleteRoom(ctx, sourcePortal.MXID, err == nil)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to delete source portal room")
|
||||
}
|
||||
}()
|
||||
return ReIDResultSourceTombstonedIntoTarget, targetPortal, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (portal *Portal) unlockedReID(ctx context.Context, target networkid.PortalKey) error {
|
||||
err := portal.Bridge.DB.Portal.ReID(ctx, portal.PortalKey, target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delete(portal.Bridge.portalsByKey, portal.PortalKey)
|
||||
portal.Bridge.portalsByKey[target] = portal
|
||||
portal.PortalKey = target
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue