AO2XP/updater.py
2021-06-22 22:12:40 -04:00

197 lines
7.4 KiB
Python

import json, sys, requests, time, os, platform, traceback
from io import BytesIO
from PyQt4 import QtGui, QtCore
from game_version import *
returncode = -1
assetfile = {
"Windows": "AO2XP-Windows.zip",
"Darwin": "AO2XP-macOS.zip",
"Linux": "AO2XP-Linux.tar.gz"
}
def checkForUpdates(force=False):
update_dialog = QtGui.QProgressDialog()
update_dialog.setWindowTitle("AO2XP updater")
update_dialog.setAutoClose(False)
update_dialog.setAutoReset(False)
update_dialog.setLabelText("Checking for client updates...")
update_dialog.resize(512, 96)
update_dialog.setMinimum(0)
update_dialog.setMaximum(0)
update_dialog.setValue(0)
thr = updateThread(update_dialog, force)
def setProgressValue(value):
update_dialog.setValue(value)
def setLabelText(msg):
update_dialog.setLabelText(msg)
def showMessageBox(icon, title, msg):
getattr(QtGui.QMessageBox, str(icon))(None, title, msg)
def updateAvailableCall(name, version, body):
updateWindow = updateAvailableWidget(name, version, body)
result = updateWindow.show()
thr.waitConfirm = 1 if result else -1
if not result:
update_dialog.close()
returncode = -2
else:
update_dialog.setMinimum(0)
update_dialog.setMaximum(100)
thr.progressValue.connect(setProgressValue)
thr.labelText.connect(setLabelText)
thr.showMessageBox.connect(showMessageBox)
thr.updateAvailable.connect(updateAvailableCall)
thr.finished.connect(update_dialog.close)
thr.start()
update_dialog.exec_()
if not update_dialog.wasCanceled() and thr.isRunning(): thr.wait()
print "update return code:", returncode
return returncode
class updateAvailableWidget(QtGui.QDialog):
def __init__(self, title, version, body):
super(self.__class__, self).__init__()
self.layout = QtGui.QVBoxLayout(self)
self.layout.setAlignment(QtCore.Qt.AlignTop)
self.available = QtGui.QLabel(text="Changelog for update \"%s\":" % title)
self.changelog = QtGui.QTextEdit()
self.confirmtext = QtGui.QLabel(text="Do you want to download and install the update?")
self.yesnolayout = QtGui.QHBoxLayout()
self.yes = QtGui.QPushButton(text="Yes")
self.no = QtGui.QPushButton(text="No")
self.setModal(True)
self.setWindowTitle("Update %s available" % version)
self.changelog.setText(body)
self.changelog.setReadOnly(True)
self.yes.clicked.connect(self.confirm)
self.no.clicked.connect(self.cancel)
self.yesnolayout.addWidget(self.yes)
self.yesnolayout.addWidget(self.no)
self.layout.addWidget(self.available)
self.layout.addWidget(self.changelog)
self.layout.addWidget(self.confirmtext)
self.layout.addLayout(self.yesnolayout, QtCore.Qt.AlignBottom)
self.result = False
self.resize(640, self.sizeHint().height())
def confirm(self):
self.close()
self.result = True
def cancel(self):
self.close()
self.result = False
def show(self):
super(self.__class__, self).show()
self.exec_()
return self.result
class updateThread(QtCore.QThread):
progressValue = QtCore.pyqtSignal(int)
labelText = QtCore.pyqtSignal(str)
showMessageBox = QtCore.pyqtSignal(str, str, str)
updateAvailable = QtCore.pyqtSignal(str, str, str)
finished = QtCore.pyqtSignal()
waitConfirm = 0
def __init__(self, nouis, force):
super(self.__class__, self).__init__()
self.jm = nouis
self.force = force
def run(self):
global returncode
try:
manifest = json.load(BytesIO(requests.get("http://api.github.com/repos/headshot2017/AO2XP/releases").content))
except:
print traceback.format_exc()
self.showMessageBox.emit("critical", "Error", "Failed to check for updates.\nPlease check your internet connection.")
self.finished.emit()
return
if self.jm.wasCanceled():
return
if GAME_VERSION != manifest[0]["tag_name"] or self.force: # update available
self.updateAvailable.emit(manifest[0]["name"], manifest[0]["tag_name"], manifest[0]["body"])
self.waitConfirm = 0
while self.waitConfirm == 0: pass
if self.waitConfirm > 0:
resume_bytes = 0
filename = assetfile[platform.system()]
link = ""
for asset in manifest[0]["assets"]:
if asset["name"] == filename:
link = asset["browser_download_url"]
if not link:
self.showMessageBox.emit("critical", "Error", "Release %s is missing the '%s' file.\nCannot continue updating." % (manifest[0]["tag_name"], filename))
self.finished.emit()
return
path = sys.argv[0]
if platform.system() == "Darwin" and ".app" in path: # bundle
path = "/".join(path.split("/")[:-4]) # right next to the .app package
os.chdir(path)
updatezip = "update" + os.path.splitext(filename)[1]
if not os.path.exists(updatezip):
downloadfile = open(updatezip, "wb")
else:
existing_data = open(updatezip, "rb").read()
downloadfile = open(updatezip, "ab")
resume_bytes = len(existing_data)
print resume_bytes
del existing_data
self.labelText.emit("Downloading '%s' for update '%s'..." % (filename, manifest[0]["name"]))
dl = resume_bytes
speed = 0.0
start = time.clock()
calcspeed_time = time.time()
zip = requests.get(link, stream=True, headers={"Range": "bytes=%d-" % resume_bytes})
length = resume_bytes + int(zip.headers.get("content-length"))
for noby in zip.iter_content(chunk_size=4096):
if not self.jm.isVisible():
downloadfile.close()
return
downloadfile.write(noby)
dl += len(noby)
percent = 100 * dl / length
if percent != self.jm.value():
self.progressValue.emit(percent)
self.labelText.emit("Downloading '%s' for update '%s'... %.1f KB/s" % (filename, manifest[0]["name"], speed))
if (time.time() - calcspeed_time) >= 0.5:
calcspeed_time = time.time()
speed = ((dl-resume_bytes)/(time.clock() - start)) / 1024.
self.labelText.emit("Downloading '%s' for update '%s'... %.1f KB/s" % (filename, manifest[0]["name"], speed))
print "downloaded update"
downloadfile.close()
if platform.system() == "Darwin" and ".app" in sys.argv[0]: # bundle
path = "/".join(sys.argv[0].split("/")[:-1]) # to Resources folder
os.system("cp -r "+path+"/base ./appbase") # move the base folder out of the app folder to avoid deletion
returncode = 0
else:
returncode = -2
self.finished.emit()