From 18c2f97002fc1a2b3f97c0da7a4138eca562a6ed Mon Sep 17 00:00:00 2001 From: Antoine Mazeas Date: Sat, 14 Jan 2023 01:58:26 +0100 Subject: [PATCH] Add ModDB download helper This helper facility will seamlessly swap a "File" url (e.g. "https://www.moddb.com/games/tribes/downloads/starsiege-tribes-retro-version") into a mirror download link. This enables installer authors to specify files to be downloaded directly from ModDB, e.g. ``` files: - t1_setup: url: https://www.moddb.com/games/tribes/downloads/starsiege-tribes-retro-version filename: t1retro.exe referer: https://www.moddb.com/games/tribes/downloads ``` Signed-off-by: Antoine Mazeas --- lutris/gui/dialogs/__init__.py | 1 + lutris/installer/installer.py | 3 +++ lutris/util/moddb/__init__.py | 0 lutris/util/moddb/downloadhelper.py | 27 +++++++++++++++++++++ lutris/util/test_config.py | 1 + setup.py | 4 +++- tests/test_moddb_helper.py | 37 +++++++++++++++++++++++++++++ 7 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 lutris/util/moddb/__init__.py create mode 100644 lutris/util/moddb/downloadhelper.py create mode 100644 tests/test_moddb_helper.py diff --git a/lutris/gui/dialogs/__init__.py b/lutris/gui/dialogs/__init__.py index 5d3aee416..e348b43b6 100644 --- a/lutris/gui/dialogs/__init__.py +++ b/lutris/gui/dialogs/__init__.py @@ -4,6 +4,7 @@ from gettext import gettext as _ import gi +gi.require_version('Gdk', '3.0') gi.require_version('Gtk', '3.0') from gi.repository import Gdk, GLib, GObject, Gtk diff --git a/lutris/installer/installer.py b/lutris/installer/installer.py index 5df065698..6168460fc 100644 --- a/lutris/installer/installer.py +++ b/lutris/installer/installer.py @@ -15,6 +15,7 @@ from lutris.services import SERVICES from lutris.util.game_finder import find_linux_game_executable, find_windows_game_executable from lutris.util.gog import convert_gog_config_to_lutris, get_gog_config_from_path, get_gog_game_path from lutris.util.log import logger +from lutris.util.moddb import downloadhelper as moddbhelper class LutrisInstaller: # pylint: disable=too-many-instance-attributes @@ -158,6 +159,8 @@ class LutrisInstaller: # pylint: disable=too-many-instance-attributes # Run variable substitution on the URLs from the script for file in self.files: file.set_url(self.interpreter._substitute(file.url)) + if moddbhelper.is_moddb_url(file.url): + file.set_url(moddbhelper.get_moddb_download_url(file.url)) if installer_file_id and self.service: logger.info("Getting files for %s", installer_file_id) diff --git a/lutris/util/moddb/__init__.py b/lutris/util/moddb/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lutris/util/moddb/downloadhelper.py b/lutris/util/moddb/downloadhelper.py new file mode 100644 index 000000000..d500d6fb8 --- /dev/null +++ b/lutris/util/moddb/downloadhelper.py @@ -0,0 +1,27 @@ +"""Helper functions to assist downloading files from ModDB""" +import moddb +import re + +MODDB_FQDN = 'https://www.moddb.com' +MODDB_URL_MATCHER = '^https://(www\.)?moddb\.com' + +def is_moddb_url(url): + return re.match(MODDB_URL_MATCHER, url.lower()) is not None + +def get_moddb_download_url(moddb_permalink_url): + return MODDB_FQDN + __autoselect_moddb_mirror(__get_html_and_resolve_mirrors_list(moddb_permalink_url))._url + +def __autoselect_moddb_mirror(mirrors_list): + # dumb autoselect for now: rank mirrors by capacity (lower is better), pick first (lowest load) + return sorted(mirrors_list, key=lambda m: m.capacity)[0] + +def __get_html_and_resolve_mirrors_list(moddb_permalink_url): + moddb_obj = moddb.parse_page(moddb_permalink_url) + if not isinstance(moddb_obj, moddb.File): + raise RuntimeError("supplied url does not point to the page of a file hosted on moddb.com") + + mirrors_list = moddb_obj.get_mirrors() + if not any(mirrors_list): + raise RuntimeError("no available mirror for the file hosted on moddb.com") + + return mirrors_list diff --git a/lutris/util/test_config.py b/lutris/util/test_config.py index 44eeff4da..aa83b5ed0 100644 --- a/lutris/util/test_config.py +++ b/lutris/util/test_config.py @@ -2,6 +2,7 @@ import os import gi +gi.require_version('Gdk', '3.0') gi.require_version('Gtk', '3.0') from lutris import startup diff --git a/setup.py b/setup.py index 19a2d93a9..94f70d608 100644 --- a/setup.py +++ b/setup.py @@ -45,6 +45,7 @@ setup( 'lutris.util.egs', 'lutris.util.graphics', 'lutris.util.mame', + 'lutris.util.moddb', 'lutris.util.steam', 'lutris.util.steam.vdf', 'lutris.util.retroarch', @@ -65,7 +66,8 @@ setup( 'pypresence', 'PyYAML', 'requests', - 'pypresence' + 'pypresence', + 'moddb' ], url='https://lutris.net', description='Video game preservation platform', diff --git a/tests/test_moddb_helper.py b/tests/test_moddb_helper.py new file mode 100644 index 000000000..3ec7778e5 --- /dev/null +++ b/tests/test_moddb_helper.py @@ -0,0 +1,37 @@ +import unittest +from lutris.util.moddb import downloadhelper as moddb + + +class ModDBHelperTests(unittest.TestCase): + def test_is_moddb_url_has_www_success(self): + url = 'https://www.moddb.com/something' + self.assertTrue(moddb.is_moddb_url(url)) + + def test_is_moddb_url_no_slug_has_www_success(self): + url = 'https://www.moddb.com' + self.assertTrue(moddb.is_moddb_url(url)) + + def test_is_moddb_url_no_www_success(self): + url = 'https://moddb.com/something' + self.assertTrue(moddb.is_moddb_url(url)) + + def test_is_moddb_url_no_slug_no_www_success(self): + url = 'https://moddb.com' + self.assertTrue(moddb.is_moddb_url(url)) + + def test_is_moddb_url_other_subdomain_failure(self): + url = 'https://subdomain.moddb.com/something' + self.assertFalse(moddb.is_moddb_url(url)) + + def test_is_moddb_url_no_slug_other_subdomain_failure(self): + url = 'https://subdomain.moddb.com' + self.assertFalse(moddb.is_moddb_url(url)) + + def test_is_moddb_url_random_domain_failure(self): + url = 'https://somedomain.com/something' + self.assertFalse(moddb.is_moddb_url(url)) + + def test_is_moddb_url_no_slug_random_domain_failure(self): + url = 'https://somedomain.com' + self.assertFalse(moddb.is_moddb_url(url)) +