🎨 Fixed some linter warnings
Applied suggested pylint fixes and suppressed unhelpful pylint messages
This commit is contained in:
@@ -110,6 +110,7 @@ class DetailsWindow(Adw.Window):
|
|||||||
file_path = _("/path/to/{}").format(file_name)
|
file_path = _("/path/to/{}").format(file_name)
|
||||||
command = "xdg-open"
|
command = "xdg-open"
|
||||||
|
|
||||||
|
# pylint: disable=line-too-long
|
||||||
exec_info_text = _(
|
exec_info_text = _(
|
||||||
'To launch the executable "{}", use the command:\n\n<tt>"{}"</tt>\n\nTo open the file "{}" with the default application, use:\n\n<tt>{} "{}"</tt>\n\nIf the path contains spaces, make sure to wrap it in double quotes!'
|
'To launch the executable "{}", use the command:\n\n<tt>"{}"</tt>\n\nTo open the file "{}" with the default application, use:\n\n<tt>{} "{}"</tt>\n\nIf the path contains spaces, make sure to wrap it in double quotes!'
|
||||||
).format(exe_name, exe_path, file_name, command, file_path)
|
).format(exe_name, exe_path, file_name, command, file_path)
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ from src import shared
|
|||||||
from src.game_cover import GameCover
|
from src.game_cover import GameCover
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-many-instance-attributes
|
||||||
@Gtk.Template(resource_path=shared.PREFIX + "/gtk/game.ui")
|
@Gtk.Template(resource_path=shared.PREFIX + "/gtk/game.ui")
|
||||||
class Game(Gtk.Box):
|
class Game(Gtk.Box):
|
||||||
__gtype_name__ = "Game"
|
__gtype_name__ = "Game"
|
||||||
@@ -187,6 +188,7 @@ class Game(Gtk.Box):
|
|||||||
)
|
)
|
||||||
|
|
||||||
logging.info("Starting %s: %s", self.name, str(args))
|
logging.info("Starting %s: %s", self.name, str(args))
|
||||||
|
# pylint: disable=consider-using-with
|
||||||
subprocess.Popen(
|
subprocess.Popen(
|
||||||
args,
|
args,
|
||||||
cwd=Path.home(),
|
cwd=Path.home(),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from gi.repository import Adw, Gio, Gtk
|
from gi.repository import Adw, Gtk
|
||||||
|
|
||||||
from src import shared
|
from src import shared
|
||||||
from src.game import Game
|
from src.game import Game
|
||||||
@@ -31,15 +31,13 @@ class Importer:
|
|||||||
@property
|
@property
|
||||||
def n_games_added(self):
|
def n_games_added(self):
|
||||||
return sum(
|
return sum(
|
||||||
[
|
1 if not (pipeline.game.blacklisted or pipeline.game.removed) else 0
|
||||||
1 if not (pipeline.game.blacklisted or pipeline.game.removed) else 0
|
for pipeline in self.game_pipelines
|
||||||
for pipeline in self.game_pipelines
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pipelines_progress(self):
|
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:
|
try:
|
||||||
progress = progress / len(self.game_pipelines)
|
progress = progress / len(self.game_pipelines)
|
||||||
except ZeroDivisionError:
|
except ZeroDivisionError:
|
||||||
@@ -126,7 +124,7 @@ class Importer:
|
|||||||
else:
|
else:
|
||||||
# Warn source implementers that an invalid type was produced
|
# Warn source implementers that an invalid type was produced
|
||||||
# Should not happen on production code
|
# Should not happen on production code
|
||||||
logging.warn(
|
logging.warning(
|
||||||
"%s produced an invalid iteration return type %s",
|
"%s produced an invalid iteration return type %s",
|
||||||
source.id,
|
source.id,
|
||||||
type(iteration_result),
|
type(iteration_result),
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ from src.store.managers.manager import Manager
|
|||||||
from src.utils.save_cover import resize_cover, save_cover
|
from src.utils.save_cover import resize_cover, save_cover
|
||||||
|
|
||||||
|
|
||||||
|
# TODO Remove by generalizing OnlineCoverManager
|
||||||
|
|
||||||
|
|
||||||
class ItchCoverManager(Manager):
|
class ItchCoverManager(Manager):
|
||||||
"""Manager in charge of downloading the game's cover from itch.io"""
|
"""Manager in charge of downloading the game's cover from itch.io"""
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class Manager:
|
|||||||
if error in self.continue_on:
|
if error in self.continue_on:
|
||||||
# Handle skippable errors (skip silently)
|
# Handle skippable errors (skip silently)
|
||||||
return
|
return
|
||||||
elif error in self.retryable_on:
|
if error in self.retryable_on:
|
||||||
if try_index < self.max_tries:
|
if try_index < self.max_tries:
|
||||||
# Handle retryable errors
|
# Handle retryable errors
|
||||||
logging.error("Retrying %s in %s for %s", *logging_args)
|
logging.error("Retrying %s in %s for %s", *logging_args)
|
||||||
|
|||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
# 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)
|
|
||||||
@@ -9,20 +9,20 @@ class PickHistory(Sized):
|
|||||||
"""Utility class used for rate limiters, counting how many picks
|
"""Utility class used for rate limiters, counting how many picks
|
||||||
happened in a given period"""
|
happened in a given period"""
|
||||||
|
|
||||||
PERIOD: int
|
period: int
|
||||||
|
|
||||||
timestamps: list[int] = None
|
timestamps: list[int] = None
|
||||||
timestamps_lock: Lock = None
|
timestamps_lock: Lock = None
|
||||||
|
|
||||||
def __init__(self, period: int) -> None:
|
def __init__(self, period: int) -> None:
|
||||||
self.PERIOD = period
|
self.period = period
|
||||||
self.timestamps = []
|
self.timestamps = []
|
||||||
self.timestamps_lock = Lock()
|
self.timestamps_lock = Lock()
|
||||||
|
|
||||||
def remove_old_entries(self):
|
def remove_old_entries(self):
|
||||||
"""Remove history entries older than the period"""
|
"""Remove history entries older than the period"""
|
||||||
now = time()
|
now = time()
|
||||||
cutoff = now - self.PERIOD
|
cutoff = now - self.period
|
||||||
with self.timestamps_lock:
|
with self.timestamps_lock:
|
||||||
self.timestamps = [entry for entry in self.timestamps if entry > cutoff]
|
self.timestamps = [entry for entry in self.timestamps if entry > cutoff]
|
||||||
|
|
||||||
@@ -58,15 +58,16 @@ class PickHistory(Sized):
|
|||||||
return self.timestamps.copy()
|
return self.timestamps.copy()
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-many-instance-attributes
|
||||||
class RateLimiter(AbstractContextManager):
|
class RateLimiter(AbstractContextManager):
|
||||||
"""Rate limiter implementing the token bucket algorithm"""
|
"""Rate limiter implementing the token bucket algorithm"""
|
||||||
|
|
||||||
# Period in which we have a max amount of tokens
|
# 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
|
# Number of tokens allowed in this period
|
||||||
REFILL_PERIOD_TOKENS: int
|
refill_period_tokens: int
|
||||||
# Max number of tokens that can be consumed instantly
|
# Max number of tokens that can be consumed instantly
|
||||||
BURST_TOKENS: int
|
burst_tokens: int
|
||||||
|
|
||||||
pick_history: PickHistory = None
|
pick_history: PickHistory = None
|
||||||
bucket: BoundedSemaphore = None
|
bucket: BoundedSemaphore = None
|
||||||
@@ -97,13 +98,13 @@ class RateLimiter(AbstractContextManager):
|
|||||||
|
|
||||||
# Initialize default values
|
# Initialize default values
|
||||||
if refill_period_seconds is not None:
|
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:
|
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:
|
if burst_tokens is not None:
|
||||||
self.BURST_TOKENS = burst_tokens
|
self.burst_tokens = burst_tokens
|
||||||
if self.pick_history is None:
|
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
|
# Create synchronization data
|
||||||
self.__n_tokens_lock = Lock()
|
self.__n_tokens_lock = Lock()
|
||||||
@@ -111,8 +112,8 @@ class RateLimiter(AbstractContextManager):
|
|||||||
self.queue = deque()
|
self.queue = deque()
|
||||||
|
|
||||||
# Initialize the token bucket
|
# Initialize the token bucket
|
||||||
self.bucket = BoundedSemaphore(self.BURST_TOKENS)
|
self.bucket = BoundedSemaphore(self.burst_tokens)
|
||||||
self.n_tokens = self.BURST_TOKENS
|
self.n_tokens = self.burst_tokens
|
||||||
|
|
||||||
# Spawn daemon thread that refills the bucket
|
# Spawn daemon thread that refills the bucket
|
||||||
refill_thread = Thread(target=self.refill_thread_func, daemon=True)
|
refill_thread = Thread(target=self.refill_thread_func, daemon=True)
|
||||||
@@ -127,8 +128,8 @@ class RateLimiter(AbstractContextManager):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Compute ideal spacing
|
# Compute ideal spacing
|
||||||
tokens_left = self.REFILL_PERIOD_TOKENS - len(self.pick_history)
|
tokens_left = self.refill_period_tokens - len(self.pick_history)
|
||||||
seconds_left = self.pick_history.start + self.REFILL_PERIOD_SECONDS - time()
|
seconds_left = self.pick_history.start + self.refill_period_seconds - time()
|
||||||
try:
|
try:
|
||||||
spacing_seconds = seconds_left / tokens_left
|
spacing_seconds = seconds_left / tokens_left
|
||||||
except ZeroDivisionError:
|
except ZeroDivisionError:
|
||||||
@@ -136,7 +137,7 @@ class RateLimiter(AbstractContextManager):
|
|||||||
spacing_seconds = seconds_left
|
spacing_seconds = seconds_left
|
||||||
|
|
||||||
# Prevent spacing dropping down lower than the natural spacing
|
# 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)
|
return max(natural_spacing, spacing_seconds)
|
||||||
|
|
||||||
def refill(self):
|
def refill(self):
|
||||||
@@ -165,7 +166,8 @@ class RateLimiter(AbstractContextManager):
|
|||||||
with self.queue_lock:
|
with self.queue_lock:
|
||||||
if len(self.queue) == 0:
|
if len(self.queue) == 0:
|
||||||
return
|
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
|
self.n_tokens -= 1
|
||||||
lock = self.queue.pop()
|
lock = self.queue.pop()
|
||||||
lock.release()
|
lock.release()
|
||||||
@@ -173,7 +175,8 @@ class RateLimiter(AbstractContextManager):
|
|||||||
def add_to_queue(self) -> Lock:
|
def add_to_queue(self) -> Lock:
|
||||||
"""Create a lock, add it to the queue and return it"""
|
"""Create a lock, add it to the queue and return it"""
|
||||||
lock = Lock()
|
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:
|
with self.queue_lock:
|
||||||
self.queue.appendleft(lock)
|
self.queue.appendleft(lock)
|
||||||
return lock
|
return lock
|
||||||
@@ -182,7 +185,8 @@ class RateLimiter(AbstractContextManager):
|
|||||||
"""Acquires a token from the bucket when it's your turn in queue"""
|
"""Acquires a token from the bucket when it's your turn in queue"""
|
||||||
lock = self.add_to_queue()
|
lock = self.add_to_queue()
|
||||||
self.update_queue()
|
self.update_queue()
|
||||||
lock.acquire()
|
# Wait until our turn in queue
|
||||||
|
lock.acquire() # pylint: disable=consider-using-with
|
||||||
self.pick_history.add()
|
self.pick_history.add()
|
||||||
|
|
||||||
# --- Support for use in with statements
|
# --- Support for use in with statements
|
||||||
|
|||||||
@@ -47,15 +47,15 @@ class SteamRateLimiter(RateLimiter):
|
|||||||
# 200 requests per 5 min seems to be the limit
|
# 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/76047820/how-am-i-exceeding-steam-apis-rate-limit
|
||||||
# https://stackoverflow.com/questions/51795457/avoiding-error-429-too-many-requests-steam-web-api
|
# https://stackoverflow.com/questions/51795457/avoiding-error-429-too-many-requests-steam-web-api
|
||||||
REFILL_PERIOD_SECONDS = 5 * 60
|
refill_period_seconds = 5 * 60
|
||||||
REFILL_PERIOD_TOKENS = 200
|
refill_period_tokens = 200
|
||||||
BURST_TOKENS = 100
|
burst_tokens = 100
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
# Load pick history from schema
|
# Load pick history from schema
|
||||||
# (Remember API limits through restarts of Cartridges)
|
# (Remember API limits through restarts of Cartridges)
|
||||||
timestamps_str = shared.state_schema.get_string("steam-limiter-tokens-history")
|
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.add(*json.loads(timestamps_str))
|
||||||
self.pick_history.remove_old_entries()
|
self.pick_history.remove_old_entries()
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
Reference in New Issue
Block a user