🎨 Black codestyle

This commit is contained in:
GeoffreyCoulaud
2023-05-05 03:04:44 +02:00
parent f432c41843
commit 8b7b23379f
4 changed files with 167 additions and 72 deletions

View File

@@ -17,25 +17,38 @@ from pathlib import Path
from os import PathLike from os import PathLike
from functools import wraps from functools import wraps
def replaced_by_path(path: PathLike): # Decorator builder
def replaced_by_path(path: PathLike): # Decorator builder
"""Replace the method's returned path with the override if the override exists on disk""" """Replace the method's returned path with the override if the override exists on disk"""
def decorator(original_function): # Built decorator (closure)
def decorator(original_function): # Built decorator (closure)
@wraps(original_function) @wraps(original_function)
def wrapper(*args, **kwargs): # func's override def wrapper(*args, **kwargs): # func's override
p = Path(path).expanduser() p = Path(path).expanduser()
if p.exists(): return p if p.exists():
else: return original_function(*args, **kwargs) return p
else:
return original_function(*args, **kwargs)
return wrapper return wrapper
return decorator return decorator
def replaced_by_schema_key(key: str): # Decorator builder
def replaced_by_schema_key(key: str): # Decorator builder
"""Replace the method's returned path with the path pointed by the key if it exists on disk""" """Replace the method's returned path with the path pointed by the key if it exists on disk"""
def decorator(original_function): # Built decorator (closure)
def decorator(original_function): # Built decorator (closure)
@wraps(original_function) @wraps(original_function)
def wrapper(*args, **kwargs): # func's override def wrapper(*args, **kwargs): # func's override
schema = args[0].win.schema schema = args[0].win.schema
try: override = schema.get_string(key) try:
except Exception: return original_function(*args, **kwargs) override = schema.get_string(key)
else: return replaced_by_path(override)(*args, **kwargs) except Exception:
return original_function(*args, **kwargs)
else:
return replaced_by_path(override)(*args, **kwargs)
return wrapper return wrapper
return decorator
return decorator

View File

@@ -1,21 +1,45 @@
from threading import Thread, Lock
from gi.repository import Adw, Gtk, Gio from gi.repository import Adw, Gtk, Gio
class Importer(): from .game import Game
from .steamgriddb import SGDBSave
# Display values
class Importer:
win = None win = None
progressbar = None progressbar = None
import_statuspage = None import_statuspage = None
import_dialog = None import_dialog = None
sources = None
# Importer values progress_lock = None
count_total = 0 counts = None
count_done = 0
sources = list() games_lock = None
games = None
def __init__(self, win) -> None: def __init__(self, win) -> None:
self.games = set()
self.sources = list()
self.counts = dict()
self.games_lock = Lock()
self.progress_lock = Lock()
self.win = win self.win = win
@property
def progress(self):
# Compute overall values
done = 0
total = 0
for source in self.sources:
done += self.counts[source.id]["done"]
total += self.counts[source.id]["total"]
# Compute progress
progress = 1
if total > 0:
progress = 1 - done / total
return progress
def create_dialog(self): def create_dialog(self):
"""Create the import dialog""" """Create the import dialog"""
self.progressbar = Gtk.ProgressBar(margin_start=12, margin_end=12) self.progressbar = Gtk.ProgressBar(margin_start=12, margin_end=12)
@@ -37,44 +61,83 @@ class Importer():
"""Close the import dialog""" """Close the import dialog"""
self.import_dialog.close() self.import_dialog.close()
def get_progress(self):
"""Get the current progression as a number between 0 and 1"""
progress = 1
if self.total_queue > 0:
progress = 1 - self.queue / self.total_queue
return progress
def update_progressbar(self): def update_progressbar(self):
"""Update the progress bar""" """Update the progress bar"""
progress = self.get_progress() progress = self.progress()
self.progressbar.set_fraction(progress) self.progressbar.set_fraction(progress)
def add_source(self, source): def add_source(self, source):
"""Add a source to import games from""" """Add a source to import games from"""
self.sources.append(source) self.sources.append(source)
self.counts[source.id] = {"done": 0, "total": 0}
def import_games(self): def import_games(self):
"""Import games from the specified sources""" """Import games from the specified sources"""
self.create_dialog() self.create_dialog()
# TODO make that async, you doofus # Scan all sources
# Every source does its job on the side, informing of the amount of work and when a game is done. threads = []
# At the end of the task, it returns the games.
# Idea 1 - Work stealing queue
# 1. Sources added to the queue
# 2. Worker A takes source X and advances it
# 3. Worker A puts back source X to the queue
# 4. Worker B takes source X, that has ended
# 5. Worker B doesn't add source X back to the queue
# Idea 2 - Gio.Task
# 1. A task is created for every source
# 2. Source X finishes
# 3. Importer adds the games
for source in self.sources: for source in self.sources:
for game in source: t = Thread(
game.save() None,
self.__import_from_source,
args=tuple(
source,
),
)
threads.append(t)
t.start()
self.close_dialog() # Wait for all of them to finish
for t in threads:
t.join()
self.close_dialog()
def __import_from_source(self, *args, **kwargs):
"""Source import thread entry point"""
# TODO just get Game objects from the sources
source, *rest = args
iterator = source.__iter__()
for game_values in iterator:
game = Game(self.win, game_values)
self.games_lock.acquire()
self.games.add(game)
self.games_lock.release()
self.progress_lock.acquire()
self.counts[source.id]["total"] = len(iterator)
if not game.blacklisted:
self.counts[source.id]["done"] += 1
self.update_progressbar()
self.progress_lock.release()
# TODO remove after not needed
def save_game(self, values=None, cover_path=None):
if values:
game = Game(self.win, values)
if save_cover:
save_cover(self.win, game.game_id, resize_cover(self.win, cover_path))
self.games.add(game)
self.games_no += 1
if game.blacklisted:
self.games_no -= 1
self.queue -= 1
self.update_progressbar()
if self.queue == 0 and not self.blocker:
if self.games:
self.total_queue = len(self.games)
self.queue = len(self.games)
self.import_statuspage.set_title(_("Importing Covers…"))
self.update_progressbar()
SGDBSave(self.win, self.games, self)
else:
self.done()

