✨ Added Itch source
- Added call stack to unretryable errors in managers - Added existing itch cover downloading code - Fixed importer not closing if no source enabled TODO - Tidying the itch cover downloading code - If possible, make save_cover and resize_cover work in AsyncManager-s
This commit is contained in:
@@ -61,10 +61,6 @@ class Importer:
|
||||
|
||||
self.create_dialog()
|
||||
|
||||
# Single SGDB cancellable shared by all its tasks
|
||||
# (If SGDB auth is bad, cancel all SGDB tasks)
|
||||
self.sgdb_cancellable = Gio.Cancellable()
|
||||
|
||||
for source in self.sources:
|
||||
logging.debug("Importing games from source %s", source.id)
|
||||
task = Task.new(None, None, self.source_callback, (source,))
|
||||
@@ -72,6 +68,8 @@ class Importer:
|
||||
task.set_task_data((source,))
|
||||
task.run_in_thread(self.source_task_thread_func)
|
||||
|
||||
self.progress_changed_callback()
|
||||
|
||||
def create_dialog(self):
|
||||
"""Create the import dialog"""
|
||||
self.progressbar = Gtk.ProgressBar(margin_start=12, margin_end=12)
|
||||
@@ -164,6 +162,7 @@ class Importer:
|
||||
Callback called when the import process has progressed
|
||||
|
||||
Triggered when:
|
||||
* All sources have been started
|
||||
* A source finishes
|
||||
* A pipeline finishes
|
||||
"""
|
||||
|
||||
@@ -45,6 +45,7 @@ class ItchSourceIterator(SourceIterator):
|
||||
"version": shared.SPEC_VERSION,
|
||||
"added": int(time()),
|
||||
"source": self.source.id,
|
||||
"name": row[1],
|
||||
"game_id": self.source.game_id_format.format(game_id=row[0]),
|
||||
"executable": self.source.executable_format.format(cave_id=row[4]),
|
||||
}
|
||||
@@ -65,6 +66,7 @@ class ItchLinuxSource(ItchSource, LinuxSource):
|
||||
variant = "linux"
|
||||
executable_format = "xdg-open itch://caves/{cave_id}/launch"
|
||||
|
||||
@property
|
||||
@ItchSource.replaced_by_schema_key()
|
||||
@replaced_by_path("~/.var/app/io.itch.itch/config/itch/")
|
||||
@replaced_by_env_path("XDG_DATA_HOME", "itch/")
|
||||
@@ -77,6 +79,7 @@ class ItchWindowsSource(ItchSource, WindowsSource):
|
||||
variant = "windows"
|
||||
executable_format = "start itch://caves/{cave_id}/launch"
|
||||
|
||||
@property
|
||||
@ItchSource.replaced_by_schema_key()
|
||||
@replaced_by_env_path("appdata", "itch/")
|
||||
def location(self) -> Path:
|
||||
|
||||
@@ -36,15 +36,16 @@ from src.game import Game
|
||||
from src.importer.importer import Importer
|
||||
from src.importer.sources.bottles_source import BottlesLinuxSource
|
||||
from src.importer.sources.heroic_source import HeroicLinuxSource, HeroicWindowsSource
|
||||
from src.importer.sources.itch_source import ItchLinuxSource, ItchWindowsSource
|
||||
from src.importer.sources.lutris_source import LutrisLinuxSource
|
||||
from src.importer.sources.steam_source import SteamLinuxSource, SteamWindowsSource
|
||||
from src.preferences import PreferencesWindow
|
||||
from src.store.managers.display_manager import DisplayManager
|
||||
from src.store.managers.file_manager import FileManager
|
||||
from src.store.managers.itch_cover_manager import ItchCoverManager
|
||||
from src.store.managers.local_cover_manager import LocalCoverManager
|
||||
from src.store.managers.sgdb_manager import SGDBManager
|
||||
from src.store.managers.steam_api_manager import SteamAPIManager
|
||||
from src.store.managers.local_cover_manager import LocalCoverManager
|
||||
from src.store.managers.itch_cover_manager import ItchCoverManager
|
||||
from src.store.store import Store
|
||||
from src.window import CartridgesWindow
|
||||
|
||||
@@ -198,6 +199,9 @@ class CartridgesApplication(Adw.Application):
|
||||
importer.add_source(HeroicWindowsSource())
|
||||
if shared.schema.get_boolean("bottles"):
|
||||
importer.add_source(BottlesLinuxSource())
|
||||
if shared.schema.get_boolean("itch"):
|
||||
importer.add_source(ItchLinuxSource())
|
||||
importer.add_source(ItchWindowsSource())
|
||||
importer.run()
|
||||
|
||||
def on_remove_game_action(self, *_args):
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
from urllib3.exceptions import SSLError
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from gi.repository import GdkPixbuf, Gio
|
||||
from requests import HTTPError
|
||||
from urllib3.exceptions import SSLError
|
||||
|
||||
from src import shared
|
||||
from src.game import Game
|
||||
from src.store.managers.async_manager import AsyncManager
|
||||
from src.store.managers.local_cover_manager import LocalCoverManager
|
||||
from src.utils.save_cover import resize_cover, save_cover
|
||||
|
||||
|
||||
class ItchCoverManager(AsyncManager):
|
||||
@@ -15,5 +19,43 @@ class ItchCoverManager(AsyncManager):
|
||||
retryable_on = set((HTTPError, SSLError))
|
||||
|
||||
def manager_logic(self, game: Game, additional_data: dict) -> None:
|
||||
# TODO move itch cover logic here
|
||||
pass
|
||||
# Get the first matching cover url
|
||||
base_cover_url: str = additional_data.get("itch_cover_url", None)
|
||||
still_cover_url: str = additional_data.get("itch_still_cover_url", None)
|
||||
cover_url = still_cover_url or base_cover_url
|
||||
if not cover_url:
|
||||
return
|
||||
|
||||
# Download cover
|
||||
tmp_file = Gio.File.new_tmp()[0]
|
||||
with requests.get(cover_url, timeout=5) as cover:
|
||||
cover.raise_for_status()
|
||||
Path(tmp_file.get_path()).write_bytes(cover.content)
|
||||
|
||||
# TODO comment the following blocks of code
|
||||
game_cover = GdkPixbuf.Pixbuf.new_from_stream_at_scale(
|
||||
tmp_file.read(), 2, 2, False
|
||||
).scale_simple(*shared.image_size, GdkPixbuf.InterpType.BILINEAR)
|
||||
|
||||
itch_pixbuf = GdkPixbuf.Pixbuf.new_from_stream(tmp_file.read())
|
||||
itch_pixbuf = itch_pixbuf.scale_simple(
|
||||
shared.image_size[0],
|
||||
itch_pixbuf.get_height() * (shared.image_size[0] / itch_pixbuf.get_width()),
|
||||
GdkPixbuf.InterpType.BILINEAR,
|
||||
)
|
||||
itch_pixbuf.composite(
|
||||
game_cover,
|
||||
0,
|
||||
(shared.image_size[1] - itch_pixbuf.get_height()) / 2,
|
||||
itch_pixbuf.get_width(),
|
||||
itch_pixbuf.get_height(),
|
||||
0,
|
||||
(shared.image_size[1] - itch_pixbuf.get_height()) / 2,
|
||||
1.0,
|
||||
1.0,
|
||||
GdkPixbuf.InterpType.BILINEAR,
|
||||
255,
|
||||
)
|
||||
|
||||
# Resize and save the cover
|
||||
save_cover(game.game_id, resize_cover(pixbuf=game_cover))
|
||||
|
||||
@@ -65,32 +65,34 @@ class Manager:
|
||||
try:
|
||||
self.manager_logic(game, additional_data)
|
||||
except Exception as error:
|
||||
logging_args = (
|
||||
type(error).__name__,
|
||||
self.name,
|
||||
f"{game.name} ({game.game_id})",
|
||||
)
|
||||
if error in self.continue_on:
|
||||
# Handle skippable errors (skip silently)
|
||||
return
|
||||
elif error in self.retryable_on:
|
||||
if try_index < self.max_tries:
|
||||
# Handle retryable errors
|
||||
logging_format = "Retrying %s in %s for %s"
|
||||
logging.error("Retrying %s in %s for %s", *logging_args)
|
||||
sleep(self.retry_delay)
|
||||
self.execute_resilient_manager_logic(
|
||||
game, additional_data, try_index + 1
|
||||
)
|
||||
else:
|
||||
# Handle being out of retries
|
||||
logging_format = "Out of retries dues to %s in %s for %s"
|
||||
logging.error(
|
||||
"Out of retries dues to %s in %s for %s", *logging_args
|
||||
)
|
||||
self.report_error(error)
|
||||
else:
|
||||
# Handle unretryable errors
|
||||
logging_format = "Unretryable %s in %s for %s"
|
||||
logging.error(
|
||||
"Unretryable %s in %s for %s", *logging_args, exc_info=error
|
||||
)
|
||||
self.report_error(error)
|
||||
# Finally log errors
|
||||
logging.error(
|
||||
logging_format,
|
||||
type(error).__name__,
|
||||
self.name,
|
||||
f"{game.name} ({game.game_id})",
|
||||
)
|
||||
|
||||
def process_game(
|
||||
self, game: Game, additional_data: dict, callback: Callable[["Manager"], Any]
|
||||
|
||||
Reference in New Issue
Block a user