use crate::{
contacts_service::storage::sqlite_db::ContactsServiceSqliteDatabase,
error::WalletStorageError,
output_manager_service::storage::sqlite_db::OutputManagerSqliteDatabase,
storage::{database::WalletDatabase, sqlite_db::WalletSqliteDatabase},
transaction_service::storage::sqlite_db::TransactionServiceSqliteDatabase,
};
use aes_gcm::{
aead::{generic_array::GenericArray, NewAead},
Aes256Gcm,
};
use diesel::{Connection, SqliteConnection};
use digest::Digest;
use fs2::FileExt;
use log::*;
use std::{
fs::File,
path::{Path, PathBuf},
sync::{Arc, Mutex, MutexGuard},
};
use tari_crypto::common::Blake256;
const LOG_TARGET: &str = "wallet::storage:sqlite_utilities";
#[derive(Clone)]
pub struct WalletDbConnection {
pub connection: Arc<Mutex<SqliteConnection>>,
_file_lock: Arc<Option<File>>,
}
impl WalletDbConnection {
pub fn new(connection: SqliteConnection, file_lock: Option<File>) -> Self {
Self {
connection: Arc::new(Mutex::new(connection)),
_file_lock: Arc::new(file_lock),
}
}
pub fn acquire_lock(&self) -> MutexGuard<SqliteConnection> {
acquire_lock!(self.connection)
}
}
pub fn run_migration_and_create_sqlite_connection<P: AsRef<Path>>(
db_path: P,
) -> Result<WalletDbConnection, WalletStorageError> {
let file_lock = acquire_exclusive_file_lock(&db_path.as_ref().to_path_buf())?;
let path_str = db_path
.as_ref()
.to_str()
.ok_or_else(|| WalletStorageError::InvalidUnicodePath)?;
let connection = SqliteConnection::establish(path_str)?;
connection.execute("PRAGMA foreign_keys = ON; PRAGMA busy_timeout = 60000;")?;
embed_migrations!("./migrations");
embedded_migrations::run(&connection)
.map_err(|err| WalletStorageError::DatabaseMigrationError(format!("Database migration failed {}", err)))?;
Ok(WalletDbConnection::new(connection, Some(file_lock)))
}
pub async fn partial_wallet_backup<P: AsRef<Path>>(current_db: P, backup_path: P) -> Result<(), WalletStorageError> {
let db_path = current_db
.as_ref()
.to_str()
.ok_or_else(|| WalletStorageError::InvalidUnicodePath)?;
let backup_path = backup_path
.as_ref()
.to_str()
.ok_or_else(|| WalletStorageError::InvalidUnicodePath)?;
std::fs::copy(db_path, backup_path)
.map_err(|_| WalletStorageError::FileError("Could not copy database file for backup".to_string()))?;
let connection = run_migration_and_create_sqlite_connection(backup_path)?;
let db = WalletDatabase::new(WalletSqliteDatabase::new(connection, None)?);
db.clear_comms_secret_key().await?;
Ok(())
}
pub fn acquire_exclusive_file_lock(db_path: &PathBuf) -> Result<File, WalletStorageError> {
let lock_file_path = match db_path.file_name() {
None => {
return Err(WalletStorageError::FileError(
"Database path should be to a file".to_string(),
))
},
Some(filename) => match db_path.parent() {
Some(p) => p.join(format!(
".{}.lock",
filename
.to_str()
.ok_or_else(|| WalletStorageError::FileError("Could not acquire database filename".to_string()))?
)),
None => return Err(WalletStorageError::DatabasePathIsRootPath),
},
};
let file = File::create(lock_file_path)?;
if let Err(e) = file.try_lock_exclusive() {
error!(
target: LOG_TARGET,
"Could not acquire exclusive write lock on database lock file: {:?}", e
);
return Err(WalletStorageError::CannotAcquireFileLock);
}
Ok(file)
}
pub fn initialize_sqlite_database_backends(
db_path: PathBuf,
passphrase: Option<String>,
) -> Result<
(
WalletSqliteDatabase,
TransactionServiceSqliteDatabase,
OutputManagerSqliteDatabase,
ContactsServiceSqliteDatabase,
),
WalletStorageError,
>
{
let cipher = match passphrase {
None => None,
Some(passphrase_str) => {
let passphrase_hash = Blake256::new().chain(passphrase_str.as_bytes()).result().to_vec();
let key = GenericArray::from_slice(passphrase_hash.as_slice());
Some(Aes256Gcm::new(key))
},
};
let connection = run_migration_and_create_sqlite_connection(&db_path).map_err(|e| {
error!(
target: LOG_TARGET,
"Error creating Sqlite Connection in Wallet: {:?}", e
);
e
})?;
let wallet_backend = WalletSqliteDatabase::new(connection.clone(), cipher.clone())?;
let transaction_backend = TransactionServiceSqliteDatabase::new(connection.clone(), cipher.clone());
let output_manager_backend = OutputManagerSqliteDatabase::new(connection.clone(), cipher);
let contacts_backend = ContactsServiceSqliteDatabase::new(connection);
Ok((
wallet_backend,
transaction_backend,
output_manager_backend,
contacts_backend,
))
}