Badly implement Series Listing from JNC Library
All checks were successful
Run Tests on Code / run-tests (push) Successful in 31s
All checks were successful
Run Tests on Code / run-tests (push) Successful in 31s
This commit is contained in:
parent
2652933bbb
commit
20d16363e3
2 changed files with 311 additions and 71 deletions
328
src/jnovel.rs
328
src/jnovel.rs
|
@ -1,7 +1,10 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use arrayvec::ArrayString;
|
use arrayvec::ArrayString;
|
||||||
use chrono::{DateTime, Local};
|
use chrono::{DateTime, Local};
|
||||||
use reqwest::StatusCode;
|
use iced::futures::future::join_all;
|
||||||
|
use iced::subscription;
|
||||||
|
use reqwest::{Error, Response, StatusCode};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
macro_rules! api {
|
macro_rules! api {
|
||||||
|
@ -16,11 +19,12 @@ pub(crate) enum Message {
|
||||||
LibraryState(LibraryState),
|
LibraryState(LibraryState),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Eq, PartialEq)]
|
#[derive(Debug, Clone, Default, PartialEq)]
|
||||||
pub(crate) struct State {
|
pub(crate) struct State {
|
||||||
pub(crate) token: Option<ArrayString<32>>,
|
pub(crate) token: Option<ArrayString<36>>,
|
||||||
pub(crate) login_state: LoginState,
|
pub(crate) login_state: LoginState,
|
||||||
pub(crate) library_state: LibraryState,
|
pub(crate) library_state: LibraryState,
|
||||||
|
pub(crate) library: HashMap<String, Series>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
|
@ -52,54 +56,186 @@ impl State {
|
||||||
pub(crate) async fn logout(state: State) {
|
pub(crate) async fn logout(state: State) {
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
let url = api!() + format!("app/v1/auth/logout?{:?}", state.token).as_str();
|
if let Some(token_str) = state.token {
|
||||||
|
let url = api!() + format!("app/v1/auth/logout?access_token={}", token_str).as_str();
|
||||||
let _ = client.post(url).send().await;
|
let res = client.post(url).send().await;
|
||||||
|
match res {
|
||||||
|
Ok(data) => println!("{}", data.status()),
|
||||||
|
Err(e) => println!("{}", e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn load_library(state: State) -> LibraryState {
|
pub(crate) fn load_library(token: ArrayString<36>) -> iced::Subscription<LibraryState> {
|
||||||
/*
|
subscription::unfold("library-loader", Progress::Ready, move |state| {
|
||||||
1: lib = https://labs.j-novel.club/app/v1/me/library
|
Self::fetch_library_all(token, state)
|
||||||
2: every vol in lib.books serie = https://labs.j-novel.club/app/v1/volumes/{vol.volume.legacyId}/serie?format=json
|
})
|
||||||
3: ser[serie.slug].info = serie
|
}
|
||||||
4: ser[serie.slug].volumes.insert(vol)
|
|
||||||
*/
|
async fn fetch_library_all(token: ArrayString<36>, state: Progress) -> (LibraryState, Progress) {
|
||||||
|
match state {
|
||||||
|
Progress::Ready => {
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
|
let url = api!() + format!("app/v1/me/library?format=json&access_token={}", token).as_str();
|
||||||
let url = api!() + format!("app/v1/me/library?format=json&{:?}", state.token).as_str();
|
match client.get(url).send().await {
|
||||||
|
Ok(res) => {
|
||||||
println!("{url}");
|
|
||||||
|
|
||||||
let res = match client.get(url)
|
|
||||||
.send()
|
|
||||||
.await {
|
|
||||||
Ok(res) => res,
|
|
||||||
Err(e) => return LibraryState::Error(e.to_string()),
|
|
||||||
};
|
|
||||||
|
|
||||||
match res.text().await {
|
match res.text().await {
|
||||||
Ok(text) => {
|
Ok(text) => {
|
||||||
let library = match serde_json::from_str(&text) {
|
match serde_json::from_str::<ApiLibrary>(&text) {
|
||||||
Ok(library) => library,
|
Ok(library) => {
|
||||||
Err(e) => return LibraryState::Error(e.to_string()),
|
let series: HashMap<String, Series> = HashMap::new();
|
||||||
|
let total = library.books.len();
|
||||||
|
|
||||||
|
(LibraryState::Loading(0.0), Progress::Downloading {
|
||||||
|
token,
|
||||||
|
series_data: series,
|
||||||
|
books: library.books.clone(),
|
||||||
|
index: 0,
|
||||||
|
total,
|
||||||
|
downloaded: 0,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Err(e) => (LibraryState::Error(e.to_string()), Progress::Finished(None)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => (LibraryState::Error(e.to_string()), Progress::Finished(None)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => (LibraryState::Error(e.to_string()), Progress::Finished(None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Progress::Downloading {
|
||||||
|
token,
|
||||||
|
mut series_data,
|
||||||
|
books,
|
||||||
|
index,
|
||||||
|
total,
|
||||||
|
downloaded
|
||||||
|
} => {
|
||||||
|
let start = Local::now(); //DEBUG
|
||||||
|
let increment = 3;
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let mut res_vec = vec![];
|
||||||
|
if total - index < increment {
|
||||||
|
let id = books[index].volume.legacy_id.clone();
|
||||||
|
let url = api!() + format!("app/v1/volumes/{}/serie?format=json&access_token={}", id, token).as_str();
|
||||||
|
let res = client.get(url).send().await.expect("This is uggly, Error handling here needs to be improved");
|
||||||
|
let data = res.text().await;
|
||||||
|
res_vec.push(data);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let mut tasks = vec![];
|
||||||
|
|
||||||
|
for i in 0..=(increment-1) {
|
||||||
|
let id = books[index + i].volume.legacy_id.clone();
|
||||||
|
let client = client.clone();
|
||||||
|
let task = tokio::spawn(async move {
|
||||||
|
let url = api!() + format!("app/v1/volumes/{}/serie?format=json&access_token={}", id, token).as_str();
|
||||||
|
let res = client.get(url).send().await.expect("This is uggly, Error handling here needs to be improved");
|
||||||
|
res.text().await
|
||||||
|
});
|
||||||
|
tasks.push(task);
|
||||||
|
}
|
||||||
|
let awaited = join_all(tasks).await;
|
||||||
|
for res in awaited {
|
||||||
|
res_vec.push(res.expect("Future should have finished by now"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let elapsed = (Local::now() - start);
|
||||||
|
println!("Elapsed After Fetch: {}.{}s", elapsed.num_seconds(), elapsed.num_milliseconds());
|
||||||
|
|
||||||
|
for (i, data) in res_vec.iter().enumerate() {
|
||||||
|
let book = &books[index + i];
|
||||||
|
match data {
|
||||||
|
Ok(json) => {
|
||||||
|
let data = serde_json::from_str::<ApiSeries>(json.as_str()).expect("JSON schema should be modeled accurately");
|
||||||
|
|
||||||
|
match series_data.get_mut(&data.slug) {
|
||||||
|
Some(entry) => {
|
||||||
|
// since the vec exists it can be assumed at least one book exists
|
||||||
|
// so we check for the highest number that is lower than the book.volume.number
|
||||||
|
entry.volumes.push(book.clone());
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let new_entry = Series {
|
||||||
|
info: data.clone(),
|
||||||
|
volumes: vec![book.clone()]
|
||||||
};
|
};
|
||||||
|
|
||||||
LibraryState::Loaded
|
series_data.insert(data.slug, new_entry);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Err(e) => println!("{e}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let downloaded = downloaded + increment;
|
||||||
|
let progress = downloaded as f32 / total as f32;
|
||||||
|
let index = index + increment;
|
||||||
|
|
||||||
|
if index < total {
|
||||||
|
(LibraryState::Loading(progress), Progress::Downloading {
|
||||||
|
token,
|
||||||
|
series_data,
|
||||||
|
books,
|
||||||
|
index,
|
||||||
|
total,
|
||||||
|
downloaded,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
(LibraryState::Loading(progress), Progress::Finished(Some(series_data)))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
Progress::Finished(mut series_data) => {
|
||||||
|
match series_data.clone() {
|
||||||
|
Some(mut data) => {
|
||||||
|
data.iter_mut().for_each(|(_, entry)| {
|
||||||
|
entry.volumes.sort_by(|a, b| a.volume.number.cmp(&b.volume.number));
|
||||||
|
});
|
||||||
|
|
||||||
|
(LibraryState::Loaded(data.clone()), Progress::Idle)
|
||||||
},
|
},
|
||||||
Err(e) => LibraryState::Error(e.to_string()),
|
None => (LibraryState::Error("Unexpected Error".to_owned()), Progress::Finished(series_data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Progress::Idle => {
|
||||||
|
(LibraryState::Idle, Progress::Idle)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize)]
|
#[derive(Debug, Clone, Default, PartialEq)]
|
||||||
pub(crate) enum LibraryState {
|
pub(crate) enum LibraryState {
|
||||||
#[default]
|
#[default]
|
||||||
Unloaded,
|
Unloaded,
|
||||||
Loading,
|
Loading(f32),
|
||||||
Loaded,
|
Loaded(HashMap<String, Series>),
|
||||||
|
Idle,
|
||||||
Error(String),
|
Error(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Progress {
|
||||||
|
Ready,
|
||||||
|
Downloading {
|
||||||
|
token: ArrayString<36>,
|
||||||
|
series_data: HashMap<String, Series>,
|
||||||
|
books: Vec<LibraryVolume>,
|
||||||
|
index: usize,
|
||||||
|
total: usize,
|
||||||
|
downloaded: usize
|
||||||
|
},
|
||||||
|
Finished (Option<HashMap<String, Series>>),
|
||||||
|
Idle
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Deserialize)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Deserialize)]
|
||||||
pub(crate) struct Otp {
|
pub(crate) struct Otp {
|
||||||
pub(crate) otp: ArrayString<6>,
|
pub(crate) otp: ArrayString<6>,
|
||||||
|
@ -133,7 +269,7 @@ impl Otp {
|
||||||
|
|
||||||
match serde_json::from_str::<OtpToken>(text.as_str()) {
|
match serde_json::from_str::<OtpToken>(text.as_str()) {
|
||||||
Ok(data) => {
|
Ok(data) => {
|
||||||
let token = data.id.get(3..35).expect("Token should fill the ArrayString<36>").parse().expect("Should fit ArrayString<32>");
|
let token = data.id;
|
||||||
(LoginState::Success(token), OtpState::Valid(token))
|
(LoginState::Success(token), OtpState::Valid(token))
|
||||||
},
|
},
|
||||||
Err(e) => (LoginState::Error(e.to_string()), OtpState::Error(e.to_string()))
|
Err(e) => (LoginState::Error(e.to_string()), OtpState::Error(e.to_string()))
|
||||||
|
@ -166,7 +302,7 @@ pub(crate) enum OtpState {
|
||||||
Starting(Otp, DateTime<Local>),
|
Starting(Otp, DateTime<Local>),
|
||||||
Waiting(Otp, DateTime<Local>),
|
Waiting(Otp, DateTime<Local>),
|
||||||
Error(String),
|
Error(String),
|
||||||
Valid(ArrayString<32>)
|
Valid(ArrayString<36>)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Eq, PartialEq)]
|
#[derive(Debug, Clone, Default, Eq, PartialEq)]
|
||||||
|
@ -174,7 +310,7 @@ pub(crate) enum LoginState {
|
||||||
#[default]
|
#[default]
|
||||||
LoggedOut,
|
LoggedOut,
|
||||||
LoggingIn,
|
LoggingIn,
|
||||||
Success(ArrayString<32>),
|
Success(ArrayString<36>),
|
||||||
AwaitingConfirmation(Otp, DateTime<Local>),
|
AwaitingConfirmation(Otp, DateTime<Local>),
|
||||||
Error(String)
|
Error(String)
|
||||||
}
|
}
|
||||||
|
@ -193,6 +329,7 @@ impl Display for LoginState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
struct ApiLibrary {
|
struct ApiLibrary {
|
||||||
books: Vec<LibraryVolume>,
|
books: Vec<LibraryVolume>,
|
||||||
pagination: Pagination
|
pagination: Pagination
|
||||||
|
@ -206,11 +343,40 @@ struct Pagination {
|
||||||
last_page: bool
|
last_page: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Series {
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) struct Series {
|
||||||
|
pub(crate) info: ApiSeries,
|
||||||
|
pub(crate) volumes: Vec<LibraryVolume>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||||
|
pub(crate) struct ApiSeries {
|
||||||
|
id: String,
|
||||||
|
#[serde(alias="legacyId")]
|
||||||
|
legacy_id: String,
|
||||||
|
r#type: Type,
|
||||||
|
status: VolumeStatus,
|
||||||
|
pub(crate) title: String,
|
||||||
|
#[serde(alias="shortTitle")]
|
||||||
|
short_title: String,
|
||||||
|
#[serde(alias="originalTitle")]
|
||||||
|
original_title: String,
|
||||||
|
pub(crate) slug: String,
|
||||||
|
hidden: bool,
|
||||||
|
created: String,
|
||||||
|
pub(crate) description: String,
|
||||||
|
#[serde(alias="shortDescription")]
|
||||||
|
short_description: String,
|
||||||
|
pub(crate) tags: Vec<String>,
|
||||||
|
cover: Cover,
|
||||||
|
following: bool,
|
||||||
|
catchup: bool,
|
||||||
|
rentals: bool,
|
||||||
|
#[serde(alias="topicId")]
|
||||||
|
topic_id: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||||
struct LibraryVolume {
|
struct LibraryVolume {
|
||||||
id: String,
|
id: String,
|
||||||
#[serde(alias="legacyId")]
|
#[serde(alias="legacyId")]
|
||||||
|
@ -221,22 +387,22 @@ struct LibraryVolume {
|
||||||
#[serde(alias="lastDownloaded")]
|
#[serde(alias="lastDownloaded")]
|
||||||
last_downloaded: Option<String>,
|
last_downloaded: Option<String>,
|
||||||
#[serde(alias="lastUpdated")]
|
#[serde(alias="lastUpdated")]
|
||||||
last_updated: Option<String>,
|
pub(crate) last_updated: Option<String>,
|
||||||
downloads: Vec<Download>,
|
downloads: Vec<Download>,
|
||||||
status: String
|
status: String
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||||
struct Volume {
|
pub(crate) struct Volume {
|
||||||
id: String,
|
id: String,
|
||||||
#[serde(alias="legacyId")]
|
#[serde(alias="legacyId")]
|
||||||
legacy_id: String,
|
legacy_id: String,
|
||||||
title: String,
|
pub(crate) title: String,
|
||||||
#[serde(alias="originalTitle")]
|
#[serde(alias="originalTitle")]
|
||||||
original_title: String,
|
original_title: String,
|
||||||
slug: String,
|
slug: String,
|
||||||
number: usize,
|
pub(crate) number: usize,
|
||||||
#[serde(alias="OriginalPublisher")]
|
#[serde(alias="originalPublisher")]
|
||||||
original_publisher: String,
|
original_publisher: String,
|
||||||
label: Label,
|
label: Label,
|
||||||
creators: Vec<Creator>,
|
creators: Vec<Creator>,
|
||||||
|
@ -244,21 +410,21 @@ struct Volume {
|
||||||
#[serde(alias="forumTopicId")]
|
#[serde(alias="forumTopicId")]
|
||||||
forum_topic_id: usize,
|
forum_topic_id: usize,
|
||||||
created: String,
|
created: String,
|
||||||
publising: String,
|
publishing: String,
|
||||||
description: String,
|
description: String,
|
||||||
#[serde(alias="shortDescription")]
|
#[serde(alias="shortDescription")]
|
||||||
short_description: String,
|
pub(crate) short_description: String,
|
||||||
cover: Cover,
|
cover: Cover,
|
||||||
owned: bool,
|
owned: bool,
|
||||||
#[serde(alias="premiumExtras")]
|
#[serde(alias="premiumExtras")]
|
||||||
premium_extras: String,
|
premium_extras: Option<String>,
|
||||||
#[serde(alias="noEbook")]
|
#[serde(alias="noEbook")]
|
||||||
no_ebook: bool,
|
no_ebook: bool,
|
||||||
#[serde(alias="totalParts")]
|
#[serde(alias="totalParts")]
|
||||||
total_parts: usize
|
total_parts: usize
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||||
struct Cover {
|
struct Cover {
|
||||||
#[serde(alias="originalUrl")]
|
#[serde(alias="originalUrl")]
|
||||||
original_url: String,
|
original_url: String,
|
||||||
|
@ -268,7 +434,7 @@ struct Cover {
|
||||||
thumbnail_url: String,
|
thumbnail_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||||
struct Creator {
|
struct Creator {
|
||||||
id: String,
|
id: String,
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -277,29 +443,73 @@ struct Creator {
|
||||||
original_name: String
|
original_name: String
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||||
struct Download {
|
struct Download {
|
||||||
link: String,
|
link: String,
|
||||||
r#type: FileType,
|
r#type: FileType,
|
||||||
label: String,
|
label: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||||
enum Label {
|
enum Label {
|
||||||
|
#[serde(alias="")]
|
||||||
|
None,
|
||||||
|
#[serde(alias="J-Novel Heart")]
|
||||||
Heart,
|
Heart,
|
||||||
|
#[serde(alias="J-Novel Club")]
|
||||||
Club,
|
Club,
|
||||||
Pulp
|
#[serde(alias="J-Novel Pulp")]
|
||||||
|
Pulp,
|
||||||
|
#[serde(alias="TOブックスラノベ", alias="TO Books")]
|
||||||
|
ToBooks,
|
||||||
|
#[serde(alias="HJ文庫")]
|
||||||
|
HobbyJapan,
|
||||||
|
#[serde(alias="オーバーラップ文庫")]
|
||||||
|
Overlap,
|
||||||
|
#[serde(other)]
|
||||||
|
Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||||
enum FileType {
|
enum FileType {
|
||||||
EPUB
|
#[serde(alias="EPUB")]
|
||||||
|
Epub,
|
||||||
|
#[serde(other)]
|
||||||
|
Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||||
enum Role {
|
enum Role {
|
||||||
|
#[serde(alias="AUTHOR")]
|
||||||
AUTHOR,
|
AUTHOR,
|
||||||
ILLUSTRATOR,
|
#[serde(alias="ARTIST")]
|
||||||
TRANSLATOR,
|
Artist,
|
||||||
EDITOR
|
#[serde(alias="EDITOR")]
|
||||||
|
Editor,
|
||||||
|
#[serde(alias="ILLUSTRATOR")]
|
||||||
|
Illustrator,
|
||||||
|
#[serde(alias="LETTERER")]
|
||||||
|
Letterer,
|
||||||
|
#[serde(alias="TRANSLATOR")]
|
||||||
|
Translator,
|
||||||
|
#[serde(other)]
|
||||||
|
Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||||
|
enum Type {
|
||||||
|
#[serde(alias="NOVEL")]
|
||||||
|
Novel,
|
||||||
|
#[serde(alias="MANGA")]
|
||||||
|
Manga,
|
||||||
|
#[serde(other)]
|
||||||
|
Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||||
|
enum VolumeStatus {
|
||||||
|
#[serde(alias="DEFAULT")]
|
||||||
|
Default,
|
||||||
|
#[serde(other)]
|
||||||
|
Unknown
|
||||||
}
|
}
|
50
src/main.rs
50
src/main.rs
|
@ -116,22 +116,19 @@ impl Application for CalibreWebImporter {
|
||||||
}
|
}
|
||||||
jnovel::Message::Logout => {
|
jnovel::Message::Logout => {
|
||||||
println!("Logging out from J-Novel Club");
|
println!("Logging out from J-Novel Club");
|
||||||
|
let jnc = self.jnc.clone();
|
||||||
self.jnc.token = None;
|
self.jnc.token = None;
|
||||||
self.jnc.login_state = jnovel::LoginState::LoggedOut;
|
self.jnc.login_state = jnovel::LoginState::LoggedOut;
|
||||||
Command::perform(jnovel::State::logout(self.jnc.clone()), |()| Message::JncAction(jnovel::Message::LoginState(jnovel::LoginState::LoggedOut)))
|
self.jnc.library_state = jnovel::LibraryState::Unloaded;
|
||||||
|
Command::perform(jnovel::State::logout(jnc), |()| Message::JncAction(jnovel::Message::LoginState(jnovel::LoginState::LoggedOut)))
|
||||||
}
|
}
|
||||||
jnovel::Message::LoginState(status) => {
|
jnovel::Message::LoginState(status) => {
|
||||||
if let jnovel::LoginState::Success(token) = status {
|
if let jnovel::LoginState::Success(token) = status {
|
||||||
self.jnc.token = Some(token);
|
self.jnc.token = Some(token);
|
||||||
self.jnc.login_state = status;
|
self.jnc.login_state = status;
|
||||||
|
|
||||||
if self.jnc.library_state == jnovel::LibraryState::Unloaded {
|
|
||||||
Command::perform(jnovel::State::load_library(self.jnc.clone()), |status| Message::JncAction(jnovel::Message::LibraryState(status)))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
self.jnc.login_state = status;
|
self.jnc.login_state = status;
|
||||||
Command::none()
|
Command::none()
|
||||||
|
@ -142,13 +139,20 @@ impl Application for CalibreWebImporter {
|
||||||
jnovel::LibraryState::Unloaded => {
|
jnovel::LibraryState::Unloaded => {
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
jnovel::LibraryState::Loading => {
|
jnovel::LibraryState::Loading(progress) => {
|
||||||
|
println!("Loading: {}", progress);
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
jnovel::LibraryState::Loaded => {
|
jnovel::LibraryState::Loaded(data) => {
|
||||||
|
self.jnc.library = data;
|
||||||
|
println!("Loaded");
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
jnovel::LibraryState::Idle => {
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
jnovel::LibraryState::Error(err) => {
|
jnovel::LibraryState::Error(err) => {
|
||||||
|
println!("{err}");
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,7 +185,15 @@ impl Application for CalibreWebImporter {
|
||||||
},
|
},
|
||||||
Menu::JNovel => {
|
Menu::JNovel => {
|
||||||
let jnc_login_button: Element<_, _> = match &self.jnc.login_state {
|
let jnc_login_button: Element<_, _> = match &self.jnc.login_state {
|
||||||
jnovel::LoginState::Success(_) => Element::from(button("Logout").on_press(Message::JncAction(jnovel::Message::Logout))),
|
jnovel::LoginState::Success(_) => {
|
||||||
|
let mut col = Column::new();
|
||||||
|
col = col.push(button("Logout").on_press(Message::JncAction(jnovel::Message::Logout)));
|
||||||
|
|
||||||
|
for (id, entry) in &self.jnc.library {
|
||||||
|
col = col.push(button(entry.info.title.as_str()).on_press(Message::JncAction(jnovel::Message::LoginState(jnovel::LoginState::LoggedOut))));
|
||||||
|
}
|
||||||
|
|
||||||
|
Element::from(col)},
|
||||||
jnovel::LoginState::LoggedOut => Element::from(button("Login").on_press(Message::JncAction(jnovel::Message::Login))),
|
jnovel::LoginState::LoggedOut => Element::from(button("Login").on_press(Message::JncAction(jnovel::Message::Login))),
|
||||||
jnovel::LoginState::LoggingIn => Element::from(text("Logging in")),
|
jnovel::LoginState::LoggingIn => Element::from(text("Logging in")),
|
||||||
jnovel::LoginState::AwaitingConfirmation(code, start) => Element::from(text(format!("Login Code: {}. Expiring: {}s", code.otp, 600 - (Local::now() - start).num_seconds()))),
|
jnovel::LoginState::AwaitingConfirmation(code, start) => Element::from(text(format!("Login Code: {}. Expiring: {}s", code.otp, 600 - (Local::now() - start).num_seconds()))),
|
||||||
|
@ -254,9 +266,27 @@ impl Application for CalibreWebImporter {
|
||||||
_ => Subscription::none(),
|
_ => Subscription::none(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let library_loading = match self.jnc.login_state {
|
||||||
|
jnovel::LoginState::Success(token) => {
|
||||||
|
match self.jnc.library_state {
|
||||||
|
jnovel::LibraryState::Unloaded => {
|
||||||
|
jnovel::State::load_library(token.clone())
|
||||||
|
.map(|status| Message::JncAction(jnovel::Message::LibraryState(status)))
|
||||||
|
},
|
||||||
|
jnovel::LibraryState::Loading(progress) => {
|
||||||
|
jnovel::State::load_library(token.clone())
|
||||||
|
.map(|status| Message::JncAction(jnovel::Message::LibraryState(status)))
|
||||||
|
},
|
||||||
|
_ => Subscription::none()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => Subscription::none()
|
||||||
|
};
|
||||||
|
|
||||||
Subscription::batch([
|
Subscription::batch([
|
||||||
system_theme,
|
system_theme,
|
||||||
jnc_otp_check
|
jnc_otp_check,
|
||||||
|
library_loading
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue