diff --git a/Cargo.toml b/Cargo.toml index 1ffb3e2..685d833 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,8 @@ edition = "2021" [dependencies] clap = { version = "4.3.3", features = ["derive"] } -iced = "0.9.0" +color-print = "0.3.5" +iced = {version="0.10.0", features=["image", "debug", "tokio"]} regex = "1.8.4" reqwest = {version="0.11.18",features=["blocking"]} scraper = "0.16.0" diff --git a/src/cache.rs b/src/cache.rs index 0789d77..baadc56 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -1,15 +1,16 @@ use std::{fs, path::Path, process}; - +use color_print::cprintln; use crate::Page; pub async fn download_page_image(cache_path: &str, page: &Page) { let replaced = cache_path.replace('~', &crate::get_home()[..]); // replace ~ with $HOME crate::ensure_exists(&replaced); - if Path::new(&format!("{cache_path}/{}.json", page.date)).exists() { - return; + if Path::new(&format!("{replaced}/{}.json", page.date)).exists() { + return } + cprintln!("downloading page image"); let image = match reqwest::get(&page.image).await { Ok(image) => match image.bytes().await { Ok(bytes) => bytes, diff --git a/src/lib.rs b/src/lib.rs index 4218ff7..59dc44a 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -138,7 +138,7 @@ pub struct Config { pub cache_folder: String, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct Page { pub date: String, pub next_page: Option, @@ -154,7 +154,7 @@ pub async fn get_page(date: &str, config: &Config) -> Page { )) .exists() { - match serde_json::from_str( + match serde_json::from_str::( &match fs::read_to_string( &format!( "{}/{date}.json", @@ -163,18 +163,20 @@ pub async fn get_page(date: &str, config: &Config) -> Page { ) { Ok(var) => var, Err(why) => { - eprintln!("lib::get_page: Couldn't read cache data: {why:?}"); + eprintln!("lib::get_page: Couldn't read cache data for page {date}: {why:?}"); process::exit(1) } }[..], ) { Ok(var) => var, Err(why) => { - eprintln!("lib::get_page: Couldn't serialize read cache data: {why:?}"); + eprintln!( + "lib::get_page: Couldn't serialize read cache data for page {date}: {why:?}" + ); process::exit(1); } - } - } + }; + }; let page_html = &match match reqwest::get(format!( "https://girlgeniusonline.com/comic.php?date={date}" @@ -210,13 +212,33 @@ pub async fn get_page(date: &str, config: &Config) -> Page { let next = Selector::parse("#topnext").unwrap(); let mut next_page = None; for element in parsed.select(&next) { - next_page = Some(element.value().attr("href").unwrap().to_string()); + next_page = element + .value() + .attr("href") + .map(|p| { + if p.is_empty() { + None + } else { + Some(link_to_datestring(p.to_string()).to_string()) + } + }) + .expect("??????????"); // i dont understand rust sometimes } let prev = Selector::parse("#topprev").unwrap(); let mut prev_page = None; for element in parsed.select(&prev) { - prev_page = Some(element.value().attr("href").unwrap().to_string()); + prev_page = element + .value() + .attr("href") + .map(|p| { + if p.is_empty() { + None + } else { + Some(link_to_datestring(p.to_string()).to_string()) + } + }) + .expect("??????????"); // if you do please yell at me as to why that expect is required } // check if cache/date.json exists @@ -228,7 +250,7 @@ pub async fn get_page(date: &str, config: &Config) -> Page { } Page { - date: date.to_string(), + date: link_to_datestring(date.to_string()).to_string(), next_page, prev_page, image, @@ -245,3 +267,47 @@ pub async fn current_page(config: &Config) -> Page { page } } + +pub async fn next_page(config: &Config) -> Option { + let current_page = get_page(&config.latest_date[..], config).await; + if current_page.next_page.is_some() { + let page = get_page( + ¤t_page + .next_page + .expect("i shall *expect* (see what i did there) war crimes if this error pops up") + [..], + config, + ) + .await; + if page.cached { + Some(page) + } else { + cache::download_page_image(&config.cache_folder[..], &page).await; + Some(page) + } + } else { + None + } +} + +pub async fn prev_page(config: &Config) -> Option { + let current_page = get_page(&config.latest_date[..], config).await; + if current_page.prev_page.is_some() { + let page = get_page( + ¤t_page + .prev_page + .expect("i shall *expect* (see what i did there) war crimes if this error pops up") + [..], + config, + ) + .await; + if page.cached { + Some(page) + } else { + cache::download_page_image(&config.cache_folder[..], &page).await; + Some(page) + } + } else { + None + } +} diff --git a/src/main.rs b/src/main.rs index 0b9966d..be7f786 100755 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,9 @@ use clap::Parser; -use ggg::cache; + +use ggg::{current_page, get_home, read_config}; + +use iced::widget::{button, column, container, image, row, text}; +use iced::{Application, Command, Element, Length, Theme, Settings}; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -9,7 +13,7 @@ struct Args { } #[tokio::main] -async fn main() { +async fn main() -> iced::Result { let args = Args::parse(); let config_file = args.config_file; @@ -20,8 +24,153 @@ async fn main() { ggg::ensure_exists(&config.cache_folder); ggg::ensure_exists(&format!("{}/images", &config.cache_folder)[..]); - let current_page = ggg::get_page(&config.latest_date, &config).await; - - println!("image url: {}\ncache folder: {}", ¤t_page.image, &config.cache_folder); - cache::download_page_image(&config.cache_folder.replace('~', &ggg::get_home()[..]), ¤t_page).await; + Reader::run(Settings::default()) +} + +enum Reader { + Loading, + Loaded { page: Book }, +} + +#[derive(Debug, Clone)] +enum Message { + PageFound(Book), + NextPage, + PreviousPage, +} + +impl Application for Reader { + type Message = Message; + type Theme = Theme; + type Executor = iced::executor::Default; + type Flags = (); + + fn new(_flags: ()) -> (Reader, Command) { + ( + Reader::Loading, + Command::perform(Book::current(), Message::PageFound), + ) + } + + fn title(&self) -> String { + format!("ggg") // i didn't feel like typing out .to_string(), plus this is easier to go + // back and change + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::NextPage => match self { + Reader::Loading => Command::none(), + _ => { + *self = Reader::Loading; + Command::perform(Book::next_page(), Message::PageFound) + } + }, + Message::PreviousPage => match self { + Reader::Loading => Command::none(), + _ => { + *self = Reader::Loading; + Command::perform(Book::prev_page(), Message::PageFound) + } + }, + Message::PageFound(page) => { + *self = Reader::Loaded { page }; + Command::none() + } + } + } + + fn view(&self) -> Element { + let content = match self { + Reader::Loading => column![text("Loading...").size(40),].width(Length::Shrink), + Reader::Loaded { page } => column![ + page.view(), + row![ + // TODO: make these disable/enable if page.next and page.prev are false + button("Next").on_press(Message::NextPage), + button("Prev").on_press(Message::PreviousPage), + ] + .spacing(20) + .align_items(iced::Alignment::Center) + ] + .spacing(20) + .align_items(iced::Alignment::Center), + }; + + container(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} + +#[derive(Debug, Clone)] +struct Book { + image: image::Handle, + // see above todo + next: bool, + prev: bool, +} + +impl Book { + fn view(&self) -> Element { + row![image::viewer(self.image.clone())] + .spacing(20) + .align_items(iced::Alignment::Center) + .into() + } + async fn current() -> Book { + let args = Args::parse(); + let config = read_config(&args.config_file[..]); + let page = current_page(&config).await; + + let image = image::Handle::from_path(format!( + "{}/images/{}.jpg", + config.cache_folder.replace('~', &get_home()), + ggg::link_to_datestring(page.date) + )); + + Book { + image, + next: page.next_page.is_some(), + prev: page.prev_page.is_some(), + } + } + async fn next_page() -> Book { + let args = Args::parse(); + let config = read_config(&args.config_file[..]); + let page = ggg::next_page(&config).await.unwrap(); + + let image = image::Handle::from_path(format!( + "{}/images/{}.jpg", + config.cache_folder.replace('~', &get_home()), + ggg::link_to_datestring(page.date) + )); + + Book { + image, + next: page.next_page.is_some(), + prev: page.prev_page.is_some(), + } + } + + async fn prev_page() -> Book { + let args = Args::parse(); + let config = read_config(&args.config_file[..]); + let page = ggg::prev_page(&config).await.unwrap(); + + let image = image::Handle::from_path(format!( + "{}/images/{}.jpg", + config.cache_folder.replace('~', &get_home()), + ggg::link_to_datestring(page.date) + )); + + Book { + image, + next: page.next_page.is_some(), + prev: page.prev_page.is_some(), + } + } }