diff --git a/src/details_window.py b/src/details_window.py
index d896bc2..767110e 100644
--- a/src/details_window.py
+++ b/src/details_window.py
@@ -110,6 +110,7 @@ class DetailsWindow(Adw.Window):
file_path = _("/path/to/{}").format(file_name)
command = "xdg-open"
+ # pylint: disable=line-too-long
exec_info_text = _(
'To launch the executable "{}", use the command:\n\n"{}"\n\nTo open the file "{}" with the default application, use:\n\n{} "{}"\n\nIf the path contains spaces, make sure to wrap it in double quotes!'
).format(exe_name, exe_path, file_name, command, file_path)
diff --git a/src/game.py b/src/game.py
index 20504f9..1b4aeef 100644
--- a/src/game.py
+++ b/src/game.py
@@ -31,6 +31,7 @@ from src import shared
from src.game_cover import GameCover
+# pylint: disable=too-many-instance-attributes
@Gtk.Template(resource_path=shared.PREFIX + "/gtk/game.ui")
class Game(Gtk.Box):
__gtype_name__ = "Game"
@@ -187,6 +188,7 @@ class Game(Gtk.Box):
)
logging.info("Starting %s: %s", self.name, str(args))
+ # pylint: disable=consider-using-with
subprocess.Popen(
args,
cwd=Path.home(),
diff --git a/src/importer/importer.py b/src/importer/importer.py
index d824b1a..eae8565 100644
--- a/src/importer/importer.py
+++ b/src/importer/importer.py
@@ -1,6 +1,6 @@
import logging
-from gi.repository import Adw, Gio, Gtk
+from gi.repository import Adw, Gtk
from src import shared
from src.game import Game
@@ -31,15 +31,13 @@ class Importer:
@property
def n_games_added(self):
return sum(
- [
- 1 if not (pipeline.game.blacklisted or pipeline.game.removed) else 0
- for pipeline in self.game_pipelines
- ]
+ 1 if not (pipeline.game.blacklisted or pipeline.game.removed) else 0
+ for pipeline in self.game_pipelines
)
@property
def pipelines_progress(self):
- progress = sum([pipeline.progress for pipeline in self.game_pipelines])
+ progress = sum(pipeline.progress for pipeline in self.game_pipelines)
try:
progress = progress / len(self.game_pipelines)
except ZeroDivisionError:
@@ -126,7 +124,7 @@ class Importer:
else:
# Warn source implementers that an invalid type was produced
# Should not happen on production code
- logging.warn(
+ logging.warning(
"%s produced an invalid iteration return type %s",
source.id,
type(iteration_result),
diff --git a/src/store/managers/itch_cover_manager.py b/src/store/managers/itch_cover_manager.py
index 3abc474..abf61b2 100644
--- a/src/store/managers/itch_cover_manager.py
+++ b/src/store/managers/itch_cover_manager.py
@@ -11,6 +11,9 @@ from src.store.managers.manager import Manager
from src.utils.save_cover import resize_cover, save_cover
+# TODO Remove by generalizing OnlineCoverManager
+
+
class ItchCoverManager(Manager):
"""Manager in charge of downloading the game's cover from itch.io"""
diff --git a/src/store/managers/manager.py b/src/store/managers/manager.py
index e5581b7..e73eb5a 100644
--- a/src/store/managers/manager.py
+++ b/src/store/managers/manager.py
@@ -73,7 +73,7 @@ class Manager:
if error in self.continue_on:
# Handle skippable errors (skip silently)
return
- elif error in self.retryable_on:
+ if error in self.retryable_on:
if try_index < self.max_tries:
# Handle retryable errors
logging.error("Retrying %s in %s for %s", *logging_args)
diff --git a/src/utils/importer.py b/src/utils/importer.py
deleted file mode 100644
index 2d651ac..0000000
--- a/src/utils/importer.py
+++ /dev/null
@@ -1,132 +0,0 @@
-# importer.py
-#
-# Copyright 2022-2023 kramo
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-#
-# SPDX-License-Identifier: GPL-3.0-or-later
-
-from gi.repository import Adw, GLib, Gtk
-
-from src import shared
-from .create_dialog import create_dialog
-from .game import Game
-from .save_cover import resize_cover, save_cover
-from .steamgriddb import SGDBSave
-
-
-class Importer:
- def __init__(self):
- self.win = shared.win
- self.total_queue = 0
- self.queue = 0
- self.games_no = 0
- self.blocker = False
- self.games = set()
- self.sgdb_exception = None
-
- self.progressbar = Gtk.ProgressBar(margin_start=12, margin_end=12)
- self.import_statuspage = Adw.StatusPage(
- title=_("Importing Games…"),
- child=self.progressbar,
- )
-
- self.import_dialog = Adw.Window(
- content=self.import_statuspage,
- modal=True,
- default_width=350,
- default_height=-1,
- transient_for=self.win,
- deletable=False,
- )
-
- self.import_dialog.present()
-
- def save_game(self, values=None, cover_path=None):
- if values:
- game = Game(values)
-
- if save_cover:
- save_cover(game.game_id, resize_cover(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.games, self)
- else:
- self.done()
-
- def done(self):
- self.update_progressbar()
- if self.queue == 0:
- self.import_dialog.close()
-
- toast = Adw.Toast()
- toast.set_priority(Adw.ToastPriority.HIGH)
-
- if self.games_no == 0:
- toast.set_title(_("No new games found"))
- toast.set_button_label(_("Preferences"))
- toast.connect(
- "button-clicked", self.response, "open_preferences", "import"
- )
-
- elif self.games_no == 1:
- toast.set_title(_("1 game imported"))
-
- elif self.games_no > 1:
- games_no = self.games_no
- toast.set_title(
- # The variable is the number of games
- _("{} games imported").format(games_no)
- )
-
- self.win.toast_overlay.add_toast(toast)
- # Add timeout to make it the last thing to happen
- GLib.timeout_add(0, self.warning, None, None)
-
- def response(self, _widget, response, page_name=None, expander_row=None):
- if response == "open_preferences":
- self.win.get_application().on_preferences_action(
- None, page_name=page_name, expander_row=expander_row
- )
-
- def warning(self, *_args):
- if self.sgdb_exception:
- create_dialog(
- self.win,
- _("Couldn't Connect to SteamGridDB"),
- self.sgdb_exception,
- "open_preferences",
- _("Preferences"),
- ).connect("response", self.response, "sgdb")
- self.sgdb_exception = None
-
- def update_progressbar(self):
- try:
- self.progressbar.set_fraction(1 - (self.queue / self.total_queue))
- except ZeroDivisionError:
- self.progressbar.set_fraction(1)
diff --git a/src/utils/rate_limiter.py b/src/utils/rate_limiter.py
index 2a80fc8..d0f1e29 100644
--- a/src/utils/rate_limiter.py
+++ b/src/utils/rate_limiter.py
@@ -9,20 +9,20 @@ class PickHistory(Sized):
"""Utility class used for rate limiters, counting how many picks
happened in a given period"""
- PERIOD: int
+ period: int
timestamps: list[int] = None
timestamps_lock: Lock = None
def __init__(self, period: int) -> None:
- self.PERIOD = period
+ self.period = period
self.timestamps = []
self.timestamps_lock = Lock()
def remove_old_entries(self):
"""Remove history entries older than the period"""
now = time()
- cutoff = now - self.PERIOD
+ cutoff = now - self.period
with self.timestamps_lock:
self.timestamps = [entry for entry in self.timestamps if entry > cutoff]
@@ -58,15 +58,16 @@ class PickHistory(Sized):
return self.timestamps.copy()
+# pylint: disable=too-many-instance-attributes
class RateLimiter(AbstractContextManager):
"""Rate limiter implementing the token bucket algorithm"""
# Period in which we have a max amount of tokens
- REFILL_PERIOD_SECONDS: int
+ refill_period_seconds: int
# Number of tokens allowed in this period
- REFILL_PERIOD_TOKENS: int
+ refill_period_tokens: int
# Max number of tokens that can be consumed instantly
- BURST_TOKENS: int
+ burst_tokens: int
pick_history: PickHistory = None
bucket: BoundedSemaphore = None
@@ -97,13 +98,13 @@ class RateLimiter(AbstractContextManager):
# Initialize default values
if refill_period_seconds is not None:
- self.REFILL_PERIOD_SECONDS = refill_period_seconds
+ self.refill_period_seconds = refill_period_seconds
if refill_period_tokens is not None:
- self.REFILL_PERIOD_TOKENS = refill_period_tokens
+ self.refill_period_tokens = refill_period_tokens
if burst_tokens is not None:
- self.BURST_TOKENS = burst_tokens
+ self.burst_tokens = burst_tokens
if self.pick_history is None:
- self.pick_history = PickHistory(self.REFILL_PERIOD_SECONDS)
+ self.pick_history = PickHistory(self.refill_period_seconds)
# Create synchronization data
self.__n_tokens_lock = Lock()
@@ -111,8 +112,8 @@ class RateLimiter(AbstractContextManager):
self.queue = deque()
# Initialize the token bucket
- self.bucket = BoundedSemaphore(self.BURST_TOKENS)
- self.n_tokens = self.BURST_TOKENS
+ self.bucket = BoundedSemaphore(self.burst_tokens)
+ self.n_tokens = self.burst_tokens
# Spawn daemon thread that refills the bucket
refill_thread = Thread(target=self.refill_thread_func, daemon=True)
@@ -127,8 +128,8 @@ class RateLimiter(AbstractContextManager):
"""
# Compute ideal spacing
- tokens_left = self.REFILL_PERIOD_TOKENS - len(self.pick_history)
- seconds_left = self.pick_history.start + self.REFILL_PERIOD_SECONDS - time()
+ tokens_left = self.refill_period_tokens - len(self.pick_history)
+ seconds_left = self.pick_history.start + self.refill_period_seconds - time()
try:
spacing_seconds = seconds_left / tokens_left
except ZeroDivisionError:
@@ -136,7 +137,7 @@ class RateLimiter(AbstractContextManager):
spacing_seconds = seconds_left
# Prevent spacing dropping down lower than the natural spacing
- natural_spacing = self.REFILL_PERIOD_SECONDS / self.REFILL_PERIOD_TOKENS
+ natural_spacing = self.refill_period_seconds / self.refill_period_tokens
return max(natural_spacing, spacing_seconds)
def refill(self):
@@ -165,7 +166,8 @@ class RateLimiter(AbstractContextManager):
with self.queue_lock:
if len(self.queue) == 0:
return
- self.bucket.acquire()
+ # Not using with because we don't want to release to the bucket
+ self.bucket.acquire() # pylint: disable=consider-using-with
self.n_tokens -= 1
lock = self.queue.pop()
lock.release()
@@ -173,7 +175,8 @@ class RateLimiter(AbstractContextManager):
def add_to_queue(self) -> Lock:
"""Create a lock, add it to the queue and return it"""
lock = Lock()
- lock.acquire()
+ # We want the lock locked until its turn in queue
+ lock.acquire() # pylint: disable=consider-using-with
with self.queue_lock:
self.queue.appendleft(lock)
return lock
@@ -182,7 +185,8 @@ class RateLimiter(AbstractContextManager):
"""Acquires a token from the bucket when it's your turn in queue"""
lock = self.add_to_queue()
self.update_queue()
- lock.acquire()
+ # Wait until our turn in queue
+ lock.acquire() # pylint: disable=consider-using-with
self.pick_history.add()
# --- Support for use in with statements
diff --git a/src/utils/steam.py b/src/utils/steam.py
index 517f9bf..c0a0677 100644
--- a/src/utils/steam.py
+++ b/src/utils/steam.py
@@ -47,15 +47,15 @@ class SteamRateLimiter(RateLimiter):
# 200 requests per 5 min seems to be the limit
# https://stackoverflow.com/questions/76047820/how-am-i-exceeding-steam-apis-rate-limit
# https://stackoverflow.com/questions/51795457/avoiding-error-429-too-many-requests-steam-web-api
- REFILL_PERIOD_SECONDS = 5 * 60
- REFILL_PERIOD_TOKENS = 200
- BURST_TOKENS = 100
+ refill_period_seconds = 5 * 60
+ refill_period_tokens = 200
+ burst_tokens = 100
def __init__(self) -> None:
# Load pick history from schema
# (Remember API limits through restarts of Cartridges)
timestamps_str = shared.state_schema.get_string("steam-limiter-tokens-history")
- self.pick_history = PickHistory(self.REFILL_PERIOD_SECONDS)
+ self.pick_history = PickHistory(self.refill_period_seconds)
self.pick_history.add(*json.loads(timestamps_str))
self.pick_history.remove_old_entries()
super().__init__()