eureka_manager_cli/commands/
transfer.rs

1use 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    /// directory targets for data to transfer in
24    #[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        // Chapter push
150        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}