View File

@@ -9,7 +9,7 @@ class SourceIterator(Iterator):
class States(IntEnum): class States(IntEnum):
DEFAULT = auto() DEFAULT = auto()
READY = auto() READY = auto()
state = States.DEFAULT state = States.DEFAULT
source = None source = None
@@ -28,7 +28,7 @@ class SourceIterator(Iterator):
class Source(Iterable): class Source(Iterable):
"""Source of games. E.g an installed app with a config file that lists game directories""" """Source of games. E.g an installed app with a config file that lists game directories"""
win = None # TODO maybe not depend on that ? win = None # TODO maybe not depend on that ?
name: str name: str
variant: str variant: str
@@ -42,17 +42,25 @@ class Source(Iterable):
"""The source's full name""" """The source's full name"""
s = self.name s = self.name
if self.variant is not None: if self.variant is not None:
s += " (%s)" % self.variant s += f" ({self.variant})"
return s
@property
def id(self):
"""The source's identifier"""
s = self.name.lower()
if self.variant is not None:
s += f"_{self.variant.lower()}"
return s return s
@property @property
def game_id_format(self): def game_id_format(self):
"""The string format used to construct game IDs""" """The string format used to construct game IDs"""
_format = self.name.lower() f = self.name.lower()
if self.variant is not None: if self.variant is not None:
_format += "_" + self.variant.lower() f += f"_{self.variant.lower()}"
_format += "_{game_id}_{game_internal_id}" f += "_{game_id}"
return _format return f
@property @property
@abstractmethod @abstractmethod
@@ -63,4 +71,4 @@ class Source(Iterable):
@abstractmethod @abstractmethod
def __iter__(self): def __iter__(self):
"""Get the source's iterator, to use in for loops""" """Get the source's iterator, to use in for loops"""
pass pass

View File

@@ -1,21 +1,20 @@
from functools import cached_property from functools import cached_property
from sqlite3 import connect from sqlite3 import connect
from src.game2 import Game from src.utils.save_cover import resize_cover, save_cover
from src.importer.source import Source, SourceIterator from src.importer.source import Source, SourceIterator
from src.importer.decorators import replaced_by_schema_key, replaced_by_path from src.importer.decorators import replaced_by_schema_key, replaced_by_path
class LutrisSourceIterator(SourceIterator): class LutrisSourceIterator(SourceIterator):
ignore_steam_games = False # TODO get that value
ignore_steam_games = False
db_connection = None db_connection = None
db_cursor = None db_cursor = None
db_location = None db_location = None
db_request = None db_request = None
def __init__(self, ignore_steam_games) -> None: def __init__(self, ignore_steam_games):
super().__init__() super().__init__()
self.ignore_steam_games = ignore_steam_games self.ignore_steam_games = ignore_steam_games
self.db_connection = None self.db_connection = None
@@ -43,10 +42,10 @@ class LutrisSourceIterator(SourceIterator):
self.db_cursor = self.db_connection.execute(self.db_request) self.db_cursor = self.db_connection.execute(self.db_request)
self.state = self.States.READY self.state = self.States.READY
# Get next DB value
while True: while True:
# Get next DB value
try: try:
row = self.db_cursor.__next__() row = self.db_cursor.__next__()
except StopIteration as e: except StopIteration as e:
self.db_connection.close() self.db_connection.close()
raise e raise e
@@ -54,29 +53,41 @@ class LutrisSourceIterator(SourceIterator):
# Ignore steam games if requested # Ignore steam games if requested
if row[3] == "steam" and self.ignore_steam_games: if row[3] == "steam" and self.ignore_steam_games:
continue continue
# Build basic game # Build basic game
values = { values = {
"name" : row[1], "hidden": row[4],
"hidden" : row[4], "name": row[1],
"source" : self.source.full_name, "source": f"{self.source.id}_{row[3]}",
"game_id" : self.source.game_id_format.format(game_id=row[2]), "game_id": self.source.game_id_format.format(
game_id=row[2], game_internal_id=row[0]
),
"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
} }
# TODO Add official image
# TODO Add SGDB image # Save official image
image_path = self.source.cache_location / "coverart" / f"{row[2]}.jpg"
if image_path.exists():
resized = resize_cover(self.win, image_path)
save_cover(self.win, values["game_id"], resized)
# Save SGDB
return values return values
class LutrisSource(Source): class LutrisSource(Source):
name = "Lutris" name = "Lutris"
executable_format = "xdg-open lutris:rungameid/{game_id}" executable_format = "xdg-open lutris:rungameid/{game_id}"
location = None location = None
cache_location = None cache_location = None
def __init__(self, win) -> None: @property
def game_id_format(self):
return super().game_id_format + "_{game_internal_id}"
def __init__(self, win):
super().__init__(win) super().__init__(win)
def __iter__(self): def __iter__(self):
@@ -116,4 +127,4 @@ class LutrisFlatpakSource(LutrisSource):
@replaced_by_schema_key("lutris-flatpak-cache-location") @replaced_by_schema_key("lutris-flatpak-cache-location")
@replaced_by_path("~/.var/app/net.lutris.Lutris/data/lutris/covers") @replaced_by_path("~/.var/app/net.lutris.Lutris/data/lutris/covers")
def cache_location(self): def cache_location(self):
raise FileNotFoundError() raise FileNotFoundError()