206 lines
6.2 KiB
Kotlin
206 lines
6.2 KiB
Kotlin
package com.rosetta.messenger.database
|
|
|
|
import android.content.Context
|
|
import com.rosetta.messenger.crypto.CryptoManager
|
|
import kotlinx.coroutines.Dispatchers
|
|
import kotlinx.coroutines.flow.Flow
|
|
import kotlinx.coroutines.withContext
|
|
import java.time.Instant
|
|
|
|
/**
|
|
* Service for secure database operations
|
|
* Matches the architecture from React Native app
|
|
*/
|
|
class DatabaseService(context: Context) {
|
|
private val database = RosettaDatabase.getDatabase(context)
|
|
private val accountDao = database.accountDao()
|
|
|
|
// 🚀 ОПТИМИЗАЦИЯ: LRU кэш для зашифрованных аккаунтов (избегаем повторных запросов к БД)
|
|
private val accountCache = mutableMapOf<String, EncryptedAccountEntity>()
|
|
private val cacheMaxSize = 10
|
|
|
|
companion object {
|
|
private const val TAG = "DatabaseService"
|
|
|
|
@Volatile
|
|
private var INSTANCE: DatabaseService? = null
|
|
|
|
fun getInstance(context: Context): DatabaseService {
|
|
return INSTANCE ?: synchronized(this) {
|
|
val instance = DatabaseService(context.applicationContext)
|
|
INSTANCE = instance
|
|
instance
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Saves encrypted account to database
|
|
*/
|
|
suspend fun saveEncryptedAccount(
|
|
publicKey: String,
|
|
privateKeyEncrypted: String,
|
|
seedPhraseEncrypted: String
|
|
): Boolean = withContext(Dispatchers.IO) {
|
|
try {
|
|
val account = EncryptedAccountEntity(
|
|
publicKey = publicKey,
|
|
privateKeyEncrypted = privateKeyEncrypted,
|
|
seedPhraseEncrypted = seedPhraseEncrypted,
|
|
createdAt = Instant.now().toString(),
|
|
lastUsed = Instant.now().toString(),
|
|
isActive = true
|
|
)
|
|
|
|
accountDao.insertAccount(account)
|
|
|
|
// 🚀 ОПТИМИЗАЦИЯ: Обновляем кэш после сохранения
|
|
accountCache[publicKey] = account
|
|
|
|
true
|
|
} catch (e: Exception) {
|
|
false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads encrypted account from database
|
|
* 🚀 ОПТИМИЗАЦИЯ: Использует кэш для избежания повторных запросов
|
|
*/
|
|
suspend fun getEncryptedAccount(publicKey: String): EncryptedAccountEntity? =
|
|
withContext(Dispatchers.IO) {
|
|
try {
|
|
// Проверяем кэш сначала
|
|
accountCache[publicKey]?.let { return@withContext it }
|
|
|
|
// Загружаем из БД и кэшируем
|
|
val account = accountDao.getAccount(publicKey)
|
|
account?.let {
|
|
accountCache[publicKey] = it
|
|
// Ограничиваем размер кэша
|
|
if (accountCache.size > cacheMaxSize) {
|
|
accountCache.remove(accountCache.keys.first())
|
|
}
|
|
}
|
|
account
|
|
} catch (e: Exception) {
|
|
null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets all encrypted accounts
|
|
*/
|
|
suspend fun getAllEncryptedAccounts(): List<EncryptedAccountEntity> =
|
|
withContext(Dispatchers.IO) {
|
|
try {
|
|
accountDao.getAllAccounts()
|
|
} catch (e: Exception) {
|
|
emptyList()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets all accounts as Flow for reactive updates
|
|
*/
|
|
fun getAllAccountsFlow(): Flow<List<EncryptedAccountEntity>> {
|
|
return accountDao.getAllAccountsFlow()
|
|
}
|
|
|
|
/**
|
|
* Updates last used timestamp for account
|
|
*/
|
|
suspend fun updateLastUsed(publicKey: String) = withContext(Dispatchers.IO) {
|
|
try {
|
|
accountDao.updateLastUsed(publicKey, Instant.now().toString())
|
|
} catch (e: Exception) {
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deletes account from database
|
|
*/
|
|
suspend fun deleteAccount(publicKey: String): Boolean = withContext(Dispatchers.IO) {
|
|
try {
|
|
accountDao.deleteAccount(publicKey)
|
|
true
|
|
} catch (e: Exception) {
|
|
false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if any accounts exist
|
|
*/
|
|
suspend fun hasAccounts(): Boolean = withContext(Dispatchers.IO) {
|
|
try {
|
|
accountDao.getAccountCount() > 0
|
|
} catch (e: Exception) {
|
|
false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Decrypts account with password
|
|
* Returns decrypted private key and seed phrase
|
|
*/
|
|
suspend fun decryptAccount(
|
|
publicKey: String,
|
|
password: String
|
|
): DecryptedAccountData? = withContext(Dispatchers.IO) {
|
|
try {
|
|
val encryptedAccount = getEncryptedAccount(publicKey) ?: run {
|
|
return@withContext null
|
|
}
|
|
|
|
// Decrypt private key
|
|
val privateKey = try {
|
|
CryptoManager.decryptWithPassword(
|
|
encryptedAccount.privateKeyEncrypted,
|
|
password
|
|
) ?: run {
|
|
return@withContext null
|
|
}
|
|
} catch (e: Exception) {
|
|
return@withContext null
|
|
}
|
|
|
|
// Decrypt seed phrase
|
|
val seedPhraseString = try {
|
|
CryptoManager.decryptWithPassword(
|
|
encryptedAccount.seedPhraseEncrypted,
|
|
password
|
|
) ?: run {
|
|
return@withContext null
|
|
}
|
|
} catch (e: Exception) {
|
|
return@withContext null
|
|
}
|
|
|
|
val seedPhrase = seedPhraseString.split(" ")
|
|
|
|
// Generate private key hash for protocol
|
|
val privateKeyHash = CryptoManager.generatePrivateKeyHash(privateKey)
|
|
|
|
DecryptedAccountData(
|
|
publicKey = publicKey,
|
|
privateKey = privateKey,
|
|
privateKeyHash = privateKeyHash,
|
|
seedPhrase = seedPhrase
|
|
)
|
|
} catch (e: Exception) {
|
|
null
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Decrypted account data
|
|
*/
|
|
data class DecryptedAccountData(
|
|
val publicKey: String,
|
|
val privateKey: String,
|
|
val privateKeyHash: String,
|
|
val seedPhrase: List<String>
|
|
)
|