From 2010fb216ba2607aced7e4cf19404b283119521f Mon Sep 17 00:00:00 2001 From: leminlimez <59540996+leminlimez@users.noreply.github.com> Date: Thu, 27 Mar 2025 12:32:23 -0400 Subject: [PATCH] add threading to applying --- controllers/video_handler.py | 7 +++-- devicemanagement/device_manager.py | 47 +++++++++++++++--------------- gui/apply_worker.py | 29 ++++++++++++++++++ gui/main_window.py | 30 ++++++++++++++++--- tweaks/posterboard_tweak.py | 11 ++++--- 5 files changed, 91 insertions(+), 33 deletions(-) create mode 100644 gui/apply_worker.py diff --git a/controllers/video_handler.py b/controllers/video_handler.py index 9c0594f..c1b41e6 100644 --- a/controllers/video_handler.py +++ b/controllers/video_handler.py @@ -40,7 +40,7 @@ def get_thumbnail_from_contents(contents: bytes, output_file: str = None): contents = get_thumbnail_from_mov(inp_file, output_file) return contents -def create_caml(video_path: str, output_file: str): +def create_caml(video_path: str, output_file: str, update_label=lambda x: None): cam = cv2.VideoCapture(video_path) assets_path = os.path.join(output_file, "assets") try: @@ -80,7 +80,10 @@ def create_caml(video_path: str, output_file: str): if ret: # if video is still left continue creating images name = 'assets/' + str(currentframe) + '.jpg' - print ('Creating...' + name) + if update_label: + update_label('Creating...' + name) + else: + print ('Creating...' + name) # writing the extracted images cv2.imwrite(os.path.join(output_file, name), frame) diff --git a/devicemanagement/device_manager.py b/devicemanagement/device_manager.py index 1cb0d45..60cc918 100644 --- a/devicemanagement/device_manager.py +++ b/devicemanagement/device_manager.py @@ -3,7 +3,7 @@ import plistlib from tempfile import TemporaryDirectory from PySide6.QtWidgets import QMessageBox -from PySide6.QtCore import QSettings +from PySide6.QtCore import QSettings, QThread from pymobiledevice3 import usbmux from pymobiledevice3.lockdown import create_using_usbmux @@ -12,37 +12,38 @@ from pymobiledevice3.exceptions import MuxException, PasswordRequiredError from devicemanagement.constants import Device, Version from devicemanagement.data_singleton import DataSingleton +from gui.apply_worker import ApplyAlertMessage from tweaks.tweaks import tweaks, FeatureFlagTweak, EligibilityTweak, AITweak, BasicPlistTweak, AdvancedPlistTweak, RdarFixTweak, NullifyFileTweak from tweaks.custom_gestalt_tweaks import CustomGestaltTweaks from tweaks.posterboard_tweak import PosterboardTweak from tweaks.basic_plist_locations import FileLocationsList, RiskyFileLocationsList from Sparserestore.restore import restore_files, FileToRestore -def show_error_msg(txt: str, detailed_txt: str = None): +def show_error_msg(txt: str, title: str = "Error!", icon = QMessageBox.Critical, detailed_txt: str = None): detailsBox = QMessageBox() - detailsBox.setIcon(QMessageBox.Critical) - detailsBox.setWindowTitle("Error!") + detailsBox.setIcon(icon) + detailsBox.setWindowTitle(title) detailsBox.setText(txt) if detailed_txt != None: detailsBox.setDetailedText(detailed_txt) detailsBox.exec() def show_apply_error(e: Exception, update_label=lambda x: None): - if "Find My" in str(e): - show_error_msg("Find My must be disabled in order to use this tool.", - detailed_txt="Disable Find My from Settings (Settings -> [Your Name] -> Find My) and then try again.") - elif "Encrypted Backup MDM" in str(e): - show_error_msg("Nugget cannot be used on this device. Click Show Details for more info.", - detailed_txt="Your device is managed and MDM backup encryption is on. This must be turned off in order for Nugget to work. Please do not use Nugget on your school/work device!") - elif "SessionInactive" in str(e): - show_error_msg("The session was terminated. Refresh the device list and try again.") - elif isinstance(e, PasswordRequiredError): - show_error_msg("Device is password protected! You must trust the computer on your device.", - detailed_txt="Unlock your device. On the popup, click \"Trust\", enter your password, then try again.") - else: - show_error_msg(type(e).__name__ + ": " + repr(e), detailed_txt=str(traceback.format_exc())) print(traceback.format_exc()) update_label("Failed to restore") + if "Find My" in str(e): + return ApplyAlertMessage("Find My must be disabled in order to use this tool.", + detailed_txt="Disable Find My from Settings (Settings -> [Your Name] -> Find My) and then try again.") + elif "Encrypted Backup MDM" in str(e): + return ApplyAlertMessage("Nugget cannot be used on this device. Click Show Details for more info.", + detailed_txt="Your device is managed and MDM backup encryption is on. This must be turned off in order for Nugget to work. Please do not use Nugget on your school/work device!") + elif "SessionInactive" in str(e): + return ApplyAlertMessage("The session was terminated. Refresh the device list and try again.") + elif "PasswordRequiredError" in str(e): + return ApplyAlertMessage("Device is password protected! You must trust the computer on your device.", + detailed_txt="Unlock your device. On the popup, click \"Trust\", enter your password, then try again.") + else: + return ApplyAlertMessage(type(e).__name__ + ": " + repr(e), detailed_txt=str(traceback.format_exc())) class DeviceManager: ## Class Functions @@ -287,7 +288,7 @@ class DeviceManager: )) ## APPLYING OR REMOVING TWEAKS AND RESTORING - def apply_changes(self, resetting: bool = False, update_label=lambda x: None): + def apply_changes(self, resetting: bool = False, update_label=lambda x: None, show_alert=lambda x: None): try: # set the tweaks and apply # first open the file in read mode @@ -332,7 +333,7 @@ class DeviceManager: tmp_pb_dir = TemporaryDirectory() tweak.apply_tweak( files_to_restore=files_to_restore, output_dir=tmp_pb_dir.name, - windows_path_fix=self.windows_path_fix + windows_path_fix=self.windows_path_fix, update_label=update_label ) if tweak.enabled: uses_domains = True @@ -341,7 +342,7 @@ class DeviceManager: gestalt_plist = tweak.apply_tweak(gestalt_plist) elif tweak.enabled: # no mobilegestalt file provided but applying mga tweaks, give warning - show_error_msg("No mobilegestalt file provided! Please select your file to apply mobilegestalt tweaks.") + show_alert(show_error_msg("No mobilegestalt file provided! Please select your file to apply mobilegestalt tweaks.", exec=False)) update_label("Failed.") return # set the custom gestalt keys @@ -427,12 +428,12 @@ class DeviceManager: msg = "Your device will now restart." if not self.auto_reboot: msg = "Please restart your device to see changes." - QMessageBox.information(None, "Success!", "All done! " + msg) + show_alert(ApplyAlertMessage(txt="All done! " + msg, title="Success!", icon=QMessageBox.Information)) update_label("Success!") except Exception as e: if tmp_pb_dir != None: tmp_pb_dir.cleanup() - show_apply_error(e, update_label) + show_alert(show_apply_error(e, update_label)) ## RESETTING MOBILE GESTALT def reset_mobilegestalt(self, settings: QSettings, update_label=lambda x: None): @@ -457,4 +458,4 @@ class DeviceManager: QMessageBox.information(None, "Success!", "All done! " + msg) update_label("Success!") except Exception as e: - show_apply_error(e) + show_error_msg(str(e)) diff --git a/gui/apply_worker.py b/gui/apply_worker.py new file mode 100644 index 0000000..6c0c586 --- /dev/null +++ b/gui/apply_worker.py @@ -0,0 +1,29 @@ +from PySide6.QtCore import Signal, QThread +from PySide6.QtWidgets import QMessageBox + +class ApplyAlertMessage: + def __init__(self, txt: str, title: str = "Error!", icon = QMessageBox.Critical, detailed_txt: str = None): + self.txt = txt + self.title = title + self.icon = icon + self.detailed_txt = detailed_txt + +class ApplyThread(QThread): + progress = Signal(str) + alert = Signal(ApplyAlertMessage) + + def update_label(self, txt: str): + self.progress.emit(txt) + def alert_window(self, msg: ApplyAlertMessage): + self.alert.emit(msg) + + def __init__(self, manager, resetting: bool = False): + super().__init__() + self.manager = manager + self.resetting = resetting + + def do_work(self): + self.manager.apply_changes(self.resetting, self.update_label, self.alert_window) + + def run(self): + self.do_work() \ No newline at end of file diff --git a/gui/main_window.py b/gui/main_window.py index 05ece30..f47a56f 100644 --- a/gui/main_window.py +++ b/gui/main_window.py @@ -14,6 +14,7 @@ from devicemanagement.constants import Version from devicemanagement.device_manager import DeviceManager from gui.dialogs import GestaltDialog, UpdateAppDialog, PBHelpDialog +from gui.apply_worker import ApplyThread, ApplyAlertMessage from tweaks.tweaks import tweaks from tweaks.custom_gestalt_tweaks import CustomGestaltTweaks, ValueTypeStrings @@ -43,6 +44,8 @@ class MainWindow(QtWidgets.QMainWindow): self.ui.setupUi(self) self.show_uuid = False self.pb_mainLayout = None + self.applying_in_progress = False + self.threadpool = QtCore.QThreadPool() self.loadSettings() # Check for an update @@ -1154,12 +1157,31 @@ class MainWindow(QtWidgets.QMainWindow): def update_bar(self, percent): self.ui.restoreProgressBar.setValue(int(percent)) def on_removeTweaksBtn_clicked(self): - # TODO: Add threading here - self.device_manager.apply_changes(resetting=True, update_label=self.update_label) + self.apply_changes(resetting=True) def on_resetGestaltBtn_clicked(self): self.device_manager.reset_mobilegestalt(self.settings, update_label=self.update_label) @QtCore.Slot() def on_applyTweaksBtn_clicked(self): - # TODO: Add threading here - self.device_manager.apply_changes(update_label=self.update_label) + self.apply_changes() + + def apply_changes(self, resetting: bool = False): + if not self.applying_in_progress: + self.applying_in_progress = True + self.worker_thread = ApplyThread(manager=self.device_manager, resetting=resetting) + self.worker_thread.progress.connect(self.ui.statusLbl.setText) + self.worker_thread.alert.connect(self.alert_message) + self.worker_thread.finished.connect(self.finish_apply_thread) + self.worker_thread.finished.connect(self.worker_thread.deleteLater) + self.worker_thread.start() + def alert_message(self, alert: ApplyAlertMessage): + print(alert.txt) + detailsBox = QtWidgets.QMessageBox() + detailsBox.setIcon(alert.icon) + detailsBox.setWindowTitle(alert.title) + detailsBox.setText(alert.txt) + if alert.detailed_txt != None: + detailsBox.setDetailedText(alert.detailed_txt) + detailsBox.exec() + def finish_apply_thread(self): + self.applying_in_progress = False diff --git a/tweaks/posterboard_tweak.py b/tweaks/posterboard_tweak.py index 7db0913..a1ad2ea 100644 --- a/tweaks/posterboard_tweak.py +++ b/tweaks/posterboard_tweak.py @@ -203,7 +203,7 @@ class PosterboardTweak(Tweak): overriding.write(thumb_contents) del thumb_contents - def create_video_loop_files(self, output_dir: str): + def create_video_loop_files(self, output_dir: str, update_label=lambda x: None): print(f"file: {self.videoFile}, looping: {self.loop_video}") if self.videoFile and self.loop_video: source_dir = get_bundle_files("files/posterboard/VideoCAML") @@ -211,11 +211,11 @@ class PosterboardTweak(Tweak): copytree(source_dir, video_output_dir, dirs_exist_ok=True) contents_path = os.path.join(video_output_dir, "versions/1/contents/9183.Custom-810w-1080h@2x~ipad.wallpaper/9183.Custom_Floating-810w-1080h@2x~ipad.ca") print(f"path at {contents_path}, creating caml") - video_handler.create_caml(video_path=self.videoFile, output_file=contents_path) + video_handler.create_caml(video_path=self.videoFile, output_file=contents_path, update_label=update_label) - def apply_tweak(self, files_to_restore: list[FileToRestore], output_dir: str, windows_path_fix: bool): + def apply_tweak(self, files_to_restore: list[FileToRestore], output_dir: str, windows_path_fix: bool, update_label=lambda x: None): # unzip the file if not self.enabled: return @@ -243,8 +243,10 @@ class PosterboardTweak(Tweak): if os.name == "nt" and windows_path_fix: # try to get past directory name limit on windows output_dir = "\\\\?\\" + output_dir + update_label("Generating PosterBoard Video...") self.create_live_photo_files(output_dir) - self.create_video_loop_files(output_dir) + self.create_video_loop_files(output_dir, update_label=update_label) + update_label("Adding tendies...") for tendie in self.tendies: zip_output = os.path.join(output_dir, str(uuid.uuid4())) os.makedirs(zip_output) @@ -252,3 +254,4 @@ class PosterboardTweak(Tweak): zip_ref.extractall(zip_output) # add the files self.recursive_add(files_to_restore, curr_path=output_dir) + update_label("Adding other tweaks...")