Skip to content

Commit 17029e2

Browse files
committed
Remove project lock file and heartbeat thread
1 parent 2109c54 commit 17029e2

1 file changed

Lines changed: 2 additions & 165 deletions

File tree

src/qualcoder/__main__.py

Lines changed: 2 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@
3838
import urllib.error as urllib_err
3939
import webbrowser
4040
from copy import copy
41-
import time
42-
import getpass
4341

4442
from PyQt6 import QtCore, QtGui, QtWidgets
4543
import qtawesome as qta
@@ -128,49 +126,6 @@
128126
handler = RotatingFileHandler(logfile, maxBytes=log_maxBytes, backupCount=2)
129127
logger.addHandler(handler)
130128

131-
lock_timeout = 30.0 # in seconds. If a project lockfile is older (= has received no heartbeat for 30 seconds),
132-
# it is assumed that the host process has died and the project is opened anyways
133-
lock_heartbeat_interval = 5 # in seconds.
134-
135-
136-
class ProjectLockHeartbeatWorker(QtCore.QObject):
137-
"""
138-
This worker thread is invoked on opening a project and will write a regular heartbeat (timestamp)
139-
to the lock file to signify that the project is still in use and the host process did not crash.
140-
"""
141-
finished = QtCore.pyqtSignal() # Signal for indicating completion
142-
io_error = QtCore.pyqtSignal() # Signal indicating an error acessing the lock file to write the heartbeat
143-
144-
def __init__(self, app, lock_file_path):
145-
super().__init__()
146-
self.app = app
147-
self.lock_file_path = lock_file_path
148-
self.is_running = True
149-
self.lost_connection = False
150-
151-
def write_heartbeat(self):
152-
"""Write heartbeat to the lock file every 10 seconds."""
153-
last_heartbeat = time.time()
154-
while self.is_running:
155-
if time.time() - last_heartbeat >= lock_heartbeat_interval:
156-
last_heartbeat = time.time()
157-
try:
158-
with open(self.lock_file_path, 'w', encoding='utf-8') as lock_file:
159-
lock_file.write(f"{getpass.getuser()}\n{str(time.time())}")
160-
self.lost_connection = False
161-
except Exception as e_:
162-
print(e_)
163-
if not self.lost_connection:
164-
self.io_error.emit()
165-
self.lost_connection = True
166-
time.sleep(0.1)
167-
168-
def stop(self):
169-
"""Stop the heartbeat process."""
170-
self.is_running = False
171-
self.finished.emit()
172-
173-
174129
class ProjectEventBus(QtCore.QObject):
175130
"""Application-wide event bus for project database changes.
176131
This is used to notify other dialogs (e.g. reports) of changes to the project database,
@@ -1394,14 +1349,14 @@ def save_backup(self, suffix=""):
13941349
msg = ""
13951350
if self.settings['backup_av_files'] == 'True':
13961351
try:
1397-
shutil.copytree(self.project_path, backup, ignore=shutil.ignore_patterns('*.lock'))
1352+
shutil.copytree(self.project_path, backup)
13981353
except FileExistsError as err:
13991354
msg = _("There is already a backup with this name")
14001355
print(f"{err}\nmsg")
14011356
logger.warning(_(msg) + f"\n{err}")
14021357
else:
14031358
shutil.copytree(self.project_path, backup,
1404-
ignore=shutil.ignore_patterns('*.lock', '*.mp3', '*.wav', '*.mp4', '*.mov', '*.ogg',
1359+
ignore=shutil.ignore_patterns('*.mp3', '*.wav', '*.mp4', '*.mov', '*.ogg',
14051360
'*.wmv', '*.MP3',
14061361
'*.WAV', '*.MP4', '*.MOV', '*.OGG', '*.WMV'))
14071362
# self.ui.textEdit.append(_("WARNING: audio and video files NOT backed up. See settings."))
@@ -1450,9 +1405,6 @@ def __init__(self, app, force_quit=False):
14501405
self.force_quit = force_quit
14511406
self.journal_display = None
14521407

1453-
self.heartbeat_thread = None
1454-
self.heartbeat_worker = None
1455-
self.lock_file_path = ''
14561408
self.ai_chat_window = None
14571409

14581410
if platform.system() == "Windows" and self.app.settings['stylesheet'] == "native":
@@ -2559,71 +2511,6 @@ def project_memo(self):
25592511
self.ui.textEdit.append(_("Project memo entered."))
25602512
self.app.delete_backup = False
25612513

2562-
# lock file helper functions:
2563-
2564-
def create_lock_file(self, break_existing_lock=False):
2565-
"""Create the lock file.
2566-
break_existing_lock: if True, the lock file will be created even if it already exists
2567-
"""
2568-
if (not break_existing_lock) and os.path.exists(self.lock_file_path):
2569-
return False
2570-
try:
2571-
mode = 'w' if break_existing_lock else 'x'
2572-
with open(self.lock_file_path, mode, encoding='utf-8') as lock_file:
2573-
lock_file.write(f"{getpass.getuser()}\n{str(time.time())}")
2574-
return True
2575-
except FileExistsError:
2576-
return False
2577-
2578-
def delete_lock_file(self):
2579-
""" Delete the lock file to release the lock. """
2580-
2581-
try:
2582-
if self.lock_file_path != '':
2583-
os.remove(self.lock_file_path)
2584-
except Exception as e_:
2585-
print("delete_lock_file", e_)
2586-
logger.debug(e_)
2587-
2588-
def lock_file_io_error(self):
2589-
msg = _('An error occured while writing to the project folder. '
2590-
'Please close the project and try to open it again.')
2591-
msg_box = Message(self.app, _("I/O Error"), msg, "critical")
2592-
btn_close = msg_box.addButton(_("Close"), QtWidgets.QMessageBox.ButtonRole.AcceptRole)
2593-
btn_ignore = msg_box.addButton("Ignore", QtWidgets.QMessageBox.ButtonRole.RejectRole)
2594-
msg_box.setDefaultButton(btn_close)
2595-
msg_box.exec()
2596-
if msg_box.clickedButton() == btn_close:
2597-
self.close_project()
2598-
logger.debug(msg)
2599-
2600-
def prepare_heartbeat_thread(self):
2601-
""" Prepare and start the heartbeat QThread. """
2602-
2603-
self.heartbeat_thread = QtCore.QThread()
2604-
self.heartbeat_worker = ProjectLockHeartbeatWorker(self.app, self.lock_file_path)
2605-
self.heartbeat_worker.moveToThread(self.heartbeat_thread)
2606-
self.heartbeat_thread.started.connect(self.heartbeat_worker.write_heartbeat)
2607-
self.heartbeat_worker.finished.connect(self.heartbeat_thread.quit)
2608-
self.heartbeat_worker.finished.connect(self.heartbeat_worker.deleteLater)
2609-
self.heartbeat_thread.finished.connect(self.heartbeat_thread.deleteLater)
2610-
self.heartbeat_worker.io_error.connect(self.lock_file_io_error)
2611-
self.heartbeat_thread.start()
2612-
2613-
def stop_heartbeat(self, wait=False):
2614-
"""Stop the heartbeat and delete the lock file (if it exists). """
2615-
2616-
if self.heartbeat_worker:
2617-
try:
2618-
self.heartbeat_worker.stop()
2619-
if wait:
2620-
self.heartbeat_thread.wait() # Wait for the thread to properly finish
2621-
except Exception as e_:
2622-
print(e_)
2623-
logger.debug(e_)
2624-
self.delete_lock_file()
2625-
self.lock_file_path = ''
2626-
26272514
def open_project(self, path_="", newproject="no"):
26282515
""" Open an existing project.
26292516
if set, also save a backup datetime stamped copy at the same time.
@@ -2659,54 +2546,6 @@ def open_project(self, path_="", newproject="no"):
26592546
if len(path_split) == 2:
26602547
proj_path = path_split[1]
26612548
if len(path) > 3 and proj_path[-4:] == ".qda":
2662-
# Lock file management
2663-
self.lock_file_path = os.path.normpath(proj_path + '/project_in_use.lock')
2664-
if not self.create_lock_file():
2665-
# Lock file already exists. Checking if it has timed out or not.
2666-
with open(self.lock_file_path, 'r', encoding='utf-8') as lock_file:
2667-
try:
2668-
lock_user = lock_file.readline()[:-1]
2669-
lock_timestamp = float(lock_file.readline())
2670-
except Exception as e_: # TODO add specific exception
2671-
print(e_)
2672-
logger.warning(e_)
2673-
# lock file seems corrupted/partially written. Retry once in case another instance was writing to the file at the same time:
2674-
time.sleep(0.5)
2675-
try:
2676-
lock_user = lock_file.readline()[:-1]
2677-
lock_timestamp = float(lock_file.readline())
2678-
except Exception as e_: # permanent error, break the lock
2679-
print(e_) # TODO determine specific exception
2680-
logger.warning(e_)
2681-
lock_user = 'unknown'
2682-
lock_timestamp = 0.0
2683-
if float(time.time()) - lock_timestamp > lock_timeout:
2684-
# has timed out, break the lock
2685-
msg = _(
2686-
'QualCoder detected that the project was not properly closed the last time it was used by "') + lock_user + '".\n'
2687-
msg += _(
2688-
'In most cases, you can still continue your work as usual. If you encounter any problems, search for a recent backup in the project folder.')
2689-
logger.warning(msg)
2690-
msg_box = Message(self.app, _("Open file"), msg, "information")
2691-
msg_box.setStandardButtons(
2692-
QtWidgets.QMessageBox.StandardButton.Ok | QtWidgets.QMessageBox.StandardButton.Abort)
2693-
msg_box.setDefaultButton(QtWidgets.QMessageBox.StandardButton.Ok)
2694-
ret = msg_box.exec()
2695-
if ret == QtWidgets.QMessageBox.StandardButton.Abort:
2696-
self.app.project_path = ""
2697-
self.app.project_name = ""
2698-
return
2699-
self.create_lock_file(break_existing_lock=True)
2700-
else:
2701-
# lock is valid, project seems to be in use by other user
2702-
msg = _('Project cannot be opened since it\'s already in use by "') + lock_user + _(
2703-
'". Please retry later.')
2704-
logger.warning(msg)
2705-
Message(self.app, _("Cannot open file"), msg, "critical").exec()
2706-
self.app.project_path = ""
2707-
self.app.project_name = ""
2708-
return
2709-
self.prepare_heartbeat_thread()
27102549
try:
27112550
self.app.create_connection(proj_path)
27122551
except Exception as err:
@@ -2716,7 +2555,6 @@ def open_project(self, path_="", newproject="no"):
27162555
if self.app.conn is None:
27172556
msg += "\n" + proj_path
27182557
Message(self.app, _("Cannot open file"), msg, "critical").exec()
2719-
self.stop_heartbeat()
27202558
self.app.project_path = ""
27212559
self.app.project_name = ""
27222560
return
@@ -3202,7 +3040,6 @@ def close_project(self):
32023040
print(e_)
32033041
logger.warning(e_)
32043042
self.app.conn = None
3205-
self.stop_heartbeat(wait=True)
32063043
self.delete_backup_folders()
32073044
self.fill_recent_projects_menu_actions()
32083045
self.app.conn = None

0 commit comments

Comments
 (0)