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,27 @@ cls->getStaticMethod("getNotificationsCryptoAccountPath"); return method(cls)->toStdString(); } + + static std::string getBackupDirectoryPath() { + static const auto cls = javaClassStatic(); + static auto method = + cls->getStaticMethod("getBackupDirectoryPath"); + 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 +76,28 @@ return path; } +std::string PlatformSpecificTools::getBackupDirectoryPath() { + std::string path; + NativeAndroidAccessProvider::runTask([&path]() { + path = PlatformSpecificToolsJavaClass::getBackupDirectoryPath(); + }); + 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,12 @@ import android.content.Context; import android.util.Log; import app.comm.android.MainApplication; +import java.io.File; +import java.lang.SecurityException; import java.nio.charset.StandardCharsets; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; + public class PlatformSpecificTools { static SecureRandom secureRandom = new SecureRandom(); @@ -26,4 +29,73 @@ .getFileStreamPath("comm_notifications_crypto_account") .getPath(); } + + public 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"); + + try { + File backupDirectory = new File(backupDirPath); + if (!backupDirectory.exists() && !backupDirectory.mkdirs()) { + throw new RuntimeException("Failed to create backup directory."); + } + return backupDirPath; + } catch (SecurityException | NullPointerException e) { + throw new RuntimeException( + "Failed to check if backup directory exists or to attempt its creation. Details: " + + e.getMessage()); + } + } + + public static String + getBackupFilePath(String backupID, boolean isAttachments) { + String backupDirPath = PlatformSpecificTools.getBackupDirectoryPath(); + + String filename; + if (isAttachments) { + filename = String.join("_", "backup", backupID, "attachments"); + } else { + filename = String.join("_", "backup", backupID); + } + return String.join(File.separator, backupDirPath, filename); + } + + 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()) { + throw new RuntimeException("Failed to remove backup directory."); + } else if (files == null) { + return; + } + + // Backup directory structure is supposed to be flat. + for (File file : files) { + if (!file.delete()) { + throw new RuntimeException( + "Failed to remove backup file at path: " + file.getPath()); + } + } + + if (!backupDirectory.delete()) { + throw new RuntimeException("Failed to remove backup directory."); + } + } catch (NullPointerException | SecurityException 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,10 @@ static void generateSecureRandomBytes(crypto::OlmBuffer &buffer, size_t size); static std::string getDeviceOS(); static std::string getNotificationsCryptoAccountPath(); + static std::string getBackupDirectoryPath(); + 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,74 @@ .path UTF8String]); } +NSURL *getBackupDirAsURL() { + 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. " + "Details: " + + std::string([err.localizedDescription UTF8String])); + } + + 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])); + } + return backupDir; +} + +std::string PlatformSpecificTools::getBackupDirectoryPath() { + return [getBackupDirAsURL().path UTF8String]; +} + +std::string PlatformSpecificTools::getBackupFilePath( + std::string backupID, + bool isAttachments) { + + NSURL *backupDir = getBackupDirAsURL(); + NSString *backupIDObjC = [NSString stringWithCString:backupID.c_str() + encoding:NSUTF8StringEncoding]; + NSString *filename; + if (isAttachments) { + filename = [@[ @"backup", backupIDObjC, @"attachments" ] + componentsJoinedByString:@"_"]; + } else { + filename = [@[ @"backup", backupIDObjC ] componentsJoinedByString:@"_"]; + } + return [[backupDir URLByAppendingPathComponent:filename].path UTF8String]; +} + +void PlatformSpecificTools::removeBackupDirectory() { + NSURL *backupDir = getBackupDirAsURL(); + 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