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>
4This file is part of otaku-info-web.
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.
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.
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"""
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
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 """
66 applicable_releasing_states = [ReleasingState.RELEASING]
67 if include_complete:
68 applicable_releasing_states += [
69 ReleasingState.FINISHED,
70 ReleasingState.CANCELLED
71 ]
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 }
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 }
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 }
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 }
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 }
156 chapter_guesses: Dict[int, int] = {
157 x.media_id_id: x.guess
158 for x in MangaChapterGuess.query.all()
159 }
161 return media_items, media_ids, user_states, user_lists, list_items, \
162 chapter_guesses
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")
184 import time
185 start = time.time()
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 )
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 )
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"])
209 data["related_ids"] = media_item_service_ids[data["media_item_id"]]
211 combined.append(data)
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 ]
227 app.logger.warning(time.time() - start)
229 return list(filter(lambda x: x.diff >= min_update_count, compiled))