loading inner collections

This commit is contained in:
2026-08-14 11:17:22 +03:00
parent 4d0788e0f5
commit 17e06256c1
4 changed files with 184 additions and 15 deletions
+48
View File
@@ -0,0 +1,48 @@
use rusqlite::Connection;
use serde_json::Value;
use std::collections::HashMap;
#[derive(Clone)]
pub struct ImportData(Vec<ImportGroup>);
#[derive(Clone, Debug)]
pub struct ImportGroup {
id: u64,
name: String,
fields: Vec<String>,
mapping: HashMap<String, String>,
}
#[derive(Clone)]
pub struct ImportNote {
name: String,
tags: String,
fields: Vec<String>,
}
pub fn load_groups(connection: &Connection) -> ImportData {
let json_models: String = connection
.query_one("select models from col", (), |row| row.get(0))
.unwrap();
let raw: Value = serde_json::from_str(json_models.as_str()).unwrap();
let sets = raw.as_object().unwrap();
let mut total_data = ImportData(vec![]);
for (_, collection) in sets {
let fields = collection["flds"]
.as_array()
.unwrap()
.iter()
.map(|x| x["name"].as_str().unwrap().to_string())
.collect();
let group = ImportGroup {
id: collection["id"].as_u64().unwrap(),
name: collection["name"].as_str().unwrap().to_string(),
fields,
mapping: Default::default(),
};
println!("{:?}", group);
total_data.0.push(group);
}
total_data
}
+1
View File
@@ -6,3 +6,4 @@ pub(crate) mod sqlite;
pub(crate) mod voice;
pub(crate) mod words;
pub(crate) mod web_api;
pub(crate) mod import;
+1 -1
View File
@@ -264,7 +264,7 @@ impl NavigatedPage<DictionaryMessage> for DictionaryState {
row![horizontal().width(8), self.words_list(),],
row![button("Добавить слово").style(jl_button).on_press(NewWord),
horizontal().width(Fill),
button("Импорт").style(jl_button).on_press(ToImport),
button("Импорт").style(text).on_press(ToImport),
]
]
.spacing(5),
+134 -14
View File
@@ -1,44 +1,164 @@
use std::sync::{Arc, Mutex};
use iced::{Element, Task};
use iced::widget::scrollable;
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 iced::widget::{column, row, button, text };
use crate::AppState;
use crate::import::ImportMessage::*;
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>>
state: Arc<Mutex<AppState>>,
path: Option<PathBuf>,
import_data: Option<ImportData>,
selected_index: usize,
}
#[derive(Clone)]
pub enum ImportMessage {
Back
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)
return Some(Page::PreviousPage);
}
None
}
fn navigated(&mut self) {
}
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![]).into(), Back)
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
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)
}
}