mirror of
https://github.com/tulir/mautrix-whatsapp
synced 2024-12-14 01:14:29 +01:00
Merge pull request #494 from mautrix/sumner/bri-3309
backfill: remove intermediate queues and unblock re-checking DB
This commit is contained in:
commit
5b73ba7efd
13 changed files with 262 additions and 174 deletions
|
@ -20,43 +20,73 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "maunium.net/go/maulogger/v2"
|
log "maunium.net/go/maulogger/v2"
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
|
|
||||||
"maunium.net/go/mautrix-whatsapp/database"
|
"maunium.net/go/mautrix-whatsapp/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BackfillQueue struct {
|
type BackfillQueue struct {
|
||||||
BackfillQuery *database.BackfillQuery
|
BackfillQuery *database.BackfillQuery
|
||||||
ImmediateBackfillRequests chan *database.Backfill
|
reCheckChannels []chan bool
|
||||||
DeferredBackfillRequests chan *database.Backfill
|
log log.Logger
|
||||||
ReCheckQueue chan bool
|
|
||||||
|
|
||||||
log log.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunLoop fetches backfills from the database, prioritizing immediate and forward backfills
|
func (bq *BackfillQueue) ReCheck() {
|
||||||
func (bq *BackfillQueue) RunLoop(user *User) {
|
bq.log.Info("Sending re-checks to %d channels", len(bq.reCheckChannels))
|
||||||
|
for _, channel := range bq.reCheckChannels {
|
||||||
|
go func(c chan bool) {
|
||||||
|
c <- true
|
||||||
|
}(channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bq *BackfillQueue) GetNextBackfill(userID id.UserID, backfillTypes []database.BackfillType, reCheckChannel chan bool) *database.Backfill {
|
||||||
for {
|
for {
|
||||||
if backfill := bq.BackfillQuery.GetNext(user.MXID); backfill != nil {
|
if backfill := bq.BackfillQuery.GetNext(userID, backfillTypes); backfill != nil {
|
||||||
if backfill.BackfillType == database.BackfillImmediate || backfill.BackfillType == database.BackfillForward {
|
backfill.MarkDispatched()
|
||||||
bq.ImmediateBackfillRequests <- backfill
|
return backfill
|
||||||
} else if backfill.BackfillType == database.BackfillDeferred {
|
|
||||||
select {
|
|
||||||
case <-bq.ReCheckQueue:
|
|
||||||
// If a queue re-check is requested, interrupt sending the
|
|
||||||
// backfill request to the deferred channel so that
|
|
||||||
// immediate backfills can happen ASAP.
|
|
||||||
continue
|
|
||||||
case bq.DeferredBackfillRequests <- backfill:
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bq.log.Debugfln("Unrecognized backfill type %d in queue", backfill.BackfillType)
|
|
||||||
}
|
|
||||||
backfill.MarkDone()
|
|
||||||
} else {
|
} else {
|
||||||
select {
|
select {
|
||||||
case <-bq.ReCheckQueue:
|
case <-reCheckChannel:
|
||||||
case <-time.After(time.Minute):
|
case <-time.After(time.Minute):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (user *User) HandleBackfillRequestsLoop(backfillTypes []database.BackfillType) {
|
||||||
|
reCheckChannel := make(chan bool)
|
||||||
|
user.BackfillQueue.reCheckChannels = append(user.BackfillQueue.reCheckChannels, reCheckChannel)
|
||||||
|
|
||||||
|
for {
|
||||||
|
req := user.BackfillQueue.GetNextBackfill(user.MXID, backfillTypes, reCheckChannel)
|
||||||
|
user.log.Infofln("Handling backfill request %s", req)
|
||||||
|
|
||||||
|
conv := user.bridge.DB.HistorySync.GetConversation(user.MXID, req.Portal)
|
||||||
|
if conv == nil {
|
||||||
|
user.log.Debugfln("Could not find history sync conversation data for %s", req.Portal.String())
|
||||||
|
req.MarkDone()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
portal := user.GetPortalByJID(conv.PortalKey.JID)
|
||||||
|
|
||||||
|
// Update the client store with basic chat settings.
|
||||||
|
if conv.MuteEndTime.After(time.Now()) {
|
||||||
|
user.Client.Store.ChatSettings.PutMutedUntil(conv.PortalKey.JID, conv.MuteEndTime)
|
||||||
|
}
|
||||||
|
if conv.Archived {
|
||||||
|
user.Client.Store.ChatSettings.PutArchived(conv.PortalKey.JID, true)
|
||||||
|
}
|
||||||
|
if conv.Pinned > 0 {
|
||||||
|
user.Client.Store.ChatSettings.PutPinned(conv.PortalKey.JID, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if conv.EphemeralExpiration != nil && portal.ExpirationTime != *conv.EphemeralExpiration {
|
||||||
|
portal.ExpirationTime = *conv.EphemeralExpiration
|
||||||
|
portal.Update(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
user.backfillInChunks(req, conv, portal)
|
||||||
|
req.MarkDone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
10
commands.go
10
commands.go
|
@ -223,7 +223,7 @@ func (handler *CommandHandler) CommandSetRelay(ce *CommandEvent) {
|
||||||
ce.Reply("Only admins are allowed to enable relay mode on this instance of the bridge")
|
ce.Reply("Only admins are allowed to enable relay mode on this instance of the bridge")
|
||||||
} else {
|
} else {
|
||||||
ce.Portal.RelayUserID = ce.User.MXID
|
ce.Portal.RelayUserID = ce.User.MXID
|
||||||
ce.Portal.Update()
|
ce.Portal.Update(nil)
|
||||||
ce.Reply("Messages from non-logged-in users in this room will now be bridged through your WhatsApp account")
|
ce.Reply("Messages from non-logged-in users in this room will now be bridged through your WhatsApp account")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -239,7 +239,7 @@ func (handler *CommandHandler) CommandUnsetRelay(ce *CommandEvent) {
|
||||||
ce.Reply("Only admins are allowed to enable relay mode on this instance of the bridge")
|
ce.Reply("Only admins are allowed to enable relay mode on this instance of the bridge")
|
||||||
} else {
|
} else {
|
||||||
ce.Portal.RelayUserID = ""
|
ce.Portal.RelayUserID = ""
|
||||||
ce.Portal.Update()
|
ce.Portal.Update(nil)
|
||||||
ce.Reply("Messages from non-logged-in users will no longer be bridged in this room")
|
ce.Reply("Messages from non-logged-in users will no longer be bridged in this room")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -461,7 +461,7 @@ func (handler *CommandHandler) CommandCreate(ce *CommandEvent) {
|
||||||
portal.Encrypted = true
|
portal.Encrypted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
portal.Update()
|
portal.Update(nil)
|
||||||
portal.UpdateBridgeInfo()
|
portal.UpdateBridgeInfo()
|
||||||
|
|
||||||
ce.Reply("Successfully created WhatsApp group %s", portal.Key.JID)
|
ce.Reply("Successfully created WhatsApp group %s", portal.Key.JID)
|
||||||
|
@ -888,10 +888,10 @@ func (handler *CommandHandler) CommandBackfill(ce *CommandEvent) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
backfillMessages := ce.Portal.bridge.DB.Backfill.NewWithValues(ce.User.MXID, database.BackfillImmediate, 0, &ce.Portal.Key, nil, nil, batchSize, -1, batchDelay)
|
backfillMessages := ce.Portal.bridge.DB.Backfill.NewWithValues(ce.User.MXID, database.BackfillImmediate, 0, &ce.Portal.Key, nil, batchSize, -1, batchDelay)
|
||||||
backfillMessages.Insert()
|
backfillMessages.Insert()
|
||||||
|
|
||||||
ce.User.BackfillQueue.ReCheckQueue <- true
|
ce.User.BackfillQueue.ReCheck()
|
||||||
}
|
}
|
||||||
|
|
||||||
const cmdListHelp = `list <contacts|groups> [page] [items per page] - Get a list of all contacts and groups.`
|
const cmdListHelp = `list <contacts|groups> [page] [items per page] - Get a list of all contacts and groups.`
|
||||||
|
|
|
@ -20,6 +20,8 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "maunium.net/go/maulogger/v2"
|
log "maunium.net/go/maulogger/v2"
|
||||||
|
@ -59,7 +61,7 @@ func (bq *BackfillQuery) New() *Backfill {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bq *BackfillQuery) NewWithValues(userID id.UserID, backfillType BackfillType, priority int, portal *PortalKey, timeStart *time.Time, timeEnd *time.Time, maxBatchEvents, maxTotalEvents, batchDelay int) *Backfill {
|
func (bq *BackfillQuery) NewWithValues(userID id.UserID, backfillType BackfillType, priority int, portal *PortalKey, timeStart *time.Time, maxBatchEvents, maxTotalEvents, batchDelay int) *Backfill {
|
||||||
return &Backfill{
|
return &Backfill{
|
||||||
db: bq.db,
|
db: bq.db,
|
||||||
log: bq.log,
|
log: bq.log,
|
||||||
|
@ -68,7 +70,6 @@ func (bq *BackfillQuery) NewWithValues(userID id.UserID, backfillType BackfillTy
|
||||||
Priority: priority,
|
Priority: priority,
|
||||||
Portal: portal,
|
Portal: portal,
|
||||||
TimeStart: timeStart,
|
TimeStart: timeStart,
|
||||||
TimeEnd: timeEnd,
|
|
||||||
MaxBatchEvents: maxBatchEvents,
|
MaxBatchEvents: maxBatchEvents,
|
||||||
MaxTotalEvents: maxTotalEvents,
|
MaxTotalEvents: maxTotalEvents,
|
||||||
BatchDelay: batchDelay,
|
BatchDelay: batchDelay,
|
||||||
|
@ -77,23 +78,28 @@ func (bq *BackfillQuery) NewWithValues(userID id.UserID, backfillType BackfillTy
|
||||||
|
|
||||||
const (
|
const (
|
||||||
getNextBackfillQuery = `
|
getNextBackfillQuery = `
|
||||||
SELECT queue_id, user_mxid, type, priority, portal_jid, portal_receiver, time_start, time_end, max_batch_events, max_total_events, batch_delay
|
SELECT queue_id, user_mxid, type, priority, portal_jid, portal_receiver, time_start, max_batch_events, max_total_events, batch_delay
|
||||||
FROM backfill_queue
|
FROM backfill_queue
|
||||||
WHERE user_mxid=$1
|
WHERE user_mxid=$1
|
||||||
AND completed_at IS NULL
|
AND type IN (%s)
|
||||||
|
AND dispatch_time IS NULL
|
||||||
ORDER BY type, priority, queue_id
|
ORDER BY type, priority, queue_id
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetNext returns the next backfill to perform
|
// GetNext returns the next backfill to perform
|
||||||
func (bq *BackfillQuery) GetNext(userID id.UserID) (backfill *Backfill) {
|
func (bq *BackfillQuery) GetNext(userID id.UserID, backfillTypes []BackfillType) (backfill *Backfill) {
|
||||||
rows, err := bq.db.Query(getNextBackfillQuery, userID)
|
types := []string{}
|
||||||
defer rows.Close()
|
for _, backfillType := range backfillTypes {
|
||||||
|
types = append(types, strconv.Itoa(int(backfillType)))
|
||||||
|
}
|
||||||
|
rows, err := bq.db.Query(fmt.Sprintf(getNextBackfillQuery, strings.Join(types, ",")), userID)
|
||||||
if err != nil || rows == nil {
|
if err != nil || rows == nil {
|
||||||
bq.log.Error(err)
|
bq.log.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer rows.Close()
|
||||||
if rows.Next() {
|
if rows.Next() {
|
||||||
backfill = bq.New().Scan(rows)
|
backfill = bq.New().Scan(rows)
|
||||||
}
|
}
|
||||||
|
@ -126,21 +132,21 @@ type Backfill struct {
|
||||||
Priority int
|
Priority int
|
||||||
Portal *PortalKey
|
Portal *PortalKey
|
||||||
TimeStart *time.Time
|
TimeStart *time.Time
|
||||||
TimeEnd *time.Time
|
|
||||||
MaxBatchEvents int
|
MaxBatchEvents int
|
||||||
MaxTotalEvents int
|
MaxTotalEvents int
|
||||||
BatchDelay int
|
BatchDelay int
|
||||||
|
DispatchTime *time.Time
|
||||||
CompletedAt *time.Time
|
CompletedAt *time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backfill) String() string {
|
func (b *Backfill) String() string {
|
||||||
return fmt.Sprintf("Backfill{QueueID: %d, UserID: %s, BackfillType: %s, Priority: %d, Portal: %s, TimeStart: %s, TimeEnd: %s, MaxBatchEvents: %d, MaxTotalEvents: %d, BatchDelay: %d, CompletedAt: %s}",
|
return fmt.Sprintf("Backfill{QueueID: %d, UserID: %s, BackfillType: %s, Priority: %d, Portal: %s, TimeStart: %s, MaxBatchEvents: %d, MaxTotalEvents: %d, BatchDelay: %d, DispatchTime: %s, CompletedAt: %s}",
|
||||||
b.QueueID, b.UserID, b.BackfillType, b.Priority, b.Portal, b.TimeStart, b.TimeEnd, b.MaxBatchEvents, b.MaxTotalEvents, b.BatchDelay, b.CompletedAt,
|
b.QueueID, b.UserID, b.BackfillType, b.Priority, b.Portal, b.TimeStart, b.MaxBatchEvents, b.MaxTotalEvents, b.BatchDelay, b.CompletedAt, b.DispatchTime,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backfill) Scan(row Scannable) *Backfill {
|
func (b *Backfill) Scan(row Scannable) *Backfill {
|
||||||
err := row.Scan(&b.QueueID, &b.UserID, &b.BackfillType, &b.Priority, &b.Portal.JID, &b.Portal.Receiver, &b.TimeStart, &b.TimeEnd, &b.MaxBatchEvents, &b.MaxTotalEvents, &b.BatchDelay)
|
err := row.Scan(&b.QueueID, &b.UserID, &b.BackfillType, &b.Priority, &b.Portal.JID, &b.Portal.Receiver, &b.TimeStart, &b.MaxBatchEvents, &b.MaxTotalEvents, &b.BatchDelay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, sql.ErrNoRows) {
|
if !errors.Is(err, sql.ErrNoRows) {
|
||||||
b.log.Errorln("Database scan failed:", err)
|
b.log.Errorln("Database scan failed:", err)
|
||||||
|
@ -153,10 +159,10 @@ func (b *Backfill) Scan(row Scannable) *Backfill {
|
||||||
func (b *Backfill) Insert() {
|
func (b *Backfill) Insert() {
|
||||||
rows, err := b.db.Query(`
|
rows, err := b.db.Query(`
|
||||||
INSERT INTO backfill_queue
|
INSERT INTO backfill_queue
|
||||||
(user_mxid, type, priority, portal_jid, portal_receiver, time_start, time_end, max_batch_events, max_total_events, batch_delay, completed_at)
|
(user_mxid, type, priority, portal_jid, portal_receiver, time_start, max_batch_events, max_total_events, batch_delay, dispatch_time, completed_at)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||||
RETURNING queue_id
|
RETURNING queue_id
|
||||||
`, b.UserID, b.BackfillType, b.Priority, b.Portal.JID, b.Portal.Receiver, b.TimeStart, b.TimeEnd, b.MaxBatchEvents, b.MaxTotalEvents, b.BatchDelay, b.CompletedAt)
|
`, b.UserID, b.BackfillType, b.Priority, b.Portal.JID, b.Portal.Receiver, b.TimeStart, b.MaxBatchEvents, b.MaxTotalEvents, b.BatchDelay, b.DispatchTime, b.CompletedAt)
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
if err != nil || !rows.Next() {
|
if err != nil || !rows.Next() {
|
||||||
b.log.Warnfln("Failed to insert %v/%s with priority %d: %v", b.BackfillType, b.Portal.JID, b.Priority, err)
|
b.log.Warnfln("Failed to insert %v/%s with priority %d: %v", b.BackfillType, b.Portal.JID, b.Priority, err)
|
||||||
|
@ -168,9 +174,20 @@ func (b *Backfill) Insert() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Backfill) MarkDispatched() {
|
||||||
|
if b.QueueID == 0 {
|
||||||
|
b.log.Errorf("Cannot mark backfill as dispatched without queue_id. Maybe it wasn't actually inserted in the database?")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err := b.db.Exec("UPDATE backfill_queue SET dispatch_time=$1 WHERE queue_id=$2", time.Now(), b.QueueID)
|
||||||
|
if err != nil {
|
||||||
|
b.log.Warnfln("Failed to mark %s/%s as dispatched: %v", b.BackfillType, b.Priority, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Backfill) MarkDone() {
|
func (b *Backfill) MarkDone() {
|
||||||
if b.QueueID == 0 {
|
if b.QueueID == 0 {
|
||||||
b.log.Errorf("Cannot delete backfill without queue_id. Maybe it wasn't actually inserted in the database?")
|
b.log.Errorf("Cannot mark backfill done without queue_id. Maybe it wasn't actually inserted in the database?")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err := b.db.Exec("UPDATE backfill_queue SET completed_at=$1 WHERE queue_id=$2", time.Now(), b.QueueID)
|
_, err := b.db.Exec("UPDATE backfill_queue SET completed_at=$1 WHERE queue_id=$2", time.Now(), b.QueueID)
|
||||||
|
|
|
@ -249,10 +249,10 @@ func (hsq *HistorySyncQuery) NewMessageWithValues(userID id.UserID, conversation
|
||||||
|
|
||||||
func (hsm *HistorySyncMessage) Insert() {
|
func (hsm *HistorySyncMessage) Insert() {
|
||||||
_, err := hsm.db.Exec(`
|
_, err := hsm.db.Exec(`
|
||||||
INSERT INTO history_sync_message (user_mxid, conversation_id, message_id, timestamp, data)
|
INSERT INTO history_sync_message (user_mxid, conversation_id, message_id, timestamp, data, inserted_time)
|
||||||
VALUES ($1, $2, $3, $4, $5)
|
VALUES ($1, $2, $3, $4, $5, $6)
|
||||||
ON CONFLICT (user_mxid, conversation_id, message_id) DO NOTHING
|
ON CONFLICT (user_mxid, conversation_id, message_id) DO NOTHING
|
||||||
`, hsm.UserID, hsm.ConversationID, hsm.MessageID, hsm.Timestamp, hsm.Data)
|
`, hsm.UserID, hsm.ConversationID, hsm.MessageID, hsm.Timestamp, hsm.Data, time.Now())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hsm.log.Warnfln("Failed to insert history sync message %s/%s: %v", hsm.ConversationID, hsm.Timestamp, err)
|
hsm.log.Warnfln("Failed to insert history sync message %s/%s: %v", hsm.ConversationID, hsm.Timestamp, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -178,16 +178,26 @@ func (msg *Message) Scan(row Scannable) *Message {
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *Message) Insert() {
|
func (msg *Message) Insert(txn *sql.Tx) {
|
||||||
var sender interface{} = msg.Sender
|
var sender interface{} = msg.Sender
|
||||||
// Slightly hacky hack to allow inserting empty senders (used for post-backfill dummy events)
|
// Slightly hacky hack to allow inserting empty senders (used for post-backfill dummy events)
|
||||||
if msg.Sender.IsEmpty() {
|
if msg.Sender.IsEmpty() {
|
||||||
sender = ""
|
sender = ""
|
||||||
}
|
}
|
||||||
_, err := msg.db.Exec(`INSERT INTO message
|
query := `
|
||||||
|
INSERT INTO message
|
||||||
(chat_jid, chat_receiver, jid, mxid, sender, timestamp, sent, type, error, broadcast_list_jid)
|
(chat_jid, chat_receiver, jid, mxid, sender, timestamp, sent, type, error, broadcast_list_jid)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||||
msg.Chat.JID, msg.Chat.Receiver, msg.JID, msg.MXID, sender, msg.Timestamp.Unix(), msg.Sent, msg.Type, msg.Error, msg.BroadcastListJID)
|
`
|
||||||
|
args := []interface{}{
|
||||||
|
msg.Chat.JID, msg.Chat.Receiver, msg.JID, msg.MXID, sender, msg.Timestamp.Unix(), msg.Sent, msg.Type, msg.Error, msg.BroadcastListJID,
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if txn != nil {
|
||||||
|
_, err = txn.Exec(query, args...)
|
||||||
|
} else {
|
||||||
|
_, err = msg.db.Exec(query, args...)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg.log.Warnfln("Failed to insert %s@%s: %v", msg.Chat, msg.JID, err)
|
msg.log.Warnfln("Failed to insert %s@%s: %v", msg.Chat, msg.JID, err)
|
||||||
}
|
}
|
||||||
|
@ -202,11 +212,18 @@ func (msg *Message) MarkSent(ts time.Time) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *Message) UpdateMXID(mxid id.EventID, newType MessageType, newError MessageErrorType) {
|
func (msg *Message) UpdateMXID(txn *sql.Tx, mxid id.EventID, newType MessageType, newError MessageErrorType) {
|
||||||
msg.MXID = mxid
|
msg.MXID = mxid
|
||||||
msg.Type = newType
|
msg.Type = newType
|
||||||
msg.Error = newError
|
msg.Error = newError
|
||||||
_, err := msg.db.Exec("UPDATE message SET mxid=$1, type=$2, error=$3 WHERE chat_jid=$4 AND chat_receiver=$5 AND jid=$6", mxid, newType, newError, msg.Chat.JID, msg.Chat.Receiver, msg.JID)
|
query := "UPDATE message SET mxid=$1, type=$2, error=$3 WHERE chat_jid=$4 AND chat_receiver=$5 AND jid=$6"
|
||||||
|
args := []interface{}{mxid, newType, newError, msg.Chat.JID, msg.Chat.Receiver, msg.JID}
|
||||||
|
var err error
|
||||||
|
if txn != nil {
|
||||||
|
_, err = txn.Exec(query, args...)
|
||||||
|
} else {
|
||||||
|
_, err = msg.db.Exec(query, args...)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg.log.Warnfln("Failed to update %s@%s: %v", msg.Chat, msg.JID, err)
|
msg.log.Warnfln("Failed to update %s@%s: %v", msg.Chat, msg.JID, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,9 +191,21 @@ func (portal *Portal) Insert() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) Update() {
|
func (portal *Portal) Update(txn *sql.Tx) {
|
||||||
_, err := portal.db.Exec("UPDATE portal SET mxid=$1, name=$2, topic=$3, avatar=$4, avatar_url=$5, encrypted=$6, first_event_id=$7, next_batch_id=$8, relay_user_id=$9, expiration_time=$10 WHERE jid=$11 AND receiver=$12",
|
query := `
|
||||||
portal.mxidPtr(), portal.Name, portal.Topic, portal.Avatar, portal.AvatarURL.String(), portal.Encrypted, portal.FirstEventID.String(), portal.NextBatchID.String(), portal.relayUserPtr(), portal.ExpirationTime, portal.Key.JID, portal.Key.Receiver)
|
UPDATE portal
|
||||||
|
SET mxid=$1, name=$2, topic=$3, avatar=$4, avatar_url=$5, encrypted=$6, first_event_id=$7, next_batch_id=$8, relay_user_id=$9, expiration_time=$10
|
||||||
|
WHERE jid=$11 AND receiver=$12
|
||||||
|
`
|
||||||
|
args := []interface{}{
|
||||||
|
portal.mxidPtr(), portal.Name, portal.Topic, portal.Avatar, portal.AvatarURL.String(), portal.Encrypted, portal.FirstEventID.String(), portal.NextBatchID.String(), portal.relayUserPtr(), portal.ExpirationTime, portal.Key.JID, portal.Key.Receiver,
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if txn != nil {
|
||||||
|
_, err = txn.Exec(query, args...)
|
||||||
|
} else {
|
||||||
|
_, err = portal.db.Exec(query, args...)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
portal.log.Warnfln("Failed to update %s: %v", portal.Key, err)
|
portal.log.Warnfln("Failed to update %s: %v", portal.Key, err)
|
||||||
}
|
}
|
||||||
|
|
34
database/upgrades/2022-05-12-backfillqueue-dispatch-time.go
Normal file
34
database/upgrades/2022-05-12-backfillqueue-dispatch-time.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package upgrades
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
upgrades[44] = upgrade{"Add dispatch time to backfill queue", func(tx *sql.Tx, ctx context) error {
|
||||||
|
// First, add dispatch_time TIMESTAMP column
|
||||||
|
_, err := tx.Exec(`
|
||||||
|
ALTER TABLE backfill_queue
|
||||||
|
ADD COLUMN dispatch_time TIMESTAMP
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// For all previous jobs, set dispatch time to the completed time.
|
||||||
|
_, err = tx.Exec(`
|
||||||
|
UPDATE backfill_queue
|
||||||
|
SET dispatch_time=completed_at
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove time_end from the backfill queue
|
||||||
|
_, err = tx.Exec(`
|
||||||
|
ALTER TABLE backfill_queue
|
||||||
|
DROP COLUMN time_end
|
||||||
|
`)
|
||||||
|
return err
|
||||||
|
}}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package upgrades
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
upgrades[45] = upgrade{"Add inserted time to history sync message", func(tx *sql.Tx, ctx context) error {
|
||||||
|
// Add the inserted time TIMESTAMP column to history_sync_message
|
||||||
|
_, err := tx.Exec(`
|
||||||
|
ALTER TABLE history_sync_message
|
||||||
|
ADD COLUMN inserted_time TIMESTAMP
|
||||||
|
`)
|
||||||
|
return err
|
||||||
|
}}
|
||||||
|
}
|
|
@ -40,7 +40,7 @@ type upgrade struct {
|
||||||
fn upgradeFunc
|
fn upgradeFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
const NumberOfUpgrades = 44
|
const NumberOfUpgrades = 46
|
||||||
|
|
||||||
var upgrades [NumberOfUpgrades]upgrade
|
var upgrades [NumberOfUpgrades]upgrade
|
||||||
|
|
||||||
|
|
143
historysync.go
143
historysync.go
|
@ -17,6 +17,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -50,27 +51,22 @@ func (user *User) handleHistorySyncsLoop() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
reCheckQueue := make(chan bool, 1)
|
|
||||||
// Start the backfill queue.
|
// Start the backfill queue.
|
||||||
user.BackfillQueue = &BackfillQueue{
|
user.BackfillQueue = &BackfillQueue{
|
||||||
BackfillQuery: user.bridge.DB.Backfill,
|
BackfillQuery: user.bridge.DB.Backfill,
|
||||||
ImmediateBackfillRequests: make(chan *database.Backfill, 1),
|
reCheckChannels: []chan bool{},
|
||||||
DeferredBackfillRequests: make(chan *database.Backfill, 1),
|
log: user.log.Sub("BackfillQueue"),
|
||||||
ReCheckQueue: make(chan bool, 1),
|
|
||||||
log: user.log.Sub("BackfillQueue"),
|
|
||||||
}
|
}
|
||||||
reCheckQueue = user.BackfillQueue.ReCheckQueue
|
|
||||||
|
|
||||||
// Immediate backfills can be done in parallel
|
// Immediate backfills can be done in parallel
|
||||||
for i := 0; i < user.bridge.Config.Bridge.HistorySync.Immediate.WorkerCount; i++ {
|
for i := 0; i < user.bridge.Config.Bridge.HistorySync.Immediate.WorkerCount; i++ {
|
||||||
go user.handleBackfillRequestsLoop(user.BackfillQueue.ImmediateBackfillRequests)
|
go user.HandleBackfillRequestsLoop([]database.BackfillType{database.BackfillImmediate, database.BackfillForward})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deferred backfills should be handled synchronously so as not to
|
// Deferred backfills should be handled synchronously so as not to
|
||||||
// overload the homeserver. Users can configure their backfill stages
|
// overload the homeserver. Users can configure their backfill stages
|
||||||
// to be more or less aggressive with backfilling at this stage.
|
// to be more or less aggressive with backfilling at this stage.
|
||||||
go user.handleBackfillRequestsLoop(user.BackfillQueue.DeferredBackfillRequests)
|
go user.HandleBackfillRequestsLoop([]database.BackfillType{database.BackfillDeferred})
|
||||||
go user.BackfillQueue.RunLoop(user)
|
|
||||||
|
|
||||||
if user.bridge.Config.Bridge.HistorySync.MediaRequests.AutoRequestMedia &&
|
if user.bridge.Config.Bridge.HistorySync.MediaRequests.AutoRequestMedia &&
|
||||||
user.bridge.Config.Bridge.HistorySync.MediaRequests.RequestMethod == config.MediaRequestMethodLocalTime {
|
user.bridge.Config.Bridge.HistorySync.MediaRequests.RequestMethod == config.MediaRequestMethodLocalTime {
|
||||||
|
@ -80,7 +76,7 @@ func (user *User) handleHistorySyncsLoop() {
|
||||||
// Always save the history syncs for the user. If they want to enable
|
// Always save the history syncs for the user. If they want to enable
|
||||||
// backfilling in the future, we will have it in the database.
|
// backfilling in the future, we will have it in the database.
|
||||||
for evt := range user.historySyncs {
|
for evt := range user.historySyncs {
|
||||||
user.handleHistorySync(reCheckQueue, evt.Data)
|
user.handleHistorySync(user.BackfillQueue, evt.Data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,36 +126,6 @@ func (user *User) dailyMediaRequestLoop() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) handleBackfillRequestsLoop(backfillRequests chan *database.Backfill) {
|
|
||||||
for req := range backfillRequests {
|
|
||||||
user.log.Infofln("Handling backfill request %s", req)
|
|
||||||
conv := user.bridge.DB.HistorySync.GetConversation(user.MXID, req.Portal)
|
|
||||||
if conv == nil {
|
|
||||||
user.log.Debugfln("Could not find history sync conversation data for %s", req.Portal.String())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
portal := user.GetPortalByJID(conv.PortalKey.JID)
|
|
||||||
|
|
||||||
// Update the client store with basic chat settings.
|
|
||||||
if conv.MuteEndTime.After(time.Now()) {
|
|
||||||
user.Client.Store.ChatSettings.PutMutedUntil(conv.PortalKey.JID, conv.MuteEndTime)
|
|
||||||
}
|
|
||||||
if conv.Archived {
|
|
||||||
user.Client.Store.ChatSettings.PutArchived(conv.PortalKey.JID, true)
|
|
||||||
}
|
|
||||||
if conv.Pinned > 0 {
|
|
||||||
user.Client.Store.ChatSettings.PutPinned(conv.PortalKey.JID, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
if conv.EphemeralExpiration != nil && portal.ExpirationTime != *conv.EphemeralExpiration {
|
|
||||||
portal.ExpirationTime = *conv.EphemeralExpiration
|
|
||||||
portal.Update()
|
|
||||||
}
|
|
||||||
|
|
||||||
user.backfillInChunks(req, conv, portal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (user *User) backfillInChunks(req *database.Backfill, conv *database.HistorySyncConversation, portal *Portal) {
|
func (user *User) backfillInChunks(req *database.Backfill, conv *database.HistorySyncConversation, portal *Portal) {
|
||||||
portal.backfillLock.Lock()
|
portal.backfillLock.Lock()
|
||||||
defer portal.backfillLock.Unlock()
|
defer portal.backfillLock.Unlock()
|
||||||
|
@ -169,6 +135,7 @@ func (user *User) backfillInChunks(req *database.Backfill, conv *database.Histor
|
||||||
}
|
}
|
||||||
|
|
||||||
var forwardPrevID id.EventID
|
var forwardPrevID id.EventID
|
||||||
|
var timeEnd *time.Time
|
||||||
if req.BackfillType == database.BackfillForward {
|
if req.BackfillType == database.BackfillForward {
|
||||||
// TODO this overrides the TimeStart set when enqueuing the backfill
|
// TODO this overrides the TimeStart set when enqueuing the backfill
|
||||||
// maybe the enqueue should instead include the prev event ID
|
// maybe the enqueue should instead include the prev event ID
|
||||||
|
@ -178,13 +145,13 @@ func (user *User) backfillInChunks(req *database.Backfill, conv *database.Histor
|
||||||
req.TimeStart = &start
|
req.TimeStart = &start
|
||||||
} else {
|
} else {
|
||||||
firstMessage := portal.bridge.DB.Message.GetFirstInChat(portal.Key)
|
firstMessage := portal.bridge.DB.Message.GetFirstInChat(portal.Key)
|
||||||
if firstMessage != nil && (req.TimeEnd == nil || firstMessage.Timestamp.Before(*req.TimeEnd)) {
|
if firstMessage != nil {
|
||||||
end := firstMessage.Timestamp.Add(-1 * time.Second)
|
end := firstMessage.Timestamp.Add(-1 * time.Second)
|
||||||
req.TimeEnd = &end
|
timeEnd = &end
|
||||||
user.log.Debugfln("Limiting backfill to end at %v", end)
|
user.log.Debugfln("Limiting backfill to end at %v", end)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
allMsgs := user.bridge.DB.HistorySync.GetMessagesBetween(user.MXID, conv.ConversationID, req.TimeStart, req.TimeEnd, req.MaxTotalEvents)
|
allMsgs := user.bridge.DB.HistorySync.GetMessagesBetween(user.MXID, conv.ConversationID, req.TimeStart, timeEnd, req.MaxTotalEvents)
|
||||||
|
|
||||||
sendDisappearedNotice := false
|
sendDisappearedNotice := false
|
||||||
// If expired messages are on, and a notice has not been sent to this chat
|
// If expired messages are on, and a notice has not been sent to this chat
|
||||||
|
@ -230,7 +197,7 @@ func (user *User) backfillInChunks(req *database.Backfill, conv *database.Histor
|
||||||
msg.Timestamp = conv.LastMessageTimestamp
|
msg.Timestamp = conv.LastMessageTimestamp
|
||||||
msg.Sent = true
|
msg.Sent = true
|
||||||
msg.Type = database.MsgFake
|
msg.Type = database.MsgFake
|
||||||
msg.Insert()
|
msg.Insert(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,7 +257,7 @@ func (user *User) shouldCreatePortalForHistorySync(conv *database.HistorySyncCon
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) handleHistorySync(reCheckQueue chan bool, evt *waProto.HistorySync) {
|
func (user *User) handleHistorySync(backfillQueue *BackfillQueue, evt *waProto.HistorySync) {
|
||||||
if evt == nil || evt.SyncType == nil || evt.GetSyncType() == waProto.HistorySync_INITIAL_STATUS_V3 || evt.GetSyncType() == waProto.HistorySync_PUSH_NAME {
|
if evt == nil || evt.SyncType == nil || evt.GetSyncType() == waProto.HistorySync_INITIAL_STATUS_V3 || evt.GetSyncType() == waProto.HistorySync_PUSH_NAME {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -381,7 +348,7 @@ func (user *User) handleHistorySync(reCheckQueue chan bool, evt *waProto.History
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tell the queue to check for new backfill requests.
|
// Tell the queue to check for new backfill requests.
|
||||||
reCheckQueue <- true
|
backfillQueue.ReCheck()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -397,7 +364,7 @@ func getConversationTimestamp(conv *waProto.Conversation) uint64 {
|
||||||
func (user *User) EnqueueImmedateBackfills(portals []*Portal) {
|
func (user *User) EnqueueImmedateBackfills(portals []*Portal) {
|
||||||
for priority, portal := range portals {
|
for priority, portal := range portals {
|
||||||
maxMessages := user.bridge.Config.Bridge.HistorySync.Immediate.MaxEvents
|
maxMessages := user.bridge.Config.Bridge.HistorySync.Immediate.MaxEvents
|
||||||
initialBackfill := user.bridge.DB.Backfill.NewWithValues(user.MXID, database.BackfillImmediate, priority, &portal.Key, nil, nil, maxMessages, maxMessages, 0)
|
initialBackfill := user.bridge.DB.Backfill.NewWithValues(user.MXID, database.BackfillImmediate, priority, &portal.Key, nil, maxMessages, maxMessages, 0)
|
||||||
initialBackfill.Insert()
|
initialBackfill.Insert()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -412,7 +379,7 @@ func (user *User) EnqueueDeferredBackfills(portals []*Portal) {
|
||||||
startDate = &startDaysAgo
|
startDate = &startDaysAgo
|
||||||
}
|
}
|
||||||
backfillMessages := user.bridge.DB.Backfill.NewWithValues(
|
backfillMessages := user.bridge.DB.Backfill.NewWithValues(
|
||||||
user.MXID, database.BackfillDeferred, stageIdx*numPortals+portalIdx, &portal.Key, startDate, nil, backfillStage.MaxBatchEvents, -1, backfillStage.BatchDelay)
|
user.MXID, database.BackfillDeferred, stageIdx*numPortals+portalIdx, &portal.Key, startDate, backfillStage.MaxBatchEvents, -1, backfillStage.BatchDelay)
|
||||||
backfillMessages.Insert()
|
backfillMessages.Insert()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -425,7 +392,7 @@ func (user *User) EnqueueForwardBackfills(portals []*Portal) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
backfill := user.bridge.DB.Backfill.NewWithValues(
|
backfill := user.bridge.DB.Backfill.NewWithValues(
|
||||||
user.MXID, database.BackfillForward, priority, &portal.Key, &lastMsg.Timestamp, nil, -1, -1, 0)
|
user.MXID, database.BackfillForward, priority, &portal.Key, &lastMsg.Timestamp, -1, -1, 0)
|
||||||
backfill.Insert()
|
backfill.Insert()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -558,9 +525,24 @@ func (portal *Portal) backfill(source *User, messages []*waProto.WebMessageInfo,
|
||||||
portal.log.Errorln("Error batch sending messages:", err)
|
portal.log.Errorln("Error batch sending messages:", err)
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
portal.finishBatch(resp.EventIDs, infos)
|
txn, err := portal.bridge.DB.Begin()
|
||||||
portal.NextBatchID = resp.NextBatchID
|
if err != nil {
|
||||||
portal.Update()
|
portal.log.Errorln("Failed to start transaction to save batch messages:", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the following block in the transaction
|
||||||
|
{
|
||||||
|
portal.finishBatch(txn, resp.EventIDs, infos)
|
||||||
|
portal.NextBatchID = resp.NextBatchID
|
||||||
|
portal.Update(txn)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = txn.Commit()
|
||||||
|
if err != nil {
|
||||||
|
portal.log.Errorln("Failed to commit transaction to save batch messages:", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if portal.bridge.Config.Bridge.HistorySync.MediaRequests.AutoRequestMedia {
|
if portal.bridge.Config.Bridge.HistorySync.MediaRequests.AutoRequestMedia {
|
||||||
go portal.requestMediaRetries(source, resp.EventIDs, infos)
|
go portal.requestMediaRetries(source, resp.EventIDs, infos)
|
||||||
}
|
}
|
||||||
|
@ -651,48 +633,27 @@ func (portal *Portal) wrapBatchEvent(info *types.MessageInfo, intent *appservice
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) finishBatch(eventIDs []id.EventID, infos []*wrappedInfo) {
|
func (portal *Portal) finishBatch(txn *sql.Tx, eventIDs []id.EventID, infos []*wrappedInfo) {
|
||||||
if len(eventIDs) != len(infos) {
|
for i, info := range infos {
|
||||||
portal.log.Errorfln("Length of event IDs (%d) and message infos (%d) doesn't match! Using slow path for mapping event IDs", len(eventIDs), len(infos))
|
if info == nil {
|
||||||
infoMap := make(map[types.MessageID]*wrappedInfo, len(infos))
|
continue
|
||||||
for _, info := range infos {
|
|
||||||
infoMap[info.ID] = info
|
|
||||||
}
|
}
|
||||||
for _, eventID := range eventIDs {
|
|
||||||
if evt, err := portal.MainIntent().GetEvent(portal.MXID, eventID); err != nil {
|
eventID := eventIDs[i]
|
||||||
portal.log.Warnfln("Failed to get event %s to register it in the database: %v", eventID, err)
|
portal.markHandled(txn, nil, info.MessageInfo, eventID, true, false, info.Type, info.Error)
|
||||||
} else if msgID, ok := evt.Content.Raw[backfillIDField].(string); !ok {
|
|
||||||
portal.log.Warnfln("Event %s doesn't include the WhatsApp message ID", eventID)
|
if info.ExpiresIn > 0 {
|
||||||
} else if info, ok := infoMap[types.MessageID(msgID)]; !ok {
|
if info.ExpirationStart > 0 {
|
||||||
portal.log.Warnfln("Didn't find info of message %s (event %s) to register it in the database", msgID, eventID)
|
remainingSeconds := time.Unix(int64(info.ExpirationStart), 0).Add(time.Duration(info.ExpiresIn) * time.Second).Sub(time.Now()).Seconds()
|
||||||
|
portal.log.Debugfln("Disappearing history sync message: expires in %d, started at %d, remaining %d", info.ExpiresIn, info.ExpirationStart, int(remainingSeconds))
|
||||||
|
portal.MarkDisappearing(eventID, uint32(remainingSeconds), true)
|
||||||
} else {
|
} else {
|
||||||
portal.finishBatchEvt(info, eventID)
|
portal.log.Debugfln("Disappearing history sync message: expires in %d (not started)", info.ExpiresIn)
|
||||||
|
portal.MarkDisappearing(eventID, info.ExpiresIn, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
for i := 0; i < len(infos); i++ {
|
|
||||||
portal.finishBatchEvt(infos[i], eventIDs[i])
|
|
||||||
}
|
|
||||||
portal.log.Infofln("Successfully sent %d events", len(eventIDs))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (portal *Portal) finishBatchEvt(info *wrappedInfo, eventID id.EventID) {
|
|
||||||
if info == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
portal.markHandled(nil, info.MessageInfo, eventID, true, false, info.Type, info.Error)
|
|
||||||
if info.ExpiresIn > 0 {
|
|
||||||
if info.ExpirationStart > 0 {
|
|
||||||
remainingSeconds := time.Unix(int64(info.ExpirationStart), 0).Add(time.Duration(info.ExpiresIn) * time.Second).Sub(time.Now()).Seconds()
|
|
||||||
portal.log.Debugfln("Disappearing history sync message: expires in %d, started at %d, remaining %d", info.ExpiresIn, info.ExpirationStart, int(remainingSeconds))
|
|
||||||
portal.MarkDisappearing(eventID, uint32(remainingSeconds), true)
|
|
||||||
} else {
|
|
||||||
portal.log.Debugfln("Disappearing history sync message: expires in %d (not started)", info.ExpiresIn)
|
|
||||||
portal.MarkDisappearing(eventID, info.ExpiresIn, false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
portal.log.Infofln("Successfully sent %d events", len(eventIDs))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) sendPostBackfillDummy(lastTimestamp time.Time, insertionEventId id.EventID) {
|
func (portal *Portal) sendPostBackfillDummy(lastTimestamp time.Time, insertionEventId id.EventID) {
|
||||||
|
@ -714,7 +675,7 @@ func (portal *Portal) sendPostBackfillDummy(lastTimestamp time.Time, insertionEv
|
||||||
msg.Timestamp = lastTimestamp.Add(1 * time.Second)
|
msg.Timestamp = lastTimestamp.Add(1 * time.Second)
|
||||||
msg.Sent = true
|
msg.Sent = true
|
||||||
msg.Type = database.MsgFake
|
msg.Type = database.MsgFake
|
||||||
msg.Insert()
|
msg.Insert(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
|
@ -74,7 +74,7 @@ func (mx *MatrixHandler) HandleEncryption(evt *event.Event) {
|
||||||
if portal != nil && !portal.Encrypted {
|
if portal != nil && !portal.Encrypted {
|
||||||
mx.log.Debugfln("%s enabled encryption in %s", evt.Sender, evt.RoomID)
|
mx.log.Debugfln("%s enabled encryption in %s", evt.Sender, evt.RoomID)
|
||||||
portal.Encrypted = true
|
portal.Encrypted = true
|
||||||
portal.Update()
|
portal.Update(nil)
|
||||||
if portal.IsPrivateChat() {
|
if portal.IsPrivateChat() {
|
||||||
err := mx.as.BotIntent().EnsureJoined(portal.MXID, appservice.EnsureJoinedParams{BotOverride: portal.MainIntent().Client})
|
err := mx.as.BotIntent().EnsureJoined(portal.MXID, appservice.EnsureJoinedParams{BotOverride: portal.MainIntent().Client})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -211,7 +211,7 @@ func (mx *MatrixHandler) createPrivatePortalFromInvite(roomID id.RoomID, inviter
|
||||||
mx.as.StateStore.SetMembership(roomID, mx.bridge.Bot.UserID, event.MembershipJoin)
|
mx.as.StateStore.SetMembership(roomID, mx.bridge.Bot.UserID, event.MembershipJoin)
|
||||||
portal.Encrypted = true
|
portal.Encrypted = true
|
||||||
}
|
}
|
||||||
portal.Update()
|
portal.Update(nil)
|
||||||
portal.UpdateBridgeInfo()
|
portal.UpdateBridgeInfo()
|
||||||
_, _ = intent.SendNotice(roomID, "Private chat portal created")
|
_, _ = intent.SendNotice(roomID, "Private chat portal created")
|
||||||
}
|
}
|
||||||
|
|
45
portal.go
45
portal.go
|
@ -19,6 +19,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -482,7 +483,7 @@ func (portal *Portal) convertMessage(intent *appservice.IntentAPI, source *User,
|
||||||
return portal.convertGroupInviteMessage(intent, info, waMsg.GetGroupInviteMessage())
|
return portal.convertGroupInviteMessage(intent, info, waMsg.GetGroupInviteMessage())
|
||||||
case waMsg.ProtocolMessage != nil && waMsg.ProtocolMessage.GetType() == waProto.ProtocolMessage_EPHEMERAL_SETTING:
|
case waMsg.ProtocolMessage != nil && waMsg.ProtocolMessage.GetType() == waProto.ProtocolMessage_EPHEMERAL_SETTING:
|
||||||
portal.ExpirationTime = waMsg.ProtocolMessage.GetEphemeralExpiration()
|
portal.ExpirationTime = waMsg.ProtocolMessage.GetEphemeralExpiration()
|
||||||
portal.Update()
|
portal.Update(nil)
|
||||||
return &ConvertedMessage{
|
return &ConvertedMessage{
|
||||||
Intent: intent,
|
Intent: intent,
|
||||||
Type: event.EventMessage,
|
Type: event.EventMessage,
|
||||||
|
@ -501,7 +502,7 @@ func (portal *Portal) UpdateGroupDisappearingMessages(sender *types.JID, timesta
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
portal.ExpirationTime = timer
|
portal.ExpirationTime = timer
|
||||||
portal.Update()
|
portal.Update(nil)
|
||||||
intent := portal.MainIntent()
|
intent := portal.MainIntent()
|
||||||
if sender != nil {
|
if sender != nil {
|
||||||
intent = portal.bridge.GetPuppetByJID(sender.ToNonAD()).IntentFor(portal)
|
intent = portal.bridge.GetPuppetByJID(sender.ToNonAD()).IntentFor(portal)
|
||||||
|
@ -685,7 +686,7 @@ func (portal *Portal) handleMessage(source *User, evt *events.Message) {
|
||||||
_, _ = portal.MainIntent().RedactEvent(portal.MXID, existingMsg.MXID, mautrix.ReqRedact{
|
_, _ = portal.MainIntent().RedactEvent(portal.MXID, existingMsg.MXID, mautrix.ReqRedact{
|
||||||
Reason: "The undecryptable message was actually the deletion of another message",
|
Reason: "The undecryptable message was actually the deletion of another message",
|
||||||
})
|
})
|
||||||
existingMsg.UpdateMXID("net.maunium.whatsapp.fake::"+existingMsg.MXID, database.MsgFake, database.MsgNoError)
|
existingMsg.UpdateMXID(nil, "net.maunium.whatsapp.fake::"+existingMsg.MXID, database.MsgFake, database.MsgNoError)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
portal.log.Warnfln("Unhandled message: %+v (%s)", evt.Info, msgType)
|
portal.log.Warnfln("Unhandled message: %+v (%s)", evt.Info, msgType)
|
||||||
|
@ -693,7 +694,7 @@ func (portal *Portal) handleMessage(source *User, evt *events.Message) {
|
||||||
_, _ = portal.MainIntent().RedactEvent(portal.MXID, existingMsg.MXID, mautrix.ReqRedact{
|
_, _ = portal.MainIntent().RedactEvent(portal.MXID, existingMsg.MXID, mautrix.ReqRedact{
|
||||||
Reason: "The undecryptable message contained an unsupported message type",
|
Reason: "The undecryptable message contained an unsupported message type",
|
||||||
})
|
})
|
||||||
existingMsg.UpdateMXID("net.maunium.whatsapp.fake::"+existingMsg.MXID, database.MsgFake, database.MsgNoError)
|
existingMsg.UpdateMXID(nil, "net.maunium.whatsapp.fake::"+existingMsg.MXID, database.MsgFake, database.MsgNoError)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -711,7 +712,7 @@ func (portal *Portal) isRecentlyHandled(id types.MessageID, error database.Messa
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) markHandled(msg *database.Message, info *types.MessageInfo, mxid id.EventID, isSent, recent bool, msgType database.MessageType, error database.MessageErrorType) *database.Message {
|
func (portal *Portal) markHandled(txn *sql.Tx, msg *database.Message, info *types.MessageInfo, mxid id.EventID, isSent, recent bool, msgType database.MessageType, errType database.MessageErrorType) *database.Message {
|
||||||
if msg == nil {
|
if msg == nil {
|
||||||
msg = portal.bridge.DB.Message.New()
|
msg = portal.bridge.DB.Message.New()
|
||||||
msg.Chat = portal.Key
|
msg.Chat = portal.Key
|
||||||
|
@ -721,13 +722,13 @@ func (portal *Portal) markHandled(msg *database.Message, info *types.MessageInfo
|
||||||
msg.Sender = info.Sender
|
msg.Sender = info.Sender
|
||||||
msg.Sent = isSent
|
msg.Sent = isSent
|
||||||
msg.Type = msgType
|
msg.Type = msgType
|
||||||
msg.Error = error
|
msg.Error = errType
|
||||||
if info.IsIncomingBroadcast() {
|
if info.IsIncomingBroadcast() {
|
||||||
msg.BroadcastListJID = info.Chat
|
msg.BroadcastListJID = info.Chat
|
||||||
}
|
}
|
||||||
msg.Insert()
|
msg.Insert(txn)
|
||||||
} else {
|
} else {
|
||||||
msg.UpdateMXID(mxid, msgType, error)
|
msg.UpdateMXID(txn, mxid, msgType, errType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if recent {
|
if recent {
|
||||||
|
@ -735,7 +736,7 @@ func (portal *Portal) markHandled(msg *database.Message, info *types.MessageInfo
|
||||||
index := portal.recentlyHandledIndex
|
index := portal.recentlyHandledIndex
|
||||||
portal.recentlyHandledIndex = (portal.recentlyHandledIndex + 1) % recentlyHandledLength
|
portal.recentlyHandledIndex = (portal.recentlyHandledIndex + 1) % recentlyHandledLength
|
||||||
portal.recentlyHandledLock.Unlock()
|
portal.recentlyHandledLock.Unlock()
|
||||||
portal.recentlyHandled[index] = recentlyHandledWrapper{msg.JID, error}
|
portal.recentlyHandled[index] = recentlyHandledWrapper{msg.JID, errType}
|
||||||
}
|
}
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
@ -756,13 +757,13 @@ func (portal *Portal) getMessageIntent(user *User, info *types.MessageInfo) *app
|
||||||
return portal.getMessagePuppet(user, info).IntentFor(portal)
|
return portal.getMessagePuppet(user, info).IntentFor(portal)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) finishHandling(existing *database.Message, message *types.MessageInfo, mxid id.EventID, msgType database.MessageType, error database.MessageErrorType) {
|
func (portal *Portal) finishHandling(existing *database.Message, message *types.MessageInfo, mxid id.EventID, msgType database.MessageType, errType database.MessageErrorType) {
|
||||||
portal.markHandled(existing, message, mxid, true, true, msgType, error)
|
portal.markHandled(nil, existing, message, mxid, true, true, msgType, errType)
|
||||||
portal.sendDeliveryReceipt(mxid)
|
portal.sendDeliveryReceipt(mxid)
|
||||||
var suffix string
|
var suffix string
|
||||||
if error == database.MsgErrDecryptionFailed {
|
if errType == database.MsgErrDecryptionFailed {
|
||||||
suffix = "(undecryptable message error notice)"
|
suffix = "(undecryptable message error notice)"
|
||||||
} else if error == database.MsgErrMediaNotFound {
|
} else if errType == database.MsgErrMediaNotFound {
|
||||||
suffix = "(media not found notice)"
|
suffix = "(media not found notice)"
|
||||||
}
|
}
|
||||||
portal.log.Debugfln("Handled message %s (%s) -> %s %s", message.ID, msgType, mxid, suffix)
|
portal.log.Debugfln("Handled message %s (%s) -> %s %s", message.ID, msgType, mxid, suffix)
|
||||||
|
@ -1028,7 +1029,7 @@ func (portal *Portal) UpdateMatrixRoom(user *User, groupInfo *types.GroupInfo) b
|
||||||
update = portal.UpdateAvatar(user, types.EmptyJID, false) || update
|
update = portal.UpdateAvatar(user, types.EmptyJID, false) || update
|
||||||
}
|
}
|
||||||
if update {
|
if update {
|
||||||
portal.Update()
|
portal.Update(nil)
|
||||||
portal.UpdateBridgeInfo()
|
portal.UpdateBridgeInfo()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -1320,7 +1321,7 @@ func (portal *Portal) CreateMatrixRoom(user *User, groupInfo *types.GroupInfo, i
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
portal.MXID = resp.RoomID
|
portal.MXID = resp.RoomID
|
||||||
portal.Update()
|
portal.Update(nil)
|
||||||
portal.bridge.portalsLock.Lock()
|
portal.bridge.portalsLock.Lock()
|
||||||
portal.bridge.portalsByMXID[portal.MXID] = portal
|
portal.bridge.portalsByMXID[portal.MXID] = portal
|
||||||
portal.bridge.portalsLock.Unlock()
|
portal.bridge.portalsLock.Unlock()
|
||||||
|
@ -1338,7 +1339,7 @@ func (portal *Portal) CreateMatrixRoom(user *User, groupInfo *types.GroupInfo, i
|
||||||
if groupInfo != nil {
|
if groupInfo != nil {
|
||||||
if groupInfo.IsEphemeral {
|
if groupInfo.IsEphemeral {
|
||||||
portal.ExpirationTime = groupInfo.DisappearingTimer
|
portal.ExpirationTime = groupInfo.DisappearingTimer
|
||||||
portal.Update()
|
portal.Update(nil)
|
||||||
}
|
}
|
||||||
portal.SyncParticipants(user, groupInfo)
|
portal.SyncParticipants(user, groupInfo)
|
||||||
if groupInfo.IsAnnounce {
|
if groupInfo.IsAnnounce {
|
||||||
|
@ -1369,14 +1370,14 @@ func (portal *Portal) CreateMatrixRoom(user *User, groupInfo *types.GroupInfo, i
|
||||||
portal.log.Errorln("Failed to send dummy event to mark portal creation:", err)
|
portal.log.Errorln("Failed to send dummy event to mark portal creation:", err)
|
||||||
} else {
|
} else {
|
||||||
portal.FirstEventID = firstEventResp.EventID
|
portal.FirstEventID = firstEventResp.EventID
|
||||||
portal.Update()
|
portal.Update(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.bridge.Config.Bridge.HistorySync.Backfill && backfill {
|
if user.bridge.Config.Bridge.HistorySync.Backfill && backfill {
|
||||||
portals := []*Portal{portal}
|
portals := []*Portal{portal}
|
||||||
user.EnqueueImmedateBackfills(portals)
|
user.EnqueueImmedateBackfills(portals)
|
||||||
user.EnqueueDeferredBackfills(portals)
|
user.EnqueueDeferredBackfills(portals)
|
||||||
user.BackfillQueue.ReCheckQueue <- true
|
user.BackfillQueue.ReCheck()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -2367,7 +2368,7 @@ func (portal *Portal) handleMediaRetry(retry *events.MediaRetry, source *User) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
portal.log.Debugfln("Successfully edited %s -> %s after retry notification for %s", msg.MXID, resp.EventID, retry.MessageID)
|
portal.log.Debugfln("Successfully edited %s -> %s after retry notification for %s", msg.MXID, resp.EventID, retry.MessageID)
|
||||||
msg.UpdateMXID(resp.EventID, database.MsgNormal, database.MsgNoError)
|
msg.UpdateMXID(nil, resp.EventID, database.MsgNormal, database.MsgNoError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) requestMediaRetry(user *User, eventID id.EventID, mediaKey []byte) (bool, error) {
|
func (portal *Portal) requestMediaRetry(user *User, eventID id.EventID, mediaKey []byte) (bool, error) {
|
||||||
|
@ -2844,7 +2845,7 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event) {
|
||||||
}
|
}
|
||||||
portal.MarkDisappearing(evt.ID, portal.ExpirationTime, true)
|
portal.MarkDisappearing(evt.ID, portal.ExpirationTime, true)
|
||||||
info := portal.generateMessageInfo(sender)
|
info := portal.generateMessageInfo(sender)
|
||||||
dbMsg := portal.markHandled(nil, info, evt.ID, false, true, database.MsgNormal, database.MsgNoError)
|
dbMsg := portal.markHandled(nil, nil, info, evt.ID, false, true, database.MsgNormal, database.MsgNoError)
|
||||||
portal.log.Debugln("Sending event", evt.ID, "to WhatsApp", info.ID)
|
portal.log.Debugln("Sending event", evt.ID, "to WhatsApp", info.ID)
|
||||||
ts, err := sender.Client.SendMessage(portal.Key.JID, info.ID, msg)
|
ts, err := sender.Client.SendMessage(portal.Key.JID, info.ID, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -2888,7 +2889,7 @@ func (portal *Portal) handleMatrixReaction(sender *User, evt *event.Event) error
|
||||||
return fmt.Errorf("unknown target event %s", content.RelatesTo.EventID)
|
return fmt.Errorf("unknown target event %s", content.RelatesTo.EventID)
|
||||||
}
|
}
|
||||||
info := portal.generateMessageInfo(sender)
|
info := portal.generateMessageInfo(sender)
|
||||||
dbMsg := portal.markHandled(nil, info, evt.ID, false, true, database.MsgReaction, database.MsgNoError)
|
dbMsg := portal.markHandled(nil, nil, info, evt.ID, false, true, database.MsgReaction, database.MsgNoError)
|
||||||
portal.upsertReaction(nil, target.JID, sender.JID, evt.ID, info.ID)
|
portal.upsertReaction(nil, target.JID, sender.JID, evt.ID, info.ID)
|
||||||
portal.log.Debugln("Sending reaction", evt.ID, "to WhatsApp", info.ID)
|
portal.log.Debugln("Sending reaction", evt.ID, "to WhatsApp", info.ID)
|
||||||
ts, err := portal.sendReactionToWhatsApp(sender, info.ID, target, content.RelatesTo.Key, evt.Timestamp)
|
ts, err := portal.sendReactionToWhatsApp(sender, info.ID, target, content.RelatesTo.Key, evt.Timestamp)
|
||||||
|
@ -3302,6 +3303,6 @@ func (portal *Portal) HandleMatrixMeta(sender *User, evt *event.Event) {
|
||||||
portal.Avatar = newID
|
portal.Avatar = newID
|
||||||
portal.AvatarURL = content.URL
|
portal.AvatarURL = content.URL
|
||||||
portal.UpdateBridgeInfo()
|
portal.UpdateBridgeInfo()
|
||||||
portal.Update()
|
portal.Update(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -280,7 +280,7 @@ func (puppet *Puppet) updatePortalAvatar() {
|
||||||
}
|
}
|
||||||
portal.AvatarURL = puppet.AvatarURL
|
portal.AvatarURL = puppet.AvatarURL
|
||||||
portal.Avatar = puppet.Avatar
|
portal.Avatar = puppet.Avatar
|
||||||
portal.Update()
|
portal.Update(nil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,7 +293,7 @@ func (puppet *Puppet) updatePortalName() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
portal.Name = puppet.Displayname
|
portal.Name = puppet.Displayname
|
||||||
portal.Update()
|
portal.Update(nil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue