Skip to content

Commit

Permalink
1. 软件添加更新检测
Browse files Browse the repository at this point in the history
2. 添加软件更新检测设置
3. 优化connector逻辑, 优先保证前台请求
4. 添加LCU请求超时时的提示
  • Loading branch information
Hpero4 committed Sep 23, 2023
1 parent ba30f34 commit 5be287e
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 8 deletions.
4 changes: 4 additions & 0 deletions app/common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ class Config(QConfig):
"Other", "SearchHistory", ""
)

enableCheckUpdate = ConfigItem("General",
"EnableCheckUpdate", True,
BoolValidator())

# enableCopyPlayersInfo = ConfigItem("Functions", "EnableCopyPlayersInfo",
# False, BoolValidator())

Expand Down
1 change: 1 addition & 0 deletions app/common/icons.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class Icon(FluentIconBase, Enum):
TEAM = 'Team'
SETTING = 'Setting'
FILTER = 'Filter'
UPDATE = 'Update'

def path(self, theme=Theme.AUTO):
return f'./app/resource/icons/{self.value}_{getIconColor(theme)}.svg'
28 changes: 28 additions & 0 deletions app/common/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import requests

from app.common.config import cfg, VERSION


class Github:
def __init__(self, user="Zzaphkiel", repositories="Seraphine"):
self.API = "http://api.github.com"

self.user = user
self.repositories = repositories
self.sess = requests.session()

def getReleasesInfo(self):
url = f"{self.API}/repos/{self.user}/{self.repositories}/releases/latest"
return self.sess.get(url, verify=False).json()

def checkUpdate(self):
"""
检查版本更新
@return: 有更新 -> info, 无更新 -> None
"""
info = self.getReleasesInfo()
if info.get("tag_name")[1:] != VERSION:
return info
return None

github = Github()
32 changes: 32 additions & 0 deletions app/components/update_message_box.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from PyQt5.QtWidgets import QLabel
from qfluentwidgets import MessageBox, MessageBoxBase, SmoothScrollArea, SubtitleLabel, BodyLabel, TextEdit, TitleLabel, \
CheckBox

from app.common.config import VERSION, cfg


class UpdateMessageBox(MessageBoxBase):
def __init__(self, info, parent=None):
super().__init__(parent=parent)
self.titleLabel = TitleLabel(self.tr('Update detected'), self)
self.titleLabel.setContentsMargins(5, 0, 5, 0)
self.content = BodyLabel(self.tr(f'{VERSION} -> {info.get("tag_name")[1:]}'), self)
self.content.setContentsMargins(8, 0, 5, 0)

textEdit = TextEdit(self)
textEdit.setFixedWidth(int(self.width() * .6))
textEdit.setMarkdown(self.tr(info.get("body")))
textEdit.setReadOnly(True)

checkBox = CheckBox()
checkBox.setText("Don't remind me again")
checkBox.clicked.connect(lambda: cfg.set(cfg.enableCheckUpdate, not checkBox.isChecked(), True))

self.viewLayout.addWidget(self.titleLabel)
self.viewLayout.addWidget(self.content)
self.viewLayout.addWidget(textEdit)
self.viewLayout.addWidget(checkBox)

self.yesButton.setText("Download")


44 changes: 38 additions & 6 deletions app/lol/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,50 @@
requests.packages.urllib3.disable_warnings()


def slowly():
def decorator(func):
def wrapper(*args, **kwargs):
while connector.tackleFlag.is_set():
time.sleep(.2)

connector.slowlyFlag.set()
res = func(*args, **kwargs)
connector.slowlyFlag.clear()
return res
return wrapper
return decorator


def tackle():
def decorator(func):
def wrapper(*args, **kwargs):
connector.tackleFlag.set()
res = func(*args, **kwargs)
connector.tackleFlag.clear()
return res
return wrapper
return decorator


def retry(count=5, retry_sep=0.5):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(count):
try:
# 低优先级请求未结束时, 避免server队列过长
# 若负载过高导致请求失败, 则在触发 retry 间隙为高优先级请求让行
while connector.slowlyFlag.is_set():
time.sleep(.2)

res = func(*args, **kwargs)
except:
time.sleep(retry_sep)
continue
else:
break
else:
# FIXME 任何异常都将以 timeout 抛出
connector.timeoutApi = func.__name__
raise Exception("Exceeded maximum retry attempts.")

return res
Expand All @@ -39,9 +71,12 @@ def __init__(self):
self.token = None
self.url = None

self.flag = threading.Event()
self.tackleFlag = threading.Event()
self.slowlyFlag = threading.Event()
self.manager = None

self.timeoutApi = None

def start(self, pid):
process = psutil.Process(pid)
cmdline = process.cmdline()
Expand Down Expand Up @@ -206,11 +241,9 @@ def getSummonerByPuuid(self, puuid):

return res

@slowly()
@retry(10, 1)
def getSummonerGamesByPuuidSlowly(self, puuid, begIndex=0, endIndex=4):
while self.flag.is_set():
time.sleep(.2)

