Server upload works
This commit is contained in:
@@ -1 +1,2 @@
|
|||||||
/target
|
/target
|
||||||
|
.idea
|
||||||
Generated
+53
@@ -1581,6 +1581,12 @@ version = "0.4.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hex-literal"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hexf-parse"
|
name = "hexf-parse"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@@ -2092,6 +2098,7 @@ dependencies = [
|
|||||||
"criterion",
|
"criterion",
|
||||||
"dirs",
|
"dirs",
|
||||||
"hex",
|
"hex",
|
||||||
|
"hex-literal",
|
||||||
"iced",
|
"iced",
|
||||||
"rand",
|
"rand",
|
||||||
"rayon",
|
"rayon",
|
||||||
@@ -2103,6 +2110,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"zstd",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2352,6 +2360,16 @@ version = "0.3.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime_guess"
|
||||||
|
version = "2.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
|
||||||
|
dependencies = [
|
||||||
|
"mime",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
@@ -3447,6 +3465,7 @@ dependencies = [
|
|||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"mime",
|
"mime",
|
||||||
|
"mime_guess",
|
||||||
"native-tls",
|
"native-tls",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
@@ -4591,6 +4610,12 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicase"
|
||||||
|
version = "2.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.18"
|
version = "0.3.18"
|
||||||
@@ -5936,6 +5961,34 @@ version = "1.0.20"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7"
|
checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd"
|
||||||
|
version = "0.13.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
|
||||||
|
dependencies = [
|
||||||
|
"zstd-safe",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd-safe"
|
||||||
|
version = "7.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
|
||||||
|
dependencies = [
|
||||||
|
"zstd-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd-sys"
|
||||||
|
version = "2.0.16+zstd.1.5.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zvariant"
|
name = "zvariant"
|
||||||
version = "5.9.2"
|
version = "5.9.2"
|
||||||
|
|||||||
+4
-2
@@ -12,12 +12,14 @@ serde_json = "1.0.149"
|
|||||||
rhai = "1.24.0"
|
rhai = "1.24.0"
|
||||||
chrono = "0.4.44"
|
chrono = "0.4.44"
|
||||||
rusqlite = { version = "0.39.0", features = ["chrono"] }
|
rusqlite = { version = "0.39.0", features = ["chrono"] }
|
||||||
reqwest = { version = "0", features = ["json", "stream"] }
|
reqwest = { version = "0", features = ["json", "stream", "multipart"] }
|
||||||
rodio = "0.22.2"
|
rodio = "0.22.2"
|
||||||
sha2 = "0.11.0"
|
sha2 = "0.11.0"
|
||||||
hex = "0.4.3"
|
|
||||||
tokio = { version = "1.50.0", features = ["rt", "rt-multi-thread", "macros"] }
|
tokio = { version = "1.50.0", features = ["rt", "rt-multi-thread", "macros"] }
|
||||||
rayon = "1.12.0"
|
rayon = "1.12.0"
|
||||||
|
hex = "0.4.3"
|
||||||
|
hex-literal = "1.1.0"
|
||||||
|
zstd = "0.13.3"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.5"
|
criterion = "0.5"
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ pub(crate) mod words;
|
|||||||
pub(crate) mod card_sets;
|
pub(crate) mod card_sets;
|
||||||
pub(crate) mod card_stats;
|
pub(crate) mod card_stats;
|
||||||
pub(crate) mod voice;
|
pub(crate) mod voice;
|
||||||
|
pub(crate) mod settings;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
use rusqlite::fallible_iterator::FallibleIterator;
|
||||||
|
use rusqlite::Connection;
|
||||||
|
|
||||||
|
pub fn get_setting(key: String, connection: &Connection) -> Option<String> {
|
||||||
|
let mut stmt = connection.prepare("SELECT value FROM settings WHERE id = ?1").unwrap();
|
||||||
|
let iter = stmt.query_map((key,), |row| {
|
||||||
|
row.get(0)
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
for row in iter {
|
||||||
|
if let Ok(value) = row {
|
||||||
|
return Some(value);
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_setting(key: String, value: String, connection: &Connection) {
|
||||||
|
let current = get_settings_list(connection);
|
||||||
|
if current.contains(&key) {
|
||||||
|
update_settings(key, value, connection);
|
||||||
|
}else {
|
||||||
|
create_settings(key, value, connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_settings(key: String, connection: &Connection) {
|
||||||
|
connection
|
||||||
|
.execute("DELETE FROM settings WHERE id = ?1", (&key,))
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
println!("{}", e);
|
||||||
|
0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_settings(key: String, value: String, connection: &Connection) {
|
||||||
|
connection
|
||||||
|
.execute(
|
||||||
|
"INSERT into settings (id, value) VALUES (?1, ?2)",
|
||||||
|
(key, value),
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
println!("{}", e);
|
||||||
|
0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_settings(key: String, value: String, connection: &Connection) {
|
||||||
|
connection
|
||||||
|
.execute(
|
||||||
|
"update settings
|
||||||
|
set value = ?2
|
||||||
|
where id = ?1",
|
||||||
|
(key, value),
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
println!("{}", e);
|
||||||
|
0
|
||||||
|
});}
|
||||||
|
|
||||||
|
fn get_settings_list(connection: &Connection) -> Vec<String> {
|
||||||
|
let mut stmt = connection.prepare("SELECT id FROM settings").unwrap();
|
||||||
|
let iter = stmt.query_map((), |row| {
|
||||||
|
row.get(0)
|
||||||
|
}).unwrap();
|
||||||
|
iter.map(|row| { row.unwrap() }).collect()
|
||||||
|
}
|
||||||
|
|
||||||
@@ -16,6 +16,16 @@ pub fn create_db() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn create_tables(conn: &Connection) {
|
fn create_tables(conn: &Connection) {
|
||||||
|
conn.execute(
|
||||||
|
"create table settings
|
||||||
|
(
|
||||||
|
id text primary key,
|
||||||
|
value text
|
||||||
|
);",
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|e| 0);
|
||||||
|
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"create table card_set
|
"create table card_set
|
||||||
(
|
(
|
||||||
@@ -28,7 +38,7 @@ fn create_tables(conn: &Connection) {
|
|||||||
);",
|
);",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap_or_else(|e| 0);
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"create table word_group
|
"create table word_group
|
||||||
(
|
(
|
||||||
@@ -38,7 +48,7 @@ fn create_tables(conn: &Connection) {
|
|||||||
);",
|
);",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap_or_else(|e| 0);
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"create table words
|
"create table words
|
||||||
(
|
(
|
||||||
@@ -55,7 +65,7 @@ fn create_tables(conn: &Connection) {
|
|||||||
);",
|
);",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap_or_else(|e| 0);
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"create table card_stats
|
"create table card_stats
|
||||||
(
|
(
|
||||||
@@ -72,13 +82,13 @@ fn create_tables(conn: &Connection) {
|
|||||||
);",
|
);",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap_or_else(|e| 0);
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"insert into word_group (name)
|
"insert into word_group (name)
|
||||||
values (\"Слова\");",
|
values (\"Слова\");",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap_or_else(|e| 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_word(word: &mut WordData, connection: &Connection) {
|
pub fn add_word(word: &mut WordData, connection: &Connection) {
|
||||||
|
|||||||
+24
-4
@@ -8,10 +8,12 @@ mod randomizer;
|
|||||||
mod repetition;
|
mod repetition;
|
||||||
mod repetitions;
|
mod repetitions;
|
||||||
mod selector;
|
mod selector;
|
||||||
|
mod sync;
|
||||||
mod word;
|
mod word;
|
||||||
mod writing;
|
mod writing;
|
||||||
|
|
||||||
use crate::data_provider::card_sets::load_sets;
|
use crate::data_provider::card_sets::load_sets;
|
||||||
|
use crate::data_provider::settings::get_setting;
|
||||||
use crate::data_provider::words::{create_db, load_word_groups, load_words};
|
use crate::data_provider::words::{create_db, load_word_groups, load_words};
|
||||||
use crate::dictionary::{app_data_dir, DictionaryMessage, DictionaryState};
|
use crate::dictionary::{app_data_dir, DictionaryMessage, DictionaryState};
|
||||||
use crate::dictionary_test::{DictionaryQuizMessage, DictionaryQuizState};
|
use crate::dictionary_test::{DictionaryQuizMessage, DictionaryQuizState};
|
||||||
@@ -21,10 +23,12 @@ use crate::randomizer::randomizer::{RandomizerMessage, RandomizerState};
|
|||||||
use crate::repetition::{RepetitionMessage, RepetitionState};
|
use crate::repetition::{RepetitionMessage, RepetitionState};
|
||||||
use crate::repetitions::{CardSetSettings, RepetitionsMessage, RepetitionsState};
|
use crate::repetitions::{CardSetSettings, RepetitionsMessage, RepetitionsState};
|
||||||
use crate::selector::*;
|
use crate::selector::*;
|
||||||
|
use crate::sync::{SyncMessage, SyncState};
|
||||||
use crate::word::{WordMessage, WordState};
|
use crate::word::{WordMessage, WordState};
|
||||||
use crate::writing::{WritingMessage, WritingState};
|
use crate::writing::{WritingMessage, WritingState};
|
||||||
use crate::Page::{
|
use crate::Page::{
|
||||||
Dictionary, DictionaryQuiz, Quiz, Randomizer, Repetition, Repetitions, Selector, Word, Writing,
|
Dictionary, DictionaryQuiz, Quiz, Randomizer, Repetition, Repetitions, Selector, Sync, Word,
|
||||||
|
Writing,
|
||||||
};
|
};
|
||||||
use crate::RootMessage::Keyboard;
|
use crate::RootMessage::Keyboard;
|
||||||
use iced::keyboard::Event;
|
use iced::keyboard::Event;
|
||||||
@@ -60,6 +64,7 @@ pub enum RootMessage {
|
|||||||
Repetitions(RepetitionsMessage),
|
Repetitions(RepetitionsMessage),
|
||||||
Repetition(RepetitionMessage),
|
Repetition(RepetitionMessage),
|
||||||
Word(WordMessage),
|
Word(WordMessage),
|
||||||
|
Sync(SyncMessage),
|
||||||
Keyboard(Event),
|
Keyboard(Event),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +78,7 @@ pub enum Page {
|
|||||||
Repetitions(RepetitionsState),
|
Repetitions(RepetitionsState),
|
||||||
Repetition(RepetitionState),
|
Repetition(RepetitionState),
|
||||||
Word(WordState),
|
Word(WordState),
|
||||||
|
Sync(SyncState),
|
||||||
PreviousPage,
|
PreviousPage,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +91,7 @@ pub struct AppState {
|
|||||||
pub card_sets: Vec<CardSetSettings>,
|
pub card_sets: Vec<CardSetSettings>,
|
||||||
pub word_groups: Vec<WordGroup>,
|
pub word_groups: Vec<WordGroup>,
|
||||||
pub connection: Connection,
|
pub connection: Connection,
|
||||||
|
pub sync_data: AppSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ScreenState {
|
impl Default for ScreenState {
|
||||||
@@ -96,12 +103,14 @@ impl Default for ScreenState {
|
|||||||
let list = load_words(&connection);
|
let list = load_words(&connection);
|
||||||
let sets = load_sets(&connection);
|
let sets = load_sets(&connection);
|
||||||
let groups = load_word_groups(&connection);
|
let groups = load_word_groups(&connection);
|
||||||
|
let setting = load_settings(&connection);
|
||||||
|
|
||||||
let state = Arc::new(Mutex::new(AppState {
|
let state = Arc::new(Mutex::new(AppState {
|
||||||
dictionary: list,
|
dictionary: list,
|
||||||
card_sets: sets,
|
card_sets: sets,
|
||||||
connection,
|
connection,
|
||||||
word_groups: groups,
|
word_groups: groups,
|
||||||
|
sync_data: setting,
|
||||||
}));
|
}));
|
||||||
ScreenState {
|
ScreenState {
|
||||||
stack: vec![Selector(SelectorState::new(state.clone()))],
|
stack: vec![Selector(SelectorState::new(state.clone()))],
|
||||||
@@ -109,6 +118,11 @@ impl Default for ScreenState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_settings(connection: &Connection) -> AppSettings {
|
||||||
|
let key = get_setting("SYNC_KEY".to_string(), connection);
|
||||||
|
AppSettings { key }
|
||||||
|
}
|
||||||
|
|
||||||
impl ScreenState {
|
impl ScreenState {
|
||||||
pub fn boot() -> (ScreenState, Task<RootMessage>) {
|
pub fn boot() -> (ScreenState, Task<RootMessage>) {
|
||||||
create_db();
|
create_db();
|
||||||
@@ -130,7 +144,8 @@ impl ScreenState {
|
|||||||
Randomizer,
|
Randomizer,
|
||||||
Repetitions,
|
Repetitions,
|
||||||
Repetition,
|
Repetition,
|
||||||
Word
|
Word,
|
||||||
|
Sync
|
||||||
);
|
);
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
@@ -145,7 +160,8 @@ impl ScreenState {
|
|||||||
Randomizer,
|
Randomizer,
|
||||||
Repetitions,
|
Repetitions,
|
||||||
Repetition,
|
Repetition,
|
||||||
Word
|
Word,
|
||||||
|
Sync
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +174,10 @@ impl ScreenState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct AppSettings {
|
||||||
|
key: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! view_navigation {
|
macro_rules! view_navigation {
|
||||||
($stack:expr, $($e:ident), *) => {
|
($stack:expr, $($e:ident), *) => {
|
||||||
@@ -207,4 +227,4 @@ trait NavigatedPage<T> {
|
|||||||
|
|
||||||
pub trait KeyPressedPage {
|
pub trait KeyPressedPage {
|
||||||
fn press(&mut self, message: &Event) -> Task<RootMessage>;
|
fn press(&mut self, message: &Event) -> Task<RootMessage>;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -193,7 +193,7 @@ impl RepetitionsState {
|
|||||||
column![
|
column![
|
||||||
text!("Худшие слова"),
|
text!("Худшие слова"),
|
||||||
container(scrollable(self.worst_words_list(&set)).height(200)).style(bordered_box),
|
container(scrollable(self.worst_words_list(&set)).height(200)).style(bordered_box),
|
||||||
radio("Начать с плохих слов", SetOrderMode::TrainWorstFirst, Some(set.open_mode), RepetitionsMessage::SetOpenMode),
|
// radio("Начать с плохих слов", SetOrderMode::TrainWorstFirst, Some(set.open_mode), RepetitionsMessage::SetOpenMode),
|
||||||
radio("Полностью случайно", SetOrderMode::FullRandom, Some(set.open_mode), RepetitionsMessage::SetOpenMode)
|
radio("Полностью случайно", SetOrderMode::FullRandom, Some(set.open_mode), RepetitionsMessage::SetOpenMode)
|
||||||
]
|
]
|
||||||
.spacing(DEFAULT_SPACING)
|
.spacing(DEFAULT_SPACING)
|
||||||
|
|||||||
+7
-1
@@ -9,6 +9,7 @@ use crate::Page::{Quiz, Writing};
|
|||||||
use crate::{AppState, NavigatedPage, Page, QuizState, RootMessage, DEFAULT_SPACING};
|
use crate::{AppState, NavigatedPage, Page, QuizState, RootMessage, DEFAULT_SPACING};
|
||||||
use iced::widget::*;
|
use iced::widget::*;
|
||||||
use iced::{alignment, Element, Task};
|
use iced::{alignment, Element, Task};
|
||||||
|
use crate::sync::SyncState;
|
||||||
|
|
||||||
pub struct SelectorState {
|
pub struct SelectorState {
|
||||||
pub set: KanaSet,
|
pub set: KanaSet,
|
||||||
@@ -25,6 +26,7 @@ pub enum SelectorMessage {
|
|||||||
ToDictionary,
|
ToDictionary,
|
||||||
ToRandomize,
|
ToRandomize,
|
||||||
ToRepetitions,
|
ToRepetitions,
|
||||||
|
ToSync,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NavigatedPage<SelectorMessage> for SelectorState {
|
impl NavigatedPage<SelectorMessage> for SelectorState {
|
||||||
@@ -48,6 +50,9 @@ impl NavigatedPage<SelectorMessage> for SelectorState {
|
|||||||
if let SelectorMessage::ToRepetitions = message {
|
if let SelectorMessage::ToRepetitions = message {
|
||||||
return Some(Page::Repetitions(RepetitionsState::new(self.state.clone())));
|
return Some(Page::Repetitions(RepetitionsState::new(self.state.clone())));
|
||||||
}
|
}
|
||||||
|
if let SelectorMessage::ToSync = message {
|
||||||
|
return Some(Page::Sync(SyncState::new(self.state.clone())));
|
||||||
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,7 +85,8 @@ impl SelectorState {
|
|||||||
button("Переключить азбуки").on_press(SelectorMessage::Change),
|
button("Переключить азбуки").on_press(SelectorMessage::Change),
|
||||||
button("Словарь").on_press(SelectorMessage::ToDictionary),
|
button("Словарь").on_press(SelectorMessage::ToDictionary),
|
||||||
button("Рандомайзер").on_press(SelectorMessage::ToRandomize),
|
button("Рандомайзер").on_press(SelectorMessage::ToRandomize),
|
||||||
button("Повторение").on_press(SelectorMessage::ToRepetitions)
|
button("Повторение").on_press(SelectorMessage::ToRepetitions),
|
||||||
|
button("Синхронизация").on_press(SelectorMessage::ToSync)
|
||||||
]
|
]
|
||||||
.spacing(DEFAULT_SPACING),
|
.spacing(DEFAULT_SPACING),
|
||||||
self.rows_selector(),
|
self.rows_selector(),
|
||||||
|
|||||||
+230
@@ -0,0 +1,230 @@
|
|||||||
|
use std::io;
|
||||||
|
use crate::data_provider::settings::set_setting;
|
||||||
|
use crate::sync::SyncMessage::NextAnimation;
|
||||||
|
use crate::Page::PreviousPage;
|
||||||
|
use crate::{AppState, NavigatedPage, Page, RootMessage, DEFAULT_SPACING};
|
||||||
|
use iced::widget::container::rounded_box;
|
||||||
|
use iced::widget::{button, column, container, progress_bar, row, space, text};
|
||||||
|
use iced::{Element, Fill, Font, Left, Task};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::time::Duration;
|
||||||
|
use zstd::{Encoder, DEFAULT_COMPRESSION_LEVEL};
|
||||||
|
use crate::dictionary::app_data_dir;
|
||||||
|
|
||||||
|
const API_URL: &str = "https://learning.micialware.ru/";
|
||||||
|
/*const API_URL: &str = "http://localhost:8089/";*/
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum SyncMessage {
|
||||||
|
Back,
|
||||||
|
CopyKey,
|
||||||
|
KeyCopied,
|
||||||
|
IdReceived(String),
|
||||||
|
InitSync,
|
||||||
|
GetKey,
|
||||||
|
Send,
|
||||||
|
GetLast,
|
||||||
|
NextAnimation,
|
||||||
|
NetworkFinished,
|
||||||
|
}
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SyncState {
|
||||||
|
state: Arc<Mutex<AppState>>,
|
||||||
|
frozen: bool,
|
||||||
|
progress: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NavigatedPage<SyncMessage> for SyncState {
|
||||||
|
fn navigate(&self, message: &SyncMessage) -> Option<Page> {
|
||||||
|
if let SyncMessage::Back = message
|
||||||
|
&& !self.frozen
|
||||||
|
{
|
||||||
|
Some(PreviousPage)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SyncState {
|
||||||
|
pub fn new(state: Arc<Mutex<AppState>>) -> SyncState {
|
||||||
|
Self {
|
||||||
|
state,
|
||||||
|
frozen: false,
|
||||||
|
progress: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, message: SyncMessage) -> Task<RootMessage> {
|
||||||
|
match message {
|
||||||
|
SyncMessage::Back => {}
|
||||||
|
SyncMessage::CopyKey => {
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
let key = state.sync_data.key.clone().unwrap();
|
||||||
|
return iced::clipboard::write(key)
|
||||||
|
.map(|_val: String| RootMessage::Sync(SyncMessage::KeyCopied));
|
||||||
|
}
|
||||||
|
SyncMessage::InitSync => {
|
||||||
|
return Task::perform(first_sync(), |id| {
|
||||||
|
RootMessage::Sync(SyncMessage::IdReceived(id))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
SyncMessage::GetKey => {
|
||||||
|
return iced::clipboard::read().map(|key| {
|
||||||
|
RootMessage::Sync(SyncMessage::IdReceived(key.unwrap_or_else(String::new)))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
SyncMessage::KeyCopied => {}
|
||||||
|
SyncMessage::IdReceived(new_id) => {
|
||||||
|
if validate_id(&new_id) == false {
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
let mut state = self.state.lock().unwrap();
|
||||||
|
state.sync_data.key = Some(new_id.clone());
|
||||||
|
set_setting("SYNC_KEY".to_string(), new_id, &state.connection);
|
||||||
|
}
|
||||||
|
SyncMessage::Send => {
|
||||||
|
self.prepare_to_db_interaction();
|
||||||
|
|
||||||
|
let id = self.state.lock().unwrap().sync_data.key.clone().unwrap();
|
||||||
|
let tasks = Task::batch([
|
||||||
|
Task::perform(
|
||||||
|
async { tokio::time::sleep(Duration::from_millis(200)).await },
|
||||||
|
|_| RootMessage::Sync(NextAnimation),
|
||||||
|
),
|
||||||
|
Task::perform(send_data(id), |_| {
|
||||||
|
RootMessage::Sync(SyncMessage::NetworkFinished)
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return tasks;
|
||||||
|
}
|
||||||
|
SyncMessage::GetLast => self.prepare_to_db_interaction(),
|
||||||
|
NextAnimation => {
|
||||||
|
self.progress += 1.0;
|
||||||
|
if self.progress > 5.0 {
|
||||||
|
self.progress = 0.0;
|
||||||
|
}
|
||||||
|
if self.frozen {
|
||||||
|
return Task::perform(
|
||||||
|
async { tokio::time::sleep(Duration::from_millis(200)).await },
|
||||||
|
|_| RootMessage::Sync(NextAnimation),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SyncMessage::NetworkFinished => {
|
||||||
|
self.frozen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
pub fn view(&self) -> Element<'_, SyncMessage> {
|
||||||
|
container(
|
||||||
|
column![
|
||||||
|
button("Назад").on_press(SyncMessage::Back),
|
||||||
|
row![self.sync_column(), self.app_updater()].spacing(DEFAULT_SPACING)
|
||||||
|
]
|
||||||
|
.align_x(Left)
|
||||||
|
.width(Fill)
|
||||||
|
.height(Fill)
|
||||||
|
.spacing(DEFAULT_SPACING),
|
||||||
|
)
|
||||||
|
.center_x(Fill)
|
||||||
|
.padding(10)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync_column(&self) -> Element<'_, SyncMessage> {
|
||||||
|
let network_view: Element<'_, SyncMessage> = if self.frozen {
|
||||||
|
progress_bar(0.0..=5.0, self.progress).into()
|
||||||
|
} else {
|
||||||
|
space().into()
|
||||||
|
};
|
||||||
|
if let Some(key) = self.state.lock().unwrap().sync_data.key.clone() {
|
||||||
|
column![
|
||||||
|
text!("Ваш ключ синхронизации"),
|
||||||
|
container(container(text!("{}", key).size(20).font(Font::MONOSPACE)).padding(3))
|
||||||
|
.style(rounded_box),
|
||||||
|
button("Скопировать в буффер обмена").on_press(SyncMessage::CopyKey),
|
||||||
|
row![
|
||||||
|
button("↑ Отправить").on_press(SyncMessage::Send),
|
||||||
|
button("↓ Скачать").on_press(SyncMessage::GetLast)
|
||||||
|
]
|
||||||
|
.spacing(DEFAULT_SPACING),
|
||||||
|
container(network_view).width(Fill),
|
||||||
|
]
|
||||||
|
.spacing(DEFAULT_SPACING)
|
||||||
|
.width(Fill)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
column![
|
||||||
|
button("Создать сохранение").on_press(SyncMessage::InitSync),
|
||||||
|
button("Вставить ключ из буффера").on_press(SyncMessage::GetKey),
|
||||||
|
]
|
||||||
|
.spacing(DEFAULT_SPACING)
|
||||||
|
.width(Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app_updater(&self) -> Element<'_, SyncMessage> {
|
||||||
|
column![].width(Fill).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_to_db_interaction(&mut self) {
|
||||||
|
if self.frozen {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.frozen = true;
|
||||||
|
self.progress = 0.0;
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
state
|
||||||
|
.connection
|
||||||
|
.query_row("PRAGMA wal_checkpoint(TRUNCATE)", [], |_| Ok(()))
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
eprintln!("Ошибка при checkpoint WAL: {e}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_data(id: String) {
|
||||||
|
let path = app_data_dir();
|
||||||
|
let db_file = path.join("data.db");
|
||||||
|
let data = tokio::fs::read(db_file).await.unwrap();
|
||||||
|
let data = compress(data);
|
||||||
|
|
||||||
|
let api_url = API_URL.to_string();
|
||||||
|
let id_url = format!("{api_url}upload/{id}");
|
||||||
|
|
||||||
|
let form = reqwest::multipart::Form::new().part("db", reqwest::multipart::Part::bytes(data));
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
client.post(&id_url).multipart(form).send().await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compress(data: Vec<u8>) -> Vec<u8> {
|
||||||
|
let mut encoder = Encoder::new(Vec::new(), DEFAULT_COMPRESSION_LEVEL).unwrap();
|
||||||
|
io::copy(&mut &data[..], &mut encoder).unwrap();
|
||||||
|
let compressed = encoder.finish().unwrap();
|
||||||
|
compressed
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn first_sync() -> String {
|
||||||
|
let id_url = API_URL.to_string() + "generate";
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let id = client
|
||||||
|
.get(&id_url)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.text()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
println!("id: {}", id);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn validate_id(id: &str) -> bool {
|
||||||
|
id.len() == 24 && id.chars().all(|c| c.is_ascii_alphanumeric())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user