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