Mercurial > minori
comparison src/core/anime_db.cc @ 369:47c9f8502269
*: clang-format all the things
I've edited the formatting a bit. Now pointer asterisks (and reference
ampersands) are on the variable instead of the type, as well as having
newlines for function braces (but nothing else)
author | Paper <paper@tflc.us> |
---|---|
date | Fri, 25 Jul 2025 10:16:02 -0400 |
parents | b5d6c27c308f |
children |
comparison
equal
deleted
inserted
replaced
368:6d37a998cf91 | 369:47c9f8502269 |
---|---|
10 | 10 |
11 #include <QDate> | 11 #include <QDate> |
12 | 12 |
13 #include <fstream> | 13 #include <fstream> |
14 | 14 |
15 #include <cstdlib> | |
15 #include <exception> | 16 #include <exception> |
16 #include <cstdlib> | |
17 #include <iostream> | 17 #include <iostream> |
18 #include <random> | 18 #include <random> |
19 | 19 |
20 namespace Anime { | 20 namespace Anime { |
21 | 21 |
22 size_t Database::GetTotalAnimeAmount() const { | 22 size_t Database::GetTotalAnimeAmount() const |
23 size_t total = 0; | 23 { |
24 | 24 size_t total = 0; |
25 for (const auto& [id, anime] : items) | 25 |
26 for (const auto &[id, anime] : items) | |
26 if (anime.IsInUserList()) | 27 if (anime.IsInUserList()) |
27 total++; | 28 total++; |
28 | 29 |
29 return total; | 30 return total; |
30 } | 31 } |
31 | 32 |
32 size_t Database::GetListsAnimeAmount(ListStatus status) const { | 33 size_t Database::GetListsAnimeAmount(ListStatus status) const |
34 { | |
33 if (status == ListStatus::NotInList) | 35 if (status == ListStatus::NotInList) |
34 return 0; | 36 return 0; |
35 | 37 |
36 size_t total = 0; | 38 size_t total = 0; |
37 | 39 |
38 for (const auto& [id, anime] : items) | 40 for (const auto &[id, anime] : items) |
39 if (anime.IsInUserList() && anime.GetUserStatus() == status) | 41 if (anime.IsInUserList() && anime.GetUserStatus() == status) |
40 total++; | 42 total++; |
41 | 43 |
42 return total; | 44 return total; |
43 } | 45 } |
44 | 46 |
45 size_t Database::GetTotalEpisodeAmount() const { | 47 size_t Database::GetTotalEpisodeAmount() const |
46 size_t total = 0; | 48 { |
47 | 49 size_t total = 0; |
48 for (const auto& [id, anime] : items) | 50 |
51 for (const auto &[id, anime] : items) | |
49 if (anime.IsInUserList()) | 52 if (anime.IsInUserList()) |
50 total += anime.GetUserRewatchedTimes() * anime.GetEpisodes() + anime.GetUserProgress(); | 53 total += anime.GetUserRewatchedTimes() * anime.GetEpisodes() + anime.GetUserProgress(); |
51 | 54 |
52 return total; | 55 return total; |
53 } | 56 } |
54 | 57 |
55 /* Returns the total watched amount in minutes. */ | 58 /* Returns the total watched amount in minutes. */ |
56 size_t Database::GetTotalWatchedAmount() const { | 59 size_t Database::GetTotalWatchedAmount() const |
57 size_t total = 0; | 60 { |
58 | 61 size_t total = 0; |
59 for (const auto& [id, anime] : items) | 62 |
63 for (const auto &[id, anime] : items) | |
60 if (anime.IsInUserList()) | 64 if (anime.IsInUserList()) |
61 total += anime.GetDuration() * anime.GetUserProgress() + | 65 total += anime.GetDuration() * anime.GetUserProgress() + |
62 anime.GetEpisodes() * anime.GetDuration() * anime.GetUserRewatchedTimes(); | 66 anime.GetEpisodes() * anime.GetDuration() * anime.GetUserRewatchedTimes(); |
63 | 67 |
64 return total; | 68 return total; |
67 /* Returns the total planned amount in minutes. | 71 /* Returns the total planned amount in minutes. |
68 Note that we should probably limit progress to the | 72 Note that we should probably limit progress to the |
69 amount of episodes, as AniList will let you | 73 amount of episodes, as AniList will let you |
70 set episode counts up to 32768. But that should | 74 set episode counts up to 32768. But that should |
71 rather be handled elsewhere. */ | 75 rather be handled elsewhere. */ |
72 size_t Database::GetTotalPlannedAmount() const { | 76 size_t Database::GetTotalPlannedAmount() const |
73 size_t total = 0; | 77 { |
74 | 78 size_t total = 0; |
75 for (const auto& [id, anime] : items) | 79 |
80 for (const auto &[id, anime] : items) | |
76 if (anime.IsInUserList()) | 81 if (anime.IsInUserList()) |
77 total += anime.GetDuration() * (anime.GetEpisodes() - anime.GetUserProgress()); | 82 total += anime.GetDuration() * (anime.GetEpisodes() - anime.GetUserProgress()); |
78 | 83 |
79 return total; | 84 return total; |
80 } | 85 } |
81 | 86 |
82 /* In Taiga this is called the mean, but "average" is | 87 /* In Taiga this is called the mean, but "average" is |
83 what's primarily used in conversation, at least | 88 what's primarily used in conversation, at least |
84 in the U.S. */ | 89 in the U.S. */ |
85 double Database::GetAverageScore() const { | 90 double Database::GetAverageScore() const |
91 { | |
86 double avg = 0; | 92 double avg = 0; |
87 size_t amt = 0; | 93 size_t amt = 0; |
88 | 94 |
89 for (const auto& [id, anime] : items) { | 95 for (const auto &[id, anime] : items) { |
90 if (anime.IsInUserList() && anime.GetUserScore()) { | 96 if (anime.IsInUserList() && anime.GetUserScore()) { |
91 avg += anime.GetUserScore(); | 97 avg += anime.GetUserScore(); |
92 amt++; | 98 amt++; |
93 } | 99 } |
94 } | 100 } |
95 return avg / amt; | 101 return avg / amt; |
96 } | 102 } |
97 | 103 |
98 double Database::GetScoreDeviation() const { | 104 double Database::GetScoreDeviation() const |
105 { | |
99 double squares_sum = 0, avg = GetAverageScore(); | 106 double squares_sum = 0, avg = GetAverageScore(); |
100 size_t amt = 0; | 107 size_t amt = 0; |
101 | 108 |
102 for (const auto& [id, anime] : items) { | 109 for (const auto &[id, anime] : items) { |
103 if (anime.IsInUserList() && anime.GetUserScore()) { | 110 if (anime.IsInUserList() && anime.GetUserScore()) { |
104 squares_sum += std::pow(static_cast<double>(anime.GetUserScore()) - avg, 2); | 111 squares_sum += std::pow(static_cast<double>(anime.GetUserScore()) - avg, 2); |
105 amt++; | 112 amt++; |
106 } | 113 } |
107 } | 114 } |
108 | 115 |
109 return (amt > 0) ? std::sqrt(squares_sum / amt) : 0; | 116 return (amt > 0) ? std::sqrt(squares_sum / amt) : 0; |
110 } | 117 } |
111 | 118 |
112 int Database::LookupAnimeTitle(const std::string& title) const { | 119 int Database::LookupAnimeTitle(const std::string &title) const |
120 { | |
113 if (title.empty()) | 121 if (title.empty()) |
114 return 0; | 122 return 0; |
115 | 123 |
116 std::string title_n(title); | 124 std::string title_n(title); |
117 Strings::NormalizeAnimeTitle(title_n); | 125 Strings::NormalizeAnimeTitle(title_n); |
118 | 126 |
119 for (const auto& [id, anime] : items) { | 127 for (const auto &[id, anime] : items) { |
120 std::vector<std::string> synonyms(anime.GetTitleSynonyms()); | 128 std::vector<std::string> synonyms(anime.GetTitleSynonyms()); |
121 synonyms.push_back(anime.GetUserPreferredTitle()); | 129 synonyms.push_back(anime.GetUserPreferredTitle()); |
122 | 130 |
123 for (auto& synonym : synonyms) { | 131 for (auto &synonym : synonyms) { |
124 Strings::NormalizeAnimeTitle(synonym); | 132 Strings::NormalizeAnimeTitle(synonym); |
125 if (synonym == title_n) | 133 if (synonym == title_n) |
126 return id; | 134 return id; |
127 } | 135 } |
128 } | 136 } |
129 | 137 |
130 return 0; | 138 return 0; |
131 } | 139 } |
132 | 140 |
133 static bool GetListDataAsJSON(const Anime& anime, nlohmann::json& json) { | 141 static bool GetListDataAsJSON(const Anime &anime, nlohmann::json &json) |
142 { | |
134 if (!anime.IsInUserList()) | 143 if (!anime.IsInUserList()) |
135 return false; | 144 return false; |
136 | 145 |
137 // clang-format off | 146 // clang-format off |
138 json = { | 147 json = { |
150 // clang-format on | 159 // clang-format on |
151 | 160 |
152 return true; | 161 return true; |
153 } | 162 } |
154 | 163 |
155 static bool GetAnimeAsJSON(const Anime& anime, nlohmann::json& json) { | 164 static bool GetAnimeAsJSON(const Anime &anime, nlohmann::json &json) |
165 { | |
156 // clang-format off | 166 // clang-format off |
157 json = { | 167 json = { |
158 {"id", anime.GetId()}, | 168 {"id", anime.GetId()}, |
159 {"synonyms", anime.GetTitleSynonyms()}, | 169 {"synonyms", anime.GetTitleSynonyms()}, |
160 {"episodes", anime.GetEpisodes()}, | 170 {"episodes", anime.GetEpisodes()}, |
171 {"poster_url", anime.GetPosterUrl()} | 181 {"poster_url", anime.GetPosterUrl()} |
172 }; | 182 }; |
173 // clang-format on | 183 // clang-format on |
174 | 184 |
175 /* now for dynamically-filled stuff */ | 185 /* now for dynamically-filled stuff */ |
176 for (const auto& lang : TitleLanguages) { | 186 for (const auto &lang : TitleLanguages) { |
177 std::optional<std::string> title = anime.GetTitle(lang); | 187 std::optional<std::string> title = anime.GetTitle(lang); |
178 if (title.has_value()) | 188 if (title.has_value()) |
179 json["title"][Strings::ToLower(Translate::ToString(lang))] = title.value(); | 189 json["title"][Strings::ToLower(Translate::ToString(lang))] = title.value(); |
180 } | 190 } |
181 | 191 |
182 for (const auto& service : Services) { | 192 for (const auto &service : Services) { |
183 std::optional<std::string> id = anime.GetServiceId(service); | 193 std::optional<std::string> id = anime.GetServiceId(service); |
184 if (id.has_value()) | 194 if (id.has_value()) |
185 json["ids"][Strings::ToLower(Translate::ToString(service))] = id.value(); | 195 json["ids"][Strings::ToLower(Translate::ToString(service))] = id.value(); |
186 } | 196 } |
187 | 197 |
190 json.push_back({"list_data", user}); | 200 json.push_back({"list_data", user}); |
191 | 201 |
192 return true; | 202 return true; |
193 } | 203 } |
194 | 204 |
195 bool Database::GetDatabaseAsJSON(nlohmann::json& json) const { | 205 bool Database::GetDatabaseAsJSON(nlohmann::json &json) const |
196 for (const auto& [id, anime] : items) { | 206 { |
207 for (const auto &[id, anime] : items) { | |
197 nlohmann::json anime_json = {}; | 208 nlohmann::json anime_json = {}; |
198 GetAnimeAsJSON(anime, anime_json); | 209 GetAnimeAsJSON(anime, anime_json); |
199 json.push_back(anime_json); | 210 json.push_back(anime_json); |
200 } | 211 } |
201 | 212 |
202 return true; | 213 return true; |
203 } | 214 } |
204 | 215 |
205 bool Database::SaveDatabaseToDisk() const { | 216 bool Database::SaveDatabaseToDisk() const |
217 { | |
206 std::filesystem::path db_path = Filesystem::GetAnimeDBPath(); | 218 std::filesystem::path db_path = Filesystem::GetAnimeDBPath(); |
207 Filesystem::CreateDirectories(db_path); | 219 Filesystem::CreateDirectories(db_path); |
208 | 220 |
209 std::ofstream db_file(db_path); | 221 std::ofstream db_file(db_path); |
210 if (!db_file) | 222 if (!db_file) |
216 | 228 |
217 db_file << std::setw(4) << json << std::endl; | 229 db_file << std::setw(4) << json << std::endl; |
218 return true; | 230 return true; |
219 } | 231 } |
220 | 232 |
221 static bool ParseAnimeUserInfoJSON(const nlohmann::json& json, Anime& anime) { | 233 static bool ParseAnimeUserInfoJSON(const nlohmann::json &json, Anime &anime) |
234 { | |
222 if (!anime.IsInUserList()) | 235 if (!anime.IsInUserList()) |
223 anime.AddToUserList(); | 236 anime.AddToUserList(); |
224 | 237 |
225 anime.SetUserStatus(Translate::ToListStatus(JSON::GetString<std::string>(json, "/status"_json_pointer, ""))); | 238 anime.SetUserStatus(Translate::ToListStatus(JSON::GetString<std::string>(json, "/status"_json_pointer, ""))); |
226 anime.SetUserProgress(JSON::GetNumber(json, "/progress"_json_pointer, 0)); | 239 anime.SetUserProgress(JSON::GetNumber(json, "/progress"_json_pointer, 0)); |
234 anime.SetUserNotes(JSON::GetString<std::string>(json, "/notes"_json_pointer, "")); | 247 anime.SetUserNotes(JSON::GetString<std::string>(json, "/notes"_json_pointer, "")); |
235 | 248 |
236 return true; | 249 return true; |
237 } | 250 } |
238 | 251 |
239 static bool ParseAnimeInfoJSON(const nlohmann::json& json, Database& database) { | 252 static bool ParseAnimeInfoJSON(const nlohmann::json &json, Database &database) |
253 { | |
240 int id = JSON::GetNumber(json, "/id"_json_pointer, 0); | 254 int id = JSON::GetNumber(json, "/id"_json_pointer, 0); |
241 if (!id) | 255 if (!id) |
242 return false; | 256 return false; |
243 | 257 |
244 Anime& anime = database.items[id]; | 258 Anime &anime = database.items[id]; |
245 | 259 |
246 anime.SetId(id); | 260 anime.SetId(id); |
247 for (const auto& service : Services) { | 261 for (const auto &service : Services) { |
248 nlohmann::json::json_pointer p("/ids/" + Strings::ToLower(Translate::ToString(service))); | 262 nlohmann::json::json_pointer p("/ids/" + Strings::ToLower(Translate::ToString(service))); |
249 | 263 |
250 if (json.contains(p) && json[p].is_string()) | 264 if (json.contains(p) && json[p].is_string()) |
251 anime.SetServiceId(service, json[p].get<std::string>()); | 265 anime.SetServiceId(service, json[p].get<std::string>()); |
252 } | 266 } |
253 | 267 |
254 for (const auto& lang : TitleLanguages) { | 268 for (const auto &lang : TitleLanguages) { |
255 nlohmann::json::json_pointer p("/title/" + Strings::ToLower(Translate::ToString(lang))); | 269 nlohmann::json::json_pointer p("/title/" + Strings::ToLower(Translate::ToString(lang))); |
256 | 270 |
257 if (json.contains(p) && json[p].is_string()) | 271 if (json.contains(p) && json[p].is_string()) |
258 anime.SetTitle(lang, json[p].get<std::string>()); | 272 anime.SetTitle(lang, json[p].get<std::string>()); |
259 } | 273 } |
276 ParseAnimeUserInfoJSON(json.at("/list_data"_json_pointer), anime); | 290 ParseAnimeUserInfoJSON(json.at("/list_data"_json_pointer), anime); |
277 | 291 |
278 return true; | 292 return true; |
279 } | 293 } |
280 | 294 |
281 bool Database::ParseDatabaseJSON(const nlohmann::json& json) { | 295 bool Database::ParseDatabaseJSON(const nlohmann::json &json) |
282 for (const auto& anime_json : json) | 296 { |
297 for (const auto &anime_json : json) | |
283 ParseAnimeInfoJSON(anime_json, *this); | 298 ParseAnimeInfoJSON(anime_json, *this); |
284 | 299 |
285 return true; | 300 return true; |
286 } | 301 } |
287 | 302 |
288 bool Database::LoadDatabaseFromDisk() { | 303 bool Database::LoadDatabaseFromDisk() |
304 { | |
289 std::filesystem::path db_path = Filesystem::GetAnimeDBPath(); | 305 std::filesystem::path db_path = Filesystem::GetAnimeDBPath(); |
290 Filesystem::CreateDirectories(db_path); | 306 Filesystem::CreateDirectories(db_path); |
291 | 307 |
292 std::ifstream db_file(db_path); | 308 std::ifstream db_file(db_path); |
293 if (!db_file) | 309 if (!db_file) |
295 | 311 |
296 /* When parsing, do NOT throw exceptions */ | 312 /* When parsing, do NOT throw exceptions */ |
297 nlohmann::json json; | 313 nlohmann::json json; |
298 try { | 314 try { |
299 json = json.parse(db_file); | 315 json = json.parse(db_file); |
300 } catch (std::exception const& ex) { | 316 } catch (std::exception const &ex) { |
301 std::cerr << "[anime/db] Failed to parse JSON! " << ex.what() << std::endl; | 317 std::cerr << "[anime/db] Failed to parse JSON! " << ex.what() << std::endl; |
302 return false; | 318 return false; |
303 } | 319 } |
304 | 320 |
305 if (!ParseDatabaseJSON(json)) /* How */ | 321 if (!ParseDatabaseJSON(json)) /* How */ |
306 return false; | 322 return false; |
307 | 323 |
308 return true; | 324 return true; |
309 } | 325 } |
310 | 326 |
311 int Database::GetUnusedId() const { | 327 int Database::GetUnusedId() const |
328 { | |
312 std::uniform_int_distribution<int> distrib(1, INT_MAX); | 329 std::uniform_int_distribution<int> distrib(1, INT_MAX); |
313 int res; | 330 int res; |
314 | 331 |
315 do { | 332 do { |
316 res = distrib(session.gen); | 333 res = distrib(session.gen); |
317 } while (items.count(res) && !res); | 334 } while (items.count(res) && !res); |
318 | 335 |
319 return res; | 336 return res; |
320 } | 337 } |
321 | 338 |
322 int Database::LookupServiceId(Service service, const std::string& id_to_find) const { | 339 int Database::LookupServiceId(Service service, const std::string &id_to_find) const |
323 for (const auto& [id, anime] : items) { | 340 { |
341 for (const auto &[id, anime] : items) { | |
324 std::optional<std::string> service_id = anime.GetServiceId(service); | 342 std::optional<std::string> service_id = anime.GetServiceId(service); |
325 if (!service_id) | 343 if (!service_id) |
326 continue; | 344 continue; |
327 | 345 |
328 if (service_id == id_to_find) | 346 if (service_id == id_to_find) |
330 } | 348 } |
331 | 349 |
332 return 0; | 350 return 0; |
333 } | 351 } |
334 | 352 |
335 int Database::LookupServiceIdOrUnused(Service service, const std::string& id_to_find) const { | 353 int Database::LookupServiceIdOrUnused(Service service, const std::string &id_to_find) const |
354 { | |
336 int id = LookupServiceId(service, id_to_find); | 355 int id = LookupServiceId(service, id_to_find); |
337 if (id) | 356 if (id) |
338 return id; | 357 return id; |
339 | 358 |
340 return GetUnusedId(); | 359 return GetUnusedId(); |
341 } | 360 } |
342 | 361 |
343 void Database::RemoveAllUserData() { | 362 void Database::RemoveAllUserData() |
344 for (auto& [id, anime] : items) { | 363 { |
364 for (auto &[id, anime] : items) { | |
345 if (anime.IsInUserList()) | 365 if (anime.IsInUserList()) |
346 anime.RemoveFromUserList(); | 366 anime.RemoveFromUserList(); |
347 } | 367 } |
348 } | 368 } |
349 | 369 |
350 std::vector<int> Database::GetAllAnimeForSeason(Season season) { | 370 std::vector<int> Database::GetAllAnimeForSeason(Season season) |
371 { | |
351 std::vector<int> res; | 372 std::vector<int> res; |
352 | 373 |
353 for (const auto& [id, anime] : items) { | 374 for (const auto &[id, anime] : items) { |
354 if (anime.GetSeason() == season) | 375 if (anime.GetSeason() == season) |
355 res.push_back(id); | 376 res.push_back(id); |
356 } | 377 } |
357 | 378 |
358 return res; | 379 return res; |