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