session-ios/Session/Signal/OWSBackupLazyRestore.swift

177 lines
5.6 KiB
Swift

//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
import PromiseKit
import SignalUtilitiesKit
@objc(OWSBackupLazyRestore)
public class BackupLazyRestore: NSObject {
// MARK: - Dependencies
private var backup: OWSBackup {
return AppEnvironment.shared.backup
}
private var primaryStorage: OWSPrimaryStorage {
return SSKEnvironment.shared.primaryStorage
}
private var tsAccountManager: TSAccountManager {
return TSAccountManager.sharedInstance()
}
// MARK: -
private var isRunning = false
private var isComplete = false
@objc
public required override init() {
super.init()
SwiftSingletons.register(self)
AppReadiness.runNowOrWhenAppDidBecomeReady {
self.runIfNecessary()
}
NotificationCenter.default.addObserver(forName: .OWSApplicationDidBecomeActive, object: nil, queue: nil) { _ in
self.runIfNecessary()
}
NotificationCenter.default.addObserver(forName: .RegistrationStateDidChange, object: nil, queue: nil) { _ in
self.runIfNecessary()
}
NotificationCenter.default.addObserver(forName: .reachabilityChanged, object: nil, queue: nil) { _ in
self.runIfNecessary()
}
NotificationCenter.default.addObserver(forName: .reachabilityChanged, object: nil, queue: nil) { _ in
self.runIfNecessary()
}
NotificationCenter.default.addObserver(forName: NSNotification.Name(NSNotificationNameBackupStateDidChange), object: nil, queue: nil) { _ in
self.runIfNecessary()
}
}
// MARK: -
private let backgroundQueue = DispatchQueue.global(qos: .background)
@objc
public func clearCompleteAndRunIfNecessary() {
AssertIsOnMainThread()
isComplete = false
runIfNecessary()
}
@objc
public func isBackupImportInProgress() -> Bool {
return backup.backupImportState == .inProgress
}
@objc
public func runIfNecessary() {
AssertIsOnMainThread()
guard !CurrentAppContext().isRunningTests else {
return
}
guard AppReadiness.isAppReady() else {
return
}
guard CurrentAppContext().isMainAppAndActive else {
return
}
guard tsAccountManager.isRegisteredAndReady() else {
return
}
guard !isBackupImportInProgress() else {
return
}
guard !isRunning, !isComplete else {
return
}
isRunning = true
backgroundQueue.async {
self.restoreAttachments()
}
}
private func restoreAttachments() {
let temporaryDirectory = OWSTemporaryDirectory()
let jobTempDirPath = (temporaryDirectory as NSString).appendingPathComponent(NSUUID().uuidString)
guard OWSFileSystem.ensureDirectoryExists(jobTempDirPath) else {
Logger.error("could not create temp directory.")
complete(errorCount: 1)
return
}
let backupIO = OWSBackupIO(jobTempDirPath: jobTempDirPath)
let attachmentIds = backup.attachmentIdsForLazyRestore()
guard attachmentIds.count > 0 else {
Logger.info("No attachments need lazy restore.")
complete(errorCount: 0)
return
}
Logger.info("Lazy restoring \(attachmentIds.count) attachments.")
tryToRestoreNextAttachment(attachmentIds: attachmentIds, errorCount: 0, backupIO: backupIO)
}
private func tryToRestoreNextAttachment(attachmentIds: [String], errorCount: UInt, backupIO: OWSBackupIO) {
guard !isBackupImportInProgress() else {
Logger.verbose("A backup import is in progress; abort.")
complete(errorCount: errorCount + 1)
return
}
var attachmentIdsCopy = attachmentIds
guard let attachmentId = attachmentIdsCopy.popLast() else {
// This job is done.
Logger.verbose("job is done.")
complete(errorCount: errorCount)
return
}
guard let attachmentPointer = TSAttachment.fetch(uniqueId: attachmentId) as? TSAttachmentPointer else {
Logger.warn("could not load attachment.")
// Not necessarily an error.
// The attachment might have been deleted since the job began.
// Continue trying to restore the other attachments.
tryToRestoreNextAttachment(attachmentIds: attachmentIds, errorCount: errorCount + 1, backupIO: backupIO)
return
}
backup.lazyRestoreAttachment(attachmentPointer,
backupIO: backupIO)
.done(on: self.backgroundQueue) { _ in
Logger.info("Restored attachment.")
// Continue trying to restore the other attachments.
self.tryToRestoreNextAttachment(attachmentIds: attachmentIdsCopy, errorCount: errorCount, backupIO: backupIO)
}.catch(on: self.backgroundQueue) { (error) in
Logger.error("Could not restore attachment: \(error)")
// Continue trying to restore the other attachments.
self.tryToRestoreNextAttachment(attachmentIds: attachmentIdsCopy, errorCount: errorCount + 1, backupIO: backupIO)
}.retainUntilComplete()
}
private func complete(errorCount: UInt) {
Logger.verbose("")
DispatchQueue.main.async {
self.isRunning = false
if errorCount == 0 {
self.isComplete = true
}
}
}
}