🎨 Better separation of threads in importer
This commit is contained in:
@@ -3,10 +3,11 @@ from pathlib import Path
|
|||||||
from threading import Lock, Thread
|
from threading import Lock, Thread
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
from requests import HTTPError
|
||||||
from gi.repository import Adw, Gio, Gtk
|
from gi.repository import Adw, Gio, Gtk
|
||||||
|
|
||||||
from .save_cover import resize_cover, save_cover
|
from .save_cover import resize_cover, save_cover
|
||||||
from .steamgriddb import SGDBHelper
|
from .steamgriddb import SGDBHelper, SGDBError
|
||||||
|
|
||||||
|
|
||||||
class Importer:
|
class Importer:
|
||||||
@@ -85,7 +86,7 @@ class Importer:
|
|||||||
print(f"{source.full_name}, installed: {source.is_installed}")
|
print(f"{source.full_name}, installed: {source.is_installed}")
|
||||||
if not source.is_installed:
|
if not source.is_installed:
|
||||||
continue
|
continue
|
||||||
thread = Thread(target=self.__import_source__, args=tuple([source])) # fmt: skip
|
thread = SourceImportThread(self.win, source, self)
|
||||||
self.source_threads.append(thread)
|
self.source_threads.append(thread)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
@@ -107,83 +108,112 @@ class Importer:
|
|||||||
|
|
||||||
self.import_dialog.close()
|
self.import_dialog.close()
|
||||||
|
|
||||||
def __import_source__(self, *args, **_kwargs):
|
|
||||||
"""Source import thread entry point"""
|
class SourceImportThread(Thread):
|
||||||
source, *_rest = args
|
"""Thread in charge of scanning a source for games"""
|
||||||
|
|
||||||
|
win = None
|
||||||
|
source = None
|
||||||
|
importer = None
|
||||||
|
|
||||||
|
def __init__(self, win, source, importer, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.win = win
|
||||||
|
self.source = source
|
||||||
|
self.importer = importer
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""Thread entry point"""
|
||||||
|
|
||||||
# Initialize source iteration
|
# Initialize source iteration
|
||||||
iterator = source.__iter__()
|
iterator = iter(self.source)
|
||||||
with self.progress_lock:
|
with self.importer.progress_lock:
|
||||||
self.counts[source.id]["total"] = len(iterator)
|
self.importer.counts[self.source.id]["total"] = len(iterator)
|
||||||
|
|
||||||
# Handle iteration exceptions
|
# Get games from source
|
||||||
def wrapper(iterator):
|
|
||||||
while True:
|
while True:
|
||||||
|
# Handle exceptions raised while iteration the source
|
||||||
try:
|
try:
|
||||||
yield next(iterator)
|
game = next(iterator)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
break
|
break
|
||||||
except Exception as exception: # pylint: disable=broad-exception-caught
|
except Exception as exception: # pylint: disable=broad-exception-caught
|
||||||
logging.exception(
|
logging.exception(
|
||||||
msg=f"Exception in source {iterator.source.id}",
|
msg=f"Exception in source {self.source.id}",
|
||||||
exc_info=exception,
|
exc_info=exception,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Get games from source
|
# Add game to importer
|
||||||
for game in wrapper(iterator):
|
with self.importer.games_lock:
|
||||||
with self.games_lock:
|
self.importer.games.add(game)
|
||||||
self.games.add(game)
|
with self.importer.progress_lock:
|
||||||
with self.progress_lock:
|
self.importer.counts[self.source.id]["games"] += 1
|
||||||
self.counts[source.id]["games"] += 1
|
self.importer.update_progressbar()
|
||||||
self.update_progressbar()
|
|
||||||
|
|
||||||
# Start sgdb lookup for game
|
# Start sgdb lookup for game in another thread
|
||||||
# HACK move to a game manager
|
# HACK move to a game manager
|
||||||
sgdb_thread = Thread(target=self.__sgdb_lookup__, args=tuple([game]))
|
# Skip obvious cases
|
||||||
with self.sgdb_threads_lock:
|
use_sgdb = self.win.schema.get_boolean("sgdb")
|
||||||
self.sgdb_threads.append(sgdb_thread)
|
if not use_sgdb or game.blacklisted:
|
||||||
|
return
|
||||||
|
sgdb_thread = SGDBLookupThread(self.win, game, self.importer)
|
||||||
|
with self.importer.sgdb_threads_lock:
|
||||||
|
self.importer.sgdb_threads.append(sgdb_thread)
|
||||||
sgdb_thread.start()
|
sgdb_thread.start()
|
||||||
|
|
||||||
def __sgdb_lookup__(self, *args, **_kwargs):
|
|
||||||
"""SGDB lookup thread entry point"""
|
|
||||||
game, *_rest = args
|
|
||||||
|
|
||||||
def inner():
|
class SGDBLookupThread(Thread):
|
||||||
# Skip obvious ones
|
"""Thread in charge of querying SGDB for a game image"""
|
||||||
if game.blacklisted:
|
|
||||||
return
|
win = None
|
||||||
use_sgdb = self.win.schema.get_boolean("sgdb")
|
game = None
|
||||||
if not use_sgdb:
|
importer = None
|
||||||
return
|
|
||||||
|
def __init__(self, win, game, importer, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.win = win
|
||||||
|
self.game = game
|
||||||
|
self.importer = importer
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""Thread entry point"""
|
||||||
|
|
||||||
# Check if we should query SGDB
|
# Check if we should query SGDB
|
||||||
prefer_sgdb = self.win.schema.get_boolean("sgdb-prefer")
|
prefer_sgdb = self.win.schema.get_boolean("sgdb-prefer")
|
||||||
prefer_animated = self.win.schema.get_boolean("sgdb-animated")
|
prefer_animated = self.win.schema.get_boolean("sgdb-animated")
|
||||||
image_trunk = self.win.covers_dir / game.game_id
|
image_trunk = self.win.covers_dir / self.game.game_id
|
||||||
still = image_trunk.with_suffix(".tiff")
|
still = image_trunk.with_suffix(".tiff")
|
||||||
animated = image_trunk.with_suffix(".gif")
|
animated = image_trunk.with_suffix(".gif")
|
||||||
# breaking down the condition
|
|
||||||
|
# Breaking down the condition
|
||||||
is_missing = not still.is_file() and not animated.is_file()
|
is_missing = not still.is_file() and not animated.is_file()
|
||||||
is_not_best = not animated.is_file() and prefer_animated
|
is_not_best = not animated.is_file() and prefer_animated
|
||||||
should_query = is_missing or is_not_best or prefer_sgdb
|
if not (is_missing or is_not_best or prefer_sgdb):
|
||||||
if not should_query:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.game.set_loading(1)
|
||||||
|
|
||||||
# Add image from sgdb
|
# Add image from sgdb
|
||||||
game.set_loading(1)
|
|
||||||
sgdb = SGDBHelper(self.win)
|
sgdb = SGDBHelper(self.win)
|
||||||
uri = sgdb.get_game_image_uri(game, animated=prefer_animated)
|
try:
|
||||||
|
sgdb_id = sgdb.get_game_id(self.game)
|
||||||
|
uri = sgdb.get_game_image_uri(sgdb_id, animated=prefer_animated)
|
||||||
response = requests.get(uri, timeout=5)
|
response = requests.get(uri, timeout=5)
|
||||||
|
except HTTPError as _error:
|
||||||
|
# TODO handle http errors
|
||||||
|
pass
|
||||||
|
except SGDBError as _error:
|
||||||
|
# TODO handle SGDB API errors
|
||||||
|
pass
|
||||||
|
else:
|
||||||
tmp_file = Gio.File.new_tmp()[0]
|
tmp_file = Gio.File.new_tmp()[0]
|
||||||
tmp_file_path = tmp_file.get_path()
|
tmp_file_path = tmp_file.get_path()
|
||||||
Path(tmp_file_path).write_bytes(response.content)
|
Path(tmp_file_path).write_bytes(response.content)
|
||||||
save_cover(self.win, game.game_id, resize_cover(self.win, tmp_file_path))
|
save_cover(
|
||||||
game.set_loading(0)
|
self.win, self.game.game_id, resize_cover(self.win, tmp_file_path)
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
self.game.set_loading(0)
|
||||||
inner()
|
with self.importer.progress_lock:
|
||||||
except Exception: # pylint: disable=broad-exception-caught
|
self.importer.counts[self.game.source.id]["covers"] += 1
|
||||||
# TODO for god's sake handle exceptions correctly
|
|
||||||
# TODO (talk about that with Kramo)
|
|
||||||
pass
|
|
||||||
with self.progress_lock:
|
|
||||||
self.counts[game.source]["covers"] += 1
|
|
||||||
|
|||||||
@@ -64,9 +64,8 @@ class SGDBHelper:
|
|||||||
raise SGDBError(res_json["errors"])
|
raise SGDBError(res_json["errors"])
|
||||||
raise SGDBError(res.status_code)
|
raise SGDBError(res.status_code)
|
||||||
|
|
||||||
def get_game_image_uri(self, game, animated=False):
|
def get_image_uri(self, game_id, animated=False):
|
||||||
"""Get the image for a game"""
|
"""Get the image for a SGDB game id"""
|
||||||
game_id = self.get_game_id(game)
|
|
||||||
uri = f"{self.base_url}grids/game/{game_id}?dimensions=600x900"
|
uri = f"{self.base_url}grids/game/{game_id}?dimensions=600x900"
|
||||||
if animated:
|
if animated:
|
||||||
uri += "&types=animated"
|
uri += "&types=animated"
|
||||||
|
|||||||
Reference in New Issue
Block a user