GUI: Add app update checks

This commit is contained in:
Mykola Grymalyuk
2023-05-13 18:19:57 -06:00
parent a2c0994bde
commit 2e964ba9c2
9 changed files with 341 additions and 58 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ from data import os_data
class Constants:
def __init__(self) -> None:
# Patcher Versioning
self.patcher_version: str = "0.6.6" # OpenCore-Legacy-Patcher
self.patcher_version: str = "0.6.5" # OpenCore-Legacy-Patcher
self.patcher_support_pkg_version: str = "1.0.0" # PatcherSupportPkg
self.copyright_date: str = "Copyright © 2020-2023 Dortania"
self.patcher_name: str = "OpenCore Legacy Patcher"
+47 -52
View File
@@ -5,6 +5,7 @@ import subprocess
import webbrowser
import logging
from pathlib import Path
import wx
from resources import utilities, updates, global_settings, network_handler, constants
from resources.sys_patch import sys_patch_detect
@@ -42,6 +43,26 @@ class AutomaticSysPatch:
logging.info("- Auto Patch option is not supported on TUI, please use GUI")
return
dict = updates.CheckBinaryUpdates(self.constants).check_binary_updates()
if dict:
for key in dict:
version = dict[key]["Version"]
logging.info(f"- Found new version: {version}")
app = wx.App()
frame = wx.Frame(None, -1, "OpenCore Legacy Patcher")
dialog = wx.MessageDialog(
parent=frame,
message=f"Current Version: {self.constants.patcher_version}{' (Nightly)' if not self.constants.commit_info[0].startswith('refs/tags') else ''}\nNew version: {version}\nWould you like to update?",
caption="Update Available for OpenCore Legacy Patcher!",
style=wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION
)
dialog.SetYesNoCancelLabels("Download and install", "Always Ignore", "Ignore Once")
response = dialog.ShowModal()
if response == wx.ID_YES:
gui_entry.EntryPoint(self.constants).start(entry=gui_entry.SupportedEntryPoints.UPDATE_APP)
return
if utilities.check_seal() is True:
logging.info("- Detected Snapshot seal intact, detecting patches")
patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set()
@@ -58,69 +79,43 @@ class AutomaticSysPatch:
for patch in patches:
if patches[patch] is True and not patch.startswith("Settings") and not patch.startswith("Validation"):
patch_string += f"- {patch}\n"
# Check for updates
dict = updates.CheckBinaryUpdates(self.constants).check_binary_updates()
if not dict:
logging.info("- No new binaries found on Github, proceeding with patching")
if self.constants.launcher_script is None:
args_string = f"'{self.constants.launcher_binary}' --gui_patch"
else:
args_string = f"{self.constants.launcher_binary} {self.constants.launcher_script} --gui_patch"
warning_str = ""
if network_handler.NetworkUtilities("https://api.github.com/repos/dortania/OpenCore-Legacy-Patcher/releases/latest").verify_network_connection() is False:
warning_str = f"""\n\nWARNING: We're unable to verify whether there are any new releases of OpenCore Legacy Patcher on Github. Be aware that you may be using an outdated version for this OS. If you're unsure, verify on Github that OpenCore Legacy Patcher {self.constants.patcher_version} is the latest official release"""
args = [
"osascript",
"-e",
f"""display dialog "OpenCore Legacy Patcher has detected you're running without Root Patches, and would like to install them.\n\nmacOS wipes all root patches during OS installs and updates, so they need to be reinstalled.\n\nFollowing Patches have been detected for your system: \n{patch_string}\nWould you like to apply these patches?{warning_str}" """
f'with icon POSIX file "{self.constants.app_icon_path}"',
]
output = subprocess.run(
args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
if output.returncode == 0:
args = [
"osascript",
"-e",
f'''do shell script "{args_string}"'''
f' with prompt "OpenCore Legacy Patcher would like to patch your root volume"'
" with administrator privileges"
" without altering line endings"
]
subprocess.run(
args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
return
logging.info("- No new binaries found on Github, proceeding with patching")
if self.constants.launcher_script is None:
args_string = f"'{self.constants.launcher_binary}' --gui_patch"
else:
for key in dict:
version = dict[key]["Version"]
github_link = dict[key]["Github Link"]
logging.info(f"- Found new version: {version}")
args_string = f"{self.constants.launcher_binary} {self.constants.launcher_script} --gui_patch"
# launch osascript to ask user if they want to apply the update
# if yes, open the link in the default browser
# we never want to run the root patcher if there are updates available
warning_str = ""
if network_handler.NetworkUtilities("https://api.github.com/repos/dortania/OpenCore-Legacy-Patcher/releases/latest").verify_network_connection() is False:
warning_str = f"""\n\nWARNING: We're unable to verify whether there are any new releases of OpenCore Legacy Patcher on Github. Be aware that you may be using an outdated version for this OS. If you're unsure, verify on Github that OpenCore Legacy Patcher {self.constants.patcher_version} is the latest official release"""
args = [
"osascript",
"-e",
f"""display dialog "OpenCore Legacy Patcher has detected you're running without Root Patches, and would like to install them.\n\nmacOS wipes all root patches during OS installs and updates, so they need to be reinstalled.\n\nFollowing Patches have been detected for your system: \n{patch_string}\nWould you like to apply these patches?{warning_str}" """
f'with icon POSIX file "{self.constants.app_icon_path}"',
]
output = subprocess.run(
args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
if output.returncode == 0:
args = [
"osascript",
"-e",
f"""display dialog "OpenCore Legacy Patcher has detected you're running without Root Patches, and would like to install them.\n\nHowever we've detected a new version of OCLP on Github. Would you like to view this?\n\nCurrent Version: {self.constants.patcher_version}\nLatest Version: {version}\n\nNote: After downloading the latest OCLP version, open the app and run the 'Post Install Root Patcher' from the main menu." """
f'with icon POSIX file "{self.constants.app_icon_path}"',
f'''do shell script "{args_string}"'''
f' with prompt "OpenCore Legacy Patcher would like to patch your root volume"'
" with administrator privileges"
" without altering line endings"
]
output = subprocess.run(
subprocess.run(
args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
if output.returncode == 0:
webbrowser.open(github_link)
return
return
else:
logging.info("- No patches detected")
else:
+9
View File
@@ -35,6 +35,12 @@ class CheckBinaryUpdates:
if local_version is None:
local_version = self.binary_version_array
if local_version == remote_version:
if not self.constants.commit_info[0].startswith("refs/tags"):
# Check for nightly builds
return True
# Pad version numbers to match length (ie. 0.1.0 vs 0.1.0.1)
while len(remote_version) > len(local_version):
local_version.append(0)
@@ -99,6 +105,9 @@ class CheckBinaryUpdates:
response = network_handler.NetworkUtilities().get(REPO_LATEST_RELEASE_URL)
data_set = response.json()
if "tag_name" not in data_set:
return None
self.remote_version = data_set["tag_name"]
self.remote_version_array = self.remote_version.split(".")
+1 -1
View File
@@ -8,7 +8,7 @@ class DownloadFrame(wx.Frame):
"""
Update provided frame with download stats
"""
def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, download_obj: network_handler.DownloadObject, item_name: str, screen_location: tuple = None):
def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, download_obj: network_handler.DownloadObject, item_name: str):
self.constants: constants.Constants = global_constants
self.title: str = title
+3 -1
View File
@@ -9,7 +9,8 @@ from resources.wx_gui import (
gui_build,
gui_install_oc,
gui_sys_patch,
gui_support
gui_support,
gui_update,
)
from resources.sys_patch import sys_patch_detect
@@ -21,6 +22,7 @@ class SupportedEntryPoints:
BUILD_OC = gui_build.BuildFrame
INSTALL_OC = gui_install_oc.InstallOCFrame
SYS_PATCH = gui_sys_patch.SysPatchMenu
UPDATE_APP = gui_update.UpdateFrame
class EntryPoint:
@@ -170,7 +170,6 @@ class macOSInstallerFrame(wx.Frame):
self,
title=self.title,
global_constants=self.constants,
screen_location=self.GetScreenPosition(),
download_obj=download_obj,
item_name=f"macOS {app['Version']} ({app['Build']})",
)
+75 -1
View File
@@ -1,4 +1,9 @@
import wx
import logging
import webbrowser
import threading
import sys
from resources.wx_gui import (
gui_build,
gui_macos_installer_download,
@@ -6,8 +11,10 @@ from resources.wx_gui import (
gui_support,
gui_help,
gui_settings,
gui_update,
)
from resources import constants
from resources import constants, global_settings, updates
from data import os_data
class MainMenu(wx.Frame):
@@ -25,6 +32,8 @@ class MainMenu(wx.Frame):
self.SetPosition(screen_location) if screen_location else self.Centre()
self.Show()
self._preflight_checks()
def _generate_elements(self) -> None:
"""
@@ -85,6 +94,61 @@ class MainMenu(wx.Frame):
self.SetSize((350, copy_label.GetPosition()[1] + 50))
def _preflight_checks(self):
if (
self.constants.computer.build_model != None and
self.constants.computer.build_model != self.constants.computer.real_model and
self.constants.host_is_hackintosh is False
):
# Notify user they're booting an unsupported configuration
pop_up = wx.MessageDialog(
self,
f"We found you are currently booting OpenCore built for a different unit: {self.constants.computer.build_model}\n\nWe builds configs to match individual units and cannot be mixed or reused with different Macs.\n\nPlease Build and Install a new OpenCore config, and reboot your Mac.",
"Unsupported Configuration Detected!",
style = wx.OK | wx.ICON_EXCLAMATION
)
pop_up.ShowModal()
self.on_build_and_install()
return
threading.Thread(target=self._check_for_updates).start()
def _check_for_updates(self):
if self.constants.has_checked_updates is True:
return
ignore_updates = global_settings.GlobalEnviromentSettings().read_property("IgnoreAppUpdates")
if ignore_updates is True:
self.constants.ignore_updates = True
return
self.constants.ignore_updates = False
self.constants.has_checked_updates = True
dict = updates.CheckBinaryUpdates(self.constants).check_binary_updates()
if not dict:
return
for entry in dict:
version = dict[entry]["Version"]
logging.info(f"New version: {version}")
dialog = wx.MessageDialog(
parent=self,
message=f"Current Version: {self.constants.patcher_version}{' (Nightly)' if not self.constants.commit_info[0].startswith('refs/tags') else ''}\nNew version: {version}\nWould you like to update?",
caption="Update Available for OpenCore Legacy Patcher!",
style=wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION
)
dialog.SetYesNoCancelLabels("Download and install", "Always Ignore", "Ignore Once")
response = dialog.ShowModal()
if response == wx.ID_YES:
wx.CallAfter(self.on_update, dict[entry]["Link"], version)
elif response == wx.ID_NO:
logging.info("- Setting IgnoreAppUpdates to True")
self.constants.ignore_updates = True
global_settings.GlobalEnviromentSettings().write_property("IgnoreAppUpdates", True)
def on_build_and_install(self, event: wx.Event = None):
self.Hide()
gui_build.BuildFrame(
@@ -131,3 +195,13 @@ class MainMenu(wx.Frame):
global_constants=self.constants,
screen_location=self.GetPosition()
)
def on_update(self, oclp_url: str, oclp_version: str):
gui_update.UpdateFrame(
parent=self,
title=self.title,
global_constants=self.constants,
screen_location=self.GetPosition(),
url=oclp_url,
item=oclp_version
)
-1
View File
@@ -95,7 +95,6 @@ class SysPatchMenu(wx.Frame):
self,
title=self.title,
global_constants=self.constants,
screen_location=self.GetScreenPosition(),
download_obj=kdk_download_obj,
item_name=f"KDK Build {self.kdk_obj.kdk_url_build}"
)
+205
View File
@@ -0,0 +1,205 @@
import wx
import sys
import subprocess
import threading
import logging
import time
from pathlib import Path
from resources.wx_gui import gui_download
from resources import constants, network_handler, updates
class UpdateFrame(wx.Frame):
def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: wx.Point, url: str = "", item: str = "") -> None:
if parent:
self.parent: wx.Frame = parent
for child in self.parent.GetChildren():
child.Hide()
parent.Hide()
else:
super(UpdateFrame, self).__init__(parent, title=title, size=(350, 300), style = wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
self.title: str = title
self.constants: constants.Constants = global_constants
self.application_path = self.constants.payload_path / "OpenCore-Patcher.app"
self.screen_location: wx.Point = screen_location
if self.screen_location is None:
if parent:
self.screen_location = parent.GetScreenPosition()
else:
self.Centre()
self.screen_location = self.GetScreenPosition()
if url == "" or item == "":
dict = updates.CheckBinaryUpdates(self.constants).check_binary_updates()
if dict:
for key in dict:
item = dict[key]["Version"]
url = dict[key]["Link"]
break
self.frame: wx.Frame = wx.Frame(
parent=parent if parent else self,
title=self.title,
size=(350, 130),
pos=self.screen_location,
style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER ^ wx.MAXIMIZE_BOX
)
# Title: Preparing update
title_label = wx.StaticText(self.frame, label="Preparing download...", pos=(-1,1))
title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
title_label.Center(wx.HORIZONTAL)
# Progress bar
progress_bar = wx.Gauge(self.frame, range=100, pos=(10, 50), size=(300, 20))
progress_bar.Center(wx.HORIZONTAL)
progress_bar.Pulse()
self.progress_bar = progress_bar
self.frame.Show()
wx.Yield()
download_obj = network_handler.DownloadObject(url, self.constants.payload_path / "OpenCore-Patcher-GUI.app.zip")
gui_download.DownloadFrame(
self.frame,
title=self.title,
global_constants=self.constants,
download_obj=download_obj,
item_name=f"OpenCore Patcher {item}"
)
if download_obj.download_complete is False:
progress_bar.SetValue(0)
wx.MessageBox("Failed to download update. If you continue to have this issue, please manually download OpenCore Legacy Patcher off Github", "Critical Error!", wx.OK | wx.ICON_ERROR)
sys.exit(1)
# Title: Extracting update
title_label.SetLabel("Extracting update...")
title_label.Center(wx.HORIZONTAL)
wx.Yield()
thread = threading.Thread(target=self._extract_update)
thread.start()
while thread.is_alive():
wx.Yield()
# Title: Installing update
title_label.SetLabel("Installing update...")
title_label.Center(wx.HORIZONTAL)
thread = threading.Thread(target=self._install_update)
thread.start()
while thread.is_alive():
wx.Yield()
# Title: Update complete
title_label.SetLabel("Update complete!")
title_label.Center(wx.HORIZONTAL)
# Progress bar
progress_bar.Hide()
# Label: 0.6.6 has been installed to:
installed_label = wx.StaticText(self.frame, label=f"{item} has been installed:", pos=(-1, progress_bar.GetPosition().y - 15))
installed_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
installed_label.Center(wx.HORIZONTAL)
# Label: '/Library/Application Support/Dortania'
installed_path_label = wx.StaticText(self.frame, label='/Library/Application Support/Dortania', pos=(-1, installed_label.GetPosition().y + 20))
installed_path_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
installed_path_label.Center(wx.HORIZONTAL)
# Label: Launching update shortly...
launch_label = wx.StaticText(self.frame, label="Launching update shortly...", pos=(-1, installed_path_label.GetPosition().y + 20))
launch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
launch_label.Center(wx.HORIZONTAL)
# Adjust frame size
self.frame.SetSize((-1, launch_label.GetPosition().y + 80))
thread = threading.Thread(target=self._launch_update)
thread.start()
while thread.is_alive():
wx.Yield()
timer = 3
while True:
wx.GetApp().Yield()
launch_label.SetLabel(f"Closing old process in {timer} seconds")
launch_label.Center(wx.HORIZONTAL)
time.sleep(1)
timer -= 1
if timer == 0:
break
sys.exit(0)
def _extract_update(self):
# Extract update
logging.info("Extracting update")
if Path(self.application_path).exists():
subprocess.run(["rm", "-rf", str(self.application_path)])
result = subprocess.run(
["ditto", "-xk", str(self.constants.payload_path / "OpenCore-Patcher-GUI.app.zip"), str(self.constants.payload_path)], capture_output=True
)
if result.returncode != 0:
wx.CallAfter(self.progress_bar.SetValue, 0)
wx.CallAfter(wx.MessageBox, f"Failed to extract update. Error: {result.stderr.decode('utf-8')}", "Critical Error!", wx.OK | wx.ICON_ERROR)
wx.CallAfter(sys.exit, 1)
def _install_update(self):
# Install update
logging.info("Installing update")
# Create bash script to run as root
script = f"""#!/bin/bash
# Check if '/Library/Application Support/Dortania' exists
if [ ! -d "/Library/Application Support/Dortania" ]; then
mkdir -p "/Library/Application Support/Dortania"
fi
# Check if '/Library/Application Support/Dortania/OpenCore-Patcher.app' exists
if [ -d "/Library/Application Support/Dortania/OpenCore-Patcher.app" ]; then
rm -rf "/Library/Application Support/Dortania/OpenCore-Patcher.app"
fi
# Move '/tmp/OpenCore-Patcher.app' to '/Library/Application Support/Dortania'
mv "{str(self.application_path)}" "/Library/Application Support/Dortania/OpenCore-Patcher.app"
# Check if '/Applications/OpenCore-Patcher.app' exists
if [ -d "/Applications/OpenCore-Patcher.app" ]; then
ln -s "/Library/Application Support/Dortania/OpenCore-Patcher.app" "/Applications/OpenCore-Patcher.app"
fi
"""
# Write script to file
with open(self.constants.payload_path / "update.sh", "w") as f:
f.write(script)
# Execute script
args = [self.constants.oclp_helper_path, "/bin/sh", str(self.constants.payload_path / "update.sh")]
logging.info(f"Executing: {args}")
result = subprocess.run(args, capture_output=True)
if result.returncode != 0:
wx.CallAfter(self.progress_bar.SetValue, 0)
wx.CallAfter(wx.MessageBox, f"Failed to install update. Error: {result.stderr.decode('utf-8')}", "Critical Error!", wx.OK | wx.ICON_ERROR)
wx.CallAfter(sys.exit, 1)
def _launch_update(self):
# Launch update
logging.info("Launching update: '/Library/Application Support/Dortania/OpenCore-Patcher.app'")
subprocess.Popen(["open", "/Library/Application Support/Dortania/OpenCore-Patcher.app"])