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 self.labelText.emit("Making a backup of AO base folder...") 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()