From 2de37ed90d25b495e79b4bae364876a903fca162 Mon Sep 17 00:00:00 2001 From: Maarifa Maarifa Date: Mon, 23 Oct 2023 15:06:41 +0300 Subject: [PATCH] Made data import/export in GUI asynchronous (#66) --- src/core/cli.rs | 4 +- src/core/database.rs | 67 ++++++++++++++------ src/gui/tabs/settings_tab/database_widget.rs | 60 ++++++++++++------ 3 files changed, 91 insertions(+), 40 deletions(-) diff --git a/src/core/cli.rs b/src/core/cli.rs index df24fd6..8f4113f 100644 --- a/src/core/cli.rs +++ b/src/core/cli.rs @@ -11,14 +11,14 @@ pub mod handle_cli { pub fn handle_cli(command: Command) -> anyhow::Result<()> { match command { Command::ImportData { file_path } => { - database::database_transfer::TransferData::import_to_db(file_path)?; + database::database_transfer::TransferData::blocking_import_to_db(file_path)?; println!("data imported successfully!"); Ok(()) } Command::ExportData { file_path: path_to_data, } => { - database::database_transfer::TransferData::export_from_db(path_to_data)?; + database::database_transfer::TransferData::blocking_export_from_db(path_to_data)?; println!("data exported successfully!"); Ok(()) } diff --git a/src/core/database.rs b/src/core/database.rs index 189fe2f..21224c7 100644 --- a/src/core/database.rs +++ b/src/core/database.rs @@ -437,7 +437,7 @@ pub enum AddResult { pub mod database_transfer { //! Implementations of importing and exporting series tracking data - use std::{fs, io, path}; + use std::{io, path}; use super::Series; use super::DB; @@ -458,7 +458,7 @@ pub mod database_transfer { Deserialization(ron::de::SpannedError), } - #[derive(Serialize, Deserialize)] + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TransferData { version: u16, series: Vec, @@ -472,23 +472,42 @@ pub mod database_transfer { } } - pub fn import(path: impl AsRef) -> Result { - let import = fs::read_to_string(path).map_err(ImportError::Io)?; - let imported_data = - ron::from_str::(&import).map_err(ImportError::Deserialization)?; - - if imported_data.version == CURRENT_DATA_VERSION { - Ok(imported_data) + fn error_when_incompatible(import_data_version: u16) -> Result<(), ImportError> { + if import_data_version == CURRENT_DATA_VERSION { + Ok(()) } else { Err(ImportError::Version( CURRENT_DATA_VERSION, - imported_data.version, + import_data_version, )) } } - pub fn import_to_db(path: impl AsRef) -> Result<(), ImportError> { - DB.import(&Self::import(path)?); + pub fn blocking_import(path: impl AsRef) -> Result { + let import = std::fs::read_to_string(path).map_err(ImportError::Io)?; + let imported_data = + ron::from_str::(&import).map_err(ImportError::Deserialization)?; + + Self::error_when_incompatible(imported_data.version).map(|_| imported_data) + } + + pub fn blocking_import_to_db(path: impl AsRef) -> Result<(), ImportError> { + DB.import(&Self::blocking_import(path)?); + Ok(()) + } + + pub async fn async_import(path: impl AsRef) -> Result { + let import = tokio::fs::read_to_string(path) + .await + .map_err(ImportError::Io)?; + let imported_data = + ron::from_str::(&import).map_err(ImportError::Deserialization)?; + + Self::error_when_incompatible(imported_data.version).map(|_| imported_data) + } + + pub async fn async_import_to_db(path: impl AsRef) -> Result<(), ImportError> { + DB.import(&Self::async_import(path).await?); Ok(()) } @@ -496,15 +515,27 @@ pub mod database_transfer { &self.series } - pub fn export(&self, path: impl AsRef) -> Result<(), io::Error> { + fn ron_str(&self) -> String { let pretty_config = ser::PrettyConfig::new().depth_limit(4); - let ron_str = - ser::to_string_pretty(self, pretty_config).expect("transfer data serialization"); - fs::write(path, ron_str) + ser::to_string_pretty(self, pretty_config).expect("transfer data serialization") + } + + pub fn blocking_export(&self, path: impl AsRef) -> Result<(), io::Error> { + let ron_str = self.ron_str(); + std::fs::write(path, ron_str) + } + + pub fn blocking_export_from_db(path: impl AsRef) -> Result<(), io::Error> { + DB.export().blocking_export(path) + } + + pub async fn async_export(&self, path: impl AsRef) -> Result<(), io::Error> { + let ron_str = self.ron_str(); + tokio::fs::write(path, ron_str).await } - pub fn export_from_db(path: impl AsRef) -> Result<(), io::Error> { - DB.export().export(path) + pub async fn async_export_from_db(path: impl AsRef) -> Result<(), io::Error> { + DB.export().async_export(path).await } } } diff --git a/src/gui/tabs/settings_tab/database_widget.rs b/src/gui/tabs/settings_tab/database_widget.rs index cf872fc..a11162f 100644 --- a/src/gui/tabs/settings_tab/database_widget.rs +++ b/src/gui/tabs/settings_tab/database_widget.rs @@ -1,6 +1,5 @@ use iced::widget::{ button, column, container, horizontal_space, progress_bar, row, text, vertical_space, Space, - Text, }; use iced::{Command, Element, Length, Renderer}; @@ -15,6 +14,8 @@ mod trakt_integration; pub enum Message { ImportDatabasePressed, ExportDatabasePressed, + ImportReceived(Result, String>), + ExportComplete(Result<(), String>), ImportTimeoutComplete, ExportTimeoutComplete, ImportCachingEvent(full_caching::Event), @@ -22,8 +23,8 @@ pub enum Message { } pub struct Database { - import_status: Option>, - export_status: Option>, + import_status: Option>, + export_status: Option>, import_progress: (usize, usize), importing: bool, transfer_data: Option, @@ -54,7 +55,18 @@ impl Database { pub fn update(&mut self, message: Message) -> Command { match message { - Message::ImportDatabasePressed => match database_transfer::import_transfer_data() { + Message::ImportDatabasePressed => { + Command::perform(database_transfer::import_transfer_data(), |result| { + Message::ImportReceived(result.map_err(|err| err.to_string())) + }) + } + Message::ExportDatabasePressed => { + Command::perform(database_transfer::export(), |result| { + Message::ExportComplete(result.map_err(|err| err.to_string())) + }) + } + // Message::ImportReceived(import_result) => todo!(), + Message::ImportReceived(import_result) => match import_result { Ok(transfer_data) => { if let Some(transfer_data) = transfer_data { let ids: Vec = transfer_data @@ -76,12 +88,12 @@ impl Database { Command::none() } Err(err) => { - self.import_status = Some(Err(anyhow::anyhow!(err))); + self.import_status = Some(Err(err)); Command::perform(status_timeout(), |_| Message::ImportTimeoutComplete) } }, - Message::ExportDatabasePressed => { - self.export_status = Some(database_transfer::export()); + Message::ExportComplete(export_result) => { + self.export_status = Some(export_result); Command::perform(status_timeout(), |_| Message::ExportTimeoutComplete) } Message::ImportTimeoutComplete => { @@ -206,15 +218,19 @@ impl Database { } } -fn get_status_text(status: Option<&anyhow::Result<()>>) -> Text { +fn get_status_text(status: Option<&Result<(), String>>) -> Element<'_, Message, Renderer> { if let Some(res) = status { if let Err(err) = res { - text(err.to_string()).style(styles::text_styles::red_text_theme()) + text(err) + .style(styles::text_styles::red_text_theme()) + .into() } else { - text("Done!").style(styles::text_styles::green_text_theme()) + text("Done!") + .style(styles::text_styles::green_text_theme()) + .into() } } else { - text("") + Space::new(0, 0).into() } } @@ -311,27 +327,31 @@ mod database_transfer { use std::path; use crate::core::database::database_transfer::TransferData; - use rfd::FileDialog; + use rfd::AsyncFileDialog; - pub fn export() -> anyhow::Result<()> { - let chosen_path = FileDialog::new() + pub async fn export() -> anyhow::Result<()> { + let chosen_path = AsyncFileDialog::new() .set_directory(get_home_directory()?) - .save_file(); + .save_file() + .await + .map(|file_handle| file_handle.path().to_owned()); if let Some(chosen_path) = chosen_path { - TransferData::export_from_db(chosen_path)?; + TransferData::async_export_from_db(chosen_path).await?; } Ok(()) } - pub fn import_transfer_data() -> anyhow::Result> { - let chosen_path = FileDialog::new() + pub async fn import_transfer_data() -> anyhow::Result> { + let chosen_path = AsyncFileDialog::new() .set_directory(get_home_directory()?) - .pick_file(); + .pick_file() + .await + .map(|file_handle| file_handle.path().to_owned()); if let Some(chosen_path) = chosen_path { - let data = TransferData::import(chosen_path)?; + let data = TransferData::async_import(chosen_path).await?; return Ok(Some(data)); }