diff --git a/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt b/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt index b11e3f6..a277b47 100644 --- a/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt +++ b/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt @@ -828,7 +828,8 @@ class MessageRepository private constructor(private val context: Context) { for (attachment in attachments) { val jsonObj = JSONObject() - // Для MESSAGES типа расшифровываем и re-encrypt + // ⚠️ НЕ сохраняем blob для IMAGE/FILE - слишком большие (SQLite CursorWindow 2MB limit) + // Только MESSAGES (reply) сохраняем - они небольшие if (attachment.type == AttachmentType.MESSAGES && attachment.blob.isNotEmpty()) { try { // 1. Расшифровываем с ChaCha ключом сообщения @@ -842,7 +843,7 @@ class MessageRepository private constructor(private val context: Context) { // 2. Re-encrypt с приватным ключом для хранения (как в Desktop Архиве) val reEncryptedBlob = CryptoManager.encryptWithPassword(decryptedBlob, privateKey) - // 3. Сохраняем ЗАШИФРОВАННЫЙ blob в БД + // 3. Сохраняем ЗАШИФРОВАННЫЙ blob в БД (только для MESSAGES - они небольшие) jsonObj.put("id", attachment.id) jsonObj.put("blob", reEncryptedBlob) // 🔒 Зашифрован приватным ключом! jsonObj.put("type", attachment.type.value) @@ -850,27 +851,27 @@ class MessageRepository private constructor(private val context: Context) { jsonObj.put("width", attachment.width) jsonObj.put("height", attachment.height) } else { - // Fallback - сохраняем как есть + // Fallback - пустой blob для IMAGE/FILE jsonObj.put("id", attachment.id) - jsonObj.put("blob", attachment.blob) + jsonObj.put("blob", "") jsonObj.put("type", attachment.type.value) jsonObj.put("preview", attachment.preview) jsonObj.put("width", attachment.width) jsonObj.put("height", attachment.height) } } catch (e: Exception) { - // Fallback - сохраняем как есть + // Fallback - пустой blob jsonObj.put("id", attachment.id) - jsonObj.put("blob", attachment.blob) + jsonObj.put("blob", "") jsonObj.put("type", attachment.type.value) jsonObj.put("preview", attachment.preview) jsonObj.put("width", attachment.width) jsonObj.put("height", attachment.height) } } else { - // Для других типов сохраняем как есть + // Для IMAGE/FILE - НЕ сохраняем blob (пустой) jsonObj.put("id", attachment.id) - jsonObj.put("blob", attachment.blob) + jsonObj.put("blob", "") // Пустой blob для IMAGE/FILE jsonObj.put("type", attachment.type.value) jsonObj.put("preview", attachment.preview) jsonObj.put("width", attachment.width) diff --git a/app/src/main/java/com/rosetta/messenger/database/RosettaDatabase.kt b/app/src/main/java/com/rosetta/messenger/database/RosettaDatabase.kt index 41b997f..764409e 100644 --- a/app/src/main/java/com/rosetta/messenger/database/RosettaDatabase.kt +++ b/app/src/main/java/com/rosetta/messenger/database/RosettaDatabase.kt @@ -15,7 +15,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase BlacklistEntity::class, AvatarCacheEntity::class ], - version = 9, + version = 10, exportSchema = false ) abstract class RosettaDatabase : RoomDatabase() { @@ -83,6 +83,21 @@ abstract class RosettaDatabase : RoomDatabase() { """) } } + + /** + * 🔥 МИГРАЦИЯ 9->10: Повторная очистка blob из attachments + * Для пользователей которые уже были на версии 9 + */ + private val MIGRATION_9_10 = object : Migration(9, 10) { + override fun migrate(database: SupportSQLiteDatabase) { + // Очищаем все attachments с большими blob'ами + database.execSQL(""" + UPDATE messages + SET attachments = '[]' + WHERE length(attachments) > 10000 + """) + } + } fun getDatabase(context: Context): RosettaDatabase { return INSTANCE ?: synchronized(this) { @@ -92,7 +107,7 @@ abstract class RosettaDatabase : RoomDatabase() { "rosetta_secure.db" ) .setJournalMode(JournalMode.WRITE_AHEAD_LOGGING) // WAL mode for performance - .addMigrations(MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9) + .addMigrations(MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9, MIGRATION_9_10) .fallbackToDestructiveMigration() // Для разработки - только если миграция не найдена .build() INSTANCE = instance diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt index 7b86012..bde74f7 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt @@ -1268,7 +1268,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { updateMessageStatus(messageId, MessageStatus.SENT) } - // 4. 💾 Сохранение в БД с attachments (зашифрованный blob для MESSAGES) + // 4. 💾 Сохранение в БД с attachments + // ⚠️ НЕ сохраняем blob для IMAGE/FILE - слишком большие (SQLite CursorWindow 2MB limit) + // Только MESSAGES (reply) сохраняем - они небольшие val attachmentsJson = if (messageAttachments.isNotEmpty()) { JSONArray().apply { messageAttachments.forEach { att -> @@ -1276,8 +1278,13 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { put("id", att.id) put("type", att.type.value) put("preview", att.preview) - // 🔥 Для MESSAGES сохраняем зашифрованный приватным ключом, для остальных - как есть - put("blob", if (att.type == AttachmentType.MESSAGES) replyBlobForDatabase else att.blob) + // Только для MESSAGES сохраняем blob (reply data небольшие) + // Для IMAGE/FILE - пустой blob + val blobToSave = when (att.type) { + AttachmentType.MESSAGES -> replyBlobForDatabase ?: "" + else -> "" // IMAGE, FILE - не сохраняем blob + } + put("blob", blobToSave) }) } }.toString() @@ -1397,13 +1404,14 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { updateMessageStatus(messageId, MessageStatus.SENT) } - // Сохраняем в БД с зашифрованным blob'ом + // ⚠️ НЕ сохраняем blob в БД - он слишком большой (SQLite CursorWindow 2MB limit) + // Изображение должно храниться в файловой системе или загружаться с сервера при необходимости val attachmentsJson = JSONArray().apply { put(JSONObject().apply { put("id", imageAttachment.id) put("type", AttachmentType.IMAGE.value) put("preview", blurhash) - put("blob", imageBlobForDatabase) + put("blob", "") // Пустой blob - не сохраняем в БД! }) }.toString() @@ -1521,12 +1529,14 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { updateMessageStatus(messageId, MessageStatus.SENT) } + // ⚠️ НЕ сохраняем blob в БД - он слишком большой (SQLite CursorWindow 2MB limit) + // Файл должен храниться в файловой системе или загружаться с сервера при необходимости val attachmentsJson = JSONArray().apply { put(JSONObject().apply { put("id", fileAttachment.id) put("type", AttachmentType.FILE.value) put("preview", preview) - put("blob", fileBlobForDatabase) + put("blob", "") // Пустой blob - не сохраняем в БД! }) }.toString()