add AO2XP auto-updater
This commit is contained in:
		
							parent
							
								
									4c5095ec9b
								
							
						
					
					
						commit
						6bff2b2b21
					
				
							
								
								
									
										18
									
								
								AO2XP.py
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								AO2XP.py
									
									
									
									
									
								
							| @ -1,9 +1,11 @@ | ||||
| import sys, thread, time, os, platform, __builtin__ | ||||
| import subprocess, sys, thread, time, os, platform, __builtin__ | ||||
| from os.path import exists, abspath | ||||
| 
 | ||||
| from PyQt4 import QtGui, QtCore | ||||
| 
 | ||||
| import audio as AUDIO | ||||
| import ini | ||||
| 
 | ||||
| __builtin__.audio = AUDIO | ||||
| del AUDIO | ||||
| 
 | ||||
| @ -62,6 +64,7 @@ class gamewindow(QtGui.QMainWindow): | ||||
| 		self.settingsgui.showSettings() | ||||
| 
 | ||||
| if not debugmode: | ||||
|     # Vanilla downloader | ||||
|     force_downloader = len(sys.argv) > 1 and sys.argv[1] == "download" | ||||
|     if force_downloader or (not exists("base/background") and not exists("base/characters") and not exists("base/sounds") and not exists("base/evidence")): | ||||
|         jm = QtGui.QMessageBox.information(None, "Warning", "You seem to be missing the included Attorney Online content.\nWould you like to download them automatically?", QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) | ||||
| @ -72,7 +75,18 @@ if not debugmode: | ||||
|         else: | ||||
|             os._exit(-3) | ||||
| 
 | ||||
| import gameview, mainmenu, options, ini | ||||
|     # AO2XP update checker | ||||
|     can_update = ini.read_ini_bool("AO2XP.ini", "General", "install updates", True) | ||||
|     force_update = "forceupdate" in sys.argv[1:] | ||||
|     if can_update or force_update: | ||||
|         import updater | ||||
|         code = updater.checkForUpdates(force_update) | ||||
|         if code == 0: | ||||
|             subprocess.Popen(["./AO2XPupdat"]) | ||||
|             os._exit(0) | ||||
| 
 | ||||
| 
 | ||||
| import gameview, mainmenu, options | ||||
| 
 | ||||
| audio.init() | ||||
| shit = gamewindow() | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| pyinstaller AO2XP.spec | ||||
| pyinstaller AO2XP_console.spec | ||||
| pyinstaller install_update.spec | ||||
| move .\dist\* . | ||||
|  | ||||
							
								
								
									
										30
									
								
								install_update.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								install_update.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| import zipfile, tarfile, platform, os, time, shutil | ||||
| 
 | ||||
