Enable download of DLC extras from GOG.

This is a bit painful; to do this we extract the downlink information when we pull the extras, and put that in the extras dict.

Then, InstallerWindow keeps the dict rather than just the ID from it; it passes the entire thing back into the service so that the GOG service can obtain the download subdict and it goes from there.

There are minimal changes for itch.io, the only other service with extras. The new code here just strips the IDs out and proceeds as before.
This commit is contained in:
Daniel Johnson 2024-01-11 17:12:10 -05:00 committed by Mathieu Comandon
parent 37d81a1cd6
commit a3e36e1650
4 changed files with 41 additions and 26 deletions

View file

@ -70,7 +70,7 @@ class InstallerWindow(ModelessDialog,
self.set_default_size(740, 460)
self.installers = installers
self.config = {}
self.selected_extra_ids = []
self.selected_extras = []
self.install_in_progress = False
self.install_complete = False
self.interpreter = None
@ -137,7 +137,7 @@ class InstallerWindow(ModelessDialog,
self.extras_tree_store = Gtk.TreeStore(
bool, # is selected?
bool, # is inconsistent?
str, # id
object, # extras dict
str, # label
)
@ -499,7 +499,7 @@ class InstallerWindow(ModelessDialog,
for extra_source, extras in all_extras.items():
parent = self.extras_tree_store.append(None, (None, None, None, extra_source))
for extra in extras:
self.extras_tree_store.append(parent, (False, False, extra["id"], self.get_extra_label(extra)))
self.extras_tree_store.append(parent, (False, False, extra, self.get_extra_label(extra)))
self.stack.navigate_to_page(self.present_extras_page)
else:
@ -577,13 +577,13 @@ class InstallerWindow(ModelessDialog,
selected_extras = []
def save_extra(store, path, iter_):
selected, _inconsistent, id_, _label = store[iter_]
if selected and id_:
selected_extras.append(id_)
selected, _inconsistent, extra, _label = store[iter_]
if selected and extra:
selected_extras.append(extra)
extra_store.foreach(save_extra)
self.selected_extra_ids = selected_extras
self.selected_extras = selected_extras
GLib.idle_add(self.on_extras_ready)
def on_extras_ready(self, *args):
@ -602,7 +602,7 @@ class InstallerWindow(ModelessDialog,
patch_version = None
AsyncCall(self.interpreter.installer.prepare_game_files,
self.on_files_prepared, self.selected_extra_ids, patch_version)
self.on_files_prepared, self.selected_extras, patch_version)
def on_files_prepared(self, _result, error):
if error:

View file

@ -148,7 +148,7 @@ class LutrisInstaller: # pylint: disable=too-many-instance-attributes
errors.append("Scripts can't have both extends and requires")
return errors
def prepare_game_files(self, extra_ids, patch_version=None):
def prepare_game_files(self, extras, patch_version=None):
"""Gathers necessary files before iterating through them."""
if not self.script_files:
return
@ -177,7 +177,7 @@ class LutrisInstaller: # pylint: disable=too-many-instance-attributes
# If a patch version is given download the patch files instead of the installer
installer_files = self.service.get_patch_files(self, installer_file_id)
else:
content_files, extra_files = self.service.get_installer_files(self, installer_file_id, extra_ids)
content_files, extra_files = self.service.get_installer_files(self, installer_file_id, extras)
self.extra_file_paths = [path for f in extra_files for path in f.get_dest_files_by_id().values()]
installer_files = content_files + extra_files
except UnavailableGameError as ex:

View file

@ -289,7 +289,8 @@ class GOGService(OnlineService):
return self.make_api_request(url)
def get_download_info(self, downlink):
"""Return file download information"""
"""Return file download information, a list of dict containing the 'url' and
'filename' for each file."""
logger.info("Getting download info for %s", downlink)
try:
response = self.make_api_request(downlink)
@ -298,12 +299,18 @@ class GOGService(OnlineService):
raise UnavailableGameError(_("The download of '%s' failed.") % downlink) from ex
if not response:
raise UnavailableGameError(_("The download of '%s' failed.") % downlink)
expanded = []
for field in ("checksum", "downlink"):
field_url = response[field]
parsed = urlparse(field_url)
query = dict(parse_qsl(parsed.query))
response[field + "_filename"] = os.path.basename(query.get("path") or parsed.path)
return response
filename = os.path.basename(query.get("path") or parsed.path)
expanded.append({
"url": response[field],
"filename": filename
})
return expanded
def get_downloads(self, gogid):
"""Return all available downloads for a GOG ID"""
@ -333,6 +340,7 @@ class GOGService(OnlineService):
"type": download.get("type", "").strip(),
"total_size": download.get("total_size", 0),
"id": str(download["id"]),
"downlinks": [f.get("downlink") for f in download.get("files") or []]
} for download in product["downloads"].get("bonus_content") or []
]
if extras:
@ -382,25 +390,31 @@ class GOGService(OnlineService):
if not downlink:
logger.error("No download information for %s", game_file)
continue
download_info = self.get_download_info(downlink)
for field in ('checksum', 'downlink'):
for info in self.get_download_info(downlink):
download_links.append({
"name": download.get("name", ""),
"os": download.get("os", ""),
"type": download.get("type", ""),
"total_size": download.get("total_size", 0),
"id": str(game_file["id"]),
"url": download_info[field],
"filename": download_info[field + "_filename"]
"url": info["url"],
"filename": info["filename"]
})
return download_links
def get_extra_files(self, downloads, installer, selected_extras):
def get_extra_files(self, installer, selected_extras):
extra_files = []
for extra in downloads["bonus_content"]:
if str(extra["id"]) not in selected_extras:
continue
links = self.query_download_links(extra)
for extra in selected_extras:
if extra.get("downlinks"):
links = [info for link in extra.get("downlinks") for info in self.get_download_info(link)]
elif str(extra["id"]) in selected_extras:
links = self.query_download_links(extra)
else:
links = []
if not links:
logger.error("No download link for bonus content '%s' could be obtained.", extra.get("name"))
for link in links:
if link["filename"].endswith(".xml"):
# GOG gives a link for checksum XML files for bonus content
@ -490,7 +504,7 @@ class GOGService(OnlineService):
extra_files = []
if selected_extras:
for extra_file in self.get_extra_files(downloads, installer, selected_extras):
for extra_file in self.get_extra_files(installer, selected_extras):
extra_files.append(extra_file)
return files, extra_files

View file

@ -529,6 +529,7 @@ class ItchIoService(OnlineService):
extra_files = []
link = None
filename = "setup.zip"
selected_extras_ids = set(x["id"] for x in selected_extras or [])
file = next(_file.copy() for _file in installer.script_files if _file.id == installer_file_id)
if not file.url.startswith("N/A"):
@ -542,9 +543,9 @@ class ItchIoService(OnlineService):
"date": int(datetime.datetime.now().timestamp())
}
if not link or len(selected_extras) > 0:
if not link or len(selected_extras_ids) > 0:
for upload in uploads["uploads"]:
if selected_extras and (upload["type"] in self.extra_types):
if selected_extras_ids and (upload["type"] in self.extra_types):
extras.append(upload)
continue
# default = games/tools ("executables")
@ -594,7 +595,7 @@ class ItchIoService(OnlineService):
)
for extra in extras:
if str(extra["id"]) not in selected_extras:
if str(extra["id"]) not in selected_extras_ids:
continue
link = self.get_download_link(extra["id"], key)
extra_files.append(