//
// C o p y r i g h t ( c ) 2 0 1 8 O p e n W h i s p e r S y s t e m s . A l l r i g h t s r e s e r v e d .
//
import Foundation
import SignalServiceKit
import CloudKit
@objc public class OWSBackupAPI : NSObject {
@objc
public class func recordIdForTest ( ) -> String {
return " test- \( NSUUID ( ) . uuidString ) "
}
// @ o b j c
// p u b l i c c l a s s f u n c r e c o r d N a m e F o r T e s t ( v a l u e : T S A t t a c h m e n t S t r e a m ) - > S t r i n g {
// g u a r d l e t u n i q u e I d = v a l u e . u n i q u e I d e l s e {
// o w s F a i l ( " D a t a b a s e e n t i t y m i s s i n g u n i q u e I d . " )
// r e t u r n " u n k n o w n "
// }
// r e t u r n " a t t a c h m e n t - s t r e a m - \ ( u n i q u e I d ) "
// }
@objc
public class func saveTestFileToCloud ( fileUrl : URL ,
success : @ escaping ( String ) -> Swift . Void ,
failure : @ escaping ( Error ) -> Swift . Void ) {
saveFileToCloud ( fileUrl : fileUrl ,
recordName : NSUUID ( ) . uuidString ,
recordType : " test " ,
success : success ,
failure : failure )
}
// " E p h e m e r a l " f i l e s a r e s p e c i f i c t o t h i s b a c k u p e x p o r t a n d w i l l a l w a y s n e e d t o
// b e s a v e d . F o r e x a m p l e , a c o m p l e t e i m a g e o f t h e d a t a b a s e i s e x p o r t e d e a c h t i m e .
// W e w o u l d n ' t w a n t t o o v e r w r i t e p r e v i o u s i m a g e s u n t i l t h e e n t i r e b a c k u p e x p o r t i s
// c o m p l e t e .
@objc
public class func saveEphemeralDatabaseFileToCloud ( fileUrl : URL ,
success : @ escaping ( String ) -> Swift . Void ,
failure : @ escaping ( Error ) -> Swift . Void ) {
saveFileToCloud ( fileUrl : fileUrl ,
recordName : NSUUID ( ) . uuidString ,
recordType : " ephemeralFile " ,
success : success ,
failure : failure )
}
// " P e r s i s t e n t " f i l e s m a y b e s h a r e d b e t w e e n b a c k u p e x p o r t ; t h e y s h o u l d o n l y b e s a v e d
// o n c e . F o r e x a m p l e , a t t a c h m e n t f i l e s s h o u l d o n l y b e u p l o a d e d o n c e . S u b s e q u e n t
//
@objc
public class func savePersistentFileOnceToCloud ( fileId : String ,
fileUrlBlock : @ escaping ( Swift . Void ) -> URL ? ,
success : @ escaping ( String ) -> Swift . Void ,
failure : @ escaping ( Error ) -> Swift . Void ) {
saveFileOnceToCloud ( recordName : " persistentFile- \( fileId ) " ,
recordType : " persistentFile " ,
fileUrlBlock : fileUrlBlock ,
success : success ,
failure : failure )
}
// TODO:
static let manifestRecordName = " manifest_ "
static let manifestRecordType = " manifest "
static let payloadKey = " payload "
@objc
public class func upsertAttachmentToCloud ( fileUrl : URL ,
success : @ escaping ( String ) -> Swift . Void ,
failure : @ escaping ( Error ) -> Swift . Void ) {
// W e w a n t t o u s e a w e l l - k n o w n r e c o r d i d a n d t y p e f o r m a n i f e s t f i l e s .
upsertFileToCloud ( fileUrl : fileUrl ,
recordName : manifestRecordName ,
recordType : manifestRecordType ,
success : success ,
failure : failure )
}
@objc
public class func upsertManifestFileToCloud ( fileUrl : URL ,
success : @ escaping ( String ) -> Swift . Void ,
failure : @ escaping ( Error ) -> Swift . Void ) {
// W e w a n t t o u s e a w e l l - k n o w n r e c o r d i d a n d t y p e f o r m a n i f e s t f i l e s .
upsertFileToCloud ( fileUrl : fileUrl ,
recordName : manifestRecordName ,
recordType : manifestRecordType ,
success : success ,
failure : failure )
}
@objc
public class func saveFileToCloud ( fileUrl : URL ,
recordName : String ,
recordType : String ,
success : @ escaping ( String ) -> Swift . Void ,
failure : @ escaping ( Error ) -> Swift . Void ) {
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 )
}
@objc
public class func saveRecordToCloud ( record : CKRecord ,
success : @ escaping ( String ) -> Swift . Void ,
failure : @ escaping ( Error ) -> Swift . Void ) {
let myContainer = CKContainer . default ( )
let privateDatabase = myContainer . privateCloudDatabase
privateDatabase . save ( record ) {
( record , error ) in
if let error = error {
Logger . error ( " \( self . logTag ) error saving record: \( error ) " )
failure ( error )
} else {
guard let recordName = record ? . recordID . recordName else {
Logger . error ( " \( self . logTag ) error retrieving saved record's name. " )
failure ( OWSErrorWithCodeDescription ( . exportBackupError ,
NSLocalizedString ( " BACKUP_EXPORT_ERROR_SAVE_FILE_TO_CLOUD_FAILED " ,
comment : " Error indicating the a backup export failed to save a file to the cloud. " ) ) )
return
}
Logger . info ( " \( self . logTag ) saved record. " )
success ( recordName )
}
}
}
@objc
public class func upsertFileToCloud ( fileUrl : URL ,
recordName : String ,
recordType : String ,
success : @ escaping ( String ) -> Swift . Void ,
failure : @ escaping ( Error ) -> Swift . Void ) {
let recordId = CKRecordID ( recordName : recordName )
let fetchOperation = CKFetchRecordsOperation ( recordIDs : [ recordId ] )
fetchOperation . perRecordCompletionBlock = { ( record , recordId , error ) in
if let error = error {
if let ckerror = error as ? CKError {
if ckerror . code = = . unknownItem {
// N o r e c o r d f o u n d t o u p d a t e , s a v i n g n e w r e c o r d .
saveFileToCloud ( fileUrl : fileUrl ,
recordName : recordName ,
recordType : recordType ,
success : success ,
failure : failure )
return
}
Logger . error ( " \( self . logTag ) error fetching record: \( error ) \( ckerror . code ) . " )
} else {
Logger . error ( " \( self . logTag ) error fetching record: \( error ) . " )
}
failure ( error )
return
}
guard let record = record else {
Logger . error ( " \( self . logTag ) error missing record. " )
Logger . flush ( )
failure ( OWSErrorWithCodeDescription ( . exportBackupError ,
NSLocalizedString ( " BACKUP_EXPORT_ERROR_SAVE_FILE_TO_CLOUD_FAILED " ,
comment : " Error indicating the a backup export failed to save a file to the cloud. " ) ) )
return
}
Logger . verbose ( " \( self . logTag ) updating record. " )
let asset = CKAsset ( fileURL : fileUrl )
record [ payloadKey ] = asset
saveRecordToCloud ( record : record ,
success : success ,
failure : failure )
}
let myContainer = CKContainer . default ( )
let privateDatabase = myContainer . privateCloudDatabase
privateDatabase . add ( fetchOperation )
}
@objc
public class func saveFileOnceToCloud ( recordName : String ,
recordType : String ,
fileUrlBlock : @ escaping ( Swift . Void ) -> URL ? ,
success : @ escaping ( String ) -> Swift . Void ,
failure : @ escaping ( Error ) -> Swift . Void ) {
let recordId = CKRecordID ( recordName : recordName )
let fetchOperation = CKFetchRecordsOperation ( recordIDs : [ recordId ] )
fetchOperation . perRecordCompletionBlock = { ( record , recordId , error ) in
if let error = error {
if let ckerror = error as ? CKError {
if ckerror . code = = . unknownItem {
// N o r e c o r d f o u n d t o u p d a t e , s a v i n g n e w r e c o r d .
guard let fileUrl = fileUrlBlock ( ) else {
Logger . error ( " \( self . logTag ) error preparing file for upload: \( error ) . " )
return
}
saveFileToCloud ( fileUrl : fileUrl ,
recordName : recordName ,
recordType : recordType ,
success : success ,
failure : failure )
return
}
Logger . error ( " \( self . logTag ) error fetching record: \( error ) \( ckerror . code ) . " )
} else {
Logger . error ( " \( self . logTag ) error fetching record: \( error ) . " )
}
failure ( error )
return
}
Logger . info ( " \( self . logTag ) record already exists; skipping save. " )
success ( recordName )
}
let myContainer = CKContainer . default ( )
let privateDatabase = myContainer . privateCloudDatabase
privateDatabase . add ( fetchOperation )
}
@objc
public class func checkCloudKitAccess ( completion : @ escaping ( Bool ) -> Swift . Void ) {
CKContainer . default ( ) . accountStatus ( completionHandler : { ( accountStatus , error ) in
DispatchQueue . main . async {
switch accountStatus {
case . couldNotDetermine :
Logger . error ( " \( self . logTag ) could not determine CloudKit account status: \( String ( describing : error ) ) . " )
OWSAlerts . showErrorAlert ( withMessage : NSLocalizedString ( " CLOUDKIT_STATUS_COULD_NOT_DETERMINE " , comment : " Error indicating that the app could not determine that user's CloudKit account status " ) )
completion ( false )
case . noAccount :
Logger . error ( " \( self . logTag ) no CloudKit account. " )
OWSAlerts . showErrorAlert ( withMessage : NSLocalizedString ( " CLOUDKIT_STATUS_NO_ACCOUNT " , comment : " Error indicating that user does not have an iCloud account. " ) )
completion ( false )
case . restricted :
Logger . error ( " \( self . logTag ) restricted CloudKit account. " )
OWSAlerts . showErrorAlert ( withMessage : NSLocalizedString ( " CLOUDKIT_STATUS_RESTRICTED " , comment : " Error indicating that the app was prevented from accessing the user's CloudKit account. " ) )
completion ( false )
case . available :
completion ( true )
}
}
} )
}
}