1use crate::{
2 api::{
3 self, model::*, GetAnimeDetailQuery, GetAnimeRankingQuery, GetMangaDetailQuery,
4 GetMangaRankingQuery, GetSeasonalAnimeQuery, GetSuggestedAnimeQuery,
5 GetUserInformationQuery, UpdateUserAnimeListStatusQuery, UpdateUserMangaStatus,
6 },
7 app::{
8 ActiveBlock, ActiveDisplayBlock, App, Data, Route, SelectedSearchTab, TopThreeBlock,
9 UserAnimeList, UserMangaList,
10 },
11 auth::OAuth,
12};
13use bytes::Bytes;
14use std::path::{Path, PathBuf};
15use std::sync::Arc;
16use tokio::sync::Mutex;
17use tracing::warn;
18
19#[derive(Debug)]
20pub enum IoEvent {
21 GetSearchResults(String),
22 GetAnimeSearchResults(String),
23 GetMangaSearchResults(String),
24 GetAnime(u64),
25 GetManga(u64),
26 GetAnimeRanking(AnimeRankingType),
27 GetMangaRanking(MangaRankingType),
28 GetSeasonalAnime,
29 GetSuggestedAnime,
30 UpdateAnimeListStatus(u64, UpdateUserAnimeListStatusQuery),
31 DeleteAnimeListStatus(String),
32 GetAnimeList(Option<UserWatchStatus>),
33 GetMangaList(Option<UserReadStatus>),
34 UpdateMangaListStatus(u64, UpdateUserMangaStatus),
35 DeleteMangaListStatus(String),
36 GetUserInfo,
37 GetTopThree(TopThreeBlock),
38}
39
40#[derive(Clone)]
41pub struct Network<'a> {
42 oauth: OAuth,
43 large_search_limit: u64,
44 app: &'a Arc<Mutex<App>>,
46}
47
48impl<'a> Network<'a> {
49 pub fn new(oauth: OAuth, app: &'a Arc<Mutex<App>>, search_limit: u64) -> Self {
50 Self {
51 oauth,
52 large_search_limit: search_limit,
53 app,
55 }
56 }
57
58 pub async fn handle_network_event(&mut self, io_event: IoEvent) {
59 match io_event {
60 IoEvent::GetSearchResults(q) => self.get_search_results(q).await,
61
62 IoEvent::GetSeasonalAnime => self.get_seasonal().await,
63
64 IoEvent::GetAnimeRanking(r) => self.get_anime_ranking(r).await,
65
66 IoEvent::GetMangaRanking(r) => self.get_manga_ranking(r).await,
67
68 IoEvent::GetSuggestedAnime => self.get_suggested().await,
69
70 IoEvent::GetAnimeList(s) => self.get_user_anime_list(s).await,
71
72 IoEvent::GetMangaList(s) => self.get_user_manga_list(s).await,
73
74 IoEvent::GetAnime(id) => self.get_anime_details(id).await,
75
76 IoEvent::GetManga(id) => self.get_manga_details(id).await,
77
78 IoEvent::GetUserInfo => self.get_user_info().await,
87 IoEvent::GetTopThree(r) => self.get_top_three(r).await,
88
89 IoEvent::UpdateAnimeListStatus(anime_id, query) => {
90 self.update_anime_list_status(anime_id, query).await
91 }
92 IoEvent::UpdateMangaListStatus(manga_id, query) => {
93 self.update_manga_list_status(manga_id, query).await
94 }
95 _ => (),
96 }
97
98 let mut app = self.app.lock().await;
99 app.is_loading = false
100 }
101
102 async fn get_anime_details(&mut self, id: u64) {
103 self.oauth.refresh().unwrap();
104 let mut app = self.app.lock().await;
105
106 let query = GetAnimeDetailQuery {
107 fields: Some(ALL_ANIME_AND_MANGA_FIELDS.to_string()),
108 nsfw: app.app_config.nsfw,
109 };
110
111 match api::get_anime_details(id, &query, &self.oauth).await {
112 Ok(result) => {
113 app.anime_details = Some(result.clone());
114 }
115 Err(e) => {
116 app.write_error(e);
117 app.active_display_block = ActiveDisplayBlock::Error;
118 return;
119 }
120 }
121
122 let mut image = None;
123 if app.picker.is_some() {
124 image = get_picture(
125 app.app_config.paths.picture_cache_dir_path.clone(),
126 app.anime_details.as_ref().unwrap().id,
127 &app.anime_details.as_ref().unwrap().main_picture,
128 app.app_config.max_cached_images,
129 )
130 .await;
131 app.media_image = image.clone();
132 app.image_state = Some(
133 app.picker
134 .as_ref()
135 .unwrap()
136 .new_resize_protocol(app.get_picture_from_cache().unwrap()),
137 );
138 }
139
140 let route = Route {
141 data: Some(Data::Anime(app.anime_details.as_ref().unwrap().clone())),
142 block: ActiveDisplayBlock::AnimeDetails,
143 title: app.anime_details.as_ref().unwrap().title.clone(),
144 image,
145 };
146 app.push_navigation_stack(route);
147 app.active_block = ActiveBlock::DisplayBlock;
148 app.active_display_block = ActiveDisplayBlock::AnimeDetails;
149 app.display_block_title = app.anime_details.as_ref().unwrap().title.clone();
150 }
151
152 async fn get_manga_details(&mut self, id: u64) {
153 self.oauth.refresh().unwrap();
154 let mut app = self.app.lock().await;
155
156 let query = GetMangaDetailQuery {
157 fields: Some(ALL_ANIME_AND_MANGA_FIELDS.to_string()),
158 nsfw: app.app_config.nsfw,
159 };
160
161 match api::get_manga_details(id, &query, &self.oauth).await {
162 Ok(result) => {
163 app.manga_details = Some(result.clone());
164 }
165 Err(e) => {
166 app.write_error(e);
167 app.active_display_block = ActiveDisplayBlock::Error;
168 return;
169 }
170 }
171
172 let mut image = None;
173 if app.picker.is_some() {
174 image = get_picture(
175 app.app_config.paths.picture_cache_dir_path.clone(),
176 app.manga_details.as_ref().unwrap().id,
177 &app.manga_details.as_ref().unwrap().main_picture,
178 app.app_config.max_cached_images,
179 )
180 .await;
181 app.media_image = image.clone();
182 app.image_state = Some(
183 app.picker
184 .as_ref()
185 .unwrap()
186 .new_resize_protocol(app.get_picture_from_cache().unwrap()),
187 );
188 }
189 let route = Route {
190 data: Some(Data::Manga(app.manga_details.as_ref().unwrap().clone())),
191 block: ActiveDisplayBlock::MangaDetails,
192 title: app.manga_details.as_ref().unwrap().title.clone(),
193 image,
194 };
195 app.push_navigation_stack(route);
196 app.active_block = ActiveBlock::DisplayBlock;
197 app.active_display_block = ActiveDisplayBlock::MangaDetails;
198 app.display_block_title = app.manga_details.as_ref().unwrap().title.clone();
199 }
200
201 async fn get_anime_ranking(&mut self, ranking_type: AnimeRankingType) {
202 self.oauth.refresh().unwrap();
203 let mut app = self.app.lock().await;
204 let query = GetAnimeRankingQuery {
205 ranking_type: ranking_type.clone(),
206 fields: Some(ALL_ANIME_AND_MANGA_FIELDS.to_string()),
207 limit: self.large_search_limit,
208 nsfw: app.app_config.nsfw,
209 offset: 0,
210 };
211 let title = format!("Top Anime by {}", ranking_type);
212 match api::get_anime_ranking(&query, &self.oauth).await {
213 Ok(result) => {
214 app.anime_ranking_data = Some(result.clone());
215 }
216 Err(e) => {
217 app.write_error(e);
218 app.active_display_block = ActiveDisplayBlock::Error;
219 return;
220 }
221 }
222
223 let route = Route {
224 data: Some(Data::AnimeRanking(
225 app.anime_ranking_data.as_ref().unwrap().clone(),
226 )),
227 block: ActiveDisplayBlock::AnimeRanking,
228 title: title.clone(),
229 image: None,
230 };
231 app.push_navigation_stack(route);
232 app.active_block = ActiveBlock::DisplayBlock;
233 app.active_display_block = ActiveDisplayBlock::AnimeRanking;
234 app.display_block_title = title;
235 }
236
237 async fn get_manga_ranking(&mut self, ranking_type: MangaRankingType) {
238 self.oauth.refresh().unwrap();
239 let mut app = self.app.lock().await;
240 let query = GetMangaRankingQuery {
241 ranking_type: ranking_type.clone(),
242 fields: Some(ALL_ANIME_AND_MANGA_FIELDS.to_string()),
243 limit: self.large_search_limit,
244 nsfw: app.app_config.nsfw,
245 offset: 0,
246 };
247 let mut rank = ranking_type.to_string();
249 if rank == *"bypopularity" {
250 rank = "Popular Manga".to_string();
251 }
252 let title = format!("Top {}", rank);
253 match api::get_manga_ranking(&query, &self.oauth).await {
254 Ok(result) => {
255 app.manga_ranking_data = Some(result.clone());
256 }
257 Err(e) => {
258 app.write_error(e);
259 app.active_display_block = ActiveDisplayBlock::Error;
260 return;
261 }
262 }
263
264 let route = Route {
265 data: Some(Data::MangaRanking(
266 app.manga_ranking_data.as_ref().unwrap().clone(),
267 )),
268 block: ActiveDisplayBlock::MangaRanking,
269 title: title.clone(),
270 image: None,
271 };
272 app.push_navigation_stack(route);
273
274 app.active_block = ActiveBlock::DisplayBlock;
275 app.active_display_block = ActiveDisplayBlock::MangaRanking;
276 app.display_block_title = title;
277 }
278
279 async fn get_top_three(&mut self, ranking_type: TopThreeBlock) {
280 match ranking_type {
281 TopThreeBlock::Anime(r) => self.get_anime_top_three(r).await,
282 TopThreeBlock::Manga(r) => self.get_manga_top_three(r).await,
283 _ => (),
284 }
285 }
286
287 async fn get_anime_top_three(&mut self, rank_type: AnimeRankingType) {
288 self.oauth.refresh().unwrap();
289 let mut app = self.app.lock().await;
290 let query = GetAnimeRankingQuery {
291 ranking_type: rank_type.clone(),
292 fields: Some(ALL_ANIME_AND_MANGA_FIELDS.to_string()),
293 limit: 3,
294 nsfw: app.app_config.nsfw,
295 offset: 0,
296 };
297 match api::get_anime_ranking(&query, &self.oauth).await {
298 Ok(result) => match &rank_type {
299 AnimeRankingType::Airing => {
300 app.top_three_anime.airing = Some([
301 result.data[0].node.clone(),
302 result.data[1].node.clone(),
303 result.data[2].node.clone(),
304 ]);
305 }
306 AnimeRankingType::All => {
307 app.top_three_anime.all = Some([
308 result.data[0].node.clone(),
309 result.data[1].node.clone(),
310 result.data[2].node.clone(),
311 ])
312 }
313 AnimeRankingType::Upcoming => {
314 app.top_three_anime.upcoming = Some([
315 result.data[0].node.clone(),
316 result.data[1].node.clone(),
317 result.data[2].node.clone(),
318 ]);
319 }
320 AnimeRankingType::ByPopularity => {
321 app.top_three_anime.popular = Some([
322 result.data[0].node.clone(),
323 result.data[1].node.clone(),
324 result.data[2].node.clone(),
325 ]);
326 }
327 AnimeRankingType::Favorite => {
328 app.top_three_anime.favourite = Some([
329 result.data[0].node.clone(),
330 result.data[1].node.clone(),
331 result.data[2].node.clone(),
332 ]);
333 }
334 AnimeRankingType::Movie => {
335 app.top_three_anime.movie = Some([
336 result.data[0].node.clone(),
337 result.data[1].node.clone(),
338 result.data[2].node.clone(),
339 ]);
340 }
341 AnimeRankingType::OVA => {
342 app.top_three_anime.ova = Some([
343 result.data[0].node.clone(),
344 result.data[1].node.clone(),
345 result.data[2].node.clone(),
346 ]);
347 }
348 AnimeRankingType::TV => {
349 app.top_three_anime.tv = Some([
350 result.data[0].node.clone(),
351 result.data[1].node.clone(),
352 result.data[2].node.clone(),
353 ]);
354 }
355 AnimeRankingType::Special => {
356 app.top_three_anime.special = Some([
357 result.data[0].node.clone(),
358 result.data[1].node.clone(),
359 result.data[2].node.clone(),
360 ]);
361 }
362 AnimeRankingType::Other(_s) => {}
363 },
364 Err(e) => {
365 app.write_error(e);
366 app.active_top_three = TopThreeBlock::Error(RankingType::AnimeRankingType(
367 app.active_top_three_anime
368 .as_ref()
369 .unwrap_or(&app.available_anime_ranking_types[0])
370 .clone(),
371 ));
372 return;
373 }
374 }
375 app.active_top_three = TopThreeBlock::Anime(
376 app.active_top_three_anime
377 .as_ref()
378 .unwrap_or(&app.available_anime_ranking_types[0])
379 .clone(),
380 );
381 }
382
383 async fn get_manga_top_three(&mut self, rank_type: MangaRankingType) {
384 self.oauth.refresh().unwrap();
385 let mut app = self.app.lock().await;
386 let query = GetMangaRankingQuery {
387 ranking_type: rank_type.clone(),
388 fields: Some(ALL_ANIME_AND_MANGA_FIELDS.to_string()),
389 limit: 3,
390 nsfw: app.app_config.nsfw,
391 offset: 0,
392 };
393
394 match api::get_manga_ranking(&query, &self.oauth).await {
395 Ok(results) => match &rank_type {
396 MangaRankingType::All => {
397 app.top_three_manga.all = Some([
398 results.data[0].node.clone(),
399 results.data[1].node.clone(),
400 results.data[2].node.clone(),
401 ]);
402 }
403 MangaRankingType::Manga => {
404 app.top_three_manga.manga = Some([
405 results.data[0].node.clone(),
406 results.data[1].node.clone(),
407 results.data[2].node.clone(),
408 ]);
409 }
410 MangaRankingType::Novels => {
411 app.top_three_manga.novels = Some([
412 results.data[0].node.clone(),
413 results.data[1].node.clone(),
414 results.data[2].node.clone(),
415 ]);
416 }
417 MangaRankingType::OneShots => {
418 app.top_three_manga.oneshots = Some([
419 results.data[0].node.clone(),
420 results.data[1].node.clone(),
421 results.data[2].node.clone(),
422 ]);
423 }
424 MangaRankingType::Favorite => {
425 app.top_three_manga.favourite = Some([
426 results.data[0].node.clone(),
427 results.data[1].node.clone(),
428 results.data[2].node.clone(),
429 ]);
430 }
431 MangaRankingType::Doujinshi => {
432 app.top_three_manga.doujin = Some([
433 results.data[0].node.clone(),
434 results.data[1].node.clone(),
435 results.data[2].node.clone(),
436 ]);
437 }
438 MangaRankingType::Manhwa => {
439 app.top_three_manga.manhwa = Some([
440 results.data[0].node.clone(),
441 results.data[1].node.clone(),
442 results.data[2].node.clone(),
443 ]);
444 }
445 MangaRankingType::Manhua => {
446 app.top_three_manga.manhua = Some([
447 results.data[0].node.clone(),
448 results.data[1].node.clone(),
449 results.data[2].node.clone(),
450 ]);
451 }
452 MangaRankingType::ByPopularity => {
453 app.top_three_manga.popular = Some([
454 results.data[0].node.clone(),
455 results.data[1].node.clone(),
456 results.data[2].node.clone(),
457 ]);
458 }
459 MangaRankingType::Other(_) => {}
460 },
461
462 Err(e) => {
463 app.write_error(e);
464 app.active_top_three = TopThreeBlock::Error(RankingType::MangaRankingType(
465 app.active_top_three_manga
466 .as_ref()
467 .unwrap_or(&app.available_manga_ranking_types[0])
468 .clone(),
469 ));
470 return;
471 }
472 }
473 app.active_top_three = TopThreeBlock::Manga(
474 app.active_top_three_manga
475 .as_ref()
476 .unwrap_or(&app.available_manga_ranking_types[0])
477 .clone(),
478 );
479 }
480
481 async fn get_user_info(&mut self) {
482 self.oauth.refresh().unwrap();
483 let mut app = self.app.lock().await;
484 let query = GetUserInformationQuery {
485 fields: Some(ALL_USER_FIELDS.to_string()),
486 };
487 match api::get_my_user_information("@me".to_string(), &query, &self.oauth).await {
489 Ok(result) => {
490 app.user_profile = Some(result.clone());
491 }
492 Err(e) => {
493 app.write_error(e);
494 app.active_display_block = ActiveDisplayBlock::Error;
495 return;
496 }
497 }
498 let route = Route {
499 data: Some(Data::UserInfo(app.user_profile.as_ref().unwrap().clone())),
500 block: ActiveDisplayBlock::UserInfo,
501 title: "Profile".to_string(),
502 image: None,
503 };
504 app.push_navigation_stack(route);
505 app.active_block = ActiveBlock::DisplayBlock;
506 app.active_display_block = ActiveDisplayBlock::UserInfo;
507 app.display_block_title = "Profile".to_string();
508 }
509
510 async fn get_suggested(&mut self) {
511 self.oauth.refresh().unwrap();
512 let mut app = self.app.lock().await;
513 let query = GetSuggestedAnimeQuery {
514 fields: Some(ALL_ANIME_AND_MANGA_FIELDS.to_string()),
515 limit: self.large_search_limit,
516 nsfw: app.app_config.nsfw,
517 offset: 0,
518 };
519 match api::get_suggested_anime(&query, &self.oauth).await {
520 Ok(result) => {
521 app.search_results.anime = Some(result.clone());
522 }
523 Err(e) => {
524 app.write_error(e);
525 app.active_display_block = ActiveDisplayBlock::Error;
526 return;
527 }
528 }
529
530 let route = Route {
531 data: Some(Data::Suggestions(app.search_results.clone())),
532 block: ActiveDisplayBlock::Suggestions,
533 title: "Suggested Anime".to_string(),
534 image: None,
535 };
536 app.push_navigation_stack(route);
537 app.active_block = ActiveBlock::DisplayBlock;
538 app.active_display_block = ActiveDisplayBlock::Suggestions;
539 app.display_block_title = "Suggested Anime".to_string();
540 }
541
542 async fn get_seasonal(&mut self) {
543 self.oauth.refresh().unwrap();
544 let mut app = self.app.lock().await;
545 let query = GetSeasonalAnimeQuery {
546 sort: Some(app.anime_season.anime_sort.clone()),
547 offset: 0,
548 fields: Some(ALL_ANIME_AND_MANGA_FIELDS.to_string()),
549 limit: self.large_search_limit,
550 nsfw: app.app_config.nsfw,
551 };
552 match api::get_seasonal_anime(&app.anime_season.anime_season, &query, &self.oauth).await {
553 Ok(result) => app.search_results.anime = Some(result),
554 Err(e) => {
555 app.write_error(e);
556 app.active_display_block = ActiveDisplayBlock::Error;
557 return;
558 }
559 }
560
561 let title = format!(
562 "Seasonal Anime: {} {}",
563 app.anime_season.anime_season.season, app.anime_season.anime_season.year,
564 );
565
566 let route = Route {
567 data: Some(Data::SearchResult(app.search_results.clone())),
568 block: ActiveDisplayBlock::Seasonal,
569 title: title.clone(),
570 image: None,
571 };
572 app.push_navigation_stack(route);
573 app.active_block = ActiveBlock::DisplayBlock;
574 app.active_display_block = ActiveDisplayBlock::Seasonal;
575 app.display_block_title = title;
576 }
577
578 async fn get_user_anime_list(&mut self, status: Option<UserWatchStatus>) {
579 self.oauth.refresh().unwrap();
580 let mut app = self.app.lock().await;
581 let query = api::GetUserAnimeListQuery {
582 fields: Some(ALL_ANIME_AND_MANGA_FIELDS.to_string()),
583 status: status.clone(),
584 sort: Some(SortStyle::ListScore),
585 limit: self.large_search_limit,
586 offset: 0,
587 nsfw: app.app_config.nsfw,
588 };
589 match api::get_user_anime_list("@me".to_string(), &query, &self.oauth).await {
590 Ok(result) => {
591 app.search_results.anime = Some(result.clone());
592 }
593 Err(e) => {
594 app.write_error(e);
595 app.active_display_block = ActiveDisplayBlock::Error;
596 return;
597 }
598 }
599
600 let data = UserAnimeList {
601 anime_list: app.search_results.anime.as_ref().unwrap().clone(),
602 status: status.clone(),
603 };
604 let route = Route {
605 block: ActiveDisplayBlock::UserAnimeList,
606 data: Some(Data::UserAnimeList(data)),
607 title: format!("My Anime List: {}", get_status_string(status)),
608 image: None,
609 };
610 app.active_block = ActiveBlock::DisplayBlock;
611 app.active_display_block = ActiveDisplayBlock::UserAnimeList;
612 app.display_block_title = route.title.clone();
613 app.push_navigation_stack(route);
614 }
615
616 async fn get_user_manga_list(&mut self, status: Option<UserReadStatus>) {
617 self.oauth.refresh().unwrap();
618 let mut app = self.app.lock().await;
619 let query = api::GetUserMangaListQuery {
620 fields: Some(ALL_ANIME_AND_MANGA_FIELDS.to_string()),
621 status: status.clone(),
622 sort: Some(SortStyle::ListScore),
623 limit: self.large_search_limit,
624 offset: 0,
625 nsfw: app.app_config.nsfw,
626 };
627 match api::get_user_manga_list("@me".to_string(), &query, &self.oauth).await {
628 Ok(result) => {
629 app.search_results.manga = Some(result.clone());
630 }
631 Err(e) => {
632 app.write_error(e);
633 app.active_display_block = ActiveDisplayBlock::Error;
634 return;
635 }
636 }
637
638 let data = UserMangaList {
639 manga_list: app.search_results.manga.as_ref().unwrap().clone(),
640 status: status.clone(),
641 };
642 let route = Route {
643 block: ActiveDisplayBlock::UserMangaList,
644 data: Some(Data::UserMangaList(data)),
645 title: format!("My Manga List: {}", get_manga_status_string(status)),
646 image: None,
647 };
648
649 app.active_block = ActiveBlock::DisplayBlock;
650 app.active_display_block = ActiveDisplayBlock::UserMangaList;
651 app.display_block_title = route.title.clone();
652 app.push_navigation_stack(route);
653 }
654
655 async fn get_search_results(&mut self, q: String) {
656 self.oauth.refresh().unwrap();
657 let mut app = self.app.lock().await;
658
659 let anime_query = api::GetAnimeListQuery {
660 q: q.clone(),
661 limit: self.large_search_limit,
662 offset: 0,
663 nsfw: app.app_config.nsfw,
664 fields: Some(ALL_ANIME_AND_MANGA_FIELDS.to_string()),
665 };
666
667 let manga_query = api::GetMangaListQuery {
668 q: q.clone(),
669 limit: self.large_search_limit,
670 offset: 0,
671 nsfw: app.app_config.nsfw,
672 fields: Some(ALL_ANIME_AND_MANGA_FIELDS.to_string()),
673 };
674
675 match api::get_anime_list(&anime_query, &self.oauth).await {
676 Ok(results) => {
677 app.search_results.anime = Some(results);
678 }
679 Err(e) => {
680 app.write_error(e);
681 app.active_display_block = ActiveDisplayBlock::Error;
682 return;
683 }
684 };
685
686 match api::get_manga_list(&manga_query, &self.oauth).await {
687 Ok(results) => {
688 app.search_results.manga = Some(results);
689 }
690 Err(e) => {
691 app.write_error(e);
692 app.active_display_block = ActiveDisplayBlock::Error;
693 return;
694 }
695 };
696
697 let route = Route {
698 data: Some(Data::SearchResult(app.search_results.clone())),
699 block: ActiveDisplayBlock::SearchResultBlock,
700 title: format!("Search Results: {}", q.clone()).to_string(),
701 image: None,
702 };
703 app.push_navigation_stack(route);
704
705 app.search_results.selected_tab = SelectedSearchTab::Anime;
706 app.active_display_block = ActiveDisplayBlock::SearchResultBlock;
707 app.display_block_title = format!("Search Results: {}", q).to_string()
708 }
709
710 async fn update_anime_list_status(
711 &mut self,
712 anime_id: u64,
713 query: UpdateUserAnimeListStatusQuery,
714 ) {
715 self.oauth.refresh().unwrap();
716 let mut app = self.app.lock().await;
717
718 match api::update_anime_list_status(anime_id, &query, &self.oauth).await {
719 Ok(result) => {
720 app.anime_details.as_mut().unwrap().my_list_status = Some(result);
721 app.popup_post_req_success_message = Some("updated Successfully".to_string());
722 app.popup_post_req_success = true;
723 }
724 Err(e) => {
725 app.write_error(e);
726 app.popup_post_req_success = false;
727 }
728 }
729 app.popup_is_loading = false;
730 }
731
732 async fn update_manga_list_status(&mut self, manga_id: u64, query: UpdateUserMangaStatus) {
733 self.oauth.refresh().unwrap();
734 let mut app = self.app.lock().await;
735 match api::update_manga_list_status(manga_id, &query, &self.oauth).await {
736 Ok(result) => {
737 app.manga_details.as_mut().unwrap().my_list_status = Some(result);
739 app.popup_post_req_success = true;
740 app.popup_post_req_success_message = Some("updated Successfully".to_string());
741 }
742 Err(e) => {
743 app.write_error(e);
744 app.popup_post_req_success = false;
745 }
746 }
747 app.popup_is_loading = false;
748 }
749}
750
751fn get_status_string(status: Option<UserWatchStatus>) -> String {
752 match status {
753 Some(s) => match s {
754 UserWatchStatus::Completed => "completed".to_string(),
755 UserWatchStatus::Watching => "watching".to_string(),
756 UserWatchStatus::OnHold => "on_hold".to_string(),
757 UserWatchStatus::Dropped => "dropped".to_string(),
758 UserWatchStatus::PlanToWatch => "plan_to_watch".to_string(),
759 UserWatchStatus::Other(_) => "All".to_string(),
760 },
761 None => "All".to_string(),
762 }
763}
764
765fn get_manga_status_string(status: Option<UserReadStatus>) -> String {
766 match status {
767 Some(s) => match s {
768 UserReadStatus::Completed => "completed".to_string(),
769 UserReadStatus::Reading => "reading".to_string(),
770 UserReadStatus::OnHold => "on_hold".to_string(),
771 UserReadStatus::Dropped => "dropped".to_string(),
772 UserReadStatus::PlanToRead => "plan_to_read".to_string(),
773 UserReadStatus::Other(_) => "All".to_string(),
774 },
775 None => "All".to_string(),
776 }
777}
778
779async fn fetch_image(url: &str) -> Result<Bytes, Box<dyn std::error::Error>> {
780 let response = reqwest::get(url).await?;
781 let bytes = response.bytes().await?;
782 Ok(bytes)
783}
784
785async fn get_picture(
786 image_dir_path: PathBuf,
787 id: u64,
788 pictures: &Option<Picture>,
789 max_limit: u16,
790) -> Option<(String, u32, u32)> {
791 let file_path = image_dir_path.join(format!("{}.png", id));
794 if file_path.exists() {
795 let image = image::open(&file_path).ok()?;
796 return Some((
797 file_path.to_string_lossy().to_string(),
798 image.width(),
799 image.height(),
800 ));
801 }
802
803 let cache_index_file = image_dir_path.join("cache_index.json");
812 if !cache_index_file.exists() {
813 let cache_index = Vec::<String>::new();
815 let mut existing_images = Vec::<String>::new();
817 if let Ok(entries) = std::fs::read_dir(&image_dir_path) {
818 for entry in entries.flatten() {
819 if let Some(file_name) = entry.file_name().to_str() {
820 if file_name != "cache_index.json" {
821 existing_images.push(file_name.to_string());
822 }
823 }
824 }
825 }
826 let cache_index_json =
827 serde_json::to_string(&cache_index).unwrap_or_else(|_| "[]".to_string());
828 std::fs::write(&cache_index_file, cache_index_json).ok();
829 }
830
831 if let Some(p) = pictures {
832 let urls = vec![&p.large, &p.medium];
833 for url in urls.into_iter().flatten() {
835 let image = fetch_image(url).await;
837 match image {
838 Ok(bytes) => {
839 let file_name = format!("{}.png", id);
840 let file_path = image_dir_path.join(file_name.clone());
841 let image = image::load_from_memory(&bytes).ok();
842 if let Some(image) = image {
843 image
844 .save_with_format(&file_path, image::ImageFormat::Png)
845 .ok();
846 let res = update_image_cache(
849 &cache_index_file,
850 &image_dir_path,
851 &file_name,
852 max_limit,
853 )
854 .await;
855 if let Err(e) = res {
856 warn!("error updating the cache index file: {}", e)
857 }
858
859 return Some((
860 file_path.to_string_lossy().to_string(),
861 image.width(),
862 image.height(),
863 ));
864 }
865 }
866 Err(e) => {
867 warn!("Error fetching image: {}", e);
868 }
869 }
870 }
871 }
872 None
873}
874
875async fn update_image_cache(
876 cache_index_file: &PathBuf,
877 image_dir_path: &Path,
878 file_name: &str,
879 max_limit: u16,
880) -> Result<(), Box<dyn std::error::Error>> {
881 let cache_content = std::fs::read_to_string(cache_index_file)?;
883 let mut cache_index: Vec<String> = serde_json::from_str(&cache_content).unwrap_or_default();
884
885 cache_index.push(file_name.to_string());
887
888 if cache_index.len() > max_limit as usize {
890 let oldest_image = cache_index.remove(0);
892 let oldest_image_path = image_dir_path.join(&oldest_image);
893 if oldest_image_path.exists() {
894 std::fs::remove_file(oldest_image_path).ok();
895 }
896 }
897
898 let updated_cache_json = serde_json::to_string(&cache_index)?;
900 std::fs::write(cache_index_file, updated_cache_json)?;
901
902 Ok(())
903}