params = {"begIndex": begIndex, "endIndex": endIndex}
res = self.__get(
f"/lol-match-history/v1/products/lol/{puuid}/matches", params
Expand All @@ -221,9 +254,9 @@ def getSummonerGamesByPuuidSlowly(self, puuid, begIndex=0, endIndex=4):

return res["games"]

@tackle()
@retry()
def getSummonerGamesByPuuid(self, puuid, begIndex=0, endIndex=4):
self.flag.set()
params = {"begIndex": begIndex, "endIndex": endIndex}
res = self.__get(
f"/lol-match-history/v1/products/lol/{puuid}/matches", params
Expand All @@ -232,7 +265,6 @@ def getSummonerGamesByPuuid(self, puuid, begIndex=0, endIndex=4):
if "games" not in res:
raise SummonerGamesNotFound()

self.flag.clear()
return res["games"]

@retry()
Expand Down
54 changes: 53 additions & 1 deletion app/view/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import sys
import traceback
import time
import webbrowser
from collections import Counter

from PyQt5.QtCore import Qt, pyqtSignal, QSize, QAbstractAnimation
Expand All @@ -20,10 +21,12 @@
from .search_interface import SearchInterface
from .game_info_interface import GameInfoInterface
from .auxiliary_interface import AuxiliaryInterface
from ..common.util import Github, github
from ..components.avatar_widget import NavigationAvatarWidget
from ..components.temp_system_tray_menu import TmpSystemTrayMenu
from ..common.icons import Icon
from ..common.config import cfg
from ..common.config import cfg, VERSION
from ..components.update_message_box import UpdateMessageBox
from ..lol.entries import Summoner
from ..lol.listener import (LolProcessExistenceListener, LolClientEventListener,
getLolProcessPid)
Expand All @@ -38,6 +41,9 @@
class MainWindow(FluentWindow):
nameOrIconChanged = pyqtSignal(str, str)
lolInstallFolderChanged = pyqtSignal(str)
showUpdateMessageBox = pyqtSignal(dict)
checkUpdateFailed = pyqtSignal()
showLcuConnectTimeout = pyqtSignal(str)

def __init__(self):
super().__init__()
Expand Down Expand Up @@ -71,6 +77,8 @@ def __init__(self):
self.__conncetSignalToSlot()

self.splashScreen.finish()
threading.Thread(target=self.checkUpdate).start()
threading.Thread(target=self.pollingConnectTimeout).start()

def __initInterface(self):
self.__lockInterface()
Expand Down Expand Up @@ -134,6 +142,9 @@ def __conncetSignalToSlot(self):

self.nameOrIconChanged.connect(self.__onNameOrIconChanged)
self.lolInstallFolderChanged.connect(self.__onLolInstallFolderChanged)
self.showUpdateMessageBox.connect(self.__onShowUpdateMessageBox)
self.checkUpdateFailed.connect(self.__onCheckUpdateFailed)
self.showLcuConnectTimeout.connect(self.__onShowLcuConnectTimeout)

self.careerInterface.searchButton.clicked.connect(
self.__onCareerInterfaceHistoryButtonClicked)
Expand Down Expand Up @@ -195,6 +206,47 @@ def __initWindow(self):
self.oldHook = sys.excepthook
sys.excepthook = self.exceptHook

def __onShowLcuConnectTimeout(self, api):
InfoBar.error(
self.tr("LCU request timeout"),
self.tr(f"Connect API {api} request timeout."),
duration=5000,
parent=self,
position=InfoBarPosition.BOTTOM_RIGHT
)

def checkUpdate(self):
if cfg.get(cfg.enableCheckUpdate):
try:
releasesInfo = github.checkUpdate()
except:
self.checkUpdateFailed.emit()
else:
if releasesInfo:
self.showUpdateMessageBox.emit(releasesInfo)

def __onCheckUpdateFailed(self):
InfoBar.warning(
self.tr("Check Update Failed"),
self.tr("Failed to check for updates, possibly unable to connect to Github."),
duration=5000,
parent=self,
position=InfoBarPosition.BOTTOM_RIGHT
)

def __onShowUpdateMessageBox(self, info):
msgBox = UpdateMessageBox(info, self.window())
if msgBox.exec():
webbrowser.open(info.get("zipball_url"))

def pollingConnectTimeout(self):
while True:
if connector.timeoutApi:
self.showLcuConnectTimeout.emit(connector.timeoutApi)
connector.timeoutApi = None

time.sleep(.5)

def __initSystemTray(self):
self.trayIcon = QSystemTrayIcon(self)
self.trayIcon.setToolTip("Seraphine")
Expand Down
10 changes: 9 additions & 1 deletion app/view/setting_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ def __init__(self, parent=None):
tr("Setting the maximum number of games shows in the career interface"
), self.functionGroup)

# TODO 逻辑
self.gameInfoFilterCard = SwitchSettingCard(
Icon.FILTER, self.tr("Rank filter other mode"),
self.tr(
Expand All @@ -91,6 +90,14 @@ def __init__(self, parent=None):
self.tr("Client Path"),
cfg.get(cfg.lolFolder),
self.generalGroup)

self.checkUpdateCard = SwitchSettingCard(
Icon.UPDATE, self.tr("Check for updates"),
self.tr(
"Automatically check for updates when software starts"),
cfg.enableCheckUpdate
)

# self.enableStartWithComputer = SwitchSettingCard(
# Icon.DESKTOPRIGHT,
# self.tr("Auto-start on boot"),
Expand Down Expand Up @@ -209,6 +216,7 @@ def __initLayout(self):
self.generalGroup.addSettingCard(self.enableStartLolWithApp)
self.generalGroup.addSettingCard(self.deleteResourceCard)
self.generalGroup.addSettingCard(self.enableCloseToTray)
self.generalGroup.addSettingCard(self.checkUpdateCard)

self.personalizationGroup.addSettingCard(self.micaCard)
self.personalizationGroup.addSettingCard(self.themeCard)
Expand Down

0 comments on commit 5be287e

Please sign in to comment.