session-ios/SignalMessaging/environment/migrations/OWS110SortIdMigration.swift

119 lines
5.4 KiB
Swift

//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
public class OWS110SortIdMigration: OWSDatabaseMigration {
// increment a similar constant for each migration.
@objc
class func migrationId() -> String {
// append char "x" because we want to rerun on some internal devices which
// have already run this migration.
return "110x"
}
override public func runUp(completion: @escaping OWSDatabaseMigrationCompletion) {
Logger.debug("")
BenchAsync(title: "Sort Migration") { completeBenchmark in
self.doMigration {
completeBenchmark()
completion()
}
}
}
private func doMigration(completion: @escaping OWSDatabaseMigrationCompletion) {
// TODO batch this?
self.dbReadWriteConnection().readWrite { transaction in
var archivedThreads: [TSThread] = []
// get archived threads before migration
TSThread.enumerateCollectionObjects(with: transaction) { (object, _) in
guard let thread = object as? TSThread else {
owsFailDebug("unexpected object: \(type(of: object))")
return
}
if thread.isArchivedByLegacyTimestampForSorting {
archivedThreads.append(thread)
}
}
guard let legacySorting: YapDatabaseAutoViewTransaction = transaction.extension(TSMessageDatabaseViewExtensionName_Legacy) as? YapDatabaseAutoViewTransaction else {
owsFailDebug("legacySorting was unexpectedly nil")
return
}
let totalCount: UInt = legacySorting.numberOfItemsInAllGroups()
var completedCount: UInt = 0
var allGroups = [String]()
legacySorting.enumerateGroups { group, _ in
allGroups.append(group)
}
var seenGroups: Set<String> = Set()
for group in allGroups {
autoreleasepool {
// Sanity Check #1
// Make sure our enumeration is monotonically increasing.
// Note: sortIds increase monotonically WRT timestampForLegacySorting, but only WRT the interaction's thread.
//
// e.g. When we migrate the **next** thread, we start with that thread's oldest interaction. So it's possible and expected
// that thread2's oldest interaction will have a smaller timestampForLegacySorting, but a greater sortId then an interaction
// in thread1. That's OK because we only sort messages with respect to the thread they belong in. We don't have any sort of
// "global sort" of messages across all threads.
var previousTimestampForLegacySorting: UInt64 = 0
// Sanity Check #2
// Ensure we only process a DB View's group (i.e. threadId) once.
guard !seenGroups.contains(group) else {
owsFail("unexpectedly seeing a repeated group: \(group)")
}
seenGroups.insert(group)
var groupKeys = [String]()
legacySorting.enumerateKeys(inGroup: group, using: { (_, key, _, _) in
groupKeys.append(key)
})
let groupKeyBatchSize: Int = 1024
for batch in groupKeys.chunked(by: groupKeyBatchSize) {
autoreleasepool {
for uniqueId in batch {
guard let interaction = TSInteraction.fetch(uniqueId: uniqueId, transaction: transaction) else {
owsFailDebug("Could not load interaction: \(uniqueId)")
return
}
if interaction.timestampForLegacySorting() < previousTimestampForLegacySorting {
owsFailDebug("unexpected object ordering previousTimestampForLegacySorting: \(previousTimestampForLegacySorting) interaction.timestampForLegacySorting: \(interaction.timestampForLegacySorting())")
}
previousTimestampForLegacySorting = interaction.timestampForLegacySorting()
interaction.saveNextSortId(transaction: transaction)
completedCount += 1
if completedCount % 100 == 0 {
// Legit usage of legacy sorting for migration to new sorting
Logger.info("thread: \(interaction.uniqueThreadId), timestampForLegacySorting:\(interaction.timestampForLegacySorting()), sortId: \(interaction.sortId) totalCount: \(totalCount), completedcount: \(completedCount)")
}
}
}
}
}
}
Logger.info("re-archiving \(archivedThreads.count) threads which were previously archived")
for archivedThread in archivedThreads {
archivedThread.archiveThread(with: transaction)
}
self.save(with: transaction)
}
completion()
}
}