165 lines
4.9 KiB
Rust
165 lines
4.9 KiB
Rust
use crate::data_provider::import::{load_groups, ImportData};
|
|
use crate::dictionary::app_data_dir;
|
|
use crate::import::ImportMessage::*;
|
|
use crate::navigation::{NavigatedPage, Page, RootMessage};
|
|
use crate::styling::*;
|
|
use crate::AppState;
|
|
use iced::widget::scrollable;
|
|
use iced::widget::{button, column, row, text};
|
|
use iced::{Element, Task};
|
|
use iced_core::Alignment::Center;
|
|
use rfd::AsyncFileDialog;
|
|
use rusqlite::Connection;
|
|
use std::fs::{File, OpenOptions};
|
|
use std::io::BufReader;
|
|
use std::path::PathBuf;
|
|
use std::sync::{Arc, Mutex};
|
|
use tokio::task::spawn_blocking;
|
|
use zip::ZipArchive;
|
|
|
|
#[derive(Clone)]
|
|
pub struct ImportState {
|
|
state: Arc<Mutex<AppState>>,
|
|
path: Option<PathBuf>,
|
|
import_data: Option<ImportData>,
|
|
selected_index: usize,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub enum ImportMessage {
|
|
Back,
|
|
SelectFile,
|
|
UpdateFile(PathBuf),
|
|
UpdateImport(ImportData),
|
|
}
|
|
|
|
impl NavigatedPage<ImportMessage> for ImportState {
|
|
fn navigate(&self, message: &ImportMessage) -> Option<Page> {
|
|
if let Back = message {
|
|
return Some(Page::PreviousPage);
|
|
}
|
|
None
|
|
}
|
|
|
|
fn navigated(&mut self) {}
|
|
|
|
fn update(&mut self, message: ImportMessage) -> Task<RootMessage> {
|
|
match message {
|
|
Back => {}
|
|
UpdateFile(path) => {
|
|
self.path = Some(path);
|
|
return self.load_package();
|
|
}
|
|
SelectFile => return Self::select_import_file(),
|
|
UpdateImport(import) => {
|
|
self.import_data = Some(import);
|
|
}
|
|
}
|
|
Task::none()
|
|
}
|
|
|
|
fn view(&self) -> Element<'_, ImportMessage> {
|
|
back_overlay(
|
|
scrollable(
|
|
column![
|
|
row![
|
|
button("Выбрать файл").style(jl_button).on_press(SelectFile),
|
|
text!("{}", {
|
|
if let Some(path) = &self.path {
|
|
path.to_string_lossy().to_string()
|
|
} else {
|
|
"Файл не выбран".to_string()
|
|
}
|
|
})
|
|
]
|
|
.align_y(Center)
|
|
.spacing(DEFAULT_SPACING)
|
|
]
|
|
.spacing(DEFAULT_SPACING),
|
|
)
|
|
.into(),
|
|
Back,
|
|
)
|
|
}
|
|
}
|
|
impl ImportState {
|
|
pub fn new(state: Arc<Mutex<AppState>>) -> ImportState {
|
|
ImportState {
|
|
state,
|
|
path: None,
|
|
import_data: None,
|
|
selected_index: 0
|
|
}
|
|
}
|
|
|
|
fn select_import_file() -> Task<RootMessage> {
|
|
Task::perform(
|
|
async {
|
|
let file = AsyncFileDialog::new()
|
|
.add_filter("Anki", &["apkg"])
|
|
.pick_file()
|
|
.await;
|
|
if let Some(file) = file {
|
|
return Some(file.path().into());
|
|
}
|
|
None
|
|
},
|
|
|result| {
|
|
if let Some(path) = result {
|
|
RootMessage::Import(UpdateFile(path))
|
|
} else {
|
|
RootMessage::None
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
pub fn load_package(&self) -> Task<RootMessage> {
|
|
let path = self.path.clone().unwrap();
|
|
Task::perform(
|
|
async {
|
|
spawn_blocking(move || {
|
|
Self::extract_import_file(path)?;
|
|
let data = Self::read_import_file()?;
|
|
return Ok(data);
|
|
})
|
|
.await
|
|
.unwrap()
|
|
},
|
|
|result: Result<ImportData, ()>| {
|
|
if let Ok(import) = result {
|
|
RootMessage::Import(UpdateImport(import))
|
|
} else {
|
|
RootMessage::None
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
fn extract_import_file(path: PathBuf) -> Result<(), ()> {
|
|
let file = File::open(path).map_err(|_| ())?;
|
|
let reader = BufReader::new(file);
|
|
let mut archive = ZipArchive::new(reader).map_err(|_| ())?;
|
|
let db_file = archive.by_name("collection.anki2");
|
|
if let Ok(mut file) = db_file {
|
|
let temp_file_path = app_data_dir().join("import");
|
|
let mut temp_file = OpenOptions::new()
|
|
.write(true)
|
|
.create(true)
|
|
.truncate(true)
|
|
.open(&temp_file_path)
|
|
.map_err(|_| ())?;
|
|
std::io::copy(&mut file, &mut temp_file).map_err(|_| ())?;
|
|
Ok(())
|
|
} else {
|
|
Err(())
|
|
}
|
|
}
|
|
fn read_import_file() -> Result<ImportData, ()> {
|
|
let temp_file_path = app_data_dir().join("import");
|
|
let connection = Connection::open(&temp_file_path).map_err(|_| ())?;
|
|
let data = load_groups(&connection);
|
|
Ok(data)
|
|
}
|
|
}
|