Convert backup logic to use promises.
This commit is contained in:
parent
af477d3bf3
commit
44d0ad34f5
|
@ -88,20 +88,10 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
OWSAssertDebug(success);
|
||||
|
||||
NSString *recipientId = self.tsAccountManager.localNumber;
|
||||
[[self.backup checkCloudKitAccess]
|
||||
.then(^{
|
||||
[OWSBackupAPI saveTestFileToCloudWithRecipientId:recipientId
|
||||
fileUrl:[NSURL fileURLWithPath:filePath]
|
||||
success:^(NSString *recordName) {
|
||||
// Do nothing, the API method will log for us.
|
||||
}
|
||||
failure:^(NSError *error){
|
||||
// Do nothing, the API method will log for us.
|
||||
}];
|
||||
})
|
||||
.catch(^(NSError *error){
|
||||
// Do nothing, the API method will log for us.
|
||||
}) retainUntilComplete];
|
||||
[[self.backup checkCloudKitAccess].then(^{
|
||||
return
|
||||
[OWSBackupAPI saveTestFileToCloudObjcWithRecipientId:recipientId fileUrl:[NSURL fileURLWithPath:filePath]];
|
||||
}) retainUntilComplete];
|
||||
}
|
||||
|
||||
+ (void)checkForBackup
|
||||
|
|
|
@ -43,16 +43,18 @@ import PromiseKit
|
|||
// MARK: - Upload
|
||||
|
||||
@objc
|
||||
public class func saveTestFileToCloudObjc(recipientId: String,
|
||||
fileUrl: URL) -> AnyPromise {
|
||||
return AnyPromise(saveTestFileToCloud(recipientId: recipientId,
|
||||
fileUrl: fileUrl))
|
||||
}
|
||||
|
||||
public class func saveTestFileToCloud(recipientId: String,
|
||||
fileUrl: URL,
|
||||
success: @escaping (String) -> Void,
|
||||
failure: @escaping (Error) -> Void) {
|
||||
fileUrl: URL) -> Promise<String> {
|
||||
let recordName = "\(recordNamePrefix(forRecipientId: recipientId))test-\(NSUUID().uuidString)"
|
||||
saveFileToCloud(fileUrl: fileUrl,
|
||||
recordName: recordName,
|
||||
recordType: signalBackupRecordType,
|
||||
success: success,
|
||||
failure: failure)
|
||||
return saveFileToCloud(fileUrl: fileUrl,
|
||||
recordName: recordName,
|
||||
recordType: signalBackupRecordType)
|
||||
}
|
||||
|
||||
// "Ephemeral" files are specific to this backup export and will always need to
|
||||
|
@ -60,16 +62,18 @@ import PromiseKit
|
|||
// We wouldn't want to overwrite previous images until the entire backup export is
|
||||
// complete.
|
||||
@objc
|
||||
public class func saveEphemeralDatabaseFileToCloudObjc(recipientId: String,
|
||||
fileUrl: URL) -> AnyPromise {
|
||||
return AnyPromise(saveEphemeralDatabaseFileToCloud(recipientId: recipientId,
|
||||
fileUrl: fileUrl))
|
||||
}
|
||||
|
||||
public class func saveEphemeralDatabaseFileToCloud(recipientId: String,
|
||||
fileUrl: URL,
|
||||
success: @escaping (String) -> Void,
|
||||
failure: @escaping (Error) -> Void) {
|
||||
fileUrl: URL) -> Promise<String> {
|
||||
let recordName = "\(recordNamePrefix(forRecipientId: recipientId))ephemeralFile-\(NSUUID().uuidString)"
|
||||
saveFileToCloud(fileUrl: fileUrl,
|
||||
recordName: recordName,
|
||||
recordType: signalBackupRecordType,
|
||||
success: success,
|
||||
failure: failure)
|
||||
return saveFileToCloud(fileUrl: fileUrl,
|
||||
recordName: recordName,
|
||||
recordType: signalBackupRecordType)
|
||||
}
|
||||
|
||||
// "Persistent" files may be shared between backup export; they should only be saved
|
||||
|
@ -125,9 +129,9 @@ import PromiseKit
|
|||
let firstRange = match.range(at: 1)
|
||||
guard firstRange.location == 0,
|
||||
firstRange.length > 0 else {
|
||||
// Match must be at start of string and non-empty.
|
||||
Logger.warn("invalid match: \(recordName) \(firstRange)")
|
||||
continue
|
||||
// Match must be at start of string and non-empty.
|
||||
Logger.warn("invalid match: \(recordName) \(firstRange)")
|
||||
continue
|
||||
}
|
||||
let recipientId = (recordName as NSString).substring(with: firstRange) as String
|
||||
recipientIds.append(recipientId)
|
||||
|
@ -139,57 +143,75 @@ import PromiseKit
|
|||
// once. For example, attachment files should only be uploaded once. Subsequent
|
||||
// backups can reuse the same record.
|
||||
@objc
|
||||
public class func savePersistentFileOnceToCloudObjc(recipientId: String,
|
||||
fileId: String,
|
||||
fileUrlBlock: @escaping () -> URL?) -> AnyPromise {
|
||||
return AnyPromise(savePersistentFileOnceToCloud(recipientId: recipientId,
|
||||
fileId: fileId,
|
||||
fileUrlBlock: fileUrlBlock))
|
||||
}
|
||||
|
||||
public class func savePersistentFileOnceToCloud(recipientId: String,
|
||||
fileId: String,
|
||||
fileUrlBlock: @escaping () -> URL?,
|
||||
success: @escaping (String) -> Void,
|
||||
failure: @escaping (Error) -> Void) {
|
||||
fileUrlBlock: @escaping () -> URL?) -> Promise<String> {
|
||||
let recordName = recordNameForPersistentFile(recipientId: recipientId, fileId: fileId)
|
||||
saveFileOnceToCloud(recordName: recordName,
|
||||
recordType: signalBackupRecordType,
|
||||
fileUrlBlock: fileUrlBlock,
|
||||
success: success,
|
||||
failure: failure)
|
||||
return saveFileOnceToCloud(recordName: recordName,
|
||||
recordType: signalBackupRecordType,
|
||||
fileUrlBlock: fileUrlBlock)
|
||||
}
|
||||
|
||||
@objc
|
||||
public class func upsertManifestFileToCloudObjc(recipientId: String,
|
||||
fileUrl: URL) -> AnyPromise {
|
||||
return AnyPromise(upsertManifestFileToCloud(recipientId: recipientId,
|
||||
fileUrl: fileUrl))
|
||||
}
|
||||
|
||||
public class func upsertManifestFileToCloud(recipientId: String,
|
||||
fileUrl: URL,
|
||||
success: @escaping (String) -> Void,
|
||||
failure: @escaping (Error) -> Void) {
|
||||
fileUrl: URL) -> Promise<String> {
|
||||
// We want to use a well-known record id and type for manifest files.
|
||||
let recordName = recordNameForManifest(recipientId: recipientId)
|
||||
upsertFileToCloud(fileUrl: fileUrl,
|
||||
recordName: recordName,
|
||||
recordType: signalBackupRecordType,
|
||||
success: success,
|
||||
failure: failure)
|
||||
return upsertFileToCloud(fileUrl: fileUrl,
|
||||
recordName: recordName,
|
||||
recordType: signalBackupRecordType)
|
||||
}
|
||||
|
||||
@objc
|
||||
public class func saveFileToCloudObjc(fileUrl: URL,
|
||||
recordName: String,
|
||||
recordType: String) -> AnyPromise {
|
||||
return AnyPromise(saveFileToCloud(fileUrl: fileUrl,
|
||||
recordName: recordName,
|
||||
recordType: recordType))
|
||||
}
|
||||
|
||||
public class func saveFileToCloud(fileUrl: URL,
|
||||
recordName: String,
|
||||
recordType: String,
|
||||
success: @escaping (String) -> Void,
|
||||
failure: @escaping (Error) -> Void) {
|
||||
recordType: String) -> Promise<String> {
|
||||
let recordID = CKRecordID(recordName: recordName)
|
||||
let record = CKRecord(recordType: recordType, recordID: recordID)
|
||||
let asset = CKAsset(fileURL: fileUrl)
|
||||
record[payloadKey] = asset
|
||||
|
||||
saveRecordToCloud(record: record,
|
||||
success: success,
|
||||
failure: failure)
|
||||
return saveRecordToCloud(record: record)
|
||||
}
|
||||
|
||||
@objc
|
||||
public class func saveRecordToCloud(record: CKRecord,
|
||||
success: @escaping (String) -> Void,
|
||||
failure: @escaping (Error) -> Void) {
|
||||
public class func saveRecordToCloudObjc(record: CKRecord) -> AnyPromise {
|
||||
return AnyPromise(saveRecordToCloud(record: record))
|
||||
}
|
||||
|
||||
public class func saveRecordToCloud(record: CKRecord) -> Promise<String> {
|
||||
let (promise, resolver) = Promise<String>.pending()
|
||||
saveRecordToCloud(record: record,
|
||||
remainingRetries: maxRetries,
|
||||
success: success,
|
||||
failure: failure)
|
||||
success: { (recordName) in
|
||||
resolver.fulfill(recordName)
|
||||
},
|
||||
failure: { (error) in
|
||||
resolver.reject(error)
|
||||
})
|
||||
return promise.retainUntilComplete()
|
||||
}
|
||||
|
||||
private class func saveRecordToCloud(record: CKRecord,
|
||||
|
@ -201,8 +223,8 @@ import PromiseKit
|
|||
saveOperation.modifyRecordsCompletionBlock = { (records, recordIds, error) in
|
||||
|
||||
let outcome = outcomeForCloudKitError(error: error,
|
||||
remainingRetries: remainingRetries,
|
||||
label: "Save Record")
|
||||
remainingRetries: remainingRetries,
|
||||
label: "Save Record")
|
||||
switch outcome {
|
||||
case .success:
|
||||
let recordName = record.recordID.recordName
|
||||
|
@ -245,32 +267,33 @@ import PromiseKit
|
|||
// * A "save once" creates a new record if none exists and
|
||||
// does nothing if there is an existing record.
|
||||
@objc
|
||||
public class func upsertFileToCloudObjc(fileUrl: URL,
|
||||
recordName: String,
|
||||
recordType: String) -> AnyPromise {
|
||||
return AnyPromise(upsertFileToCloud(fileUrl: fileUrl,
|
||||
recordName: recordName,
|
||||
recordType: recordType))
|
||||
}
|
||||
|
||||
public class func upsertFileToCloud(fileUrl: URL,
|
||||
recordName: String,
|
||||
recordType: String,
|
||||
success: @escaping (String) -> Void,
|
||||
failure: @escaping (Error) -> Void) {
|
||||
recordType: String) -> Promise<String> {
|
||||
|
||||
checkForFileInCloud(recordName: recordName,
|
||||
remainingRetries: maxRetries,
|
||||
success: { (record) in
|
||||
if let record = record {
|
||||
// Record found, updating existing record.
|
||||
let asset = CKAsset(fileURL: fileUrl)
|
||||
record[payloadKey] = asset
|
||||
saveRecordToCloud(record: record,
|
||||
success: success,
|
||||
failure: failure)
|
||||
} else {
|
||||
// No record found, saving new record.
|
||||
saveFileToCloud(fileUrl: fileUrl,
|
||||
recordName: recordName,
|
||||
recordType: recordType,
|
||||
success: success,
|
||||
failure: failure)
|
||||
}
|
||||
},
|
||||
failure: failure)
|
||||
return checkForFileInCloud(recordName: recordName,
|
||||
remainingRetries: maxRetries)
|
||||
.then { (record: CKRecord?) -> Promise<String> in
|
||||
if let record = record {
|
||||
// Record found, updating existing record.
|
||||
let asset = CKAsset(fileURL: fileUrl)
|
||||
record[payloadKey] = asset
|
||||
return saveRecordToCloud(record: record)
|
||||
}
|
||||
|
||||
// No record found, saving new record.
|
||||
return saveFileToCloud(fileUrl: fileUrl,
|
||||
recordName: recordName,
|
||||
recordType: recordType)
|
||||
}
|
||||
}
|
||||
|
||||
// Compare:
|
||||
|
@ -279,54 +302,55 @@ import PromiseKit
|
|||
// * A "save once" creates a new record if none exists and
|
||||
// does nothing if there is an existing record.
|
||||
@objc
|
||||
public class func saveFileOnceToCloudObjc(recordName: String,
|
||||
recordType: String,
|
||||
fileUrlBlock: @escaping () -> URL?) -> AnyPromise {
|
||||
return AnyPromise(saveFileOnceToCloud(recordName: recordName,
|
||||
recordType: recordType,
|
||||
fileUrlBlock: fileUrlBlock))
|
||||
}
|
||||
|
||||
public class func saveFileOnceToCloud(recordName: String,
|
||||
recordType: String,
|
||||
fileUrlBlock: @escaping () -> URL?,
|
||||
success: @escaping (String) -> Void,
|
||||
failure: @escaping (Error) -> Void) {
|
||||
fileUrlBlock: @escaping () -> URL?) -> Promise<String> {
|
||||
|
||||
checkForFileInCloud(recordName: recordName,
|
||||
remainingRetries: maxRetries,
|
||||
success: { (record) in
|
||||
if record != nil {
|
||||
// Record found, skipping save.
|
||||
success(recordName)
|
||||
} else {
|
||||
// No record found, saving new record.
|
||||
guard let fileUrl = fileUrlBlock() else {
|
||||
Logger.error("error preparing file for upload.")
|
||||
failure(OWSErrorWithCodeDescription(.exportBackupError,
|
||||
NSLocalizedString("BACKUP_EXPORT_ERROR_SAVE_FILE_TO_CLOUD_FAILED",
|
||||
comment: "Error indicating the backup export failed to save a file to the cloud.")))
|
||||
return
|
||||
}
|
||||
return checkForFileInCloud(recordName: recordName,
|
||||
remainingRetries: maxRetries)
|
||||
.then { (record: CKRecord?) -> Promise<String> in
|
||||
if record != nil {
|
||||
// Record found, skipping save.
|
||||
return Promise.value(recordName)
|
||||
}
|
||||
// No record found, saving new record.
|
||||
guard let fileUrl = fileUrlBlock() else {
|
||||
Logger.error("error preparing file for upload.")
|
||||
return Promise(error: OWSErrorWithCodeDescription(.exportBackupError,
|
||||
NSLocalizedString("BACKUP_EXPORT_ERROR_SAVE_FILE_TO_CLOUD_FAILED",
|
||||
comment: "Error indicating the backup export failed to save a file to the cloud.")))
|
||||
}
|
||||
|
||||
saveFileToCloud(fileUrl: fileUrl,
|
||||
recordName: recordName,
|
||||
recordType: recordType,
|
||||
success: success,
|
||||
failure: failure)
|
||||
}
|
||||
},
|
||||
failure: failure)
|
||||
return saveFileToCloud(fileUrl: fileUrl,
|
||||
recordName: recordName,
|
||||
recordType: recordType)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Delete
|
||||
|
||||
@objc
|
||||
public class func deleteRecordsFromCloud(recordNames: [String],
|
||||
success: @escaping () -> Void,
|
||||
failure: @escaping (Error) -> Void) {
|
||||
success: @escaping () -> Void,
|
||||
failure: @escaping (Error) -> Void) {
|
||||
deleteRecordsFromCloud(recordNames: recordNames,
|
||||
remainingRetries: maxRetries,
|
||||
success: success,
|
||||
failure: failure)
|
||||
remainingRetries: maxRetries,
|
||||
success: success,
|
||||
failure: failure)
|
||||
}
|
||||
|
||||
private class func deleteRecordsFromCloud(recordNames: [String],
|
||||
remainingRetries: Int,
|
||||
success: @escaping () -> Void,
|
||||
failure: @escaping (Error) -> Void) {
|
||||
remainingRetries: Int,
|
||||
success: @escaping () -> Void,
|
||||
failure: @escaping (Error) -> Void) {
|
||||
|
||||
let recordIDs = recordNames.map { CKRecordID(recordName: $0) }
|
||||
let deleteOperation = CKModifyRecordsOperation(recordsToSave: nil, recordIDsToDelete: recordIDs)
|
||||
|
@ -343,16 +367,16 @@ import PromiseKit
|
|||
case .failureRetryAfterDelay(let retryDelay):
|
||||
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + retryDelay, execute: {
|
||||
deleteRecordsFromCloud(recordNames: recordNames,
|
||||
remainingRetries: remainingRetries - 1,
|
||||
success: success,
|
||||
failure: failure)
|
||||
remainingRetries: remainingRetries - 1,
|
||||
success: success,
|
||||
failure: failure)
|
||||
})
|
||||
case .failureRetryWithoutDelay:
|
||||
DispatchQueue.global().async {
|
||||
deleteRecordsFromCloud(recordNames: recordNames,
|
||||
remainingRetries: remainingRetries - 1,
|
||||
success: success,
|
||||
failure: failure)
|
||||
remainingRetries: remainingRetries - 1,
|
||||
success: success,
|
||||
failure: failure)
|
||||
}
|
||||
case .unknownItem:
|
||||
owsFailDebug("unexpected CloudKit response.")
|
||||
|
@ -365,9 +389,10 @@ import PromiseKit
|
|||
// MARK: - Exists?
|
||||
|
||||
private class func checkForFileInCloud(recordName: String,
|
||||
remainingRetries: Int,
|
||||
success: @escaping (CKRecord?) -> Void,
|
||||
failure: @escaping (Error) -> Void) {
|
||||
remainingRetries: Int) -> Promise<CKRecord?> {
|
||||
|
||||
let (promise, resolver) = Promise<CKRecord?>.pending()
|
||||
|
||||
let recordId = CKRecordID(recordName: recordName)
|
||||
let fetchOperation = CKFetchRecordsOperation(recordIDs: [recordId ])
|
||||
// Don't download the file; we're just using the fetch to check whether or
|
||||
|
@ -376,39 +401,46 @@ import PromiseKit
|
|||
fetchOperation.perRecordCompletionBlock = { (record, recordId, error) in
|
||||
|
||||
let outcome = outcomeForCloudKitError(error: error,
|
||||
remainingRetries: remainingRetries,
|
||||
label: "Check for Record")
|
||||
remainingRetries: remainingRetries,
|
||||
label: "Check for Record")
|
||||
switch outcome {
|
||||
case .success:
|
||||
guard let record = record else {
|
||||
owsFailDebug("missing fetching record.")
|
||||
failure(invalidServiceResponseError())
|
||||
resolver.reject(invalidServiceResponseError())
|
||||
return
|
||||
}
|
||||
// Record found.
|
||||
success(record)
|
||||
resolver.fulfill(record)
|
||||
case .failureDoNotRetry(let outcomeError):
|
||||
failure(outcomeError)
|
||||
resolver.reject(outcomeError)
|
||||
case .failureRetryAfterDelay(let retryDelay):
|
||||
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + retryDelay, execute: {
|
||||
checkForFileInCloud(recordName: recordName,
|
||||
remainingRetries: remainingRetries - 1,
|
||||
success: success,
|
||||
failure: failure)
|
||||
remainingRetries: remainingRetries - 1)
|
||||
.done { (record) in
|
||||
resolver.fulfill(record)
|
||||
}.catch { (error) in
|
||||
resolver.reject(error)
|
||||
}
|
||||
})
|
||||
case .failureRetryWithoutDelay:
|
||||
DispatchQueue.global().async {
|
||||
checkForFileInCloud(recordName: recordName,
|
||||
remainingRetries: remainingRetries - 1,
|
||||
success: success,
|
||||
failure: failure)
|
||||
remainingRetries: remainingRetries - 1)
|
||||
.done { (record) in
|
||||
resolver.fulfill(record)
|
||||
}.catch { (error) in
|
||||
resolver.reject(error)
|
||||
}
|
||||
}
|
||||
case .unknownItem:
|
||||
// Record not found.
|
||||
success(nil)
|
||||
resolver.fulfill(nil)
|
||||
}
|
||||
}
|
||||
database().add(fetchOperation)
|
||||
return promise.retainUntilComplete()
|
||||
}
|
||||
|
||||
@objc
|
||||
|
@ -418,17 +450,12 @@ import PromiseKit
|
|||
|
||||
public class func checkForManifestInCloud(recipientId: String) -> Promise<Bool> {
|
||||
|
||||
let (promise, resolver) = Promise<Bool>.pending()
|
||||
let recordName = recordNameForManifest(recipientId: recipientId)
|
||||
checkForFileInCloud(recordName: recordName,
|
||||
remainingRetries: maxRetries,
|
||||
success: { (record) in
|
||||
resolver.fulfill(record != nil)
|
||||
},
|
||||
failure: { (error) in
|
||||
resolver.reject(error)
|
||||
})
|
||||
return promise
|
||||
return checkForFileInCloud(recordName: recordName,
|
||||
remainingRetries: maxRetries)
|
||||
.map { (record) in
|
||||
return record != nil
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
|
@ -506,8 +533,8 @@ import PromiseKit
|
|||
queryOperation.queryCompletionBlock = { (cursor, error) in
|
||||
|
||||
let outcome = outcomeForCloudKitError(error: error,
|
||||
remainingRetries: remainingRetries,
|
||||
label: "Fetch All Records")
|
||||
remainingRetries: remainingRetries,
|
||||
label: "Fetch All Records")
|
||||
switch outcome {
|
||||
case .success:
|
||||
if let cursor = cursor {
|
||||
|
@ -626,8 +653,8 @@ import PromiseKit
|
|||
fetchOperation.perRecordCompletionBlock = { (record, recordId, error) in
|
||||
|
||||
let outcome = outcomeForCloudKitError(error: error,
|
||||
remainingRetries: remainingRetries,
|
||||
label: "Download Record")
|
||||
remainingRetries: remainingRetries,
|
||||
label: "Download Record")
|
||||
switch outcome {
|
||||
case .success:
|
||||
guard let record = record else {
|
||||
|
@ -734,8 +761,8 @@ import PromiseKit
|
|||
}
|
||||
|
||||
private class func outcomeForCloudKitError(error: Error?,
|
||||
remainingRetries: Int,
|
||||
label: String) -> APIOutcome {
|
||||
remainingRetries: Int,
|
||||
label: String) -> APIOutcome {
|
||||
if let error = error as? CKError {
|
||||
if error.code == CKError.unknownItem {
|
||||
// This is not always an error for our purposes.
|
||||
|
|
|
@ -755,20 +755,19 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
if (self.isComplete) {
|
||||
return resolve(OWSBackupErrorWithDescription(@"Backup export no longer active."));
|
||||
}
|
||||
|
||||
[OWSBackupAPI saveEphemeralDatabaseFileToCloudWithRecipientId:self.recipientId
|
||||
fileUrl:[NSURL fileURLWithPath:item.encryptedItem.filePath]
|
||||
success:^(NSString *recordName) {
|
||||
item.recordName = recordName;
|
||||
[self.savedDatabaseItems addObject:item];
|
||||
resolve(@(1));
|
||||
}
|
||||
failure:^(NSError *error) {
|
||||
// Database files are critical so any error uploading them is unrecoverable.
|
||||
OWSLogVerbose(@"error while saving file: %@", item.encryptedItem.filePath);
|
||||
resolve(error);
|
||||
}];
|
||||
}];
|
||||
resolve(@(1));
|
||||
}]
|
||||
.thenInBackground(^{
|
||||
return [OWSBackupAPI
|
||||
saveEphemeralDatabaseFileToCloudObjcWithRecipientId:self.recipientId
|
||||
fileUrl:[NSURL fileURLWithPath:item.encryptedItem
|
||||
.filePath]];
|
||||
})
|
||||
.thenInBackground(^(NSString *recordName) {
|
||||
item.recordName = recordName;
|
||||
[self.savedDatabaseItems addObject:item];
|
||||
return [AnyPromise promiseWithValue:@(1)];
|
||||
});
|
||||
});
|
||||
}
|
||||
[self.unsavedDatabaseItems removeAllObjects];
|
||||
|
@ -782,19 +781,17 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
for (OWSAttachmentExport *attachmentExport in self.unsavedAttachmentExports) {
|
||||
promise = promise.thenInBackground(^{
|
||||
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
|
||||
if (self.isComplete) {
|
||||
return resolve(OWSBackupErrorWithDescription(@"Backup export no longer active."));
|
||||
}
|
||||
[self saveAttachmentFileToCloud:attachmentExport resolve:resolve];
|
||||
}];
|
||||
if (self.isComplete) {
|
||||
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup export no longer active.")];
|
||||
}
|
||||
return [self saveAttachmentFileToCloud:attachmentExport];
|
||||
});
|
||||
}
|
||||
[self.unsavedAttachmentExports removeAllObjects];
|
||||
return promise;
|
||||
}
|
||||
|
||||
- (void)saveAttachmentFileToCloud:(OWSAttachmentExport *)attachmentExport resolve:(PMKResolver)resolve
|
||||
- (AnyPromise *)saveAttachmentFileToCloud:(OWSAttachmentExport *)attachmentExport
|
||||
{
|
||||
if (self.lastValidRecordNames) {
|
||||
// Wherever possible, we do incremental backups and re-use fragments of the last
|
||||
|
@ -831,7 +828,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
OWSLogVerbose(@"recycled attachment: %@ as %@",
|
||||
attachmentExport.attachmentFilePath,
|
||||
attachmentExport.relativeFilePath);
|
||||
return resolve(@(1));
|
||||
return [AnyPromise promiseWithValue:@(1)];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -840,26 +837,27 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
// attachment to disk.
|
||||
if (![attachmentExport prepareForUpload]) {
|
||||
// Attachment files are non-critical so any error uploading them is recoverable.
|
||||
return resolve(@(1));
|
||||
return [AnyPromise promiseWithValue:@(1)];
|
||||
}
|
||||
OWSAssertDebug(attachmentExport.relativeFilePath.length > 0);
|
||||
OWSAssertDebug(attachmentExport.encryptedItem);
|
||||
}
|
||||
|
||||
[OWSBackupAPI savePersistentFileOnceToCloudWithRecipientId:self.recipientId
|
||||
fileId:attachmentExport.attachmentId
|
||||
fileUrlBlock:^{
|
||||
if (attachmentExport.encryptedItem.filePath.length < 1) {
|
||||
OWSLogError(@"attachment export missing temp file path");
|
||||
return (NSURL *)nil;
|
||||
}
|
||||
if (attachmentExport.relativeFilePath.length < 1) {
|
||||
OWSLogError(@"attachment export missing relative file path");
|
||||
return (NSURL *)nil;
|
||||
}
|
||||
return [NSURL fileURLWithPath:attachmentExport.encryptedItem.filePath];
|
||||
}
|
||||
success:^(NSString *recordName) {
|
||||
return [OWSBackupAPI
|
||||
savePersistentFileOnceToCloudObjcWithRecipientId:self.recipientId
|
||||
fileId:attachmentExport.attachmentId
|
||||
fileUrlBlock:^{
|
||||
if (attachmentExport.encryptedItem.filePath.length < 1) {
|
||||
OWSLogError(@"attachment export missing temp file path");
|
||||
return (NSURL *)nil;
|
||||
}
|
||||
if (attachmentExport.relativeFilePath.length < 1) {
|
||||
OWSLogError(@"attachment export missing relative file path");
|
||||
return (NSURL *)nil;
|
||||
}
|
||||
return [NSURL fileURLWithPath:attachmentExport.encryptedItem.filePath];
|
||||
}]
|
||||
.thenInBackground(^(NSString *recordName) {
|
||||
if (![attachmentExport cleanUp]) {
|
||||
OWSLogError(@"couldn't clean up attachment export.");
|
||||
// Attachment files are non-critical so any error uploading them is recoverable.
|
||||
|
@ -882,17 +880,17 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
OWSLogVerbose(
|
||||
@"saved attachment: %@ as %@", attachmentExport.attachmentFilePath, attachmentExport.relativeFilePath);
|
||||
return resolve(@(1));
|
||||
}
|
||||
failure:^(NSError *error) {
|
||||
return [AnyPromise promiseWithValue:@(1)];
|
||||
})
|
||||
.catchInBackground(^{
|
||||
if (![attachmentExport cleanUp]) {
|
||||
OWSLogError(@"couldn't clean up attachment export.");
|
||||
// Attachment files are non-critical so any error uploading them is recoverable.
|
||||
}
|
||||
|
||||
// Attachment files are non-critical so any error uploading them is recoverable.
|
||||
return resolve(@(1));
|
||||
}];
|
||||
return [AnyPromise promiseWithValue:@(1)];
|
||||
});
|
||||
}
|
||||
|
||||
- (AnyPromise *)saveManifestFileToCloud
|
||||
|
@ -909,21 +907,15 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
OWSBackupExportItem *exportItem = [OWSBackupExportItem new];
|
||||
exportItem.encryptedItem = encryptedItem;
|
||||
|
||||
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
|
||||
[OWSBackupAPI upsertManifestFileToCloudWithRecipientId:self.recipientId
|
||||
fileUrl:[NSURL fileURLWithPath:encryptedItem.filePath]
|
||||
success:^(NSString *recordName) {
|
||||
exportItem.recordName = recordName;
|
||||
self.manifestItem = exportItem;
|
||||
return [OWSBackupAPI upsertManifestFileToCloudObjcWithRecipientId:self.recipientId
|
||||
fileUrl:[NSURL fileURLWithPath:encryptedItem.filePath]]
|
||||
.thenInBackground(^(NSString *recordName) {
|
||||
exportItem.recordName = recordName;
|
||||
self.manifestItem = exportItem;
|
||||
|
||||
// All files have been saved to the cloud.
|
||||
resolve(@(1));
|
||||
}
|
||||
failure:^(NSError *error) {
|
||||
// The manifest file is critical so any error uploading them is unrecoverable.
|
||||
resolve(error);
|
||||
}];
|
||||
}];
|
||||
// All files have been saved to the cloud.
|
||||
return [AnyPromise promiseWithValue:@(1)];
|
||||
});
|
||||
}
|
||||
|
||||
- (nullable OWSBackupEncryptedItem *)writeManifestFile
|
||||
|
|
Loading…
Reference in New Issue