Skip to content

Commit

Permalink
Made data import/export in GUI asynchronous (#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
MaarifaMaarifa authored Oct 23, 2023
1 parent c33bf07 commit 2de37ed
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 40 deletions.
4 changes: 2 additions & 2 deletions src/core/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
Expand Down
67 changes: 49 additions & 18 deletions src/core/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Series>,
Expand All @@ -472,39 +472,70 @@ pub mod database_transfer {
}
}

pub fn import(path: impl AsRef<path::Path>) -> Result<Self, ImportError> {
let import = fs::read_to_string(path).map_err(ImportError::Io)?;
let imported_data =
ron::from_str::<Self>(&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<path::Path>) -> Result<(), ImportError> {
DB.import(&Self::import(path)?);
pub fn blocking_import(path: impl AsRef<path::Path>) -> Result<Self, ImportError> {
let import = std::fs::read_to_string(path).map_err(ImportError::Io)?;
let imported_data =
ron::from_str::<Self>(&import).map_err(ImportError::Deserialization)?;

Self::error_when_incompatible(imported_data.version).map(|_| imported_data)
}

pub fn blocking_import_to_db(path: impl AsRef<path::Path>) -> Result<(), ImportError> {
DB.import(&Self::blocking_import(path)?);
Ok(())
}

pub async fn async_import(path: impl AsRef<path::Path>) -> Result<Self, ImportError> {
let import = tokio::fs::read_to_string(path)
.await
.map_err(ImportError::Io)?;
let imported_data =
ron::from_str::<Self>(&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<path::Path>) -> Result<(), ImportError> {
DB.import(&Self::async_import(path).await?);
Ok(())
}

pub fn get_series(&self) -> &[Series] {
&self.series
}

pub fn export(&self, path: impl AsRef<path::Path>) -> 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<path::Path>) -> Result<(), io::Error> {
let ron_str = self.ron_str();
std::fs::write(path, ron_str)
}

pub fn blocking_export_from_db(path: impl AsRef<path::Path>) -> Result<(), io::Error> {
DB.export().blocking_export(path)
}

pub async fn async_export(&self, path: impl AsRef<path::Path>) -> Result<(), io::Error> {
let ron_str = self.ron_str();
tokio::fs::write(path, ron_str).await
}

pub fn export_from_db(path: impl AsRef<path::Path>) -> Result<(), io::Error> {
DB.export().export(path)
pub async fn async_export_from_db(path: impl AsRef<path::Path>) -> Result<(), io::Error> {
DB.export().async_export(path).await
}
}
}
60 changes: 40 additions & 20 deletions src/gui/tabs/settings_tab/database_widget.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -15,15 +14,17 @@ mod trakt_integration;
pub enum Message {
ImportDatabasePressed,
ExportDatabasePressed,
ImportReceived(Result<Option<TransferData>, String>),
ExportComplete(Result<(), String>),
ImportTimeoutComplete,
ExportTimeoutComplete,
ImportCachingEvent(full_caching::Event),
TraktIntegration(trakt_integration::Message),
}

pub struct Database {
import_status: Option<anyhow::Result<()>>,
export_status: Option<anyhow::Result<()>>,
import_status: Option<Result<(), String>>,
export_status: Option<Result<(), String>>,
import_progress: (usize, usize),
importing: bool,
transfer_data: Option<TransferData>,
Expand Down Expand Up @@ -54,7 +55,18 @@ impl Database {

pub fn update(&mut self, message: Message) -> Command<Message> {
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<u32> = transfer_data
Expand All @@ -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 => {
Expand Down Expand Up @@ -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()
}
}

Expand Down Expand Up @@ -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<Option<TransferData>> {
let chosen_path = FileDialog::new()
pub async fn import_transfer_data() -> anyhow::Result<Option<TransferData>> {
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));
}

Expand Down

0 comments on commit 2de37ed

Please sign in to comment.