Hide keyboard shortcuts

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> 

3 

4This file is part of toktokkie. 

5 

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. 

10 

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. 

15 

16You should have received a copy of the GNU General Public License 

17along with toktokkie. If not, see <http://www.gnu.org/licenses/>. 

18LICENSE""" 

19 

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 

31 

32 

33class MangadexUpdater(Updater): 

34 """ 

35 Class that implements a mangadex updater 

36 """ 

37 

38 @classmethod 

39 def name(cls) -> str: 

40 """ 

41 :return: The name of the Updater 

42 """ 

43 return "mangadex" 

44 

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] 

51 

52 def update(self): 

53 """ 

54 Executes the update 

55 :return: None 

56 """ 

57 self.execute_rename() 

58 

59 chapters = self.load_chapters() 

60 if chapters is None: 

61 return 

62 

63 if not self.args["no_check_newest_chapter_length"]: 

64 self.check_latest_chapter_completeness(chapters) 

65 self.update_main_chapters(chapters) 

66 

67 if not self.args["skip_special"]: 

68 self.update_special_chapters(chapters) 

69 

70 if not self.args["dry_run"]: 

71 self.execute_rename() 

72 

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) 

87 

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 """ 

93 

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 

100 

101 mangadex_id = mangadex_ids[0] 

102 

103 chapters = MangaDexScraper().load_chapters(None, mangadex_id) 

104 return chapters 

105 

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) 

119 

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) 

123 

124 if current_latest == 0: 

125 return 

126 

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 

134 

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) 

139 

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 ) 

156 

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) 

164 

165 current_latest = len(listdir(metadata.main_path)) 

166 

167 main_chapters = list(filter( 

168 lambda x: not x.is_special 

169 and int(x.chapter_number) > current_latest, 

170 chapters 

171 )) 

172 

173 maxchar = max(metadata.name) 

174 

175 total_chapters = len(main_chapters) + current_latest 

176 

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 

186 

187 if c.chapter_number in downloaded: 

188 continue 

189 downloaded.append(c.chapter_number) 

190 

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") 

202 

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)) 

214 

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 

222 

223 for c in special_chapters: 

224 

225 name = "{} - Chapter {}.cbz".format( 

226 metadata.name, 

227 c.chapter_number.zfill(special_fill) 

228 ) 

229 

230 path = os.path.join( 

231 metadata.special_path, replace_illegal_ntfs_chars(name) 

232 ) 

233 

234 if os.path.exists(path): 

235 continue 

236 elif c.chapter_number not in metadata.special_chapters: 

237 

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")