1 | // SuperTux - Add-on Manager |
2 | // Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de> |
3 | // 2014 Ingo Ruhnke <grumbel@gmail.com> |
4 | // |
5 | // This program is free software: you can redistribute it and/or modify |
6 | // it under the terms of the GNU General Public License as published by |
7 | // the Free Software Foundation, either version 3 of the License, or |
8 | // (at your option) any later version. |
9 | // |
10 | // This program is distributed in the hope that it will be useful, |
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | // GNU General Public License for more details. |
14 | // |
15 | // You should have received a copy of the GNU General Public License |
16 | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
17 | |
18 | #include "addon/addon_manager.hpp" |
19 | |
20 | #include <physfs.h> |
21 | |
22 | #include "addon/addon.hpp" |
23 | #include "addon/md5.hpp" |
24 | #include "physfs/util.hpp" |
25 | #include "supertux/globals.hpp" |
26 | #include "util/file_system.hpp" |
27 | #include "util/gettext.hpp" |
28 | #include "util/log.hpp" |
29 | #include "util/reader.hpp" |
30 | #include "util/reader_collection.hpp" |
31 | #include "util/reader_document.hpp" |
32 | #include "util/reader_mapping.hpp" |
33 | #include "util/string_util.hpp" |
34 | |
35 | namespace { |
36 | |
37 | static const char* ADDON_INFO_PATH = "/addons/repository.nfo" ; |
38 | |
39 | MD5 md5_from_file(const std::string& filename) |
40 | { |
41 | // TODO: this does not work as expected for some files -- IFileStream seems to not always behave like an ifstream. |
42 | //IFileStream ifs(installed_physfs_filename); |
43 | //std::string md5 = MD5(ifs).hex_digest(); |
44 | |
45 | MD5 md5; |
46 | |
47 | auto file = PHYSFS_openRead(filename.c_str()); |
48 | if (!file) |
49 | { |
50 | std::ostringstream out; |
51 | out << "PHYSFS_openRead() failed: " << PHYSFS_getLastErrorCode(); |
52 | throw std::runtime_error(out.str()); |
53 | } |
54 | else |
55 | { |
56 | while (true) |
57 | { |
58 | unsigned char buffer[1024]; |
59 | PHYSFS_sint64 len = PHYSFS_readBytes(file, buffer, sizeof(buffer)); |
60 | if (len <= 0) break; |
61 | md5.update(buffer, static_cast<unsigned int>(len)); |
62 | } |
63 | PHYSFS_close(file); |
64 | |
65 | return md5; |
66 | } |
67 | } |
68 | |
69 | MD5 md5_from_archive(const std::string& filename) |
70 | { |
71 | if (physfsutil::is_directory(filename)) { |
72 | return MD5(); |
73 | } else { |
74 | return md5_from_file(filename); |
75 | } |
76 | } |
77 | |
78 | static Addon& get_addon(const AddonManager::AddonList& list, const AddonId& id, |
79 | bool installed) |
80 | { |
81 | auto it = std::find_if(list.begin(), list.end(), |
82 | [&id](const std::unique_ptr<Addon>& addon) |
83 | { |
84 | return addon->get_id() == id; |
85 | }); |
86 | |
87 | if (it != list.end()) |
88 | { |
89 | return **it; |
90 | } |
91 | else |
92 | { |
93 | std::string type = installed ? "installed" : "repository" ; |
94 | throw std::runtime_error("Couldn't find " + type + " addon with id: " + id); |
95 | } |
96 | } |
97 | |
98 | static std::vector<AddonId> get_addons(const AddonManager::AddonList& list) |
99 | { |
100 | std::vector<AddonId> results; |
101 | results.reserve(list.size()); |
102 | std::transform(list.begin(), list.end(), |
103 | std::back_inserter(results), |
104 | [](const std::unique_ptr<Addon>& addon) |
105 | { |
106 | return addon->get_id(); |
107 | }); |
108 | return results; |
109 | } |
110 | |
111 | static PHYSFS_EnumerateCallbackResult add_to_dictionary_path(void *data, const char *origdir, const char *fname) |
112 | { |
113 | std::string full_path = FileSystem::join(origdir, fname); |
114 | if (physfsutil::is_directory(full_path)) |
115 | { |
116 | log_debug << "Adding \"" << full_path << "\" to dictionary search path" << std::endl; |
117 | // We want translations from addons to have precedence |
118 | g_dictionary_manager->add_directory(full_path, true); |
119 | } |
120 | return PHYSFS_ENUM_OK; |
121 | } |
122 | |
123 | static PHYSFS_EnumerateCallbackResult remove_from_dictionary_path(void *data, const char *origdir, const char *fname) |
124 | { |
125 | std::string full_path = FileSystem::join(origdir, fname); |
126 | if (physfsutil::is_directory(full_path)) |
127 | { |
128 | g_dictionary_manager->remove_directory(full_path); |
129 | } |
130 | return PHYSFS_ENUM_OK; |
131 | } |
132 | } // namespace |
133 | |
134 | AddonManager::AddonManager(const std::string& addon_directory, |
135 | std::vector<Config::Addon>& addon_config) : |
136 | m_downloader(), |
137 | m_addon_directory(addon_directory), |
138 | m_repository_url("https://raw.githubusercontent.com/SuperTux/addons/master/index-0_6.nfo" ), |
139 | m_addon_config(addon_config), |
140 | m_installed_addons(), |
141 | m_repository_addons(), |
142 | m_has_been_updated(false), |
143 | m_transfer_status() |
144 | { |
145 | if (!PHYSFS_mkdir(m_addon_directory.c_str())) |
146 | { |
147 | std::ostringstream msg; |
148 | msg << "Couldn't create directory for addons '" |
149 | << m_addon_directory << "': " << PHYSFS_getLastErrorCode(); |
150 | throw std::runtime_error(msg.str()); |
151 | } |
152 | |
153 | add_installed_addons(); |
154 | |
155 | // FIXME: We should also restore the order here |
156 | for (auto& addon : m_addon_config) |
157 | { |
158 | if (addon.enabled) |
159 | { |
160 | try |
161 | { |
162 | enable_addon(addon.id); |
163 | } |
164 | catch(const std::exception& err) |
165 | { |
166 | log_warning << "failed to enable addon from config: " << err.what() << std::endl; |
167 | } |
168 | } |
169 | } |
170 | |
171 | if (PHYSFS_exists(ADDON_INFO_PATH)) |
172 | { |
173 | try |
174 | { |
175 | m_repository_addons = parse_addon_infos(ADDON_INFO_PATH); |
176 | } |
177 | catch(const std::exception& err) |
178 | { |
179 | log_warning << "parsing repository.nfo failed: " << err.what() << std::endl; |
180 | } |
181 | } |
182 | else |
183 | { |
184 | log_info << "repository.nfo doesn't exist, not loading" << std::endl; |
185 | } |
186 | |
187 | if (!g_config->repository_url.empty() && |
188 | g_config->repository_url != m_repository_url) |
189 | { |
190 | m_repository_url = g_config->repository_url; |
191 | } |
192 | } |
193 | |
194 | AddonManager::~AddonManager() |
195 | { |
196 | // sync enabled/disabled addons into the config for saving |
197 | m_addon_config.clear(); |
198 | for (const auto& addon : m_installed_addons) |
199 | { |
200 | m_addon_config.push_back({addon->get_id(), addon->is_enabled()}); |
201 | } |
202 | } |
203 | |
204 | Addon& |
205 | AddonManager::get_repository_addon(const AddonId& id) const |
206 | { |
207 | return get_addon(m_repository_addons, id, false); |
208 | } |
209 | |
210 | Addon& |
211 | AddonManager::get_installed_addon(const AddonId& id) const |
212 | { |
213 | return get_addon(m_installed_addons, id, true); |
214 | } |
215 | |
216 | std::vector<AddonId> |
217 | AddonManager::get_repository_addons() const |
218 | { |
219 | return get_addons(m_repository_addons); |
220 | } |
221 | |
222 | |
223 | std::vector<AddonId> |
224 | AddonManager::get_installed_addons() const |
225 | { |
226 | return get_addons(m_installed_addons); |
227 | } |
228 | |
229 | bool |
230 | AddonManager::has_online_support() const |
231 | { |
232 | return true; |
233 | } |
234 | |
235 | bool |
236 | AddonManager::has_been_updated() const |
237 | { |
238 | return m_has_been_updated; |
239 | } |
240 | |
241 | TransferStatusPtr |
242 | AddonManager::request_check_online() |
243 | { |
244 | if (m_transfer_status) |
245 | { |
246 | throw std::runtime_error("only async request can be made to AddonManager at a time" ); |
247 | } |
248 | else |
249 | { |
250 | m_transfer_status = m_downloader.request_download(m_repository_url, ADDON_INFO_PATH); |
251 | |
252 | m_transfer_status->then( |
253 | [this](bool success) |
254 | { |
255 | m_transfer_status = {}; |
256 | |
257 | if (success) |
258 | { |
259 | m_repository_addons = parse_addon_infos(ADDON_INFO_PATH); |
260 | m_has_been_updated = true; |
261 | } |
262 | }); |
263 | |
264 | return m_transfer_status; |
265 | } |
266 | } |
267 | |
268 | void |
269 | AddonManager::check_online() |
270 | { |
271 | m_downloader.download(m_repository_url, ADDON_INFO_PATH); |
272 | m_repository_addons = parse_addon_infos(ADDON_INFO_PATH); |
273 | m_has_been_updated = true; |
274 | } |
275 | |
276 | TransferStatusPtr |
277 | AddonManager::request_install_addon(const AddonId& addon_id) |
278 | { |
279 | if (m_transfer_status) |
280 | { |
281 | throw std::runtime_error("only one addon install request allowed at a time" ); |
282 | } |
283 | else |
284 | { |
285 | { // remove addon if it already exists |
286 | auto it = std::find_if(m_installed_addons.begin(), m_installed_addons.end(), |
287 | [&addon_id](const std::unique_ptr<Addon>& addon) |
288 | { |
289 | return addon->get_id() == addon_id; |
290 | }); |
291 | if (it != m_installed_addons.end()) |
292 | { |
293 | log_debug << "reinstalling addon " << addon_id << std::endl; |
294 | if ((*it)->is_enabled()) |
295 | { |
296 | disable_addon((*it)->get_id()); |
297 | } |
298 | m_installed_addons.erase(it); |
299 | } |
300 | else |
301 | { |
302 | log_debug << "installing addon " << addon_id << std::endl; |
303 | } |
304 | } |
305 | |
306 | auto& addon = get_repository_addon(addon_id); |
307 | |
308 | std::string install_filename = FileSystem::join(m_addon_directory, addon.get_filename()); |
309 | |
310 | m_transfer_status = m_downloader.request_download(addon.get_url(), install_filename); |
311 | |
312 | m_transfer_status->then( |
313 | [this, install_filename, addon_id](bool success) |
314 | { |
315 | m_transfer_status = {}; |
316 | |
317 | if (success) |
318 | { |
319 | // complete the addon install |
320 | Addon& repository_addon = get_repository_addon(addon_id); |
321 | |
322 | MD5 md5 = md5_from_file(install_filename); |
323 | if (repository_addon.get_md5() != md5.hex_digest()) |
324 | { |
325 | if (PHYSFS_delete(install_filename.c_str()) == 0) |
326 | { |
327 | log_warning << "PHYSFS_delete failed: " << PHYSFS_getLastErrorCode() << std::endl; |
328 | } |
329 | |
330 | throw std::runtime_error("Downloading Add-on failed: MD5 checksums differ" ); |
331 | } |
332 | else |
333 | { |
334 | const char* realdir = PHYSFS_getRealDir(install_filename.c_str()); |
335 | if (!realdir) |
336 | { |
337 | throw std::runtime_error("PHYSFS_getRealDir failed: " + install_filename); |
338 | } |
339 | else |
340 | { |
341 | add_installed_archive(install_filename, md5.hex_digest()); |
342 | } |
343 | } |
344 | } |
345 | }); |
346 | |
347 | return m_transfer_status; |
348 | } |
349 | } |
350 | |
351 | void |
352 | AddonManager::install_addon(const AddonId& addon_id) |
353 | { |
354 | { // remove addon if it already exists |
355 | auto it = std::find_if(m_installed_addons.begin(), m_installed_addons.end(), |
356 | [&addon_id](const std::unique_ptr<Addon>& addon) |
357 | { |
358 | return addon->get_id() == addon_id; |
359 | }); |
360 | if (it != m_installed_addons.end()) |
361 | { |
362 | log_debug << "reinstalling addon " << addon_id << std::endl; |
363 | if ((*it)->is_enabled()) |
364 | { |
365 | disable_addon((*it)->get_id()); |
366 | } |
367 | m_installed_addons.erase(it); |
368 | } |
369 | else |
370 | { |
371 | log_debug << "installing addon " << addon_id << std::endl; |
372 | } |
373 | } |
374 | |
375 | auto& repository_addon = get_repository_addon(addon_id); |
376 | |
377 | std::string install_filename = FileSystem::join(m_addon_directory, repository_addon.get_filename()); |
378 | |
379 | m_downloader.download(repository_addon.get_url(), install_filename); |
380 | |
381 | MD5 md5 = md5_from_file(install_filename); |
382 | if (repository_addon.get_md5() != md5.hex_digest()) |
383 | { |
384 | if (PHYSFS_delete(install_filename.c_str()) == 0) |
385 | { |
386 | log_warning << "PHYSFS_delete failed: " << PHYSFS_getLastErrorCode() << std::endl; |
387 | } |
388 | |
389 | throw std::runtime_error("Downloading Add-on failed: MD5 checksums differ" ); |
390 | } |
391 | else |
392 | { |
393 | const char* realdir = PHYSFS_getRealDir(install_filename.c_str()); |
394 | if (!realdir) |
395 | { |
396 | throw std::runtime_error("PHYSFS_getRealDir failed: " + install_filename); |
397 | } |
398 | else |
399 | { |
400 | add_installed_archive(install_filename, md5.hex_digest()); |
401 | } |
402 | } |
403 | } |
404 | |
405 | void |
406 | AddonManager::uninstall_addon(const AddonId& addon_id) |
407 | { |
408 | log_debug << "uninstalling addon " << addon_id << std::endl; |
409 | auto& addon = get_installed_addon(addon_id); |
410 | if (addon.is_enabled()) |
411 | { |
412 | disable_addon(addon_id); |
413 | } |
414 | log_debug << "deleting file \"" << addon.get_install_filename() << "\"" << std::endl; |
415 | PHYSFS_delete(addon.get_install_filename().c_str()); |
416 | m_installed_addons.erase(std::remove_if(m_installed_addons.begin(), m_installed_addons.end(), |
417 | [&addon](const std::unique_ptr<Addon>& rhs) |
418 | { |
419 | return addon.get_id() == rhs->get_id(); |
420 | }), |
421 | m_installed_addons.end()); |
422 | } |
423 | |
424 | void |
425 | AddonManager::enable_addon(const AddonId& addon_id) |
426 | { |
427 | log_debug << "enabling addon " << addon_id << std::endl; |
428 | auto& addon = get_installed_addon(addon_id); |
429 | if (addon.is_enabled()) |
430 | { |
431 | log_warning << "Tried enabling already enabled Add-on" << std::endl; |
432 | } |
433 | else |
434 | { |
435 | log_debug << "Adding archive \"" << addon.get_install_filename() << "\" to search path" << std::endl; |
436 | //int PHYSFS_mount(addon.installed_install_filename.c_str(), "addons/", 0) |
437 | |
438 | std::string mountpoint; |
439 | switch (addon.get_format()) { |
440 | case Addon::ORIGINAL: |
441 | mountpoint = "" ; |
442 | break; |
443 | default: |
444 | mountpoint = "custom/" + addon_id; |
445 | break; |
446 | } |
447 | |
448 | if (PHYSFS_mount(addon.get_install_filename().c_str(), mountpoint.c_str(), 0) == 0) |
449 | { |
450 | log_warning << "Could not add " << addon.get_install_filename() << " to search path: " |
451 | << PHYSFS_getLastErrorCode() << std::endl; |
452 | } |
453 | else |
454 | { |
455 | if (addon.get_type() == Addon::LANGUAGEPACK) |
456 | { |
457 | PHYSFS_enumerate(addon.get_id().c_str(), add_to_dictionary_path, nullptr); |
458 | } |
459 | addon.set_enabled(true); |
460 | } |
461 | } |
462 | } |
463 | |
464 | void |
465 | AddonManager::disable_addon(const AddonId& addon_id) |
466 | { |
467 | log_debug << "disabling addon " << addon_id << std::endl; |
468 | auto& addon = get_installed_addon(addon_id); |
469 | if (!addon.is_enabled()) |
470 | { |
471 | log_warning << "Tried disabling already disabled Add-On" << std::endl; |
472 | } |
473 | else |
474 | { |
475 | log_debug << "Removing archive \"" << addon.get_install_filename() << "\" from search path" << std::endl; |
476 | if (PHYSFS_unmount(addon.get_install_filename().c_str()) == 0) |
477 | { |
478 | log_warning << "Could not remove " << addon.get_install_filename() << " from search path: " |
479 | << PHYSFS_getLastErrorCode() << std::endl; |
480 | } |
481 | else |
482 | { |
483 | if (addon.get_type() == Addon::LANGUAGEPACK) |
484 | { |
485 | PHYSFS_enumerate(addon.get_id().c_str(), remove_from_dictionary_path, nullptr); |
486 | } |
487 | addon.set_enabled(false); |
488 | } |
489 | } |
490 | } |
491 | |
492 | bool |
493 | AddonManager::is_old_enabled_addon(const std::unique_ptr<Addon>& addon) const |
494 | { |
495 | return addon->get_format() == Addon::ORIGINAL && |
496 | addon->get_type() != Addon::LANGUAGEPACK && |
497 | addon->is_enabled(); |
498 | } |
499 | |
500 | bool |
501 | AddonManager::is_old_addon_enabled() const { |
502 | auto it = std::find_if(m_installed_addons.begin(), m_installed_addons.end(), |
503 | [this](const std::unique_ptr<Addon>& addon) |
504 | { |
505 | return is_old_enabled_addon(addon); |
506 | }); |
507 | |
508 | return it != m_installed_addons.end(); |
509 | } |
510 | |
511 | void |
512 | AddonManager::disable_old_addons() |
513 | { |
514 | for (auto& addon : m_installed_addons) { |
515 | if (is_old_enabled_addon(addon)) { |
516 | disable_addon(addon->get_id()); |
517 | } |
518 | } |
519 | } |
520 | |
521 | void |
522 | AddonManager::mount_old_addons() |
523 | { |
524 | std::string mountpoint; |
525 | for (auto& addon : m_installed_addons) { |
526 | if (is_old_enabled_addon(addon)) { |
527 | if (PHYSFS_mount(addon->get_install_filename().c_str(), mountpoint.c_str(), 0) == 0) |
528 | { |
529 | log_warning << "Could not add " << addon->get_install_filename() << " to search path: " |
530 | << PHYSFS_getLastErrorCode() << std::endl; |
531 | } |
532 | } |
533 | } |
534 | } |
535 | |
536 | void |
537 | AddonManager::unmount_old_addons() |
538 | { |
539 | for (auto& addon : m_installed_addons) { |
540 | if (is_old_enabled_addon(addon)) { |
541 | if (PHYSFS_unmount(addon->get_install_filename().c_str()) == 0) |
542 | { |
543 | log_warning << "Could not remove " << addon->get_install_filename() << " from search path: " |
544 | << PHYSFS_getLastErrorCode() << std::endl; |
545 | } |
546 | } |
547 | } |
548 | } |
549 | |
550 | bool |
551 | AddonManager::is_from_old_addon(const std::string& filename) const |
552 | { |
553 | std::string real_path = PHYSFS_getRealDir(filename.c_str()); |
554 | for (auto& addon : m_installed_addons) { |
555 | if (is_old_enabled_addon(addon) && |
556 | addon->get_install_filename() == real_path) { |
557 | return true; |
558 | } |
559 | } |
560 | return false; |
561 | } |
562 | |
563 | bool |
564 | AddonManager::is_addon_installed(const std::string& id) const |
565 | { |
566 | return std::any_of(get_installed_addons().begin(), get_installed_addons().end(), |
567 | [id] (const auto& installed_addon) { |
568 | return installed_addon == id; |
569 | }); |
570 | } |
571 | |
572 | std::vector<std::string> |
573 | AddonManager::scan_for_archives() const |
574 | { |
575 | std::vector<std::string> archives; |
576 | |
577 | // Search for archives and add them to the search path |
578 | std::unique_ptr<char*, decltype(&PHYSFS_freeList)> |
579 | rc(PHYSFS_enumerateFiles(m_addon_directory.c_str()), |
580 | PHYSFS_freeList); |
581 | for (char** i = rc.get(); *i != nullptr; ++i) |
582 | { |
583 | const std::string fullpath = FileSystem::join(m_addon_directory, *i); |
584 | if (physfsutil::is_directory(fullpath)) |
585 | { |
586 | // ignore dot files (e.g. '.git/') |
587 | if ((*i)[0] != '.') { |
588 | archives.push_back(fullpath); |
589 | } |
590 | } |
591 | else |
592 | { |
593 | if (StringUtil::has_suffix(StringUtil::tolower(*i), ".zip" )) { |
594 | if (PHYSFS_exists(fullpath.c_str())) { |
595 | archives.push_back(fullpath); |
596 | } |
597 | } |
598 | } |
599 | } |
600 | |
601 | return archives; |
602 | } |
603 | |
604 | std::string |
605 | AddonManager::scan_for_info(const std::string& archive_os_path) const |
606 | { |
607 | std::unique_ptr<char*, decltype(&PHYSFS_freeList)> |
608 | rc2(PHYSFS_enumerateFiles("/" ), |
609 | PHYSFS_freeList); |
610 | for (char** j = rc2.get(); *j != nullptr; ++j) |
611 | { |
612 | if (StringUtil::has_suffix(*j, ".nfo" )) |
613 | { |
614 | std::string nfo_filename = FileSystem::join("/" , *j); |
615 | |
616 | // make sure it's in the current archive_os_path |
617 | const char* realdir = PHYSFS_getRealDir(nfo_filename.c_str()); |
618 | if (!realdir) |
619 | { |
620 | log_warning << "PHYSFS_getRealDir() failed for " << nfo_filename << ": " << PHYSFS_getLastErrorCode() << std::endl; |
621 | } |
622 | else |
623 | { |
624 | if (realdir == archive_os_path) |
625 | { |
626 | return nfo_filename; |
627 | } |
628 | } |
629 | } |
630 | } |
631 | |
632 | return std::string(); |
633 | } |
634 | |
635 | void |
636 | AddonManager::add_installed_archive(const std::string& archive, const std::string& md5) |
637 | { |
638 | const char* realdir = PHYSFS_getRealDir(archive.c_str()); |
639 | if (!realdir) |
640 | { |
641 | log_warning << "PHYSFS_getRealDir() failed for " << archive << ": " |
642 | << PHYSFS_getLastErrorCode() << std::endl; |
643 | } |
644 | else |
645 | { |
646 | std::string os_path = FileSystem::join(realdir, archive); |
647 | |
648 | PHYSFS_mount(os_path.c_str(), nullptr, 0); |
649 | |
650 | std::string nfo_filename = scan_for_info(os_path); |
651 | |
652 | if (nfo_filename.empty()) |
653 | { |
654 | log_warning << "Couldn't find .nfo file for " << os_path << std::endl; |
655 | } |
656 | else |
657 | { |
658 | try |
659 | { |
660 | std::unique_ptr<Addon> addon = Addon::parse(nfo_filename); |
661 | addon->set_install_filename(os_path, md5); |
662 | m_installed_addons.push_back(std::move(addon)); |
663 | } |
664 | catch (const std::runtime_error& e) |
665 | { |
666 | log_warning << "Could not load add-on info for " << archive << ": " << e.what() << std::endl; |
667 | } |
668 | } |
669 | |
670 | PHYSFS_unmount(os_path.c_str()); |
671 | } |
672 | } |
673 | |
674 | void |
675 | AddonManager::add_installed_addons() |
676 | { |
677 | auto archives = scan_for_archives(); |
678 | |
679 | for (const auto& archive : archives) |
680 | { |
681 | MD5 md5 = md5_from_archive(archive); |
682 | add_installed_archive(archive, md5.hex_digest()); |
683 | } |
684 | } |
685 | |
686 | AddonManager::AddonList |
687 | AddonManager::parse_addon_infos(const std::string& filename) const |
688 | { |
689 | AddonList m_addons; |
690 | |
691 | try |
692 | { |
693 | register_translation_directory(filename); |
694 | auto doc = ReaderDocument::from_file(filename); |
695 | auto root = doc.get_root(); |
696 | if (root.get_name() != "supertux-addons" ) |
697 | { |
698 | throw std::runtime_error("Downloaded file is not an Add-on list" ); |
699 | } |
700 | else |
701 | { |
702 | auto addon_collection = root.get_collection(); |
703 | for (auto const& addon_node : addon_collection.get_objects()) |
704 | { |
705 | if (addon_node.get_name() != "supertux-addoninfo" ) |
706 | { |
707 | log_warning << "Unknown token '" << addon_node.get_name() << "' in Add-on list" << std::endl; |
708 | } |
709 | else |
710 | { |
711 | try |
712 | { |
713 | std::unique_ptr<Addon> addon = Addon::parse(addon_node.get_mapping()); |
714 | m_addons.push_back(std::move(addon)); |
715 | } |
716 | catch(const std::exception& e) |
717 | { |
718 | log_warning << "Problem when reading Add-on entry: " << e.what() << std::endl; |
719 | } |
720 | } |
721 | } |
722 | |
723 | return m_addons; |
724 | } |
725 | } |
726 | catch(const std::exception& e) |
727 | { |
728 | std::stringstream msg; |
729 | msg << "Problem when reading Add-on list: " << e.what(); |
730 | throw std::runtime_error(msg.str()); |
731 | } |
732 | } |
733 | |
734 | void |
735 | AddonManager::update() |
736 | { |
737 | m_downloader.update(); |
738 | } |
739 | |
740 | void |
741 | AddonManager::check_for_langpack_updates() |
742 | { |
743 | const std::string& language = g_dictionary_manager->get_language().get_language(); |
744 | if (language == "en" ) |
745 | return; |
746 | |
747 | try |
748 | { |
749 | check_online(); |
750 | try |
751 | { |
752 | const std::string& addon_id = "language-pack" ; |
753 | log_debug << "Looking for language addon with ID " << addon_id << "..." << std::endl; |
754 | Addon& langpack = get_repository_addon(addon_id); |
755 | |
756 | try |
757 | { |
758 | auto& installed_langpack = get_installed_addon(addon_id); |
759 | if (installed_langpack.get_md5() == langpack.get_md5() || |
760 | installed_langpack.get_version() > langpack.get_version()) |
761 | { |
762 | log_debug << "Language addon " << addon_id << " is already the latest version." << std::endl; |
763 | return; |
764 | } |
765 | |
766 | // Langpack update available. Let's install it! |
767 | install_addon(addon_id); |
768 | enable_addon(addon_id); |
769 | } |
770 | catch(const std::exception&) |
771 | { |
772 | log_debug << "Language addon " << addon_id << " is not installed. Installing..." << std::endl; |
773 | install_addon(addon_id); |
774 | enable_addon(addon_id); |
775 | } |
776 | } |
777 | catch(std::exception&) |
778 | { |
779 | log_debug << "Language addon for current locale not found." << std::endl; |
780 | } |
781 | } |
782 | catch(...) |
783 | { |
784 | // If anything fails here, just silently ignore. |
785 | } |
786 | } |
787 | |
788 | /* EOF */ |
789 | |