|
|
|
@ -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) {
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
recordType: String) -> Promise<String> {
|
|
|
|
|
|
|
|
|
|
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> {
|
|
|
|
|
|
|
|
|
|
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.")))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|