Rewrite history page

This commit is contained in:
micialware committed 2026-09-04 11:18:23 +03:00
1 parent f19a6f64c3
commit 532640058e
11 files changed
+185 -214

No files matched your search

+1 -1
View File
@@ -3,7 +3,7 @@ create table history
id INTEGER id INTEGER
primary key autoincrement, primary key autoincrement,
set_id INTEGER not null references decks on delete cascade, set_id INTEGER not null references decks on delete cascade,
stat_id INTEGER not null references states on delete cascade, word_id INTEGER not null references words on delete cascade,
timestamp INTEGER not null, timestamp INTEGER not null,
answer INTEGER not null, answer INTEGER not null,
before INTEGER not null, before INTEGER not null,
+3 -5
View File
@@ -1,13 +1,10 @@
use std::sync::Arc; use std::sync::Arc;
use crate::data::Id;
use crate::data_provider::decks::{DeckRepository}; use crate::data_provider::decks::{DeckRepository};
use crate::data_provider::settings::get_setting; use crate::data_provider::settings::get_setting;
use crate::data_provider::sqlite::{SqliteConnection}; use crate::data_provider::sqlite::{SqliteConnection};
use crate::data_provider::words::{WordRepository}; use crate::data_provider::words::{WordRepository};
use crate::navigation::RootMessage::Keyboard; use crate::navigation::RootMessage::Keyboard;
use crate::navigation::{AppSettings, RootMessage, ScreenState}; use crate::navigation::{AppSettings, RootMessage, ScreenState};
use chrono::NaiveDate;
use hashbrown::HashMap;
use iced::{Subscription, keyboard, window}; use iced::{Subscription, keyboard, window};
use iced_core::window::Position; use iced_core::window::Position;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
@@ -18,6 +15,7 @@ use parking_lot::RwLock;
use rusqlite::Connection; use rusqlite::Connection;
use crate::data::repositories::Repository; use crate::data::repositories::Repository;
use crate::data_provider::card_states::CardStateRepository; use crate::data_provider::card_states::CardStateRepository;
use crate::data_provider::history::HistoryRepository;
use crate::data_provider::word_groups::WordGroupRepository; use crate::data_provider::word_groups::WordGroupRepository;
pub const USER_FONT: Font = Font::with_name("Noto Sans JP"); pub const USER_FONT: Font = Font::with_name("Noto Sans JP");
@@ -69,7 +67,7 @@ pub struct AppState {
pub card_states: CardStateRepository, pub card_states: CardStateRepository,
pub connection: Arc<RwLock<SqliteConnection>>, pub connection: Arc<RwLock<SqliteConnection>>,
pub sync_data: Arc<RwLock<AppSettings>>, pub sync_data: Arc<RwLock<AppSettings>>,
pub activity: HashMap<Id, Vec<(NaiveDate, u32)>>, pub history: HistoryRepository,
} }
impl Default for AppState { impl Default for AppState {
@@ -86,11 +84,11 @@ impl AppState {
decks: DeckRepository::new(connection.clone()), decks: DeckRepository::new(connection.clone()),
word_groups: WordGroupRepository::new(connection.clone()), word_groups: WordGroupRepository::new(connection.clone()),
card_states: CardStateRepository::new(connection.clone()), card_states: CardStateRepository::new(connection.clone()),
history: HistoryRepository::new(connection.clone()),
sync_data: Arc::new(RwLock::new(AppSettings { sync_data: Arc::new(RwLock::new(AppSettings {
key: None, key: None,
auto_web_fetch: false, auto_web_fetch: false,
})), })),
activity: Default::default(),
connection, connection,
} }
} }
+13 -13
View File
@@ -1,12 +1,11 @@
use crate::app::AppState; use crate::app::AppState;
use crate::core::srs::OrderModule::{Fsrs, RandomSRS, SemiRandomSRS, WorstWordsSRS}; use crate::core::srs::OrderModule::{Fsrs, RandomSRS, SemiRandomSRS, WorstWordsSRS};
use crate::core::srs::{ use crate::core::srs::{
FsrsModule, OrderMode, OrderModule, RandomSRSModule, SRSModule, SemiRandomSRSModule, OrderMode, OrderModule, RandomSRSModule, SRSModule, SemiRandomSRSModule,
WordOpenMode, WorstWordsSRSModule, WordOpenMode, WorstWordsSRSModule,
}; };
use crate::data::entities::*; use crate::data::entities::*;
use crate::data_provider::card_states::{load_stats_of_deck, update_stat_score}; use crate::data_provider::card_states::update_stat_score;
use crate::data_provider::history::push_note;
use crate::notification::{MaybeError, NotificationData}; use crate::notification::{MaybeError, NotificationData};
use chrono::Utc; use chrono::Utc;
@@ -116,16 +115,17 @@ impl DeckData {
} }
} }
update_stat_score(word, &self.state.connection.read())?; update_stat_score(word, &self.state.connection.read())?;
let _ = push_note( todo!("Implement history pushing")
self.settings.id, // let _ = push_note(
HistoryItem { // self.settings.id,
timestamp: Utc::now(), // HistoryNote {
word_id: word.word_id, // timestamp: Utc::now(),
mode: status, // word_id: word.word_id,
before: old_score, // answer: status,
after: new_score, // before: old_score,
}, // after: new_score,
); // },
// );
} }
Ok(()) Ok(())
+15 -2
View File
@@ -62,12 +62,25 @@ impl WordData {
} }
#[derive(Clone)] #[derive(Clone)]
pub struct HistoryItem { pub struct HistoryNote {
pub id: Id,
pub timestamp: DateTime<Utc>, pub timestamp: DateTime<Utc>,
pub set_id: Id,
pub word_id: Id, pub word_id: Id,
pub mode: WordOpenMode, pub answer: WordOpenMode,
pub before: u8, pub before: u8,
pub after: u8, pub after: u8,
pub fsrs: bool
}
impl EntityData for HistoryNote {
fn id(&self) -> Id {
self.id
}
fn set_id(&mut self, id: Id) {
self.id = id;
}
} }
#[derive(Clone)] #[derive(Clone)]
+14 -5
View File
@@ -188,7 +188,7 @@ impl<T: EntityData + Clone + Send + Sync, R: Repository<T, D>, D: DbLayer<T>> Qu
); );
} }
pub fn handles(&self, data_repository: &DataRepositoryInner<T, D>) -> impl Iterator<Item = EntityHandle<T>> { pub fn handles(&self) -> impl Iterator<Item = EntityHandle<T>> {
self.cache.iter().cloned() self.cache.iter().cloned()
} }
@@ -216,6 +216,7 @@ where
D: BatchedDbLayer<T>, D: BatchedDbLayer<T>,
T: EntityData, T: EntityData,
{ {
fn create_now_many(&mut self, entities: &mut [T]) -> Result<Vec<EntityHandle<T>>, NotificationData>;
fn save_many(&mut self, handles: &mut [EntityHandle<T>]) -> MaybeError; fn save_many(&mut self, handles: &mut [EntityHandle<T>]) -> MaybeError;
fn delete_many(&mut self, handles: &mut [EntityHandle<T>]) -> MaybeError; fn delete_many(&mut self, handles: &mut [EntityHandle<T>]) -> MaybeError;
} }
@@ -236,11 +237,11 @@ where
} }
#[derive(Clone)] #[derive(Clone)]
pub struct DataRepository<T: std::clone::Clone + EntityData, D: DbLayer<T>> { pub struct DataRepository<T: Clone + EntityData, D: DbLayer<T>> {
inner: Arc<RwLock<DataRepositoryInner<T, D>>>, inner: Arc<RwLock<DataRepositoryInner<T, D>>>,
} }
pub struct DataRepositoryInner<T: std::clone::Clone + EntityData, D: DbLayer<T>> { pub struct DataRepositoryInner<T: Clone + EntityData, D: DbLayer<T>> {
cache: Vec<T>, cache: Vec<T>,
temporal: Vec<T>, temporal: Vec<T>,
next_temporal: u32, next_temporal: u32,
@@ -251,7 +252,7 @@ pub struct DataRepositoryInner<T: std::clone::Clone + EntityData, D: DbLayer<T>>
shifts: usize, shifts: usize,
} }
impl<T: std::clone::Clone + EntityData, D: DbLayer<T> + std::default::Default> DataRepository<T, D> { impl<T: Clone + EntityData, D: DbLayer<T> + Default> DataRepository<T, D> {
pub fn new(connection: Arc<RwLock<SqliteConnection>>) -> DataRepository<T, D> { pub fn new(connection: Arc<RwLock<SqliteConnection>>) -> DataRepository<T, D> {
Self{ Self{
inner: Arc::new(RwLock::new(DataRepositoryInner{ inner: Arc::new(RwLock::new(DataRepositoryInner{
@@ -269,7 +270,7 @@ impl<T: std::clone::Clone + EntityData, D: DbLayer<T> + std::default::Default>
} }
impl<T: std::clone::Clone + EntityData, D: DbLayer<T>> Deref for DataRepository<T, D> { impl<T: Clone + EntityData, D: DbLayer<T>> Deref for DataRepository<T, D> {
type Target = Arc<RwLock<DataRepositoryInner<T, D>>>; type Target = Arc<RwLock<DataRepositoryInner<T, D>>>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@@ -536,6 +537,14 @@ impl<T: EntityData + Clone, D: DbLayer<T>> Repository<T, D> for DataRepositoryIn
impl<T: EntityData + Clone, D: BatchedDbLayer<T> + DbLayer<T>> BatchedRepository<T, D> impl<T: EntityData + Clone, D: BatchedDbLayer<T> + DbLayer<T>> BatchedRepository<T, D>
for DataRepositoryInner<T, D> for DataRepositoryInner<T, D>
{ {
fn create_now_many(&mut self, entities: &mut [T]) -> Result<Vec<EntityHandle<T>>, NotificationData> {
self.data_layer.add_many_entities(entities, &mut self.connection.write())?;
let len = self.cache.len();
let handles = entities.iter().enumerate().map(|(index, entity)| {EntityHandle::new(entity.id(), index + len, true)}).collect();
self.cache.extend(entities.to_vec());
Ok(handles)
}
fn save_many(&mut self, handles: &mut [EntityHandle<T>]) -> MaybeError { fn save_many(&mut self, handles: &mut [EntityHandle<T>]) -> MaybeError {
let mut temp = Vec::with_capacity(handles.len() / 2); let mut temp = Vec::with_capacity(handles.len() / 2);
let mut cached = Vec::with_capacity(handles.len() / 2); let mut cached = Vec::with_capacity(handles.len() / 2);
+76 -81
View File
@@ -1,97 +1,92 @@
use crate::core::srs::WordOpenMode; use crate::core::srs::WordOpenMode;
use crate::data::Id; use crate::data::entities::HistoryNote;
use crate::data::entities::HistoryItem; use crate::data::repositories::{DataRepository, DataRepositoryInner};
use crate::fs::*; use crate::data_provider::{SqliteDbLayer, TableAccessQuerySet};
use crate::notification::NotificationData::PermissionDenied; use rusqlite::types::{FromSql, FromSqlResult, ToSqlOutput, ValueRef};
use crate::notification::{MaybeError, NotificationData}; use rusqlite::{Error, Params, Row, ToSql};
use chrono::{DateTime, NaiveDate};
use rusqlite::Connection;
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
pub fn get_open_count_of_set( pub type HistoryRepository = DataRepository<HistoryNote, HistoryDataLayer>;
id: Id, pub type HistoryRepositoryInner = DataRepositoryInner<HistoryNote, HistoryDataLayer>;
connection: &Connection, pub type HistoryDataLayer = SqliteDbLayer<HistoryNote, HistoryQuerySet>;
) -> Result<Vec<(NaiveDate, u32)>, NotificationData> {
todo!() #[derive(Clone)]
pub struct HistoryQuerySet;
impl TableAccessQuerySet<HistoryNote> for HistoryQuerySet {
fn load() -> &'static str {
"SELECT rowid, set_id, state_id, timestamp, answer, before, after, fsrs FROM history"
} }
pub fn get_history_of_set(id: Id) -> Result<Vec<HistoryItem>, NotificationData> { fn save() -> &'static str {
let app_dir = history_dir()?; todo!("History is readonly")
let head = app_dir.clone().join(format!("set_{}_history.csv", id));
let mut lines = Vec::new();
append_lines_if_exists(head, &mut lines)?;
lines.reverse();
Ok(parse_history_items(lines))
} }
fn append_lines_if_exists(path: PathBuf, lines: &mut Vec<String>) -> MaybeError { fn add() -> &'static str {
if path.exists() { "INSERT INTO history (set_id, state_id, timestamp, answer, before, after, fsrs) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"
let reader = BufReader::new(File::open(path).map_err(|_| PermissionDenied)?);
reader
.lines()
.map_while(Result::ok)
.for_each(|i| lines.push(i));
}
Ok(())
} }
fn parse_history_items(strings: Vec<String>) -> Vec<HistoryItem> { fn name() -> &'static str {
let mut items = Vec::with_capacity(strings.len()); "history"
for string in strings {
if let [time, word, mode, before, after] = string.split(';').collect::<Vec<&str>>()[..] {
let id = word.parse::<Id>();
if id.is_err() {
continue;
} }
let id = id.unwrap_or_default();
if !id.is_valid() { fn from_sql(row: &Row) -> Result<HistoryNote, Error> {
continue; Ok(HistoryNote {
id: row.get(0)?,
timestamp: row.get(3)?,
set_id: row.get(1)?,
word_id: row.get(2)?,
answer: row.get(4)?,
before: row.get(5)?,
after: row.get(6)?,
fsrs: row.get(7)?,
})
} }
items.push(HistoryItem {
timestamp: DateTime::from_timestamp(time.parse().unwrap_or_default(), 0) fn insert_params(entity: &HistoryNote) -> impl Params {
.unwrap_or_default(), (
word_id: id, entity.set_id,
mode: match mode.parse::<u8>().unwrap_or_default() { entity.word_id,
2 => WordOpenMode::Hard, entity.timestamp,
3 => WordOpenMode::Ok, entity.answer,
4 => WordOpenMode::Easy, entity.before,
_ => WordOpenMode::None, entity.after,
}, entity.fsrs,
before: before.parse().unwrap_or_default(), )
after: after.parse().unwrap_or_default(), }
});
fn update_params(entity: &HistoryNote) -> impl Params {
(
entity.set_id,
entity.word_id,
entity.timestamp,
entity.answer,
entity.before,
entity.after,
entity.fsrs,
)
} }
} }
items impl FromSql for WordOpenMode {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
if let ValueRef::Integer(s) = value {
return match s {
1 => Ok(WordOpenMode::Hard),
2 => Ok(WordOpenMode::Ok),
3 => Ok(WordOpenMode::Easy),
_ => Ok(WordOpenMode::None),
}
}
Ok(WordOpenMode::None)
}
} }
pub fn push_note(set_id: Id, item: HistoryItem) -> MaybeError { impl ToSql for WordOpenMode {
let app_dir = history_dir()?; fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
let path = app_dir.clone().join(format!("set_{}_history.csv", set_id)); Ok(ToSqlOutput::from(match self {
WordOpenMode::Easy => 3,
let mut file = OpenOptions::new() WordOpenMode::Ok => 2,
.create(true) WordOpenMode::Hard => 1,
.append(true) WordOpenMode::None => 0
.open(path) }))
.map_err(|_| PermissionDenied)?; }
let line_str = format!(
"{};{};{};{};{}",
item.timestamp.timestamp(),
item.word_id,
match item.mode {
WordOpenMode::Easy => 4,
WordOpenMode::Ok => 3,
WordOpenMode::Hard => 2,
WordOpenMode::None => 1,
},
item.before,
item.after
);
writeln!(&mut file, "{}", line_str).map_err(|_| PermissionDenied)?;
Ok(())
} }
+4 -53
View File
@@ -1,15 +1,12 @@
use crate::app::{AppState, fill_state}; use crate::app::{fill_state, AppState};
use crate::data::Id; use crate::data::Id;
use crate::data_provider::history::get_history_of_set; use crate::data_provider::sqlite::{create_db, SqliteConnection};
use crate::data_provider::sqlite::{create_db, default_connection, SqliteConnection};
use crate::data_provider::web_api::{ use crate::data_provider::web_api::{
get_local_version, get_web_version, load_data, set_local_version, get_local_version, get_web_version, load_data, set_local_version,
}; };
use crate::fs::*; use crate::fs::*;
use crate::navigation::Page::*; use crate::navigation::Page::*;
use crate::navigation::RootMessage::{ use crate::navigation::RootMessage::{Keyboard, Notification, PushNotification, UpdateData};
DataLoaded, Keyboard, Notification, PushNotification, UpdateData,
};
use crate::notification::NotificationData::PermissionDenied; use crate::notification::NotificationData::PermissionDenied;
use crate::notification::{ use crate::notification::{
NotificationData, NotificationManager, NotificationMessage, NotificationRequest, NotificationData, NotificationManager, NotificationMessage, NotificationRequest,
@@ -31,16 +28,11 @@ use crate::state_navigate_handle;
use crate::state_update; use crate::state_update;
use crate::view_navigation; use crate::view_navigation;
use crate::{message_navigation, push_error, push_error_task}; use crate::{message_navigation, push_error, push_error_task};
use chrono::NaiveDate;
use hashbrown::HashMap;
use iced::keyboard::Event; use iced::keyboard::Event;
use iced::widget::stack; use iced::widget::stack;
use iced::{Element, Task}; use iced::{Element, Task};
use iced_core::Length::Fill; use iced_core::Length::Fill;
use log::{debug, info}; use log::{debug, info};
use parking_lot::Mutex;
use rusqlite::Connection;
use std::sync::Arc;
use std::time::Instant; use std::time::Instant;
impl Default for ScreenState { impl Default for ScreenState {
@@ -71,7 +63,6 @@ pub enum RootMessage {
RepetitionSettings(RepetitionSettingsMessage), RepetitionSettings(RepetitionSettingsMessage),
Import(ImportMessage), Import(ImportMessage),
Keyboard(Event), Keyboard(Event),
DataLoaded(HashMap<Id, Vec<(NaiveDate, u32)>>),
PushNotification(NotificationRequest), PushNotification(NotificationRequest),
Notification(NotificationMessage), Notification(NotificationMessage),
None, None,
@@ -108,11 +99,8 @@ impl ScreenState {
create_db(); create_db();
info!("Db created"); info!("Db created");
let state = ScreenState::default(); let state = ScreenState::default();
let reading_additional_task = Task::perform(Self::load_additional_data(), |data| {
data.unwrap_or_else(|err| push_error!(err))
});
let mut final_task = reading_additional_task; let mut final_task = Task::none();
let state_guard = &state.app_state; let state_guard = &state.app_state;
if state_guard.sync_data.read().auto_web_fetch if state_guard.sync_data.read().auto_web_fetch
&& let Some(key) = state_guard.sync_data.read().key.clone() && let Some(key) = state_guard.sync_data.read().key.clone()
@@ -136,38 +124,6 @@ impl ScreenState {
(state, final_task) (state, final_task)
} }
async fn load_additional_data() -> Result<RootMessage, NotificationData> {
let directory = history_dir()?;
let mut map = HashMap::new();
for file in directory
.read_dir()
.map_err(|_| PermissionDenied)?
.filter_map(Result::ok)
{
let mut vec = vec![];
let history_file_name = match file.file_name().into_string() {
Err(_) => continue,
Ok(v) => v,
};
if let Ok(id) = history_file_name.split('_').collect::<Vec<_>>()[1].parse::<Id>()
&& let Ok(history) = get_history_of_set(id)
{
let by_date = history.chunk_by(|x, x1| {
x.timestamp.naive_local().date() == x1.timestamp.naive_local().date()
});
for group in by_date {
vec.push((group[0].timestamp.naive_local().date(), group.len() as u32));
}
map.insert(id, vec);
} else {
continue;
}
}
Ok(DataLoaded(map))
}
async fn load_web_backup(string: String) -> Result<bool, NotificationData> { async fn load_web_backup(string: String) -> Result<bool, NotificationData> {
let local = get_local_version().await?; let local = get_local_version().await?;
let web = get_web_version(string.as_str()).await?; let web = get_web_version(string.as_str()).await?;
@@ -189,11 +145,6 @@ impl ScreenState {
return self.handle_keyboard(e); return self.handle_keyboard(e);
} }
if let DataLoaded(data) = message {
self.app_state.activity = data;
return Task::none();
}
if let UpdateData = message { if let UpdateData = message {
info!("Loading data"); info!("Loading data");
let state = &mut self.app_state; let state = &mut self.app_state;
+2 -2
View File
@@ -81,7 +81,7 @@ impl NavigatedPage<DictionaryMessage> for DictionaryState {
let dict = &self.state.dictionary.read(); let dict = &self.state.dictionary.read();
let mut query = dict.query(Arc::new(|_| true)); let mut query = dict.query(Arc::new(|_| true));
let handles = query.handles(dict); let handles = query.handles();
words = handles.filter(|h| self.included.contains(h)).collect(); words = handles.filter(|h| self.included.contains(h)).collect();
return Some(Page::DictionaryQuiz(DictionaryQuizState::new( return Some(Page::DictionaryQuiz(DictionaryQuizState::new(
@@ -552,7 +552,7 @@ impl DictionaryState {
let groups = &self.state.word_groups.read(); let groups = &self.state.word_groups.read();
for mut group in self.groups_query.handles(groups) { for mut group in self.groups_query.handles() {
let group_name = groups.get(&mut group).unwrap().name.clone(); let group_name = groups.get(&mut group).unwrap().name.clone();
row = row.push( row = row.push(
button(text!("{}", group_name)) button(text!("{}", group_name))
+29 -27
View File
@@ -1,22 +1,20 @@
use crate::app::AppState; use crate::app::AppState;
use crate::data::INVALID_ID; use crate::data::entities::{HistoryNote, WordData};
use crate::data::entities::{HistoryItem, WordData}; use crate::data::repositories::Repository;
use crate::data_provider::history::get_history_of_set;
use crate::navigation::*; use crate::navigation::*;
use crate::styling::*; use crate::styling::*;
use HistoryMessage::Back;
use chrono::Local; use chrono::Local;
use iced::alignment::Horizontal::Center; use iced::alignment::Horizontal::Center;
use iced::widget::space::horizontal; use iced::widget::space::horizontal;
use iced::widget::*; use iced::widget::*;
use iced::{Element, Fill, FillPortion, Task}; use iced::{Element, Fill, FillPortion, Task};
use parking_lot::Mutex;
use std::sync::Arc; use std::sync::Arc;
use HistoryMessage::Back;
#[derive(Clone)] #[derive(Clone)]
pub struct HistoryState { pub struct HistoryState {
list: Vec<HistoryItem>, list: Vec<(WordData, HistoryNote)>,
words: Vec<WordData>, pub state: AppState,
} }
impl NavigatedPage<HistoryMessage> for HistoryState { impl NavigatedPage<HistoryMessage> for HistoryState {
@@ -49,50 +47,54 @@ impl NavigatedPage<HistoryMessage> for HistoryState {
impl HistoryState { impl HistoryState {
pub fn new(id: crate::data::Id, state: AppState) -> Self { pub fn new(id: crate::data::Id, state: AppState) -> Self {
let state = state.lock(); let dict = &state.dictionary.read();
let history = get_history_of_set(id).unwrap_or_default(); let history_repo = &mut state.history.write();
history_repo.init().expect("TODO: panic message");
let id = id.clone();
let mut history = history_repo.query(Arc::new(move |h| h.set_id == id));
let words = history let words = history
.iter() .iter(history_repo)
.map( .map(
|item| match state.dictionary.iter().find(|w| w.id == item.word_id) { |item| {let handle = dict.handle_by_id(item.word_id);
None => WordData { let word = dict.get_static(handle).unwrap_or(WordData {
id, id: Default::default(),
group_id: Default::default(),
key: "Удаленное слово".to_string(), key: "Удаленное слово".to_string(),
value: "Удаленное слово".to_string(), value: "".to_string(),
tags: "Удаленное слово".to_string(), tags: "".to_string(),
additional: Default::default(), additional: Default::default(),
group_id: INVALID_ID, });
}, (word, item.clone())
Some(word) => word.clone(), }
},
) )
.collect(); .collect();
Self { Self {
list: history, list: words,
words, state: state.clone(),
} }
} }
fn history_lines(&self) -> Column<'_, HistoryMessage> { fn history_lines(&self) -> Column<'_, HistoryMessage> {
let mut column = Column::new(); let mut column = Column::new();
for (index, item) in self.list.iter().enumerate() { for (index, item) in &self.list {
let activity = item;
column = column.push( column = column.push(
row![ row![
iced::widget::column![ iced::widget::column![
text!("{}", self.words[index].key) text!("{}", index.key)
.width(Fill) .width(Fill)
.align_x(Center), .align_x(Center),
text!("{}", self.words[index].value) text!("{}", index.value)
.width(Fill) .width(Fill)
.align_x(Center), .align_x(Center),
] ]
.align_x(Center), .align_x(Center),
iced::widget::column![ iced::widget::column![
text!("{} ➞ {}", item.before, item.after), text!("{} ➞ {}", activity.before, activity.after),
text!( text!(
"{}", "{}",
item.timestamp.with_timezone(&Local).format("%d.%m %H:%M") activity.timestamp.with_timezone(&Local).format("%d.%m %H:%M")
) )
] ]
.spacing(5) .spacing(5)
+27 -24
View File
@@ -1,5 +1,5 @@
use crate::app::AppState; use crate::app::AppState;
use crate::data::entities::{WordData, WordGroup}; use crate::data::entities::{EntityData, WordData, WordGroup};
use crate::data_provider::import::{ImportData, ImportGroup, get_words_of_group, load_groups}; use crate::data_provider::import::{ImportData, ImportGroup, get_words_of_group, load_groups};
use crate::data_provider::words::{add_group, add_words}; use crate::data_provider::words::{add_group, add_words};
use crate::fs::*; use crate::fs::*;
@@ -29,6 +29,7 @@ use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use tokio::task::spawn_blocking; use tokio::task::spawn_blocking;
use zip::ZipArchive; use zip::ZipArchive;
use crate::data::repositories::{BatchedRepository, Repository};
const DEFAULT_FIELDS: [&str; 6] = ["key", "value", "tags", "reading", "context", "description"]; const DEFAULT_FIELDS: [&str; 6] = ["key", "value", "tags", "reading", "context", "description"];
@@ -431,29 +432,18 @@ impl ImportState {
info!("{}", group.name); info!("{}", group.name);
Task::perform( Task::perform(
async move { async move {
spawn_blocking(move || { let words_list = spawn_blocking(move || {
let temp_file_path = temp_import_path(); let temp_file_path = temp_import_path();
let connection = let connection = Connection::open(&temp_file_path).map_err(|_| OpenFileError)?;
Connection::open(&temp_file_path).map_err(|_| OpenFileError)?;
let import_list = get_words_of_group(&connection, group.id)?; let import_list = get_words_of_group(&connection, group.id)?;
let mut words_list = Vec::with_capacity(import_list.len()); let mut words_list = Vec::with_capacity(import_list.len());
let map_indices = Self::get_mapping_indices(&group); let map_indices = Self::get_mapping_indices(&group);
let mut group_entity = WordGroup {
id: 0.into(),
name: group.name.clone(),
};
{
let state = state.lock();
add_group(&mut group_entity, &state.connection)?;
}
let group_id = group_entity.id;
for import in import_list { for import in import_list {
let mut word = WordData::new(); let mut word = WordData::new();
word.tags = import.tags.trim().replace(" ", ", "); word.tags = import.tags.trim().replace(" ", ", ");
word.group_id = group_id;
for (dest, indices) in &map_indices { for (dest, indices) in &map_indices {
let collected_string = Self::collect_strings( let collected_string = Self::collect_strings(
&import.fields, &import.fields,
@@ -480,24 +470,37 @@ impl ImportState {
words_list.push(word); words_list.push(word);
} }
let mut state = state.lock(); Ok::<_, NotificationData>(words_list)
let connection = &mut state.connection; })
.await
.unwrap()?;
let group_handle = {
let group_entity = WordGroup {
id: 0.into(),
name: group.name.clone(),
};
let mut groups_repo = state.word_groups.write();
groups_repo.create_now(group_entity)?
};
let group_id = group_handle.id;
let mut words_list = words_list;
for word in &mut words_list {
word.group_id = group_id;
}
let mut words_repo = state.dictionary.write();
let mut index = 0; let mut index = 0;
let total_len = words_list.len() as f32 / 1024.0; let total_len = words_list.len() as f32 / 1024.0;
for word in &mut words_list.chunks_mut(1024) { for words in &mut words_list.chunks_mut(1024) {
add_words(word, connection)?; words_repo.create_now_many(words)?;
index += 1; index += 1;
let mut progress = progress.lock(); let mut progress = progress.lock();
*progress = (index as f32 / total_len).min(1.0); *progress = (index as f32 / total_len).min(1.0);
} }
state.dictionary.append(&mut words_list);
state.word_groups.push(group_entity);
Ok(()) Ok(())
})
.await
.unwrap()
}, },
|r: Result<(), NotificationData>| match r { |r: Result<(), NotificationData>| match r {
Ok(_) => RootMessage::Import(ImportFinished), Ok(_) => RootMessage::Import(ImportFinished),
+1 -1
View File
@@ -167,7 +167,7 @@ impl RepetitionState {
} }
fn increment_counter(&mut self) { fn increment_counter(&mut self) {
let mut state = self.state.lock(); let mut state = self.state;
let id = self.settings.id; let id = self.settings.id;
let activity = state.activity.get_mut(&id); let activity = state.activity.get_mut(&id);