use futures::FutureExt as _;
use log::error;
use std::{borrow::BorrowMut as _, fs::remove_file, sync::atomic};
use super::LightClient;
use crate::data::PollReport;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SaveShutdown {
ShutDown,
NotRunning,
}
async fn write_to_path(wallet_path: &std::path::Path, bytes: &[u8]) -> std::io::Result<()> {
let wallet_path = wallet_path.to_path_buf();
let bytes = bytes.to_vec();
tokio::task::spawn_blocking(move || {
let temp_wallet_path: std::path::PathBuf = wallet_path.with_extension(
wallet_path
.extension()
.map(|e| format!("{}.tmp", e.to_string_lossy()))
.unwrap_or_else(|| "tmp".to_string()),
);
let file = std::fs::OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(&temp_wallet_path)?;
let mut writer = std::io::BufWriter::new(file);
std::io::Write::write_all(&mut writer, &bytes)?;
let file = writer.into_inner().map_err(|e| e.into_error())?;
file.sync_all()?;
std::fs::rename(&temp_wallet_path, &wallet_path)?;
#[cfg(unix)]
{
if let Some(parent) = wallet_path.parent() {
let wallet_dir = std::fs::File::open(parent)?;
let _ignore_error = wallet_dir.sync_all(); }
}
Ok(())
})
.await
.expect("write_to_path task panicked")
}
impl LightClient {
pub async fn save_task(&mut self) {
if self.save_active.load(atomic::Ordering::Acquire) {
return;
}
self.save_active.store(true, atomic::Ordering::Release);
let save_active = self.save_active.clone();
let wallet = self.wallet().clone();
let wallet_path = self.wallet_path();
let mut interval = tokio::time::interval(std::time::Duration::from_secs(1));
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
let save_handle = tokio::spawn(async move {
loop {
interval.tick().await;
let mut wallet_guard = wallet.write().await;
if let Some(wallet_bytes) = wallet_guard.save()? {
write_to_path(&wallet_path, &wallet_bytes).await?;
wallet_guard.save_required = false;
}
if !save_active.load(atomic::Ordering::Acquire) {
return Ok(());
}
}
});
self.save_handle = Some(save_handle);
}
pub async fn wait_for_save(&self) {
let mut interval = tokio::time::interval(std::time::Duration::from_secs(1));
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
loop {
interval.tick().await;
if !self.wallet().read().await.save_required {
return;
}
}
}
fn poll_save_task(&mut self) -> PollReport<(), std::io::Error> {
if let Some(mut save_handle) = self.save_handle.take() {
if let Some(save_result) = save_handle.borrow_mut().now_or_never() {
PollReport::Ready(save_result.expect("task panicked"))
} else {
self.save_handle = Some(save_handle);
PollReport::NotReady
}
} else {
PollReport::NoHandle
}
}
pub async fn check_save_error(&mut self) -> std::io::Result<()> {
match self.poll_save_task() {
PollReport::Ready(save_result) => {
if save_result.is_err() {
self.save_task().await;
}
save_result
}
_ => Ok(()),
}
}
pub async fn shutdown_save_task(&mut self) -> std::io::Result<SaveShutdown> {
self.save_active.store(false, atomic::Ordering::Release);
if let Some(save_handle) = self.save_handle.take() {
save_handle.await.expect("task panicked")?;
Ok(SaveShutdown::ShutDown)
} else {
Ok(SaveShutdown::NotRunning)
}
}
pub async fn flush(&mut self) -> std::io::Result<()> {
if let Some(bytes) = self.wallet().write().await.save()? {
write_to_path(&self.wallet_path(), &bytes).await?;
}
Ok(())
}
pub async fn delete_wallet_file(&self) -> std::io::Result<()> {
if self.wallet_path().exists() {
match remove_file(self.wallet_path()) {
Ok(()) => {
log::debug!("File deleted successfully!");
Ok(())
}
Err(e) => {
error!("ERR: {e}");
log::debug!("DELETE FAIL ON FILE!");
Err(e)
}
}
} else {
let err = std::io::Error::new(
std::io::ErrorKind::NotFound,
"File does not exist, nothing to delete.",
);
error!("{err}");
log::debug!("File does not exist, nothing to delete.");
Err(err)
}
}
}