162 lines
5.6 KiB
Swift
162 lines
5.6 KiB
Swift
//
|
|
// Copyright 2024 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
/// Tracks progress of a Backup export, as a fraction of the number of database
|
|
/// rows we've exported so far over the approximate number we expect to.
|
|
/// - Note
|
|
/// Number of exported database rows is not a perfect metric for time spent, as
|
|
/// some rows require more time and work than others.
|
|
public struct MessageBackupExportProgress {
|
|
private let progressSource: OWSProgressSource
|
|
|
|
private init(progressSource: OWSProgressSource) {
|
|
self.progressSource = progressSource
|
|
}
|
|
|
|
public static func prepare(
|
|
sink: OWSProgressSink,
|
|
db: any DB
|
|
) async throws -> Self {
|
|
var estimatedFrameCount = try db.read { tx in
|
|
// Get all the major things we iterate over. It doesn't have
|
|
// to be perfect; we'll skip some of these and besides they're
|
|
// all weighted evenly. Its just an estimate.
|
|
return try
|
|
SignalRecipient.fetchCount(tx.database)
|
|
+ ThreadRecord.fetchCount(tx.database)
|
|
+ InteractionRecord.fetchCount(tx.database)
|
|
+ CallLinkRecord.fetchCount(tx.database)
|
|
+ StickerPackRecord.fetchCount(tx.database)
|
|
}
|
|
// Add a fixed extra amount for:
|
|
// * header frame
|
|
// * self recipient
|
|
// * account data frame
|
|
// * release notes channel
|
|
estimatedFrameCount += 4
|
|
|
|
let progressSource = await sink.addSource(
|
|
withLabel: "Backup Export",
|
|
unitCount: UInt64(estimatedFrameCount)
|
|
)
|
|
return MessageBackupExportProgress(progressSource: progressSource)
|
|
}
|
|
|
|
public func didExportFrame() {
|
|
progressSource.incrementCompletedUnitCount(by: 1)
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
/// Tracks the progress of importing frames from a Backup, as a fraction of the
|
|
/// total number of bytes read from the Backup file so far.
|
|
/// - Note
|
|
/// Number of bytes read is not a perfect metric for time spent, as some frames
|
|
/// require more time and work than others.
|
|
public struct MessageBackupImportFrameRestoreProgress {
|
|
private let progressSource: OWSProgressSource
|
|
|
|
private init(progressSource: OWSProgressSource) {
|
|
self.progressSource = progressSource
|
|
}
|
|
|
|
public static func prepare(
|
|
sink: OWSProgressSink,
|
|
fileUrl: URL
|
|
) async throws -> Self {
|
|
guard let totalByteCount = OWSFileSystem.fileSize(of: fileUrl)?.uint64Value else {
|
|
throw OWSAssertionError("Unable to read file size")
|
|
}
|
|
|
|
let progressSource = await sink.addSource(
|
|
withLabel: "Backup Import: Frame Restore",
|
|
unitCount: totalByteCount
|
|
)
|
|
return MessageBackupImportFrameRestoreProgress(
|
|
progressSource: progressSource
|
|
)
|
|
}
|
|
|
|
public func didReadBytes(count byteLength: Int) {
|
|
guard let byteLength = UInt64(exactly: byteLength) else {
|
|
owsFailDebug("How did we get such a huge byte length?")
|
|
return
|
|
}
|
|
if byteLength > 0 {
|
|
progressSource.incrementCompletedUnitCount(by: byteLength)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
public class MessageBackupImportRecreateIndexesProgress {
|
|
private enum Constants {
|
|
static let progressSourceUnitCount: UInt64 = .max
|
|
}
|
|
|
|
@Atomic
|
|
private var progressSource: OWSProgressSource
|
|
private var updateProcessPeriodicallyTask: Task<Void, Error>?
|
|
|
|
private init(progressSource: OWSProgressSource) {
|
|
self.progressSource = progressSource
|
|
}
|
|
|
|
public static func prepare(
|
|
sink: OWSProgressSink
|
|
) async -> MessageBackupImportRecreateIndexesProgress {
|
|
let progressSource = await sink.addSource(
|
|
withLabel: "Backup Import: Recreate Indexes",
|
|
unitCount: Constants.progressSourceUnitCount
|
|
)
|
|
return MessageBackupImportRecreateIndexesProgress(
|
|
progressSource: progressSource
|
|
)
|
|
}
|
|
|
|
/// Start approximating progress towards recreating indexes during a Backup
|
|
/// import.
|
|
///
|
|
/// SQLite doesn't report progress as it creates a database index, so we
|
|
/// can't report progress precisely. Instead, we'll increment progress
|
|
/// automatically over time, with a rough heuristic for how much progress we
|
|
/// made during that time based on the number of restored frames.
|
|
///
|
|
/// - Important
|
|
/// Callers must pair a call to this method with a call to
|
|
/// ``didFinishIndexRecreation()``.
|
|
public func willStartIndexRecreation(totalFramesRestored: UInt64) {
|
|
owsPrecondition(updateProcessPeriodicallyTask == nil)
|
|
|
|
// Ballpark that we can recreate indexes for 5k frames-worth of database
|
|
// rows per second. This number will depend on external factors like
|
|
// device CPU as well as internal factors like how many indexes we're
|
|
// creating.
|
|
let framesEstimatePerSecond = 5_000
|
|
let _unitCountPerSecond = Double(framesEstimatePerSecond) / Double(totalFramesRestored) * Double(Constants.progressSourceUnitCount)
|
|
let unitCountPerSecond = UInt64(clamping: _unitCountPerSecond)
|
|
|
|
updateProcessPeriodicallyTask = Task {
|
|
while true {
|
|
try Task.checkCancellation()
|
|
try await Task.sleep(nanoseconds: NSEC_PER_SEC)
|
|
|
|
progressSource.incrementCompletedUnitCount(by: unitCountPerSecond)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Finish approximating progress towards recreating indexes during a Backup
|
|
/// import.
|
|
public func didFinishIndexRecreation() {
|
|
updateProcessPeriodicallyTask?.cancel()
|
|
updateProcessPeriodicallyTask = nil
|
|
|
|
progressSource.complete()
|
|
}
|
|
}
|