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 2020 Hermann Krumrey <hermann@krumreyh.com> 

3 

4This file is part of otaku-info-web. 

5 

6otaku-info-web 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 

11otaku-info-web 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 otaku-info-web. If not, see <http://www.gnu.org/licenses/>. 

18LICENSE""" 

19 

20from typing import List, Tuple, Dict, Any 

21from puffotter.flask.db.User import User 

22from puffotter.flask.base import app 

23from otaku_info_web.db.MangaChapterGuess import MangaChapterGuess 

24from otaku_info_web.db.MediaItem import MediaItem 

25from otaku_info_web.db.MediaList import MediaList 

26from otaku_info_web.db.MediaListItem import MediaListItem 

27from otaku_info_web.db.MediaUserState import MediaUserState 

28from otaku_info_web.db.MediaId import MediaId 

29from otaku_info_web.utils.manga_updates.MangaUpdate import MangaUpdate 

30from otaku_info_web.utils.db_model_helper import build_title 

31from otaku_info_web.utils.enums \ 

32 import MediaType, MediaSubType, ConsumingState, ReleasingState, ListService 

33 

34 

35def load_applicable_data( 

36 user: User, 

37 service: ListService, 

38 media_list: str, 

39 include_complete: bool 

40) -> Tuple[ 

41 Dict[int, Dict[str, Any]], 

42 Dict[int, Dict[str, Any]], 

43 Dict[int, Dict[str, Any]], 

44 Dict[int, Dict[str, Any]], 

45 Dict[int, Dict[str, Any]], 

46 Dict[int, int] 

47]: 

48 """ 

49 Loads the applicable data from the database in an efficient manner. 

50 By only loading the data we need and avoiding JOINs, the performance 

51 is increased drastically. 

52 Since this method is called for every call to a manga/updates page, 

53 this should be fast. 

54 The return values are mostly database IDs mapped to dictionaries 

55 containing the data required for displaying manga updates. 

56 A notable exception are the manga chapter guesses, which are simply 

57 MediaId IDs mapped to the chapter guess value. 

58 :param user: The user requesting the manga updates 

59 :param service: The service for which to fetch the updates 

60 :param media_list: The media list for which to fetch the updates 

61 :param include_complete: Whether or not to include completed series 

62 :return: media items, media ids, media user states, media lists, 

63 media list items, manga chapter guesses 

