mirror of https://github.com/oxen-io/session-ios
Started caching pending ReadReceipt messages to resolve an edge-case
Fixed an issue where read receipts could be sent for already read messages Fixed an issue where the read state change might not update the UIpull/784/head
parent
3344e58716
commit
08b1e9a131
@ -0,0 +1,41 @@
|
||||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import SessionUtilitiesKit
|
||||
|
||||
/// This migration adds a table to track pending read receipts (it's possible to receive a read receipt message before getting the original
|
||||
/// message due to how one-to-one conversations work, by storing pending read receipts we should be able to prevent this case)
|
||||
enum _011_AddPendingReadReceipts: Migration {
|
||||
static let target: TargetMigrations.Identifier = .messagingKit
|
||||
static let identifier: String = "AddPendingReadReceipts"
|
||||
static let needsConfigSync: Bool = false
|
||||
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||
|
||||
static func migrate(_ db: Database) throws {
|
||||
// Can't actually alter a virtual table in SQLite so we need to drop and recreate it,
|
||||
// luckily this is actually pretty quick
|
||||
if try db.tableExists(Interaction.fullTextSearchTableName) {
|
||||
try db.drop(table: Interaction.fullTextSearchTableName)
|
||||
try db.dropFTS5SynchronizationTriggers(forTable: Interaction.fullTextSearchTableName)
|
||||
}
|
||||
|
||||
try db.create(table: PendingReadReceipt.self) { t in
|
||||
t.column(.threadId, .text)
|
||||
.notNull()
|
||||
.indexed() // Quicker querying
|
||||
.references(SessionThread.self, onDelete: .cascade) // Delete if Thread deleted
|
||||
t.column(.interactionTimestampMs, .integer)
|
||||
.notNull()
|
||||
.indexed() // Quicker querying
|
||||
t.column(.readTimestampMs, .integer)
|
||||
.notNull()
|
||||
t.column(.serverExpirationTimestamp, .double)
|
||||
.notNull()
|
||||
|
||||
t.primaryKey([.threadId, .interactionTimestampMs])
|
||||
}
|
||||
|
||||
Storage.update(progress: 1, for: self, in: target) // In case this is the last migration
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import SessionUtilitiesKit
|
||||
|
||||
public struct PendingReadReceipt: Codable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
|
||||
public static var databaseTableName: String { "pendingReadReceipt" }
|
||||
public static let threadForeignKey = ForeignKey([Columns.threadId], to: [SessionThread.Columns.id])
|
||||
|
||||
public typealias Columns = CodingKeys
|
||||
public enum CodingKeys: String, CodingKey, ColumnExpression {
|
||||
case threadId
|
||||
case interactionTimestampMs
|
||||
case readTimestampMs
|
||||
case serverExpirationTimestamp
|
||||
}
|
||||
|
||||
/// The id for the thread this ReadReceipt belongs to
|
||||
public let threadId: String
|
||||
|
||||
/// The timestamp in milliseconds since epoch for the interaction this read receipt relates to
|
||||
public let interactionTimestampMs: Int64
|
||||
|
||||
/// The timestamp in milliseconds since epoch that the interaction this read receipt relates to was read
|
||||
public let readTimestampMs: Int64
|
||||
|
||||
/// The timestamp for when this message will expire on the server (will be used for garbage collection)
|
||||
public let serverExpirationTimestamp: TimeInterval
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
public init(
|
||||
threadId: String,
|
||||
interactionTimestampMs: Int64,
|
||||
readTimestampMs: Int64,
|
||||
serverExpirationTimestamp: TimeInterval
|
||||
) {
|
||||
self.threadId = threadId
|
||||
self.interactionTimestampMs = interactionTimestampMs
|
||||
self.readTimestampMs = readTimestampMs
|
||||
self.serverExpirationTimestamp = serverExpirationTimestamp
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue