| 
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -57,6 +57,16 @@ import PromiseKit
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                               recordType: signalBackupRecordType)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // "Ephemeral" files are specific to this backup export and will always need to
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // be saved.  For example, a complete image of the database is exported each time.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // We wouldn't want to overwrite previous images until the entire backup export is
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // complete.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @objc
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    public class func recordNameForEphemeralFile(recipientId: String,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                 label: String) -> String {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return "\(recordNamePrefix(forRecipientId: recipientId))ephemeral-\(label)-\(NSUUID().uuidString)"
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // "Ephemeral" files are specific to this backup export and will always need to
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // be saved.  For example, a complete image of the database is exported each time.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // We wouldn't want to overwrite previous images until the entire backup export is
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -73,7 +83,7 @@ import PromiseKit
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    public class func saveEphemeralFileToCloud(recipientId: String,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                               label: String,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                               fileUrl: URL) -> Promise<String> {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let recordName = "\(recordNamePrefix(forRecipientId: recipientId))ephemeral-\(label)-\(NSUUID().uuidString)"
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let recordName = recordNameForEphemeralFile(recipientId: recipientId, label: label)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return saveFileToCloud(fileUrl: fileUrl,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                               recordName: recordName,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                               recordType: signalBackupRecordType)
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -181,11 +191,10 @@ import PromiseKit
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @objc
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    public class func saveFileToCloudObjc(fileUrl: URL,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                          recordName: String,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                          recordType: String) -> AnyPromise {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                          recordName: String) -> AnyPromise {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return AnyPromise(saveFileToCloud(fileUrl: fileUrl,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                          recordName: recordName,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                          recordType: recordType))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                          recordType: signalBackupRecordType))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    public class func saveFileToCloud(fileUrl: URL,
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -216,7 +225,7 @@ import PromiseKit
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return Promise { resolver in
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            let saveOperation = CKModifyRecordsOperation(recordsToSave: [record ], recordIDsToDelete: nil)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            saveOperation.modifyRecordsCompletionBlock = { (records, recordIds, error) in
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            saveOperation.modifyRecordsCompletionBlock = { (_, _, error) in
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                let outcome = outcomeForCloudKitError(error: error,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                      remainingRetries: remainingRetries,
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -264,6 +273,99 @@ import PromiseKit
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @objc
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    public class func record(forFileUrl fileUrl: URL,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                             recordName: String) -> CKRecord {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let recordType = signalBackupRecordType
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let recordID = CKRecordID(recordName: recordName)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let record = CKRecord(recordType: recordType, recordID: recordID)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let asset = CKAsset(fileURL: fileUrl)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        record[payloadKey] = asset
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return record
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @objc
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    public class func saveRecordsToCloudObjc(records: [CKRecord]) -> AnyPromise {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return AnyPromise(saveRecordsToCloud(records: records))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    public class func saveRecordsToCloud(records: [CKRecord]) -> Promise<Void> {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return saveRecordsToCloud(records: records,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                  remainingRetries: maxRetries)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    private class func saveRecordsToCloud(records: [CKRecord],
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                          remainingRetries: Int) -> Promise<Void> {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let recordNames = records.map { (record) in
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return record.recordID.recordName
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        Logger.verbose("recordNames \(recordNames)")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return Promise { resolver in
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            let saveOperation = CKModifyRecordsOperation(recordsToSave: records, recordIDsToDelete: nil)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            saveOperation.modifyRecordsCompletionBlock = { (savedRecords: [CKRecord]?, _, error) in
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                let retry = {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    // Only retry records which didn't already succeed.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    var savedRecordNames = [String]()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    if let savedRecords = savedRecords {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        savedRecordNames = savedRecords.map { (record) in
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            return record.recordID.recordName
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    let retryRecords = records.filter({ (record) in
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        return !savedRecordNames.contains(record.recordID.recordName)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    })
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    saveRecordsToCloud(records: retryRecords,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                       remainingRetries: remainingRetries - 1)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        .done { _ in
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            resolver.fulfill(())
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        }.catch { (error) in
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            resolver.reject(error)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        }.retainUntilComplete()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                let outcome = outcomeForCloudKitError(error: error,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                      remainingRetries: remainingRetries,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                      label: "Save Record")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                switch outcome {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                case .success:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    resolver.fulfill(())
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                case .failureDoNotRetry(let outcomeError):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    resolver.reject(outcomeError)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                case .failureRetryAfterDelay(let retryDelay):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + retryDelay, execute: {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        retry()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    })
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                case .failureRetryWithoutDelay:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    DispatchQueue.global().async {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        retry()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                case .unknownItem:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    owsFailDebug("unexpected CloudKit response.")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    resolver.reject(invalidServiceResponseError())
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            saveOperation.isAtomic = false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            saveOperation.savePolicy = .allKeys
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // TODO: use perRecordProgressBlock and perRecordCompletionBlock.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				//            open var perRecordProgressBlock: ((CKRecord, Double) -> Void)?
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				//            open var perRecordCompletionBlock: ((CKRecord, Error?) -> Void)?
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // These APIs are only available in iOS 9.3 and later.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if #available(iOS 9.3, *) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                saveOperation.isLongLived = true
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                saveOperation.qualityOfService = .background
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            database().add(saveOperation)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // Compare:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // * An "upsert" creates a new record if none exists and
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    //   or updates if there is an existing record.
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
						
					 | 
				
			
			 | 
			 | 
			
				
 
 |