mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-23 19:23:36 +00:00
android, desktop, ios: refuse migration before exporting data over the upload limit
This commit is contained in:
1 parent
cf5acd0ce3
commit
6b28a254a4
7 files changed
+63
-30
No files matched your search
@@ -475,7 +475,7 @@ struct DatabaseView: View {
|
||||
await MainActor.run {
|
||||
progressIndicator = true
|
||||
}
|
||||
if let s = await exportStorageShortfall() {
|
||||
if let s = await exportStorageShortfall(await exportDataBytes()) {
|
||||
await MainActor.run {
|
||||
alert = .lowStorage(required: s.required, available: s.available)
|
||||
progressIndicator = false
|
||||
|
||||
@@ -473,7 +473,13 @@ struct MigrateFromDevice: View {
|
||||
|
||||
private func exportArchive() {
|
||||
Task {
|
||||
if let s = await exportStorageShortfall() {
|
||||
let dataBytes = await exportDataBytes()
|
||||
if dataBytes > MAX_FILE_SIZE_XFTP_HARD {
|
||||
await MainActor.run {
|
||||
alert = databaseTooLargeAlert(dataBytes)
|
||||
migrationState = .uploadConfirmation
|
||||
}
|
||||
} else if let s = await exportStorageShortfall(dataBytes) {
|
||||
await MainActor.run { alert = .lowStorage(required: s.required, available: s.available) }
|
||||
} else {
|
||||
await archiveAndUpload()
|
||||
@@ -489,14 +495,7 @@ struct MigrateFromDevice: View {
|
||||
if archiveBytes > MAX_FILE_SIZE_XFTP_HARD {
|
||||
try? FileManager.default.removeItem(at: archivePath)
|
||||
await MainActor.run {
|
||||
alert = .error(
|
||||
title: "Database is too large",
|
||||
error: String.localizedStringWithFormat(
|
||||
NSLocalizedString("The exported archive (%@) is larger than the maximum size supported for migration (%@).", comment: "migration alert"),
|
||||
ByteCountFormatter.string(fromByteCount: archiveBytes, countStyle: .binary),
|
||||
ByteCountFormatter.string(fromByteCount: MAX_FILE_SIZE_XFTP_HARD, countStyle: .binary)
|
||||
)
|
||||
)
|
||||
alert = databaseTooLargeAlert(archiveBytes)
|
||||
migrationState = .uploadConfirmation
|
||||
}
|
||||
} else if errs.isEmpty {
|
||||
@@ -815,9 +814,23 @@ struct MigrateFromDevice_Previews: PreviewProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private func databaseTooLargeAlert(_ bytes: Int64) -> MigrateFromDeviceViewAlert {
|
||||
.error(
|
||||
title: "Database is too large",
|
||||
error: String.localizedStringWithFormat(
|
||||
NSLocalizedString("The database (%@) is larger than the maximum size supported for migration (%@).", comment: "migration alert"),
|
||||
ByteCountFormatter.string(fromByteCount: bytes, countStyle: .binary),
|
||||
ByteCountFormatter.string(fromByteCount: MAX_FILE_SIZE_XFTP_HARD, countStyle: .binary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func exportDataBytes() async -> Int64 {
|
||||
await Task.detached { estimatedExportBytes() }.value
|
||||
}
|
||||
|
||||
// export transiently holds a copy of the data and the archive of it
|
||||
func exportStorageShortfall() async -> (required: Int64, available: Int64)? {
|
||||
let dataBytes = await Task.detached { estimatedExportBytes() }.value
|
||||
func exportStorageShortfall(_ dataBytes: Int64) async -> (required: Int64, available: Int64)? {
|
||||
let required = dataBytes * 2
|
||||
guard let available = availableImportantBytes(at: getDocumentsDirectory()), available < required else { return nil }
|
||||
return (required, available)
|
||||
@@ -837,12 +850,20 @@ func lowStorageAlert(_ required: Int64, _ available: Int64, onContinue: @escapin
|
||||
}
|
||||
|
||||
private func estimatedExportBytes() -> Int64 {
|
||||
let files = Int64(directoryFileCountAndSize(getAppFilesDirectory())?.1 ?? 0)
|
||||
let wallpapers = Int64(directoryFileCountAndSize(getWallpaperDirectory())?.1 ?? 0)
|
||||
let dbPrefix = getAppDatabasePath().path
|
||||
let chatDb = Int64(fileSize(URL(fileURLWithPath: dbPrefix + "_chat.db")) ?? 0)
|
||||
let agentDb = Int64(fileSize(URL(fileURLWithPath: dbPrefix + "_agent.db")) ?? 0)
|
||||
return files + wallpapers + chatDb + agentDb
|
||||
return filesSize(getAppFilesDirectory()) + filesSize(getWallpaperDirectory()) + fileContentSize(dbPrefix + "_chat.db") + fileContentSize(dbPrefix + "_agent.db")
|
||||
}
|
||||
|
||||
private func filesSize(_ dir: URL) -> Int64 {
|
||||
let urls = (try? FileManager.default.contentsOfDirectory(at: dir, includingPropertiesForKeys: [.fileSizeKey, .isRegularFileKey])) ?? []
|
||||
return urls.reduce(0) { sum, url in
|
||||
let v = try? url.resourceValues(forKeys: [.fileSizeKey, .isRegularFileKey])
|
||||
return sum + (v?.isRegularFile == true ? Int64(v?.fileSize ?? 0) : 0)
|
||||
}
|
||||
}
|
||||
|
||||
private func fileContentSize(_ path: String) -> Int64 {
|
||||
(try? FileManager.default.attributesOfItem(atPath: path)[.size] as? Int64) ?? 0
|
||||
}
|
||||
|
||||
private func availableImportantBytes(at url: URL) -> Int64? {
|
||||
|
||||
@@ -36,7 +36,7 @@ public let MAX_FILE_SIZE_XFTP_LEGEND: Int64 = 5_368_709_120 // 5GB
|
||||
// a badge raises the limit at send for this long after its expiry, shorter than the receiver's grace
|
||||
public let BADGE_SND_GRACE_INTERVAL = TimeInterval(86400)
|
||||
|
||||
// Hard limit for standalone XFTP uploads (device migration archive);
|
||||
// Hard limit for XFTP uploads (e.g. the device migration archive);
|
||||
// mirrors maxFileSizeHard (gb 5) in simplexmq Simplex/FileTransfer/Description.hs
|
||||
public let MAX_FILE_SIZE_XFTP_HARD: Int64 = 5_368_709_120 // 5GB
|
||||
|
||||
|
||||
+7
-5
@@ -605,13 +605,9 @@ fun deleteDatabaseBackups() {
|
||||
|
||||
// export holds a copy of the data in databaseExportDir while it writes the archive to archiveDir,
|
||||
// where a migration upload then also writes an encrypted copy of the archive
|
||||
suspend fun confirmExportStorage(archiveDir: File = databaseExportDir): Boolean {
|
||||
suspend fun confirmExportStorage(archiveDir: File = databaseExportDir, dataBytes: Long = exportDataBytes()): Boolean {
|
||||
databaseExportDir.mkdirs()
|
||||
archiveDir.mkdirs()
|
||||
val dataBytes = directoryFileCountAndSize(appFilesDir.absolutePath).second +
|
||||
directoryFileCountAndSize(wallpapersDir.absolutePath).second +
|
||||
File(dataDir, chatDatabaseFileName).length() +
|
||||
File(dataDir, agentDatabaseFileName).length()
|
||||
val (shortDir, requiredBytes) = listOf(archiveDir to 2L * dataBytes, databaseExportDir to dataBytes)
|
||||
.firstOrNull { (dir, needed) -> dir.usableSpace < needed } ?: return true
|
||||
val availableBytes = shortDir.usableSpace
|
||||
@@ -627,6 +623,12 @@ suspend fun confirmExportStorage(archiveDir: File = databaseExportDir): Boolean
|
||||
return proceed.await()
|
||||
}
|
||||
|
||||
fun exportDataBytes(): Long =
|
||||
directoryFileCountAndSize(appFilesDir.absolutePath).second +
|
||||
directoryFileCountAndSize(wallpapersDir.absolutePath).second +
|
||||
File(dataDir, chatDatabaseFileName).length() +
|
||||
File(dataDir, agentDatabaseFileName).length()
|
||||
|
||||
private suspend fun exportArchive(
|
||||
m: ChatModel,
|
||||
progressIndicator: MutableState<Boolean>,
|
||||
|
||||
+1
-1
@@ -133,7 +133,7 @@ const val MAX_FILE_SIZE_XFTP_LEGEND: Long = 5_368_709_120 // 5GB
|
||||
// a badge raises the limit at send for this long after its expiry, shorter than the receiver's grace
|
||||
val BADGE_SND_GRACE_INTERVAL: Duration = 1.days
|
||||
|
||||
// Hard limit for standalone XFTP uploads (e.g. device migration archive);
|
||||
// Hard limit for XFTP uploads (e.g. the device migration archive);
|
||||
// mirrors maxFileSizeHard (gb 5) in simplexmq Simplex/FileTransfer/Description.hs
|
||||
const val MAX_FILE_SIZE_XFTP_HARD: Long = 5_368_709_120 // 5GB
|
||||
|
||||
|
||||
+15
-5
@@ -479,9 +479,22 @@ private suspend fun MutableState<MigrationFromState>.verifyDatabasePassphrase(db
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDatabaseTooLargeAlert(bytes: Long) {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.migrate_from_device_database_too_large_title),
|
||||
String.format(generalGetString(MR.strings.migrate_from_device_database_too_large_desc), formatBytes(bytes), formatBytes(MAX_FILE_SIZE_XFTP_HARD))
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableState<MigrationFromState>.exportArchive() {
|
||||
withLongRunningApi {
|
||||
if (!confirmExportStorage(getMigrationTempFilesDirectory())) {
|
||||
val dataBytes = exportDataBytes()
|
||||
if (dataBytes > MAX_FILE_SIZE_XFTP_HARD) {
|
||||
showDatabaseTooLargeAlert(dataBytes)
|
||||
state = MigrationFromState.UploadConfirmation
|
||||
return@withLongRunningApi
|
||||
}
|
||||
if (!confirmExportStorage(getMigrationTempFilesDirectory(), dataBytes)) {
|
||||
state = MigrationFromState.UploadConfirmation
|
||||
return@withLongRunningApi
|
||||
}
|
||||
@@ -491,10 +504,7 @@ private fun MutableState<MigrationFromState>.exportArchive() {
|
||||
val archiveBytes = File(archivePath).length()
|
||||
if (archiveBytes > MAX_FILE_SIZE_XFTP_HARD) {
|
||||
File(archivePath).delete()
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.migrate_from_device_database_too_large_title),
|
||||
String.format(generalGetString(MR.strings.migrate_from_device_database_too_large_desc), formatBytes(archiveBytes), formatBytes(MAX_FILE_SIZE_XFTP_HARD))
|
||||
)
|
||||
showDatabaseTooLargeAlert(archiveBytes)
|
||||
state = MigrationFromState.UploadConfirmation
|
||||
} else if (archiveErrors.isEmpty()) {
|
||||
uploadArchive(archivePath)
|
||||
|
||||
@@ -2879,7 +2879,7 @@
|
||||
<string name="migrate_from_device_error_saving_settings">Error saving settings</string>
|
||||
<string name="migrate_from_device_exported_file_doesnt_exist">Exported file doesn\'t exist</string>
|
||||
<string name="migrate_from_device_database_too_large_title">Database is too large</string>
|
||||
<string name="migrate_from_device_database_too_large_desc">The exported archive (%1$s) is larger than the maximum size supported for migration (%2$s).</string>
|
||||
<string name="migrate_from_device_database_too_large_desc">The database (%1$s) is larger than the maximum size supported for migration (%2$s).</string>
|
||||
<string name="not_enough_storage_title">You may not have enough storage</string>
|
||||
<string name="not_enough_storage_desc">Exporting the database may need about %1$s of free space, but only %2$s is available. Continue anyway?</string>
|
||||
<string name="migrate_from_device_error_exporting_archive">Error exporting chat database</string>
|
||||
|
||||
Reference in new issue
Block a user