feat: Implement AttachmentFileManager for handling image attachments; add methods for saving, reading, and managing attachment files
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user