diff --git a/native/android/app/src/cpp/PlatformSpecificTools.cpp b/native/android/app/src/cpp/PlatformSpecificTools.cpp --- a/native/android/app/src/cpp/PlatformSpecificTools.cpp +++ b/native/android/app/src/cpp/PlatformSpecificTools.cpp @@ -31,6 +31,20 @@ cls->getStaticMethod("getNotificationsCryptoAccountPath"); return method(cls)->toStdString(); } + + static std::string + getBackupFilePath(std::string backupID, bool isAttachments) { + static const auto cls = javaClassStatic(); + static auto method = + cls->getStaticMethod("getBackupFilePath"); + return method(cls, backupID, isAttachments)->toStdString(); + } + + static void removeBackupDirectory() { + static const auto cls = javaClassStatic(); + static auto method = cls->getStaticMethod("removeBackupDirectory"); + method(cls); + } }; namespace comm { @@ -55,4 +69,20 @@ return path; } +std::string PlatformSpecificTools::getBackupFilePath( + std::string backupID, + bool isAttachments) { + std::string path; + NativeAndroidAccessProvider::runTask([&path, backupID, isAttachments]() { + path = PlatformSpecificToolsJavaClass::getBackupFilePath( + backupID, isAttachments); + }); + return path; +} + +void PlatformSpecificTools::removeBackupDirectory() { + NativeAndroidAccessProvider::runTask( + []() { PlatformSpecificToolsJavaClass::removeBackupDirectory(); }); +} + } // namespace comm diff --git a/native/android/app/src/main/java/app/comm/android/fbjni/PlatformSpecificTools.java b/native/android/app/src/main/java/app/comm/android/fbjni/PlatformSpecificTools.java --- a/native/android/app/src/main/java/app/comm/android/fbjni/PlatformSpecificTools.java +++ b/native/android/app/src/main/java/app/comm/android/fbjni/PlatformSpecificTools.java @@ -3,9 +3,11 @@ import android.content.Context; import android.util.Log; import app.comm.android.MainApplication; +import java.io.File; import java.nio.charset.StandardCharsets; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; + public class PlatformSpecificTools { static SecureRandom secureRandom = new SecureRandom(); @@ -26,4 +28,63 @@ .getFileStreamPath("comm_notifications_crypto_account") .getPath(); } + + private static String getBackupDirectoryPath() { + Context mainApplicationContext = + MainApplication.getMainApplicationContext(); + if (mainApplicationContext == null) { + throw new RuntimeException( + "Failed to resolve backup path - main application context not initialized."); + } + + String filesDirPath = mainApplicationContext.getFilesDir().getPath(); + String backupDirPath = String.join(File.separator, filesDirPath, "backup"); + return backupDirPath; + } + + public static String + getBackupFilePath(String backupID, boolean isAttachments) { + String backupDirPath = PlatformSpecificTools.getBackupDirectoryPath(); + + try { + File backupDirectory = new File(backupDirPath); + if (!backupDirectory.exists()) { + backupDirectory.mkdirs(); + } + } catch (Exception e) { + throw new RuntimeException( + "Failed to create backup directory. Details: " + e.getMessage()); + } + + if (isAttachments) { + return String.join( + File.separator, backupDirPath, "backup-id-attachments-" + backupID); + } + return String.join(File.separator, backupDirPath, "backup-id-" + backupID); + } + + public static void removeBackupDirectory() { + String backupDirPath = PlatformSpecificTools.getBackupDirectoryPath(); + try { + File backupDirectory = new File(backupDirPath); + if (!backupDirectory.exists()) { + return; + } + + File[] files = backupDirectory.listFiles(); + if (files == null) { + backupDirectory.delete(); + return; + } + + // Backup directory structure is supposed to be flat. + for (File file : files) { + file.delete(); + } + backupDirectory.delete(); + } catch (Exception e) { + throw new RuntimeException( + "Failed to remove backup directory. Details: " + e.getMessage()); + } + } } diff --git a/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp b/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp --- a/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp +++ b/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp @@ -2,6 +2,7 @@ #include "../Notifications/BackgroundDataStorage/NotificationsCryptoModule.h" #include "../Tools/CommSecureStore.h" #include "Logger.h" +#include "PlatformSpecificTools.h" #include "SQLiteQueryExecutor.h" namespace comm { @@ -34,6 +35,7 @@ CommSecureStore::set(CommSecureStore::deviceID, ""); CommSecureStore::set(CommSecureStore::commServicesAccessToken, ""); SQLiteQueryExecutor::clearSensitiveData(); + PlatformSpecificTools::removeBackupDirectory(); NotificationsCryptoModule::clearSensitiveData(); DatabaseManager::setDatabaseStatusAsWorkable(); } diff --git a/native/cpp/CommonCpp/Tools/PlatformSpecificTools.h b/native/cpp/CommonCpp/Tools/PlatformSpecificTools.h --- a/native/cpp/CommonCpp/Tools/PlatformSpecificTools.h +++ b/native/cpp/CommonCpp/Tools/PlatformSpecificTools.h @@ -9,6 +9,9 @@ static void generateSecureRandomBytes(crypto::OlmBuffer &buffer, size_t size); static std::string getDeviceOS(); static std::string getNotificationsCryptoAccountPath(); + static std::string + getBackupFilePath(std::string backupID, bool isAttachments); + static void removeBackupDirectory(); }; } // namespace comm diff --git a/native/ios/Comm/PlatformSpecificTools.mm b/native/ios/Comm/PlatformSpecificTools.mm --- a/native/ios/Comm/PlatformSpecificTools.mm +++ b/native/ios/Comm/PlatformSpecificTools.mm @@ -37,4 +37,80 @@ .path UTF8String]); } +std::string PlatformSpecificTools::getBackupFilePath( + std::string backupID, + bool isAttachments) { + + NSError *err = nil; + NSURL *documentsUrl = + [NSFileManager.defaultManager URLForDirectory:NSDocumentDirectory + inDomain:NSUserDomainMask + appropriateForURL:nil + create:false + error:&err]; + if (err) { + NSLog(@"Error: %@", err); + throw std::runtime_error( + "Failed to resolve backup path - could not find documentsUrl"); + } + + NSURL *backupDir = [documentsUrl URLByAppendingPathComponent:@"backup"]; + + NSError *backupDirCreateError = nil; + if (![NSFileManager.defaultManager fileExistsAtPath:backupDir.path]) { + [NSFileManager.defaultManager createDirectoryAtURL:backupDir + withIntermediateDirectories:YES + attributes:nil + error:&backupDirCreateError]; + } + if (backupDirCreateError) { + throw std::runtime_error( + "Failed to create backup directory. Details: " + + std::string([backupDirCreateError.localizedDescription UTF8String])); + } + + NSString *backupIDObjC = [NSString stringWithCString:backupID.c_str() + encoding:NSUTF8StringEncoding]; + if (isAttachments) { + return [[backupDir + URLByAppendingPathComponent: + [@"backup-id-attachments-" + stringByAppendingString:backupIDObjC]].path UTF8String]; + } + return [[backupDir + URLByAppendingPathComponent: + [@"backup-id-" + stringByAppendingString:backupIDObjC]].path UTF8String]; +} + +void PlatformSpecificTools::removeBackupDirectory() { + NSError *err = nil; + NSURL *documentsUrl = + [NSFileManager.defaultManager URLForDirectory:NSDocumentDirectory + inDomain:NSUserDomainMask + appropriateForURL:nil + create:false + error:&err]; + if (err) { + NSLog(@"Error: %@", err); + throw std::runtime_error( + "Failed to resolve backup path - could not find documentsUrl"); + } + + NSURL *backupDir = [documentsUrl URLByAppendingPathComponent:@"backup"]; + if (![NSFileManager.defaultManager fileExistsAtPath:backupDir.path]) { + return; + } + + NSError *backupDirRemovalError = nil; + [NSFileManager.defaultManager removeItemAtURL:backupDir + error:&backupDirRemovalError]; + + if (backupDirRemovalError) { + throw std::runtime_error( + "Failed to remove backup directory. Details: " + + std::string([backupDirRemovalError.localizedDescription UTF8String])); + } +} + }; // namespace comm