Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""LICENSE
2Copyright 2015 Hermann Krumrey <hermann@krumreyh.com>
4This file is part of toktokkie.
6toktokkie is free software: you can redistribute it and/or modify
7it under the terms of the GNU General Public License as published by
8the Free Software Foundation, either version 3 of the License, or
9(at your option) any later version.
11toktokkie is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
16You should have received a copy of the GNU General Public License
17along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
18LICENSE"""
20import os
21from zipfile import ZipFile
22from typing import List, Optional, cast
23from manga_dl.scrapers.mangadex import MangaDexScraper
24from manga_dl.entities.Chapter import Chapter
25from puffotter.os import makedirs, listdir, replace_illegal_ntfs_chars
26from puffotter.print import pprint
27from toktokkie.enums import IdType
28from toktokkie.enums import MediaType
29from toktokkie.metadata.comic.Comic import Comic
30from toktokkie.utils.update.Updater import Updater
33class MangadexUpdater(Updater):
34 """
35 Class that implements a mangadex updater
36 """
38 @classmethod
39 def name(cls) -> str:
40 """
41 :return: The name of the Updater
42 """
43 return "mangadex"
45 @classmethod
46 def applicable_media_types(cls) -> List[MediaType]:
47 """
48 :return: A list of media type with which the updater can be used with
49 """
50 return [MediaType.COMIC]
52 def update(self):
53 """
54 Executes the update
55 :return: None
56 """
57 self.execute_rename()
59 chapters = self.load_chapters()
60 if chapters is None:
61 return
63 if not self.args["no_check_newest_chapter_length"]:
64 self.check_latest_chapter_completeness(chapters)
65 self.update_main_chapters(chapters)
67 if not self.args["skip_special"]:
68 self.update_special_chapters(chapters)
70 if not self.args["dry_run"]:
71 self.execute_rename()
73 def execute_rename(self):
74 """
75 Renames the current directory content. Automatically checks if the
76 dry-run flag is set. If it is set, prints out any chapters that
77 would have been renamed
78 :return: None
79 """
80 self.logger.info("Running rename")
81 if not self.args["dry_run"]:
82 self.metadata.rename(noconfirm=True, skip_title=True)
83 else:
84 ops = self.metadata.create_rename_operations()
85 for op in ops:
86 print(op)
88 def load_chapters(self) -> Optional[List[Chapter]]:
89 """
90 Loads chapter information from mangadex.org
91 :return: Either a list of chapters or None if no mangadex ID was found
92 """
94 mangadex_ids = self.metadata.ids.get(IdType.MANGADEX)
95 if mangadex_ids is None or len(mangadex_ids) == 0:
96 pprint(
97 "No mangadex ID for {}".format(self.metadata.name), fg="lred"
98 )
99 return None
101 mangadex_id = mangadex_ids[0]
103 chapters = MangaDexScraper().load_chapters(None, mangadex_id)
104 return chapters
106 def check_latest_chapter_completeness(
107 self,
108 chapters: List[Chapter]
109 ):
110 """
111 Checks the latest regular chapter for completeness.
112 This is necessary since some groups release their chapters in parts
113 (i.e. 10.1 and then 10.2). manga-dl merges these chapter parts, so
114 we only need to check if the local files has less pages.
115 :param chapters: The chapters for the series
116 :return: None
117 """
118 metadata = cast(Comic, self.metadata)
120 main_chapters = list(filter(lambda x: not x.is_special, chapters))
121 current_files = listdir(metadata.main_path)
122 current_latest = len(current_files)
124 if current_latest == 0:
125 return
127 try:
128 current_chapter = list(filter(
129 lambda x: int(x.chapter_number) == current_latest,
130 main_chapters
131 ))[0]
132 except IndexError: # If mangadex does not have the latest chapter
133 return
135 past_file = current_files[current_chapter.macro_chapter - 1][1]
136 with ZipFile(past_file, "r") as zip_obj:
137 filecount = len(zip_obj.namelist())
138 pagecount = len(current_chapter.pages)
140 if filecount < pagecount:
141 if not self.args["dry_run"]:
142 pprint("Updating chapter {}".format(current_chapter),
143 fg="lgreen")
144 os.remove(past_file)
145 current_chapter.download(past_file)
146 else:
147 pprint("Updated Chapter found: {}".format(current_chapter),
148 fg="lyellow")
149 elif filecount != pagecount:
150 self.logger.warning(
151 "Page counts do not match for chapter {}: "
152 "Ours:{}, Theirs:{}".format(
153 current_chapter, filecount, pagecount
154 )
155 )
157 def update_main_chapters(self, chapters: List[Chapter]):
158 """
159 Updates the regular chapters of the series
160 :param chapters: The chapters of the series
161 :return: None
162 """
163 metadata = cast(Comic, self.metadata)
165 current_latest = len(listdir(metadata.main_path))
167 main_chapters = list(filter(
168 lambda x: not x.is_special
169 and int(x.chapter_number) > current_latest,
170 chapters
171 ))
173 maxchar = max(metadata.name)
175 total_chapters = len(main_chapters) + current_latest
177 downloaded = [] # type: List[str]
178 for c in main_chapters:
179 if current_latest + 1 != c.macro_chapter:
180 pprint("Missing chapter {}, expected {}".format(
181 current_latest + 1,
182 c.chapter_number,
183 ), fg="lred")
184 break
185 current_latest += 1
187 if c.chapter_number in downloaded:
188 continue
189 downloaded.append(c.chapter_number)
191 name = "{}{} - Chapter {}.cbz".format(
192 maxchar,
193 metadata.name,
194 c.chapter_number.zfill(len(str(total_chapters)))
195 )
196 dest = os.path.join(metadata.main_path, name)
197 if not self.args["dry_run"]:
198 print("Downloading Chapter {}".format(c))
199 c.download(dest)
200 else:
201 pprint("Found chapter: {}".format(c), fg="lyellow")
203 def update_special_chapters(
204 self,
205 chapters: List[Chapter]
206 ):
207 """
208 Updates the special chapters of the series
209 :param chapters: The chapters of the series
210 :return: None
211 """
212 metadata = cast(Comic, self.metadata)
213 special_chapters = list(filter(lambda x: x.is_special, chapters))
215 try:
216 special_fill = len(max(
217 metadata.special_chapters,
218 key=lambda x: len(x)
219 ))
220 except ValueError:
221 special_fill = 0
223 for c in special_chapters:
225 name = "{} - Chapter {}.cbz".format(
226 metadata.name,
227 c.chapter_number.zfill(special_fill)
228 )
230 path = os.path.join(
231 metadata.special_path, replace_illegal_ntfs_chars(name)
232 )
234 if os.path.exists(path):
235 continue
236 elif c.chapter_number not in metadata.special_chapters:
238 if self.args["dry_run"]:
239 pprint("Found unknown chapter {}".format(c.chapter_number),
240 fg="lyellow")
241 else:
242 pprint("Adding chapter {} to metadata".format(c),
243 fg="lgreen")
244 chapter_entries = metadata.special_chapters
245 chapter_entries.append(c.chapter_number)
246 metadata.special_chapters = chapter_entries
247 metadata.write()
248 makedirs(metadata.special_path)
249 print("Downloading special chapter {}".format(c))
250 c.download(path)
251 else:
252 if not self.args["dry_run"]:
253 makedirs(metadata.special_path)
254 print("Downloading special chapter {}".format(c))
255 c.download(path)
256 else:
257 pprint("Found chapter: {}".format(c), fg="lyellow")