feat: Implement AttachmentFileManager for handling image attachments; add methods for saving, reading, and managing attachment files

This commit is contained in:
2026-01-26 17:43:23 +05:00
parent 44c0151294
commit 0f652bea86
3 changed files with 265 additions and 5 deletions

View File

@@ -0,0 +1,175 @@
package com.rosetta.messenger.utils
import android.content.Context
import android.util.Log
import com.rosetta.messenger.crypto.CryptoManager
import java.io.File
import java.security.MessageDigest
/**
* Менеджер для работы с файлами вложений (изображения)
* Совместимо с desktop версией:
* - Все файлы зашифрованы приватным ключом пользователя
* - Формат пути: "m/md5hash" (без расширения)
* - MD5 генерируется из (attachmentId + publicKey)
*
* ВАЖНО: Файлы (FILE тип) НЕ сохраняются локально - они слишком большие
* Они загружаются с CDN при необходимости
*/
object AttachmentFileManager {
private const val TAG = "AttachmentFileManager"
private const val ATTACHMENTS_DIR = "attachments"
/**
* Генерирует MD5-хэш для пути к файлу (как в desktop)
* @param attachmentId ID вложения
* @param publicKey Публичный ключ пользователя
* @return Путь формата "m/md5hash"
*/
fun generatePath(attachmentId: String, publicKey: String): String {
val input = attachmentId + publicKey
val md5 = MessageDigest.getInstance("MD5")
.digest(input.toByteArray())
.joinToString("") { "%02x".format(it) }
return "m/$md5"
}
/**
* Сохранить вложение (изображение) в файловую систему
* @param context Android context
* @param blob Base64-encoded данные изображения
* @param attachmentId ID вложения
* @param publicKey Публичный ключ пользователя
* @param privateKey Приватный ключ для шифрования
* @return true если успешно сохранено
*/
fun saveAttachment(
context: Context,
blob: String,
attachmentId: String,
publicKey: String,
privateKey: String
): Boolean {
return try {
if (blob.isEmpty()) {
Log.w(TAG, "💾 Empty blob, skipping save")
return false
}
val filePath = generatePath(attachmentId, publicKey)
Log.d(TAG, "💾 Saving attachment: $attachmentId -> $filePath")
// Шифруем данные приватным ключом (как в desktop)
val encrypted = CryptoManager.encryptWithPassword(blob, privateKey)
// Создаем директории
val dir = File(context.filesDir, ATTACHMENTS_DIR)
dir.mkdirs()
val parts = filePath.split("/")
if (parts.size == 2) {
val subDir = File(dir, parts[0])
subDir.mkdirs()
val file = File(subDir, parts[1])
file.writeText(encrypted)
Log.d(TAG, "💾 Saved to: ${file.absolutePath} (${encrypted.length} bytes)")
} else {
val file = File(dir, filePath)
file.writeText(encrypted)
}
true
} catch (e: Exception) {
Log.e(TAG, "❌ Error saving attachment", e)
false
}
}
/**
* Прочитать и расшифровать вложение
* @param context Android context
* @param attachmentId ID вложения
* @param publicKey Публичный ключ пользователя
* @param privateKey Приватный ключ для расшифровки
* @return Base64-encoded данные или null если не найдено
*/
fun readAttachment(
context: Context,
attachmentId: String,
publicKey: String,
privateKey: String
): String? {
return try {
val filePath = generatePath(attachmentId, publicKey)
val dir = File(context.filesDir, ATTACHMENTS_DIR)
val file = File(dir, filePath)
if (!file.exists()) {
Log.d(TAG, "📖 File not found: $filePath")
return null
}
val encrypted = file.readText()
val decrypted = CryptoManager.decryptWithPassword(encrypted, privateKey)
Log.d(TAG, "📖 Read attachment: $attachmentId (${decrypted?.length ?: 0} bytes)")
decrypted
} catch (e: Exception) {
Log.e(TAG, "❌ Error reading attachment", e)
null
}
}
/**
* Проверить существует ли вложение в кэше
*/
fun hasAttachment(
context: Context,
attachmentId: String,
publicKey: String
): Boolean {
val filePath = generatePath(attachmentId, publicKey)
val dir = File(context.filesDir, ATTACHMENTS_DIR)
val file = File(dir, filePath)
return file.exists()
}
/**
* Удалить вложение из кэша
*/
fun deleteAttachment(
context: Context,
attachmentId: String,
publicKey: String
): Boolean {
return try {
val filePath = generatePath(attachmentId, publicKey)
val dir = File(context.filesDir, ATTACHMENTS_DIR)
val file = File(dir, filePath)
if (file.exists()) {
file.delete()
} else {
true
}
} catch (e: Exception) {
Log.e(TAG, "❌ Error deleting attachment", e)
false
}
}
/**
* Очистить весь кэш вложений
*/
fun clearCache(context: Context): Boolean {
return try {
val dir = File(context.filesDir, ATTACHMENTS_DIR)
dir.deleteRecursively()
Log.d(TAG, "🗑️ Cache cleared")
true
} catch (e: Exception) {
Log.e(TAG, "❌ Error clearing cache", e)
false
}
}
}