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
@@ -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"])