Skip to content

Commit 7fff145

Browse files
committed
Adding a lock around saving projects, and moving the save project to it's own thread (to not block the UI).
1 parent 3ff6c62 commit 7fff145

File tree

1 file changed

+59
-60
lines changed

1 file changed

+59
-60
lines changed

src/windows/main_window.py

+59-60
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@
3636
import webbrowser
3737
from time import sleep, time
3838
from datetime import datetime
39-
from threading import Thread
4039
from uuid import uuid4
4140
import zipfile
41+
import threading
4242

4343
import openshot # Python module for libopenshot (required video editing module installed separately)
4444
from PyQt5.QtCore import (
@@ -482,31 +482,32 @@ def actionClearHistory_trigger(self):
482482

483483
def save_project(self, file_path):
484484
""" Save a project to a file path, and refresh the screen """
485-
app = get_app()
486-
_ = app._tr # Get translation function
485+
with self.lock:
486+
app = get_app()
487+
_ = app._tr # Get translation function
487488

488-
try:
489-
# Update history in project data
490-
s = app.get_settings()
491-
app.updates.save_history(app.project, s.get("history-limit"))
489+
try:
490+
# Update history in project data
491+
s = app.get_settings()
492+
app.updates.save_history(app.project, s.get("history-limit"))
492493

493-
# Save recovery file first
494-
self.save_recovery(file_path)
494+
# Save recovery file
495+
self.save_recovery(file_path)
495496

496-
# Save project to file
497-
app.project.save(file_path)
497+
# Save project to file
498+
app.project.save(file_path)
498499

499-
# Set Window title
500-
self.SetWindowTitle()
500+
# Set Window title
501+
self.SetWindowTitle()
501502

502-
# Load recent projects again
503-
self.load_recent_menu()
503+
# Load recent projects again
504+
self.load_recent_menu()
504505

505-
log.info("Saved project %s", file_path)
506+
log.info("Saved project %s", file_path)
506507

507-
except Exception as ex:
508-
log.error("Couldn't save project %s", file_path, exc_info=1)
509-
QMessageBox.warning(self, _("Error Saving Project"), str(ex))
508+
except Exception as ex:
509+
log.error("Couldn't save project %s", file_path, exc_info=1)
510+
QMessageBox.warning(self, _("Error Saving Project"), str(ex))
510511

511512
def save_recovery(self, file_path):
512513
"""Saves the project and manages recovery files based on configured limits."""
@@ -523,17 +524,13 @@ def save_recovery(self, file_path):
523524
recovery_filename = f"{timestamp}-{file_name}.zip"
524525
recovery_path = os.path.join(info.RECOVERY_PATH, recovery_filename)
525526

526-
def create_recovery():
527-
try:
528-
with zipfile.ZipFile(recovery_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
529-
zipf.write(file_path, os.path.basename(file_path))
530-
log.debug(f"Zipped recovery file created: {recovery_path}")
531-
self.manage_recovery_files(daily_limit, historical_limit, file_name)
532-
except Exception as e:
533-
log.error(f"Failed to create zipped recovery file {recovery_path}: {e}")
534-
535-
# Run the recovery process in a thread
536-
Thread(target=create_recovery, daemon=True).start()
527+
try:
528+
with zipfile.ZipFile(recovery_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
529+
zipf.write(file_path, os.path.basename(file_path))
530+
log.debug(f"Zipped recovery file created: {recovery_path}")
531+
self.manage_recovery_files(daily_limit, historical_limit, file_name)
532+
except Exception as e:
533+
log.error(f"Failed to create zipped recovery file {recovery_path}: {e}")
537534

538535
def manage_recovery_files(self, daily_limit, historical_limit, file_name):
539536
"""Ensures recovery files adhere to the configured daily and historical limits."""
@@ -747,7 +744,7 @@ def actionSave_trigger(self):
747744
file_path = "%s.osp" % file_path
748745

749746
# Save project
750-
self.save_project(file_path)
747+
threading.Thread(target=self.save_project, args=(file_path,), daemon=True).start()
751748

752749
def auto_save_project(self):
753750
"""Auto save the project"""
@@ -768,7 +765,7 @@ def auto_save_project(self):
768765

769766
# Save project
770767
log.info("Auto save project file: %s", file_path)
771-
self.save_project(file_path)
768+
threading.Thread(target=self.save_project, args=(file_path,), daemon=True).start()
772769

773770
# Remove backup.osp (if any)
774771
if os.path.exists(info.BACKUP_FILE):
@@ -813,7 +810,7 @@ def actionSaveAs_trigger(self):
813810
file_path = "%s.osp" % file_path
814811

815812
# Save new project
816-
self.save_project(file_path)
813+
threading.Thread(target=self.save_project, args=(file_path,), daemon=True).start()
817814

818815
def actionImportFiles_trigger(self):
819816
app = get_app()
@@ -2918,36 +2915,37 @@ def populate_restore_menu(self):
29182915

29192916
def restore_version_clicked(self, file_path):
29202917
"""Restore a previous project file from the recovery folder"""
2921-
app = get_app()
2922-
current_filepath = app.project.current_filepath if app.project else None
2923-
_ = get_app()._tr
2918+
with self.lock:
2919+
app = get_app()
2920+
current_filepath = app.project.current_filepath if app.project else None
2921+
_ = get_app()._tr
29242922

2925-
try:
2926-
# Rename the original project file
2927-
recovered_filename = os.path.splitext(os.path.basename(current_filepath))[0] + f"-{int(time())}-backup.osp"
2928-
recovered_filepath = os.path.join(os.path.dirname(current_filepath), recovered_filename)
2929-
if os.path.exists(current_filepath):
2930-
shutil.move(current_filepath, recovered_filepath)
2931-
log.info(f"Backup current project to: {recovered_filepath}")
2932-
2933-
# Unzip if the selected recovery file is a .zip file
2934-
if file_path.endswith(".zip"):
2935-
with zipfile.ZipFile(file_path, 'r') as zipf:
2936-
# Extract over top original project *.osp file
2937-
zipf.extractall(os.path.dirname(current_filepath))
2938-
extracted_files = zipf.namelist()
2939-
if len(extracted_files) != 1:
2940-
raise ValueError("Unexpected number of files in recovery zip.")
2941-
else:
2942-
# Replace the original *.osp project file with the recovery file *.osp
2943-
shutil.copyfile(file_path, current_filepath)
2944-
log.info(f"Recovery file `{file_path}` restored to: `{current_filepath}`")
2923+
try:
2924+
# Rename the original project file
2925+
recovered_filename = os.path.splitext(os.path.basename(current_filepath))[0] + f"-{int(time())}-backup.osp"
2926+
recovered_filepath = os.path.join(os.path.dirname(current_filepath), recovered_filename)
2927+
if os.path.exists(current_filepath):
2928+
shutil.move(current_filepath, recovered_filepath)
2929+
log.info(f"Backup current project to: {recovered_filepath}")
2930+
2931+
# Unzip if the selected recovery file is a .zip file
2932+
if file_path.endswith(".zip"):
2933+
with zipfile.ZipFile(file_path, 'r') as zipf:
2934+
# Extract over top original project *.osp file
2935+
zipf.extractall(os.path.dirname(current_filepath))
2936+
extracted_files = zipf.namelist()
2937+
if len(extracted_files) != 1:
2938+
raise ValueError("Unexpected number of files in recovery zip.")
2939+
else:
2940+
# Replace the original *.osp project file with the recovery file *.osp
2941+
shutil.copyfile(file_path, current_filepath)
2942+
log.info(f"Recovery file `{file_path}` restored to: `{current_filepath}`")
29452943

2946-
# Open the recovered project
2947-
self.OpenProjectSignal.emit(current_filepath)
2944+
# Open the recovered project
2945+
self.OpenProjectSignal.emit(current_filepath)
29482946

2949-
except Exception as ex:
2950-
log.error(f"Error recovering project from `{file_path}` to `{current_filepath}`: {ex}", exc_info=True)
2947+
except Exception as ex:
2948+
log.error(f"Error recovering project from `{file_path}` to `{current_filepath}`: {ex}", exc_info=True)
29512949

29522950
def remove_recent_project(self, file_path):
29532951
"""Remove a project from the Recent menu if OpenShot can't find it"""
@@ -3554,6 +3552,7 @@ def __init__(self, *args):
35543552
super().__init__(*args)
35553553
self.initialized = False
35563554
self.shutting_down = False
3555+
self.lock = threading.Lock()
35573556
self.installEventFilter(self)
35583557

35593558
# set window on app for reference during initialization of children

0 commit comments

Comments
 (0)