| ext = { | ||||
|     "Windows": "zip", | ||||
|     "Darwin": "zip", | ||||
|     "Linux": "gz" | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| def extractzip(): # Mac | ||||
|     archive = zipfile.ZipFile("update.zip") | ||||
|     if platform.system() == "Darwin": shutil.rmtree("AO2XP.app", ignore_errors=True) # delete the old app package. | ||||
|     archive.extractall() # extract the new version | ||||
| 
 | ||||
| def extractgz(): # Linux | ||||
|     archive = tarfile.open("update.tar.gz") | ||||
|     archive.extractall() | ||||
| 
 | ||||
| 
 | ||||
| if os.path.exists("update." + ext[platform.system()]): | ||||
|     print "Waiting 3 seconds for AO2XP to close..." | ||||
|     time.sleep(3) | ||||
|     print "Extracting update." + ext[platform.system()] + "..." | ||||
|     globals()["extract" + ext[platform.system()]]() # call the extract function according to OS | ||||
|     print "Done!" | ||||
|     os.remove("update." + ext[platform.system()]) | ||||
| 
 | ||||
| else: | ||||
|     print "This program will be automatically run by AO2XP to apply updates.\nYou do not need to run this yourself." | ||||
|     raw_input("Press enter to exit.\n") | ||||
							
								
								
									
										34
									
								
								install_update.spec
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								install_update.spec
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| # -*- mode: python ; coding: utf-8 -*- | ||||
| 
 | ||||
| block_cipher = None | ||||
| 
 | ||||
| 
 | ||||
| a = Analysis(['install_update.py'], | ||||
|              pathex=['C:\\Users\\Public\\1.7.5 Cut Content Patch\\Client'], | ||||
|              binaries=[], | ||||
|              datas=[], | ||||
|              hiddenimports=[], | ||||
|              hookspath=[], | ||||
|              runtime_hooks=[], | ||||
|              excludes=[], | ||||
|              win_no_prefer_redirects=False, | ||||
|              win_private_assemblies=False, | ||||
|              cipher=block_cipher, | ||||
|              noarchive=False) | ||||
| pyz = PYZ(a.pure, a.zipped_data, | ||||
|              cipher=block_cipher) | ||||
| exe = EXE(pyz, | ||||
|           a.scripts, | ||||
|           a.binaries, | ||||
|           a.zipfiles, | ||||
|           a.datas, | ||||
|           [], | ||||
|           name='AO2XPupdat', | ||||
|           debug=False, | ||||
|           bootloader_ignore_signals=False, | ||||
|           strip=False, | ||||
|           upx=True, | ||||
|           upx_exclude=[], | ||||
|           runtime_tmpdir=None, | ||||
| 		  manifest=None, | ||||
|           console=True ) | ||||
							
								
								
									
										29
									
								
								options.py
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								options.py
									
									
									
									
									
								
							| @ -1,6 +1,7 @@ | ||||
| from ConfigParser import ConfigParser | ||||
| from os.path import exists | ||||
| from os import listdir | ||||
| from os import listdir, _exit | ||||
| import subprocess | ||||
| 
 | ||||
| from PyQt4 import QtGui, QtCore | ||||
| 
 | ||||
| @ -46,9 +47,9 @@ class Settings(QtGui.QDialog): | ||||
| 		cancelbtn.clicked.connect(self.onCancelClicked) | ||||
| 		 | ||||
| 		separators = [] | ||||
| 		for i in range(4): | ||||
| 		for i in range(5): | ||||
| 			separator = QtGui.QFrame() | ||||
| 			separator.setFixedSize(separator.size().width(), 16) | ||||
| 			separator.setFixedSize(separator.size().width(), 12) | ||||
| 			separators.append(separator) | ||||
| 		 | ||||
| 		###### General tab ###### | ||||
| @ -93,6 +94,14 @@ class Settings(QtGui.QDialog): | ||||
| 		currtheme_layout.addWidget(currtheme_label) | ||||
| 		currtheme_layout.addWidget(self.currtheme) | ||||
| 
 | ||||
| 		update_layout = QtGui.QHBoxLayout() | ||||
| 		self.check_updates = QtGui.QCheckBox("Check for AO2XP updates on startup") | ||||
| 		self.check_updates_btn = QtGui.QPushButton() | ||||
| 		self.check_updates_btn.setText("Check now...") | ||||
| 		self.check_updates_btn.clicked.connect(self.onUpdateClicked) | ||||
| 		update_layout.addWidget(self.check_updates) | ||||
| 		update_layout.addWidget(self.check_updates_btn) | ||||
| 
 | ||||
| 		savechangeswarn = QtGui.QLabel() | ||||
| 		savechangeswarn.setText("* Change takes effect upon restarting the client") | ||||
| 		 | ||||
| @ -106,6 +115,8 @@ class Settings(QtGui.QDialog): | ||||
| 		general_layout.addLayout(allowdownload_layout) | ||||
| 		general_layout.addWidget(separators[2]) | ||||
| 		general_layout.addLayout(currtheme_layout) | ||||
| 		general_layout.addWidget(separators[3]) | ||||
| 		general_layout.addLayout(update_layout) | ||||
| 		general_layout.addWidget(savechangeswarn, 50, QtCore.Qt.AlignBottom) | ||||
| 
 | ||||
| 		###### Audio tab ###### | ||||
| @ -113,7 +124,7 @@ class Settings(QtGui.QDialog): | ||||
| 		self.device_list = QtGui.QComboBox() | ||||
| 		audio_layout.setWidget(0, QtGui.QFormLayout.LabelRole, device_label) | ||||
| 		audio_layout.setWidget(0, QtGui.QFormLayout.FieldRole, self.device_list) | ||||
| 		audio_layout.setWidget(1, QtGui.QFormLayout.FieldRole, separators[3]) | ||||
| 		audio_layout.setWidget(1, QtGui.QFormLayout.FieldRole, separators[4]) | ||||
| 
 | ||||
| 		volumelabel = QtGui.QLabel("Sound volume") | ||||
| 		musiclabel = QtGui.QLabel("Music") | ||||
| @ -176,6 +187,7 @@ class Settings(QtGui.QDialog): | ||||
| 			self.allowdownload_music.setChecked(ini.read_ini_bool(self.inifile, "General", "download music")) | ||||
| 			self.allowdownload_evidence.setChecked(ini.read_ini_bool(self.inifile, "General", "download evidence")) | ||||
| 			self.currtheme.setCurrentIndex(self.themes.index(ini.read_ini(self.inifile, "General", "theme", "default"))) | ||||
| 			self.check_updates.setChecked(ini.read_ini_bool(self.inifile, "General", "install updates", True)) | ||||
| 
 | ||||
| 			self.device_list.setCurrentIndex(ini.read_ini_int(self.inifile, "Audio", "device", audio.getcurrdevice())) | ||||
| 			self.musicslider.setValue(ini.read_ini_int(self.inifile, "Audio", "Music volume", 100)) | ||||
| @ -190,6 +202,7 @@ class Settings(QtGui.QDialog): | ||||
| 			self.allowdownload_music.setChecked(True) | ||||
| 			self.allowdownload_evidence.setChecked(True) | ||||
| 			self.currtheme.setCurrentIndex(self.themes.index("default")) | ||||
| 			self.check_updates.setChecked(True) | ||||
| 
 | ||||
| 			self.device_list.setCurrentIndex(audio.getcurrdevice()) | ||||
| 			self.musicslider.setValue(100) | ||||
| @ -223,6 +236,7 @@ class Settings(QtGui.QDialog): | ||||
| 		self.inifile.set("General", "download music", self.allowdownload_music.isChecked()) | ||||
| 		self.inifile.set("General", "download evidence", self.allowdownload_evidence.isChecked()) | ||||
| 		self.inifile.set("General", "theme", self.currtheme.currentText()) | ||||
| 		self.inifile.set("General", "install updates", self.check_updates.isChecked()) | ||||
| 
 | ||||
| 		self.inifile.set("Audio", "device", self.device_list.currentIndex()) | ||||
| 		self.inifile.set("Audio", "Music volume", self.musicslider.value()) | ||||
| @ -238,3 +252,10 @@ class Settings(QtGui.QDialog): | ||||
| 	 | ||||
| 	def onCancelClicked(self): | ||||
| 		self.hide() | ||||
| 
 | ||||
| 	def onUpdateClicked(self): | ||||
| 		import updater | ||||
| 		code = updater.checkForUpdates() | ||||
| 		if code == 0: | ||||
| 			subprocess.Popen(["./AO2XPupdat"]) | ||||
| 			_exit(0) | ||||
							
								
								
									
										184
									
								
								updater.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								updater.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,184 @@ | ||||
| import json, urllib, sys, requests, time, os, platform | ||||
| 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(urllib.urlopen("http://api.github.com/repos/headshot2017/AO2XP/releases")) | ||||
|         except: | ||||
|             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 | ||||
| 
 | ||||
|                 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() | ||||
|                 returncode = 0 | ||||
|         else: | ||||
|             returncode = -2 | ||||
| 
 | ||||
|         self.finished.emit() | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Headshotnoby
						Headshotnoby