🐛 Task fix + progress bar
This commit is contained in:
@@ -60,7 +60,7 @@ class Game(Gtk.Box):
|
|||||||
blacklisted = None
|
blacklisted = None
|
||||||
game_cover = None
|
game_cover = None
|
||||||
|
|
||||||
def __init__(self, win, data, allow_side_effects=False, **kwargs):
|
def __init__(self, win, data, allow_side_effects=True, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
self.win = shared.win
|
self.win = shared.win
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import logging
|
|||||||
from requests import HTTPError
|
from requests import HTTPError
|
||||||
from gi.repository import Adw, Gio, Gtk
|
from gi.repository import Adw, Gio, Gtk
|
||||||
|
|
||||||
from .task import make_task_thread_func_closure
|
from .task import Task
|
||||||
from .create_dialog import create_dialog
|
from .create_dialog import create_dialog
|
||||||
from .steamgriddb import SGDBAuthError, SGDBError, SGDBHelper
|
from .steamgriddb import SGDBAuthError, SGDBError, SGDBHelper
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ class Importer:
|
|||||||
@property
|
@property
|
||||||
def progress(self):
|
def progress(self):
|
||||||
try:
|
try:
|
||||||
progress = 1 - self.n_tasks_created / self.n_tasks_done
|
progress = self.n_tasks_done / self.n_tasks_created
|
||||||
except ZeroDivisionError:
|
except ZeroDivisionError:
|
||||||
progress = 1
|
progress = 1
|
||||||
return progress
|
return progress
|
||||||
@@ -62,30 +62,12 @@ class Importer:
|
|||||||
# (If SGDB auth is bad, cancel all SGDB tasks)
|
# (If SGDB auth is bad, cancel all SGDB tasks)
|
||||||
self.sgdb_cancellable = Gio.Cancellable()
|
self.sgdb_cancellable = Gio.Cancellable()
|
||||||
|
|
||||||
"""
|
|
||||||
# Create a task for each source
|
|
||||||
def make_closure(source):
|
|
||||||
def closure(task, obj, _data, cancellable):
|
|
||||||
self.source_task_thread_func(task, obj, (source,), cancellable)
|
|
||||||
|
|
||||||
return closure
|
|
||||||
|
|
||||||
for source in self.sources:
|
for source in self.sources:
|
||||||
self.n_source_tasks_created += 1
|
|
||||||
logging.debug("Importing games from source %s", source.id)
|
logging.debug("Importing games from source %s", source.id)
|
||||||
task = Task.new(None, None, self.source_task_callback, (source,))
|
task = Task.new(None, None, self.source_task_callback, (source,))
|
||||||
closure = make_closure(source)
|
|
||||||
task.run_in_thread(closure)
|
|
||||||
"""
|
|
||||||
|
|
||||||
for source in self.sources:
|
|
||||||
self.n_source_tasks_created += 1
|
self.n_source_tasks_created += 1
|
||||||
logging.debug("Importing games from source %s", source.id)
|
task.set_task_data((source,))
|
||||||
task = Gio.Task.new(None, None, self.source_task_callback, (source,))
|
task.run_in_thread(self.source_task_thread_func)
|
||||||
closure = make_task_thread_func_closure(
|
|
||||||
self.source_task_thread_func, (source,)
|
|
||||||
)
|
|
||||||
task.run_in_thread(closure)
|
|
||||||
|
|
||||||
def create_dialog(self):
|
def create_dialog(self):
|
||||||
"""Create the import dialog"""
|
"""Create the import dialog"""
|
||||||
@@ -105,6 +87,9 @@ class Importer:
|
|||||||
self.import_dialog.present()
|
self.import_dialog.present()
|
||||||
|
|
||||||
def update_progressbar(self):
|
def update_progressbar(self):
|
||||||
|
logging.debug(
|
||||||
|
"Progressbar updated (%f)", self.progress
|
||||||
|
) # TODO why progress not workie?
|
||||||
self.progressbar.set_fraction(self.progress)
|
self.progressbar.set_fraction(self.progress)
|
||||||
|
|
||||||
def source_task_thread_func(self, _task, _obj, data, _cancellable):
|
def source_task_thread_func(self, _task, _obj, data, _cancellable):
|
||||||
@@ -151,12 +136,12 @@ class Importer:
|
|||||||
|
|
||||||
# Start sgdb lookup for game
|
# Start sgdb lookup for game
|
||||||
# HACK move to its own manager
|
# HACK move to its own manager
|
||||||
task = Gio.Task.new(
|
task = Task.new(
|
||||||
None, self.sgdb_cancellable, self.sgdb_task_callback, (game,)
|
None, self.sgdb_cancellable, self.sgdb_task_callback, (game,)
|
||||||
)
|
)
|
||||||
closure = make_task_thread_func_closure(self.sgdb_task_thread_func, (game,))
|
|
||||||
self.n_sgdb_tasks_created += 1
|
self.n_sgdb_tasks_created += 1
|
||||||
task.run_in_thread(closure)
|
task.set_task_data((game,))
|
||||||
|
task.run_in_thread(self.sgdb_task_thread_func)
|
||||||
|
|
||||||
def source_task_callback(self, _obj, _result, data):
|
def source_task_callback(self, _obj, _result, data):
|
||||||
"""Source import callback"""
|
"""Source import callback"""
|
||||||
@@ -213,6 +198,7 @@ class Importer:
|
|||||||
self.dialog_response_callback,
|
self.dialog_response_callback,
|
||||||
"open_preferences",
|
"open_preferences",
|
||||||
"import",
|
"import",
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
|
|
||||||
elif self.n_games_added == 1:
|
elif self.n_games_added == 1:
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ class LutrisSourceIterator(SourceIterator):
|
|||||||
"executable": self.source.executable_format.format(game_id=row[2]),
|
"executable": self.source.executable_format.format(game_id=row[2]),
|
||||||
"developer": None, # TODO get developer metadata on Lutris
|
"developer": None, # TODO get developer metadata on Lutris
|
||||||
}
|
}
|
||||||
game = Game(self.source.win, values)
|
game = Game(self.source.win, values, allow_side_effects=False)
|
||||||
|
|
||||||
# Save official image
|
# Save official image
|
||||||
image_path = self.source.cache_location / "coverart" / f"{row[2]}.jpg"
|
image_path = self.source.cache_location / "coverart" / f"{row[2]}.jpg"
|
||||||
|
|||||||
@@ -1,7 +1,68 @@
|
|||||||
def make_task_thread_func_closure(func, data):
|
from gi.repository import Gio
|
||||||
"""Prepare a Gio.TaskThreadFunc with its data bound in a closure"""
|
from functools import wraps
|
||||||
|
|
||||||
def closure(task, obj, _data, cancellable):
|
|
||||||
func(task, obj, data, cancellable)
|
def create_task_thread_func_closure(func, data):
|
||||||
|
"""Wrap a Gio.TaskThreadFunc with the given data in a closure"""
|
||||||
|
|
||||||
|
def closure(task, source_object, _data, cancellable):
|
||||||
|
func(task, source_object, data, cancellable)
|
||||||
|
|
||||||
return closure
|
return closure
|
||||||
|
|
||||||
|
|
||||||
|
def decorate_set_task_data(task):
|
||||||
|
"""Decorate Gio.Task.set_task_data to replace it"""
|
||||||
|
|
||||||
|
def decorator(original_method):
|
||||||
|
@wraps(original_method)
|
||||||
|
def new_method(task_data):
|
||||||
|
task.__task_data = task_data
|
||||||
|
pass
|
||||||
|
|
||||||
|
return new_method
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def decorate_run_in_thread(task):
|
||||||
|
"""Decorate Gio.Task.run_in_thread to pass the task data correctly
|
||||||
|
Creates a closure around task_thread_func with the task data available."""
|
||||||
|
|
||||||
|
def decorator(original_method):
|
||||||
|
@wraps(original_method)
|
||||||
|
def new_method(task_thread_func):
|
||||||
|
closure = create_task_thread_func_closure(
|
||||||
|
task_thread_func, task.__task_data
|
||||||
|
)
|
||||||
|
original_method(closure)
|
||||||
|
|
||||||
|
return new_method
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
class Task:
|
||||||
|
"""Wrapper around Gio.Task to patch task data not being passed"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def new(cls, source_object, cancellable, callback, callback_data):
|
||||||
|
"""Create a new, monkey-patched Gio.Task.
|
||||||
|
The `set_task_data` and `run_in_thread` methods are decorated.
|
||||||
|
|
||||||
|
As of 2023-05-19, pygobject does not work well with Gio.Task, so to pass data
|
||||||
|
the only viable way it to create a closure with the thread function and its data.
|
||||||
|
This class is supposed to make Gio.Task comply with its expected behaviour
|
||||||
|
per the docs:
|
||||||
|
|
||||||
|
http://lazka.github.io/pgi-docs/#Gio-2.0/classes/Task.html#Gio.Task.set_task_data
|
||||||
|
|
||||||
|
This code may break if pygobject overrides change in the future.
|
||||||
|
We need to manually pass `self` to the decorators since it's otherwise bound but
|
||||||
|
not accessible from Python's side.
|
||||||
|
"""
|
||||||
|
|
||||||
|
task = Gio.Task.new(source_object, cancellable, callback, callback_data)
|
||||||
|
task.set_task_data = decorate_set_task_data(task)(task.set_task_data)
|
||||||
|
task.run_in_thread = decorate_run_in_thread(task)(task.run_in_thread)
|
||||||
|
return task
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ class CartridgesWindow(Adw.ApplicationWindow):
|
|||||||
):
|
):
|
||||||
path.unlink(missing_ok=True)
|
path.unlink(missing_ok=True)
|
||||||
else:
|
else:
|
||||||
Game(game, allow_side_effects=True).update()
|
Game(game).update()
|
||||||
|
|
||||||
# Connect search entries
|
# Connect search entries
|
||||||
self.search_bar.connect_entry(self.search_entry)
|
self.search_bar.connect_entry(self.search_entry)
|
||||||
|
|||||||
Reference in New Issue
Block a user