From 4d4ff4996cdb3d2ceda2e7d57c93b0ed0fc760a8 Mon Sep 17 00:00:00 2001 From: Wolfmyths Date: Thu, 21 Sep 2023 20:33:29 -0400 Subject: [PATCH 1/9] Auto updater + Added auto updater - Downloads and extracts latest release when checkupdate.exe is ran - Uses startup.exe to install which is started by checkupdate.exe. When that ends then continue startRPC.bat --- requirements.txt | 3 +- src/checkupdate/checkupdate.py | 25 +++++++---- src/checkupdate/downloadupdate.py | 72 +++++++++++++++++++++++++++++++ src/discordRPC/hoi4RPC.py | 14 +++--- src/setup.py | 20 +++++++-- 5 files changed, 114 insertions(+), 20 deletions(-) create mode 100644 src/checkupdate/downloadupdate.py diff --git a/requirements.txt b/requirements.txt index 0abffb6..fd60aa9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ pypresence==4.2.1 semantic-version==2.10.0 -pyinstaller==5.13.0 \ No newline at end of file +pyinstaller==5.13.0 +requests==2.31.0 \ No newline at end of file diff --git a/src/checkupdate/checkupdate.py b/src/checkupdate/checkupdate.py index 3543cf4..8847890 100644 --- a/src/checkupdate/checkupdate.py +++ b/src/checkupdate/checkupdate.py @@ -1,4 +1,5 @@ # COMPILE THIS USING --onefile +import subprocess import semantic_version import time import urllib.request @@ -7,6 +8,8 @@ import sys import os +from checkupdate.downloadupdate import downloadUpdate + def checkUpdate(): try: # picking the current dir @@ -16,8 +19,8 @@ def checkUpdate(): else: version_path = os.path.dirname(os.path.abspath(__file__)) - localVersion = open(version_path + "/version.json", "r") # use something like .resolve() or clean code - localVersion = json.load(localVersion) + with open(version_path + "/version.json", "r") as f: + localVersion = json.load(f) print("\nLocal version: ") print(localVersion) @@ -31,12 +34,16 @@ def checkUpdate(): cloudVersion = json.loads(URL.read()) print("Checking for updates in hoi4 presence...") + if semantic_version.Version(localVersion["version"]) < semantic_version.Version(cloudVersion["version"]): - print("\n\n\nUpdate available!") - print("Please, download the latest version from:\nhttps://github.com/ThiaudioTT/hoi4-presence/releases") - time.sleep(120) - else: - print("Update not found.") - time.sleep(5) -checkUpdate() \ No newline at end of file + downloadPath = downloadUpdate() + + if downloadPath is not None: + + installerPath = os.path.join(downloadPath, "setup.exe") + + # Using `subproccess.call()` so the script can end when `setup.exe` ends, which will continue `runRPC.bat` + subprocess.call([installerPath, "-auto"]) + +checkUpdate() diff --git a/src/checkupdate/downloadupdate.py b/src/checkupdate/downloadupdate.py new file mode 100644 index 0000000..4888027 --- /dev/null +++ b/src/checkupdate/downloadupdate.py @@ -0,0 +1,72 @@ +import os +import shutil + +import requests + +def downloadUpdate() -> str | None: + ''' + Downloads and installs the newest hoi4 update. + + Returns download path if successful, else None + ''' + + try: + + downloadTemp: str = None + extractedTemp: str = None + + # Get latest release data + latestRelease = requests.get("https://api.github.com/repos/ThiaudioTT/hoi4-presence/releases/latest") + + latestReleaseData: dict = latestRelease.json() + + # Find asset data api link + assetUrl: str = latestReleaseData["assets_url"] + + # Get asset data + assetsData: dict = requests.get(assetUrl , headers={"accept":"application/vnd.github+json"}).json() + + # Incase there are muiltiple assets create a for loop + downloadLink: str = None + fileName = f"hoi4-presence-{latestReleaseData['tag_name']}.zip" + + for asset in assetsData: + + if asset["name"] == fileName: + + # Found the download link + downloadLink = asset["browser_download_url"] + + break + + if downloadLink: + + # Instance a session + session = requests.Session() + + downloadTemp = os.path.join(os.environ["TEMP"], fileName) + + # Creating a new file in "write in binary" mode + with open(downloadTemp, "wb") as f: + + # Get data for download + request = session.get(downloadLink, allow_redirects=True, stream=True) + + # Write it to fileName in chunks + for chunk in request.iter_content(1024**2): + f.write(chunk) + + print("Download successful!") + + extractedTemp = os.path.join(os.environ["TEMP"], f"hoi4-presence-{latestReleaseData['tag_name']}") + + # Unzipping + shutil.unpack_archive(downloadTemp, extractedTemp) + + return extractedTemp + + except Exception as e: + + print(f"An error has occured while updating:\n{e}\n\nStarting current version...") + + return None diff --git a/src/discordRPC/hoi4RPC.py b/src/discordRPC/hoi4RPC.py index 00150f1..ed6e647 100644 --- a/src/discordRPC/hoi4RPC.py +++ b/src/discordRPC/hoi4RPC.py @@ -37,7 +37,7 @@ path = os.path.dirname(sys.executable) else: path = os.path.dirname(os.path.abspath(__file__)) - + path = os.path.abspath(path + "/../" + "/save games/*.hoi4") # getting the last modified save file @@ -48,7 +48,7 @@ now = int(time.time()) saveNew = (now - dateFile) <= 120 # calc to see if save is recently (2 min recently) # saveNew = True # for testing - + if(saveNew): print("New save found!") # putting this in a function might be good. @@ -69,7 +69,7 @@ year = data[2][:4] mode = data[3] - + # hint: use discord developer portal RPC.update( state="Year: " + year, @@ -80,17 +80,17 @@ small_text="In " + mode + " mode", start=playTime ) - + print(data) # debug except Exception as e: print(e) time.sleep(5) - - time.sleep(30) #Wait a wee bit + + time.sleep(30) # Wait a wee bit gameRunning = False for proc in psutil.process_iter(): if proc.name() == "hoi4.exe": gameRunning = True break -sys.exit(0) # when game running is false, exit script \ No newline at end of file +sys.exit(0) # when game running is false, exit script diff --git a/src/setup.py b/src/setup.py index d4e9322..0c1ef4e 100644 --- a/src/setup.py +++ b/src/setup.py @@ -4,8 +4,19 @@ import sys import json +IS_AUTO = '-auto' in sys.argv + +def customInput() -> str | None: + """ + Checks first if this has been started by the auto updater + so the auto installation doesn't freeze from `input()` + """ + + if not IS_AUTO: + return input() + print("This script will install the hoi4-presence in your game/save path\nPress enter to continue...") -input() +customInput() # 1 - Move hoi4Presence to documents/paradox/hearts of iron/ WITH CURRENTLY version.json and uninstaller.py # 1.1 - If can't move, exit. @@ -29,7 +40,7 @@ source = os.path.join(os.path.abspath(os.curdir), "discordRPC\\dist") # If any files are needed within the /dist/ folder, add them here - requiredFiles = ("checkupdate.exe", "hoi4Presence.exe", "version.json", "runRPC.bat") + requiredFiles = ("hoi4Presence.exe", "version.json", "runRPC.bat") filesNotFound = [] # Checking if the required files are in /dist/ @@ -175,6 +186,9 @@ print("\n\nSuccess! The hoi4Presence is installed in your game folder.\n\n") print("Execute the game via launcher to auto activate the presence.\n\nYou can delete this folder now.\n\n") print("See https://github.com/ThiaudioTT/hoi4-presence for updates and more information.\n\n") -input() + +customInput() + time.sleep(5) + sys.exit() From 33a8d0892d4069947ec06c5f7c7ded89e154472c Mon Sep 17 00:00:00 2001 From: Wolfmyths Date: Thu, 21 Sep 2023 20:46:09 -0400 Subject: [PATCH 2/9] Updating commits --- requirements.txt | 1 + version.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index fd60aa9..90cbfce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ pypresence==4.2.1 semantic-version==2.10.0 pyinstaller==5.13.0 +psutil==5.9.5 requests==2.31.0 \ No newline at end of file diff --git a/version.json b/version.json index 10926a6..f1b9ac8 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "version": "1.0.0", + "version": "1.1.0", "auto-update": false, - "date": "22-july-2023" + "date": "19-august-2023" } \ No newline at end of file From 690c0a8071c610056118d70f6aaafeb2829e7100 Mon Sep 17 00:00:00 2001 From: Wolfmyths Date: Mon, 25 Sep 2023 00:47:08 -0400 Subject: [PATCH 3/9] Refactor --- src/checkupdate/checkupdate.py | 7 ++++--- src/setup.py | 9 ++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/checkupdate/checkupdate.py b/src/checkupdate/checkupdate.py index 8847890..b60bc0e 100644 --- a/src/checkupdate/checkupdate.py +++ b/src/checkupdate/checkupdate.py @@ -1,5 +1,4 @@ # COMPILE THIS USING --onefile -import subprocess import semantic_version import time import urllib.request @@ -43,7 +42,9 @@ def checkUpdate(): installerPath = os.path.join(downloadPath, "setup.exe") - # Using `subproccess.call()` so the script can end when `setup.exe` ends, which will continue `runRPC.bat` - subprocess.call([installerPath, "-auto"]) + # Starting setup.exe in -update mode so it will automatically install and start up the mod + os.startfile(installerPath, "-update") + + sys.exit() checkUpdate() diff --git a/src/setup.py b/src/setup.py index 0c1ef4e..ab83d05 100644 --- a/src/setup.py +++ b/src/setup.py @@ -4,7 +4,7 @@ import sys import json -IS_AUTO = '-auto' in sys.argv +IS_UPDATE = '-update' in sys.argv def customInput() -> str | None: """ @@ -12,7 +12,7 @@ def customInput() -> str | None: so the auto installation doesn't freeze from `input()` """ - if not IS_AUTO: + if not IS_UPDATE: return input() print("This script will install the hoi4-presence in your game/save path\nPress enter to continue...") @@ -40,7 +40,7 @@ def customInput() -> str | None: source = os.path.join(os.path.abspath(os.curdir), "discordRPC\\dist") # If any files are needed within the /dist/ folder, add them here - requiredFiles = ("hoi4Presence.exe", "version.json", "runRPC.bat") + requiredFiles = ("checkupdate.exe", "hoi4Presence.exe", "version.json", "runRPC.bat") filesNotFound = [] # Checking if the required files are in /dist/ @@ -191,4 +191,7 @@ def customInput() -> str | None: time.sleep(5) +if IS_UPDATE: + os.startfile(os.path.join(gameFolder, "runRPC.bat")) + sys.exit() From f37ba25d27db8516229f2dbae7927a0afc2c010c Mon Sep 17 00:00:00 2001 From: Wolfmyths Date: Tue, 26 Sep 2023 00:46:02 -0400 Subject: [PATCH 4/9] Updated installer + Created arguments to further automate setup.exe + Gave sys.exit in checkUpdate.exe an exit code + Added print statements to better communicate to the user what's happening + Removed the use of request.session in downloadupdate.py as it might not be needed --- src/checkupdate/checkupdate.py | 21 ++++++++++++++++----- src/checkupdate/downloadupdate.py | 12 ++++++++---- src/setup.py | 28 +++++++++++++++++++++++++--- 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/checkupdate/checkupdate.py b/src/checkupdate/checkupdate.py index b60bc0e..d38fed3 100644 --- a/src/checkupdate/checkupdate.py +++ b/src/checkupdate/checkupdate.py @@ -1,4 +1,5 @@ # COMPILE THIS USING --onefile +import subprocess import semantic_version import time import urllib.request @@ -7,10 +8,13 @@ import sys import os -from checkupdate.downloadupdate import downloadUpdate +from downloadupdate import downloadUpdate def checkUpdate(): try: + + print("Checking for updates in hoi4 presence...") + # picking the current dir version_path = "" if getattr(sys, 'frozen', False): @@ -32,19 +36,26 @@ def checkUpdate(): URL = urllib.request.urlopen("https://raw.githubusercontent.com/ThiaudioTT/hoi4-presence/main/version.json") cloudVersion = json.loads(URL.read()) - print("Checking for updates in hoi4 presence...") - if semantic_version.Version(localVersion["version"]) < semantic_version.Version(cloudVersion["version"]): + print("Update found!") + downloadPath = downloadUpdate() if downloadPath is not None: installerPath = os.path.join(downloadPath, "setup.exe") + print('Updating...\n\n') + + gameFolder = os.path.dirname(os.getcwd()) + + print(gameFolder) + # Starting setup.exe in -update mode so it will automatically install and start up the mod - os.startfile(installerPath, "-update") + subprocess.Popen([installerPath, "-update", gameFolder], start_new_session=True) - sys.exit() + # Close checkUpdate.exe (setup.exe will still run and in the new window) + sys.exit(0) checkUpdate() diff --git a/src/checkupdate/downloadupdate.py b/src/checkupdate/downloadupdate.py index 4888027..9ee1ed5 100644 --- a/src/checkupdate/downloadupdate.py +++ b/src/checkupdate/downloadupdate.py @@ -41,21 +41,25 @@ def downloadUpdate() -> str | None: if downloadLink: - # Instance a session - session = requests.Session() - downloadTemp = os.path.join(os.environ["TEMP"], fileName) # Creating a new file in "write in binary" mode with open(downloadTemp, "wb") as f: # Get data for download - request = session.get(downloadLink, allow_redirects=True, stream=True) + request = requests.get(downloadLink, allow_redirects=True, stream=True) + + chunksDownloaded = 0 + totalChunks = len(request.content) + print("Starting Download") # Write it to fileName in chunks for chunk in request.iter_content(1024**2): + print(f"{chunksDownloaded / totalChunks * 100:.0f}%") f.write(chunk) + chunksDownloaded += len(chunk) + print("100%") print("Download successful!") extractedTemp = os.path.join(os.environ["TEMP"], f"hoi4-presence-{latestReleaseData['tag_name']}") diff --git a/src/setup.py b/src/setup.py index ab83d05..5895016 100644 --- a/src/setup.py +++ b/src/setup.py @@ -4,7 +4,21 @@ import sys import json -IS_UPDATE = '-update' in sys.argv +# Parsing arguments +IS_UPDATE = False + +GAMEFOLDER: str | None = None + +num_of_args = len(sys.argv) + +if num_of_args >= 2: + IS_UPDATE = sys.argv[1] == "-update" + +if num_of_args >= 3: + DOCUMENTS = sys.argv[2] + +if num_of_args >= 4: + GAMEFOLDER = sys.argv[3] def customInput() -> str | None: """ @@ -76,7 +90,11 @@ def customInput() -> str | None: sys.exit() # 1 -documents = os.environ['USERPROFILE'] + "\\Documents\\Paradox Interactive\\Hearts of Iron IV" +if DOCUMENTS: + documents = DOCUMENTS +else: + documents = os.environ['USERPROFILE'] + "\\Documents\\Paradox Interactive\\Hearts of Iron IV" + while True: try: if 'settings.txt' not in os.listdir(documents): @@ -141,7 +159,11 @@ def customInput() -> str | None: sys.exit() # 3 -gameFolder = os.environ['PROGRAMFILES(X86)'] + "\\Steam\\steamapps\\common\\Hearts of Iron IV" +if GAMEFOLDER: + gameFolder = GAMEFOLDER +else: + gameFolder = os.environ['PROGRAMFILES(X86)'] + "\\Steam\\steamapps\\common\\Hearts of Iron IV" + while True: try: if "hoi4.exe" not in os.listdir(gameFolder): From 695f81a22e81be662154d6d88e0e249476a6ce47 Mon Sep 17 00:00:00 2001 From: Wolfmyths Date: Mon, 2 Oct 2023 02:09:26 -0400 Subject: [PATCH 5/9] Removed args, finishing touches --- src/checkupdate/checkupdate.py | 10 +++------- src/checkupdate/downloadupdate.py | 2 +- src/setup.py | 19 ++----------------- 3 files changed, 6 insertions(+), 25 deletions(-) diff --git a/src/checkupdate/checkupdate.py b/src/checkupdate/checkupdate.py index d38fed3..73f73c1 100644 --- a/src/checkupdate/checkupdate.py +++ b/src/checkupdate/checkupdate.py @@ -1,6 +1,6 @@ # COMPILE THIS USING --onefile import subprocess -import semantic_version +from semantic_version import Version import time import urllib.request import time @@ -36,7 +36,7 @@ def checkUpdate(): URL = urllib.request.urlopen("https://raw.githubusercontent.com/ThiaudioTT/hoi4-presence/main/version.json") cloudVersion = json.loads(URL.read()) - if semantic_version.Version(localVersion["version"]) < semantic_version.Version(cloudVersion["version"]): + if Version(localVersion["version"]) < Version(cloudVersion["version"]): print("Update found!") @@ -48,12 +48,8 @@ def checkUpdate(): print('Updating...\n\n') - gameFolder = os.path.dirname(os.getcwd()) - - print(gameFolder) - # Starting setup.exe in -update mode so it will automatically install and start up the mod - subprocess.Popen([installerPath, "-update", gameFolder], start_new_session=True) + subprocess.Popen([installerPath, "-update"], start_new_session=True) # Close checkUpdate.exe (setup.exe will still run and in the new window) sys.exit(0) diff --git a/src/checkupdate/downloadupdate.py b/src/checkupdate/downloadupdate.py index 9ee1ed5..8bd85fa 100644 --- a/src/checkupdate/downloadupdate.py +++ b/src/checkupdate/downloadupdate.py @@ -39,7 +39,7 @@ def downloadUpdate() -> str | None: break - if downloadLink: + if downloadLink is not None: downloadTemp = os.path.join(os.environ["TEMP"], fileName) diff --git a/src/setup.py b/src/setup.py index 5895016..155b081 100644 --- a/src/setup.py +++ b/src/setup.py @@ -7,19 +7,11 @@ # Parsing arguments IS_UPDATE = False -GAMEFOLDER: str | None = None - num_of_args = len(sys.argv) if num_of_args >= 2: IS_UPDATE = sys.argv[1] == "-update" -if num_of_args >= 3: - DOCUMENTS = sys.argv[2] - -if num_of_args >= 4: - GAMEFOLDER = sys.argv[3] - def customInput() -> str | None: """ Checks first if this has been started by the auto updater @@ -90,11 +82,7 @@ def customInput() -> str | None: sys.exit() # 1 -if DOCUMENTS: - documents = DOCUMENTS -else: - documents = os.environ['USERPROFILE'] + "\\Documents\\Paradox Interactive\\Hearts of Iron IV" - +documents = os.environ['USERPROFILE'] + "\\Documents\\Paradox Interactive\\Hearts of Iron IV" while True: try: if 'settings.txt' not in os.listdir(documents): @@ -159,10 +147,7 @@ def customInput() -> str | None: sys.exit() # 3 -if GAMEFOLDER: - gameFolder = GAMEFOLDER -else: - gameFolder = os.environ['PROGRAMFILES(X86)'] + "\\Steam\\steamapps\\common\\Hearts of Iron IV" +gameFolder = os.environ['PROGRAMFILES(X86)'] + "\\Steam\\steamapps\\common\\Hearts of Iron IV" while True: try: From 85b0077156affae70897c71bbde88b9c7f766111 Mon Sep 17 00:00:00 2001 From: Wolfmyths Date: Wed, 4 Oct 2023 15:11:24 -0400 Subject: [PATCH 6/9] Bump to 1.2.0, utilize auto-update setting Bumped version.json to 1.2.0 Changed auto-update to true by default Added a check in checkupdate.py to see if version.json's auto-update flag is true Changed the version compare expression into a variable for readability to prevent an if statement being a little too long --- src/checkupdate/checkupdate.py | 4 +++- version.json | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/checkupdate/checkupdate.py b/src/checkupdate/checkupdate.py index 73f73c1..8ff2793 100644 --- a/src/checkupdate/checkupdate.py +++ b/src/checkupdate/checkupdate.py @@ -36,7 +36,9 @@ def checkUpdate(): URL = urllib.request.urlopen("https://raw.githubusercontent.com/ThiaudioTT/hoi4-presence/main/version.json") cloudVersion = json.loads(URL.read()) - if Version(localVersion["version"]) < Version(cloudVersion["version"]): + isClientOutdated = Version(localVersion["version"]) < Version(cloudVersion["version"]) + + if localVersion['auto-update'] and isClientOutdated: print("Update found!") diff --git a/version.json b/version.json index f1b9ac8..4c6b966 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "version": "1.1.0", - "auto-update": false, + "version": "1.2.0", + "auto-update": true, "date": "19-august-2023" } \ No newline at end of file From 8908b2355d8642ab79376d0122c569efdbaaca0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thiago=20Ara=C3=BAjo?= Date: Fri, 20 Oct 2023 17:17:23 -0300 Subject: [PATCH 7/9] test: adds auto update as true --- src/checkupdate/version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/checkupdate/version.json b/src/checkupdate/version.json index 5b9e38e..f1c6541 100644 --- a/src/checkupdate/version.json +++ b/src/checkupdate/version.json @@ -1,6 +1,6 @@ { "OBS:": "THIS IS A TEST VERSION ONLY FOR DELEVOPING PURPOSES", "version": "0.1.0", - "auto-update": false, + "auto-update": true, "date": "23-sep-2022" } \ No newline at end of file From fae205ff8833a7e6974a2b3cc6dfa92a0f300de6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thiago=20Ara=C3=BAjo?= Date: Fri, 20 Oct 2023 18:10:34 -0300 Subject: [PATCH 8/9] fix: adds cwd to checkupdat knows where he is --- src/checkupdate/checkupdate.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/checkupdate/checkupdate.py b/src/checkupdate/checkupdate.py index 8ff2793..27577ed 100644 --- a/src/checkupdate/checkupdate.py +++ b/src/checkupdate/checkupdate.py @@ -51,7 +51,9 @@ def checkUpdate(): print('Updating...\n\n') # Starting setup.exe in -update mode so it will automatically install and start up the mod - subprocess.Popen([installerPath, "-update"], start_new_session=True) + subprocess.Popen([installerPath, "-update"], start_new_session=True, cwd=downloadPath) # passing cwd to the script knows where he is + + print('Closing checkUpdate.exe...') # Close checkUpdate.exe (setup.exe will still run and in the new window) sys.exit(0) From 89c309c199bd317a918fc2c815526a7d4864ff4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thiago=20Ara=C3=BAjo?= Date: Fri, 20 Oct 2023 18:10:54 -0300 Subject: [PATCH 9/9] fix: closes any running instances before install --- src/setup.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/setup.py b/src/setup.py index 155b081..6ba88de 100644 --- a/src/setup.py +++ b/src/setup.py @@ -24,6 +24,7 @@ def customInput() -> str | None: print("This script will install the hoi4-presence in your game/save path\nPress enter to continue...") customInput() +# 0 - Stop any running instances of hoi4Presence.exe # 1 - Move hoi4Presence to documents/paradox/hearts of iron/ WITH CURRENTLY version.json and uninstaller.py # 1.1 - If can't move, exit. # 1.2 - If can't search, ask the user where is the path @@ -81,6 +82,18 @@ def customInput() -> str | None: time.sleep(10) sys.exit() +# 0 - Stop any running instances of hoi4Presence.exe +# TODO: probably theres a better way to do this and this should be executed always +if IS_UPDATE: + print("Stopping any running instances of hoi4Presence.exe...") + result = os.system("taskkill /f /im hoi4Presence.exe") + + if result == 0: + print("Process terminated successfully.") + else: + print("Error occurred while terminating the process.") + sys.exit(1) + # 1 documents = os.environ['USERPROFILE'] + "\\Documents\\Paradox Interactive\\Hearts of Iron IV" while True: