add AO2XP auto-updater

This commit is contained in:
Headshotnoby 2021-06-21 22:57:55 -04:00
parent 4c5095ec9b
commit 6bff2b2b21
6 changed files with 291 additions and 7 deletions

View File

@ -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()

View File

@ -1,3 +1,4 @@
pyinstaller AO2XP.spec
pyinstaller AO2XP_console.spec
pyinstaller install_update.spec
move .\dist\* .

30
install_update.py Normal file
View 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
View 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 )

View File

@ -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 ######
@ -92,7 +93,15 @@ class Settings(QtGui.QDialog):
self.currtheme.addItem(theme)
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
View 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()