64 """ 

65 

66 applicable_releasing_states = [ReleasingState.RELEASING] 

67 if include_complete: 

68 applicable_releasing_states += [ 

69 ReleasingState.FINISHED, 

70 ReleasingState.CANCELLED 

71 ] 

72 

73 media_items: Dict[int, Dict[str, Any]] = { 

74 x[0]: { 

75 "title": build_title(x[1], x[2]), 

76 "cover_url": x[3], 

77 "latest_release": x[4] 

78 } 

79 for x in MediaItem.query 

80 .with_entities( 

81 MediaItem.id, 

82 MediaItem.english_title, 

83 MediaItem.romaji_title, 

84 MediaItem.cover_url, 

85 MediaItem.latest_release, 

86 MediaItem.releasing_state 

87 ) 

88 .filter(MediaItem.media_type == MediaType.MANGA) 

89 .filter(MediaItem.media_subtype == MediaSubType.MANGA) 

90 .all() 

91 if x[5] in applicable_releasing_states 

92 } 

93 

94 media_ids: Dict[int, Dict[str, Any]] = { 

95 x[0]: { 

96 "media_item_id": x[1], 

97 "service": x[2], 

98 "service_id": x[3] 

99 } 

100 for x in MediaId.query 

101 .with_entities( 

102 MediaId.id, 

103 MediaId.media_item_id, 

104 MediaId.service, 

105 MediaId.service_id 

106 ) 

107 .all() 

108 if x[1] in media_items 

109 } 

110 

111 user_states: Dict[int, Dict[str, Any]] = { 

112 x[0]: { 

113 "media_id_id": x[1], 

114 "progress": x[2], 

115 "score": x[3] 

116 } 

117 for x in MediaUserState.query 

118 .with_entities( 

119 MediaUserState.id, 

120 MediaUserState.media_id_id, 

121 MediaUserState.progress, 

122 MediaUserState.score, 

123 MediaUserState.consuming_state 

124 ) 

125 .all() 

126 if x[1] in media_ids and x[4] in [ 

127 ConsumingState.CURRENT, ConsumingState.PAUSED 

128 ] 

129 } 

130 

131 user_lists: Dict[int, Dict[str, Any]] = { 

132 x[0]: {} 

133 for x in MediaList.query 

134 .with_entities(MediaList.id) 

135 .filter_by(user_id=user.id) 

136 .filter_by(name=media_list) 

137 .filter_by(service=service) 

138 .all() 

139 } 

140 

141 list_items: Dict[int, Dict[str, Any]] = { 

142 x[0]: { 

143 "media_list_id": x[1], 

144 "media_user_state_id": x[2] 

145 } 

146 for x in MediaListItem.query 

147 .with_entities( 

148 MediaListItem.id, 

149 MediaListItem.media_list_id, 

150 MediaListItem.media_user_state_id 

151 ) 

152 .all() 

153 if x[1] in user_lists and x[2] in user_states 

154 } 

155 

156 chapter_guesses: Dict[int, int] = { 

157 x.media_id_id: x.guess 

158 for x in MangaChapterGuess.query.all() 

159 } 

160 

161 return media_items, media_ids, user_states, user_lists, list_items, \ 

162 chapter_guesses 

163 

164 

165def prepare_manga_updates( 

166 user: User, 

167 service: ListService, 

168 media_list: str, 

169 include_complete: bool, 

170 min_update_count: int 

171) -> List[MangaUpdate]: 

172 """ 

173 Prepares easily understandable objects to display for manga updates 

174 :param user: The user requesting the manga updates 

175 :param service: The service for which to fetch the updates 

176 :param media_list: The media list for which to fetch the updates 

177 :param include_complete: Whether or not to include completed series 

178 :param min_update_count: The minimum amount of new chapters required 

179 for an update to be generated 

180 :return: A list of MangaUpdate objects, sorted by score 

181 """ 

182 app.logger.debug("Starting preparing manga updates") 

183 

184 import time 

185 start = time.time() 

186 

187 media_items, media_ids, user_states, user_lists, list_items, \ 

188 chapter_guesses = load_applicable_data( 

189 user, service, media_list, include_complete 

190 ) 

191 

192 media_item_service_ids: Dict[int, List[Tuple[ListService, str]]] = { 

193 x: [] for x in media_items 

194 } 

195 for _, media_id in media_ids.items(): 

196 media_item_service_ids[media_id["media_item_id"]].append( 

197 (media_id["service"], media_id["service_id"]) 

198 ) 

199 

200 combined = [] 

201 for media_list_item_id, media_list_item in list_items.items(): 

202 data = media_list_item 

203 data.update(user_lists[data["media_list_id"]]) 

204 data.update(user_states[data["media_user_state_id"]]) 

205 data.update(media_ids[data["media_id_id"]]) 

206 data.update(media_items[data["media_item_id"]]) 

207 data["guess"] = chapter_guesses.get(data["media_id_id"]) 

208 

209 data["related_ids"] = media_item_service_ids[data["media_item_id"]] 

210 

211 combined.append(data) 

212 

213 compiled = [ 

214 MangaUpdate( 

215 x["media_item_id"], 

216 x["title"], 

217 x["cover_url"], 

218 x["latest_release"], 

219 x["progress"], 

220 x["score"], 

221 x["guess"], 

222 x["related_ids"] 

223 ) 

224 for x in combined 

225 ] 

226 

227 app.logger.warning(time.time() - start) 

228 

229 return list(filter(lambda x: x.diff >= min_update_count, compiled))