Skip to content

Commit 3fde885

Browse files
committed
增加了相对详细的注释,增加了取消网络请求的逻辑,此前会发生点击取消下载后仍继续下载的bug,现在每个异步请求都会返回一个唯一的标识,可使用closeRequest(id)对指定请求进行取消。
1 parent 0343a25 commit 3fde885

2 files changed

Lines changed: 159 additions & 35 deletions

File tree

src/CheckUpdateGui.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
QSizePolicy, QPushButton, QFrame, QMessageBox, QFormLayout, QProgressDialog, QTextEdit, QComboBox
33
from githubApi import GitHub, Release, SourceManager, PingThread
44
from PyQt5.QtCore import QObject, pyqtSlot, Qt, pyqtSignal, QUrl, QPropertyAnimation, \
5-
QRect, QSize, pyqtProperty, QVariantAnimation
5+
QRect, QSize, pyqtProperty, QVariantAnimation,QDateTime
66
from PyQt5.QtGui import QDesktopServices, QFont, QIcon, QPainter, QPixmap, QPaintEvent
77

88

@@ -53,6 +53,7 @@ def __init__(self, release: Release, mode=">", parent=None):
5353
self.showButton = AnimationButton()
5454
self.showButton.setCheckable(True)
5555
self.showButton.pixmap = QPixmap("media/unfold.png")
56+
self.dateTimeLabel = QLabel()
5657
self.titleWidget = QWidget()
5758
self.formWidget = QWidget()
5859
self.downloadButton = QPushButton(QObject.tr(self, "Download"))
@@ -70,6 +71,10 @@ def initUi(self):
7071
row1 = QHBoxLayout()
7172
row1.addWidget(self.showButton)
7273
row1.addWidget(QLabel(self.release.tag_name))
74+
row1.addItem(QSpacerItem(
75+
20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
76+
self.dateTimeLabel.setText(QDateTime.fromString(self.release.assets_created_at, "yyyy-MM-ddThh:mm:ssZ").toString("yyyy-MM-dd hh:mm:ss"))
77+
row1.addWidget(self.dateTimeLabel)
7378
row1.addItem(QSpacerItem(
7479
20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
7580
self.downloadButton.setEnabled(self.mode == ">")
@@ -93,7 +98,7 @@ def initUi(self):
9398
formLayout.addRow(QObject.tr(self, "download_count"),
9499
QLabel(str(self.release.assets_download_count)))
95100
formLayout.addRow(QObject.tr(self, "created_at"),
96-
QLabel(self.release.assets_created_at))
101+
QLabel(QDateTime.fromString(self.release.assets_created_at, "yyyy-MM-ddThh:mm:ssZ").toString("yyyy-MM-dd hh:mm:ss")))
97102
downloadUrlLabel = QLabel()
98103
downloadUrlLabel.setText("<a href='" + self.release.assets_browser_download_url +
99104
"'>" + QObject.tr(self, "open download links") + "</a>")
@@ -236,6 +241,8 @@ def changeSource(self, source: str):
236241
self.sourceSpeedLabel.setText("---ms")
237242
self.pingThread.pingSignal.connect(lambda x,y: self.sourceSpeedLabel.setText(f"{int(y)}ms"))
238243
self.pingThread.start()
244+
self.github.sourceManager.currentSource = source
245+
self.checkUpdateButton.click()
239246
@pyqtSlot(list)
240247
def checkUpdate(self, releases: list):
241248
widget = self.releaseArea.widget()
@@ -266,6 +273,10 @@ def showDownloadDialog(self, release: Release):
266273
if self.processDialog is not None:
267274
self.processDialog.close()
268275
self.processDialog = QProgressDialog(self)
276+
# 取消信号
277+
self.processDialog.canceled.connect(
278+
self.downloadCancel
279+
)
269280
self.processDialog.setWindowTitle(QObject.tr(
270281
self, f"{release.tag_name} Downloading..."))
271282

@@ -283,6 +294,11 @@ def hideDownloadDialog(self, path: str):
283294
# 使用系统默认方式打开文件
284295
QDesktopServices.openUrl(QUrl.fromLocalFile(path))
285296

297+
def downloadCancel(self):
298+
self.github.closeAllRequest()
299+
if self.processDialog is not None:
300+
self.processDialog.close()
301+
self.processDialog = None
286302

287303
if __name__ == '__main__':
288304
import sys

src/githubApi.py

Lines changed: 141 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import os
77
import tempfile
88
import subprocess
9-
9+
import uuid
1010

1111
class PingThread(QThread):
1212
pingSignal = pyqtSignal(str,float)
@@ -74,10 +74,22 @@ def sources(self,sources:dict):
7474

7575
@property
7676
def currentSource(self):
77+
"""
78+
当前使用的源
79+
"""
7780
return self.__currentSource
7881

7982
@currentSource.setter
8083
def currentSource(self,currentSource:str):
84+
"""
85+
设置当前使用的源
86+
87+
Args:
88+
currentSource (str): 源名称
89+
90+
Raises:
91+
ValueError: 如果currentSource不在sources中
92+
"""
8193
if currentSource not in self.sources:
8294
raise ValueError
8395
self.__currentSource = currentSource
@@ -87,6 +99,9 @@ def currentSourceUrl(self):
8799
return self.sources[self.currentSource]
88100

89101
def checkSourceSpeed(self):
102+
"""
103+
检查所有源的速度,并发射quickSource(name:str,time:float)信号
104+
"""
90105
self.threads.clear()
91106
self.speedData = {}
92107
for source in self.sources:
@@ -127,12 +142,21 @@ class GitHub(QObject):
127142
downloadReleaseAsyncFinishSignal = pyqtSignal(str)
128143
errorSignal = pyqtSignal(str)
129144
def __init__(self,sourcemanager:SourceManager,owner:str,repo:str,version:str,versionReStr:str,parent=None):
145+
"""
146+
:param sourcemanager: 一个SourceManager实例,提供了多个github api的url
147+
:param owner: github的owner
148+
:param repo: github的仓库名
149+
:param version: 当前的版本号
150+
:param versionReStr: 一个正则表达式串,用于从github的release中,提取版本号
151+
:param parent: 一个QObject实例,父对象
152+
"""
130153
super().__init__(parent)
131154
self.__sourcemanager = sourcemanager
132155
self.__owner = owner
133156
self.__repo = repo
134157
self.__version = version
135158
self.__versionReStr = versionReStr
159+
self.__replyDict = {}
136160

137161
@property
138162
def sourceManager(self):
@@ -175,20 +199,36 @@ def versionReStr(self,versionReStr:str):
175199
self.__versionReStr = versionReStr
176200
@property
177201
def latestReleaseUrl(self) -> str:
202+
"""
203+
获取最新的release的url
204+
"""
178205
return f'{self.sourceManager.currentSourceUrl}/{self.__owner}/{self.__repo}/releases/latest'
179206
@property
180207
def releasesUrl(self) -> str:
208+
"""
209+
获取所有release的url
210+
"""
181211
return f'{self.sourceManager.currentSourceUrl}/{self.__owner}/{self.__repo}/releases'
182212
@property
183213
def header(self) -> dict:
184214
return {
185215
'Accept':'application/vnd.github.v3+json',
186216
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'
187217
}
188-
def isNeedUpdate(self,isAsync:bool = True) -> bool | None:
218+
def isNeedUpdate(self,isAsync:bool = True) -> bool | str | None:
219+
"""
220+
判断是否需要更新
221+
222+
:param isAsync: 是否异步
223+
:return: bool :如果不是异步,返回True表示需要更新,False表示不需要更新,None表示网络错误 \n
224+
str: 如果是异步,返回一个当前请求的唯一标识,并发射isNeedUpdateAsyncSignal信号
225+
"""
226+
189227
nam = QNetworkAccessManager(self)
190228
request = QNetworkRequest(QUrl(self.latestReleaseUrl))
191229
reply = nam.get(request)
230+
reply.setObjectName(str(uuid.uuid1()))
231+
self.__replyDict[reply.objectName()] = reply
192232
if not isAsync:
193233
loop = QEventLoop()
194234
reply.finished.connect(loop.quit)
@@ -197,11 +237,12 @@ def isNeedUpdate(self,isAsync:bool = True) -> bool | None:
197237
self.error(reply.errorString())
198238
return None
199239
data = reply.readAll().data().decode('utf-8')
240+
self.__replyDict.pop(reply.objectName())
200241
reply.deleteLater()
201242
return self.__isNeedUpdate(data)
202243
else:
203244
nam.finished.connect(self.__isNeedUpdateAsync)
204-
return None
245+
return reply.objectName()
205246

206247
def __isNeedUpdate(self,data)->bool:
207248
release = Release(json.loads(data))
@@ -215,16 +256,29 @@ def __isNeedUpdate(self,data)->bool:
215256
return True
216257

217258
def __isNeedUpdateAsync(self,reply:QNetworkReply):
218-
if reply.error() != QNetworkReply.NoError:
259+
if reply.error() == QNetworkReply.NetworkError.OperationCanceledError:
260+
pass
261+
elif reply.error() != QNetworkReply.NoError:
219262
self.error(reply.errorString())
220-
return
221-
self.isNeedUpdateAsyncSignal.emit(self.__isNeedUpdate(reply.readAll().data().decode('utf-8')))
263+
else:
264+
self.isNeedUpdateAsyncSignal.emit(self.__isNeedUpdate(reply.readAll().data().decode('utf-8')))
265+
self.__replyDict.pop(reply.objectName())
222266
reply.deleteLater()
223267

224-
def latestRelease(self,isAsync:bool = True) -> Release | None:
268+
def latestRelease(self,isAsync:bool = True) -> Release | str | None:
269+
"""
270+
获取最新的release
271+
272+
:param isAsync: 是否异步
273+
:return: bool :如果不是异步,返回一个Release实例
274+
str: 如果是异步,返回一个当前请求的唯一标识,并发射latestReleaseAsyncSignal信号
275+
"""
276+
225277
nam = QNetworkAccessManager(self)
226278
request = QNetworkRequest(QUrl(self.latestReleaseUrl))
227279
reply = nam.get(request)
280+
reply.setObjectName(str(uuid.uuid1()))
281+
self.__replyDict[reply.objectName()] = reply
228282
if not isAsync:
229283
loop = QEventLoop()
230284
reply.finished.connect(loop.quit)
@@ -233,23 +287,36 @@ def latestRelease(self,isAsync:bool = True) -> Release | None:
233287
self.error(reply.errorString())
234288
return None
235289
data = reply.readAll().data().decode('utf-8')
290+
self.__replyDict.pop(reply.objectName())
236291
reply.deleteLater()
237292
return Release(json.loads(data))
238293
else:
239294
nam.finished.connect(self.__lastReleaseAsync)
240-
return None
295+
return reply.objectName()
241296

242297
def __lastReleaseAsync(self,reply:QNetworkReply):
243-
if reply.error() != QNetworkReply.NoError:
298+
if reply.error() == QNetworkReply.NetworkError.OperationCanceledError:
299+
pass
300+
elif reply.error() != QNetworkReply.NoError:
244301
self.error(reply.errorString())
245-
return
246-
self.latestReleaseAsyncSignal.emit(Release(json.loads(reply.readAll().data().decode('utf-8'))))
247-
302+
else:
303+
self.latestReleaseAsyncSignal.emit(Release(json.loads(reply.readAll().data().decode('utf-8'))))
304+
self.__replyDict.pop(reply.objectName())
305+
reply.deleteLater()
248306

249-
def releases(self,isAsync:bool = True) -> list | None:
307+
def releases(self,isAsync:bool = True) -> list | str | None:
308+
"""
309+
获取所有release
310+
311+
:param isAsync: 是否异步
312+
:return: list :如果不是异步,返回一个Release实例的列表
313+
str: 如果是异步,返回一个当前请求的唯一标识,并发射releasesAsyncSignal信号
314+
"""
250315
nam = QNetworkAccessManager(self)
251316
request = QNetworkRequest(QUrl(self.releasesUrl))
252317
reply = nam.get(request)
318+
reply.setObjectName(str(uuid.uuid1()))
319+
self.__replyDict[reply.objectName()] = reply
253320
if not isAsync:
254321
loop = QEventLoop()
255322
reply.finished.connect(loop.quit)
@@ -258,51 +325,79 @@ def releases(self,isAsync:bool = True) -> list | None:
258325
self.error(reply.errorString())
259326
return None
260327
data = reply.readAll().data().decode('utf-8')
328+
self.__replyDict.pop(reply.objectName())
261329
reply.deleteLater()
262330
return self.__releases(data)
263331
else:
264332
nam.finished.connect(self.__releasesAsync)
265-
return None
333+
return reply.objectName()
266334

267335

268336
def __releases(self,data) -> list | None:
269337
return [Release(x) for x in json.loads(data)]
270338

271339
def __releasesAsync(self,reply:QNetworkReply):
272-
if reply.error() != QNetworkReply.NoError:
340+
if reply.error() == QNetworkReply.NetworkError.OperationCanceledError:
341+
pass
342+
elif reply.error() != QNetworkReply.NoError:
273343
self.error(reply.errorString())
274-
return
275-
releases = self.__releases(reply.readAll().data().decode('utf-8'))
344+
else:
345+
releases = self.__releases(reply.readAll().data().decode('utf-8'))
346+
self.releasesAsyncSignal.emit(releases)
347+
self.__replyDict.pop(reply.objectName(),None)
276348
reply.deleteLater()
277-
self.releasesAsyncSignal.emit(releases)
278349

279-
def downloadRelease(self,release:Release):
280-
nam = QNetworkAccessManager(self)
350+
351+
def downloadRelease(self,release:Release) -> str:
352+
"""
353+
下载release
354+
355+
:param release: 需要下载的release
356+
:return: 一个当前请求的唯一标识
357+
"""
281358
self.downloadReleaseAsyncStartSignal.emit(release)
359+
nam = QNetworkAccessManager(self)
282360
request = QNetworkRequest(QUrl(release.assets_browser_download_url))
283361
request.setAttribute(QNetworkRequest.Attribute.FollowRedirectsAttribute,True)
284362
reply = nam.get(request)
363+
reply.setObjectName(str(uuid.uuid1()))
364+
self.__replyDict[reply.objectName()] = reply
285365
reply.downloadProgress.connect(self.downloadProgress)
286366
reply.finished.connect( lambda : self.saveFile(release))
367+
return reply.objectName()
287368

288369
def downloadProgress(self,bytesReceived:int,bytesTotal:int):
370+
"""
371+
用于触发downloadReleaseAsyncProgressSignal信号
372+
373+
:param bytesReceived: 已经下载的字节数
374+
:param bytesTotal: 需要下载的总字节数
375+
"""
289376
self.downloadReleaseAsyncProgressSignal.emit(bytesReceived,bytesTotal)
290377
def saveFile(self,release:Release):
378+
"""
379+
从reply中读取数据并根据release.assets_name保存到临时文件夹
380+
381+
:param release: 需要保存的release
382+
:return: None
383+
"""
291384
reply:QNetworkReply = self.sender()
292-
if reply.error() != QNetworkReply.NoError:
385+
if reply.error() == QNetworkReply.NetworkError.OperationCanceledError:
386+
pass
387+
elif reply.error() != QNetworkReply.NoError:
293388
self.error(reply.errorString())
294-
return
295-
# 临时文件夹
296-
tempDir = tempfile.gettempdir()
297-
path = os.path.join(tempDir,release.assets_name)
298-
# 判断是否存在
299-
if os.path.exists(path):
300-
os.remove(path)
301-
with open(path,'wb') as f:
302-
f.write(reply.readAll())
389+
else:
390+
# 临时文件夹
391+
tempDir = tempfile.gettempdir()
392+
path = os.path.join(tempDir,release.assets_name)
393+
# 判断是否存在
394+
if os.path.exists(path):
395+
os.remove(path)
396+
with open(path,'wb') as f:
397+
f.write(reply.readAll())
398+
self.downloadReleaseAsyncFinishSignal.emit(path)
399+
self.__replyDict.pop(reply.objectName())
303400
reply.deleteLater()
304-
self.downloadReleaseAsyncFinishSignal.emit(path)
305-
306401
def compareVersion(self,v2) -> str:
307402
v = re.findall(self.__versionReStr,self.__version)
308403
if len(v) == 0:
@@ -324,8 +419,21 @@ def compareVersion(self,v2) -> str:
324419
def error(self,errorStr:str):
325420
self.errorSignal.emit(errorStr)
326421

327-
422+
def closeAllRequest(self):
423+
"""关闭所有的请求"""
424+
keys = list(self.__replyDict.keys())
425+
for key in keys:
426+
self.__replyDict[key].abort()
427+
self.__replyDict.clear()
428+
def closeRequest(self,id:str):
429+
"""关闭单个请求
328430
431+
Args:
432+
id (str): 请求的唯一标识
433+
"""
434+
if id in self.__replyDict:
435+
self.__replyDict[id].abort()
436+
self.__replyDict.pop(id)
329437
if __name__ == '__main__':
330438
app = QCoreApplication([])
331439
data = {

0 commit comments

Comments
 (0)