eureka_manager_cli/commands/download/
chapter.rs1use std::{fs::File, io::BufReader, path::PathBuf};
2
3use actix::Addr;
4use clap::{Args, ValueEnum};
5use eureka_mmanager::{
6 download::{
7 chapter::{task::DownloadMode, ChapterDownloadMessage},
8 cover::CoverDownloadMessage,
9 manga::MangaDownloadMessage,
10 state::DownloadMessageState,
11 },
12 history::service::messages::is_in::IsInMessage,
13 prelude::*,
14};
15use indicatif::ProgressBar;
16use log::{info, trace};
17use mangadex_api_types_rust::RelationshipType;
18use uuid::Uuid;
19
20use crate::commands::{AsyncRun, AsyncRunContext};
21
22#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, ValueEnum, Default)]
23pub enum ChapterDownloadMode {
24 #[default]
26 Data,
27 DataSaver,
29}
30
31impl From<ChapterDownloadMode> for DownloadMode {
32 fn from(value: ChapterDownloadMode) -> Self {
33 match value {
34 ChapterDownloadMode::Data => Self::Normal,
35 ChapterDownloadMode::DataSaver => Self::DataSaver,
36 }
37 }
38}
39
40#[derive(Debug, Args)]
41pub struct ChapterDownloadArgs {
42 #[arg(long = "id")]
44 pub ids: Vec<Uuid>,
45 #[arg(long)]
46 pub id_text_file: Vec<PathBuf>,
47 #[arg(short, long)]
48 pub mode: ChapterDownloadMode,
49}
50
51impl ChapterDownloadArgs {
52 pub fn get_id_and_modes(&self) -> Vec<(Uuid, ChapterDownloadMode)> {
53 let mut res = Vec::new();
54 let mut push_res = |value: (Uuid, ChapterDownloadMode)| {
55 res.push(value);
56 };
57
58 self.ids
59 .iter()
60 .map(|id| (*id, self.mode))
61 .for_each(&mut push_res);
62
63 self.id_text_file
64 .iter()
65 .map(|e| (e, File::open(e)))
66 .flat_map(|(path, res)| match res {
67 Ok(file) => Some(id_list_txt_reader::IdListTxtReader::new(BufReader::new(
68 file,
69 ))),
70 Err(err) => {
71 log::error!("Cannot open the {} file: {}", path.to_string_lossy(), err);
72 None
73 }
74 })
75 .flat_map(|reader| {
76 reader.flat_map(|entry| -> Option<(Uuid, ChapterDownloadMode)> {
77 if entry.contains(';') {
78 let mut split = entry.split(';');
79 let id: Uuid = split.next()?.parse().ok()?;
80 let mode = split
81 .next()
82 .and_then(|part| ChapterDownloadMode::from_str(part, true).ok())
83 .unwrap_or(self.mode);
84 Some((id, mode))
85 } else {
86 Some((entry.parse().ok()?, self.mode))
87 }
88 })
89 })
90 .for_each(&mut push_res);
91 res
92 }
93}
94
95impl AsyncRun for ChapterDownloadArgs {
96 async fn run(&self, ctx: AsyncRunContext) -> anyhow::Result<()> {
97 let ids = self.get_id_and_modes();
98 let mut progress = ProgressBar::new(ids.len() as u64);
99 progress = ctx.progress.add(progress);
100 trace!(
101 "Downloading {} chapters with their titles and cover if needed",
102 ids.len()
103 );
104 for (id, mode) in ids {
105 let manager = ctx.manager.clone();
106 let task = async move {
107 trace!("Downloading chapter {id}");
108 let dirs =
109 <Addr<DownloadManager> as GetManagerStateData>::get_dir_options(&manager)
110 .await?;
111 let manga = {
112 let chapter_manager = <Addr<DownloadManager> as GetManager<
113 ChapterDownloadManager,
114 >>::get(&manager)
115 .await?;
116 let mut task = chapter_manager
117 .send(
118 ChapterDownloadMessage::new(id)
119 .state(DownloadMessageState::Downloading)
120 .mode(mode),
121 )
122 .await?;
123 let data = task.wait().await?.await?;
124 info!(
125 "downloaded chapter {} = {:?}",
126 data.id, data.attributes.title
127 );
128 data.find_first_relationships(RelationshipType::Manga)
129 .ok_or(anyhow::Error::msg(format!(
130 "Cannot find the chapter {id} title"
131 )))?
132 .clone()
133 };
134 if !dirs
135 .send(IsInMessage(HistoryEntry::new(
136 manga.id,
137 RelationshipType::Manga,
138 )))
139 .await?
140 {
141 let cover = {
142 trace!("Downloading title {}", manga.id);
143 let manga_manager = <Addr<DownloadManager> as GetManager<
144 MangaDownloadManager,
145 >>::get(&manager)
146 .await?;
147 let mut task = manga_manager
148 .send(
149 MangaDownloadMessage::new(manga.id)
150 .state(DownloadMessageState::Downloading),
151 )
152 .await?;
153 let data = task.wait().await?.await?;
154 info!(
155 "downloaded title {} = {:?}",
156 data.id,
157 data.attributes.title.values().next()
158 );
159 data.find_first_relationships(RelationshipType::CoverArt)
160 .ok_or(anyhow::Error::msg(format!(
161 "Cannot find the title {} cover art",
162 manga.id
163 )))?
164 .clone()
165 };
166 if !dirs
167 .send(IsInMessage(HistoryEntry::new(
168 cover.id,
169 RelationshipType::CoverArt,
170 )))
171 .await?
172 {
173 trace!("Downloading {} cover art", cover.id);
174 let cover_manager = <Addr<DownloadManager> as GetManager<
175 CoverDownloadManager,
176 >>::get(&manager)
177 .await?;
178 let mut task = cover_manager
179 .send(
180 CoverDownloadMessage::new(cover.id)
181 .state(DownloadMessageState::Downloading),
182 )
183 .await?;
184 task.wait().await?.await?;
185 info!("Downloaded {} cover art", cover.id);
186 }
187 }
188 Ok::<_, anyhow::Error>(())
189 };
190 let res = task.await;
191
192 if let Err(err) = res {
193 log::error!("{err}");
194 }
195 progress.inc(1);
196 }
197 progress.finish();
198 ctx.progress.remove(&progress);
199 Ok(())
200 }
201}