eureka_manager_cli/commands/
transfer.rs1use std::{fs::File, io::BufReader, path::PathBuf, str::FromStr};
2
3use actix::Actor;
4use clap::Args;
5use eureka_mmanager::{
6 download::chapter::task::DownloadMode,
7 prelude::{
8 ChapterDataPullAsyncTrait, ChapterImagePushEntry, CoverDataPullAsyncTrait, DirsOptions,
9 DirsOptionsCore, GetManagerStateData, MangaDataPullAsyncTrait, PushActorAddr,
10 },
11};
12use log::info;
13use mangadex_api_types_rust::RelationshipType;
14use tokio_stream::StreamExt;
15use uuid::Uuid;
16
17use crate::DirsOptionsArgs;
18
19use super::{AsyncRun, AsyncRunContext};
20
21#[derive(Debug, Args)]
22pub struct TransferCommand {
23 #[command(flatten)]
25 pub transfer_dirs: DirsOptionsArgs,
26 #[command(flatten)]
27 pub mangas: TransferMangaArgs,
28 #[command(flatten)]
29 pub covers: TransferCoverArgs,
30 #[command(flatten)]
31 pub chapters: TransferChapterArgs,
32 #[arg(long)]
33 pub verify: bool,
34}
35
36#[derive(Debug, Args)]
37pub struct TransferMangaArgs {
38 #[arg(long)]
39 pub manga: Vec<Uuid>,
40 #[arg(long)]
41 pub manga_ids_text_file: Vec<PathBuf>,
42}
43
44impl TransferMangaArgs {
45 pub fn get_ids(&self) -> Vec<Uuid> {
46 let mut ids = self.manga.clone();
47 self.manga_ids_text_file
48 .iter()
49 .map(|e| (e, File::open(e)))
50 .flat_map(|(path, res)| match res {
51 Ok(file) => Some(id_list_txt_reader::IdListTxtReader::new(BufReader::new(
52 file,
53 ))),
54 Err(err) => {
55 log::error!("Cannot open the {} file: {}", path.to_string_lossy(), err);
56 None
57 }
58 })
59 .flat_map(|file| file.flat_map(|s| Uuid::from_str(&s)))
60 .for_each(|id| {
61 ids.push(id);
62 });
63 ids.dedup();
64 ids
65 }
66}
67
68#[derive(Debug, Args)]
69pub struct TransferCoverArgs {
70 #[arg(long)]
71 pub cover: Vec<Uuid>,
72 #[arg(long)]
73 pub cover_ids_text_file: Vec<PathBuf>,
74}
75
76impl TransferCoverArgs {
77 pub fn get_ids(&self) -> Vec<Uuid> {
78 let mut ids = self.cover.clone();
79 self.cover_ids_text_file
80 .iter()
81 .map(|e| (e, File::open(e)))
82 .flat_map(|(path, res)| match res {
83 Ok(file) => Some(id_list_txt_reader::IdListTxtReader::new(BufReader::new(
84 file,
85 ))),
86 Err(err) => {
87 log::error!("Cannot open the {} file: {}", path.to_string_lossy(), err);
88 None
89 }
90 })
91 .flat_map(|file| file.flat_map(|s| Uuid::from_str(&s)))
92 .for_each(|id| {
93 ids.push(id);
94 });
95 ids.dedup();
96 ids
97 }
98}
99
100#[derive(Debug, Args)]
101pub struct TransferChapterArgs {
102 #[arg(long)]
103 pub chapter: Vec<Uuid>,
104 #[arg(long)]
105 pub chapter_ids_text_file: Vec<PathBuf>,
106}
107
108impl TransferChapterArgs {
109 pub fn get_ids(&self) -> Vec<Uuid> {
110 let mut ids = self.chapter.clone();
111 self.chapter_ids_text_file
112 .iter()
113 .map(|e| (e, File::open(e)))
114 .flat_map(|(path, res)| match res {
115 Ok(file) => Some(id_list_txt_reader::IdListTxtReader::new(BufReader::new(
116 file,
117 ))),
118 Err(err) => {
119 log::error!("Cannot open the {} file: {}", path.to_string_lossy(), err);
120 None
121 }
122 })
123 .flat_map(|file| file.flat_map(|s| Uuid::from_str(&s)))
124 .for_each(|id| {
125 ids.push(id);
126 });
127 ids.dedup();
128 ids
129 }
130}
131
132impl AsyncRun for TransferCommand {
133 async fn run(&self, ctx: AsyncRunContext) -> anyhow::Result<()> {
134 let mut chapters = self.chapters.get_ids();
135 let mut mangas = self.mangas.get_ids();
136 let mut covers = self.covers.get_ids();
137
138 let target_opts = {
139 let opts: DirsOptionsCore = self.transfer_dirs.clone().into();
140 let opts: DirsOptions = opts.into();
141 opts.start()
142 };
143
144 let current_opts = ctx.manager.get_dir_options().await?;
145 chapters.dedup();
146 let mut chapter_stream = current_opts
147 .get_chapters_by_ids(chapters.into_iter())
148 .await?;
149 while let Some(chapter) = StreamExt::next(&mut chapter_stream).await {
151 let id = chapter.id;
152 let images = current_opts.get_chapter_images(id).await?;
153 let images = {
154 let mut entries: Vec<ChapterImagePushEntry<BufReader<File>>> = Vec::new();
155 for data_entry_filename in images.data.iter() {
156 let entry = ChapterImagePushEntry::new(
157 chapter.id,
158 data_entry_filename.clone(),
159 BufReader::new(
160 current_opts
161 .get_chapter_image(chapter.id, data_entry_filename.clone())
162 .await?,
163 ),
164 )
165 .mode(DownloadMode::Normal);
166 entries.push(entry);
167 }
168 for data_saver_entry_filename in images.data_saver.iter() {
169 let entry = ChapterImagePushEntry::new(
170 chapter.id,
171 data_saver_entry_filename.clone(),
172 BufReader::new(
173 current_opts
174 .get_chapter_image_data_saver(
175 chapter.id,
176 data_saver_entry_filename.clone(),
177 )
178 .await?,
179 ),
180 )
181 .mode(DownloadMode::DataSaver);
182 entries.push(entry);
183 }
184 entries
185 };
186 info!("Transfering {id} chapter...");
187 let manga = chapter
188 .find_first_relationships(RelationshipType::Manga)
189 .cloned();
190
191 if self.verify {
192 target_opts.verify_and_push(chapter).await?;
193 target_opts.verify_and_push(images).await?;
194 } else {
195 target_opts.push(chapter).await?;
196 target_opts.push(images).await?;
197 }
198
199 info!("Transfered {id} chapter!");
200 if let Some(manga) = manga {
201 if mangas.contains(&manga.id) {
202 mangas.push(manga.id);
203 }
204 }
205 }
206
207 mangas.dedup();
208 let mut mangas_stream = current_opts
209 .get_manga_list_by_ids(mangas.into_iter())
210 .await?;
211 while let Some(manga) = StreamExt::next(&mut mangas_stream).await {
212 let id = manga.id;
213 if let Some(cover) = manga
214 .find_first_relationships(RelationshipType::CoverArt)
215 .cloned()
216 {
217 covers.push(cover.id);
218 }
219 info!("Transfering {id} title...");
220 if self.verify {
221 target_opts.verify_and_push(manga).await?;
222 } else {
223 target_opts.push(manga).await?;
224 }
225 info!("Transfered {id} title!");
226 }
227
228 covers.dedup();
229 let mut cover_stream = current_opts.get_covers_by_ids(covers.into_iter()).await?;
230 while let Some(cover) = StreamExt::next(&mut cover_stream).await {
231 let id = cover.id;
232 info!("Transfering {id} cover...");
233 let image = current_opts.get_cover_image(cover.id).await?;
234 if self.verify {
235 target_opts
236 .verify_and_push((cover, BufReader::new(image)))
237 .await?;
238 } else {
239 target_opts.push((cover, BufReader::new(image))).await?;
240 }
241 info!("Transfered {id} cover!");
242 }
243 Ok(())
244 }
245}