You've already forked OneDriveGUI
mirror of
https://github.com/bpozdena/OneDriveGUI.git
synced 2026-10-24 04:59:17 +03:00
Offer to take over an OneDrive client left running outside the GUI
The client refuses to start a second instance for the same configuration directory, so when OneDriveGUI starts and a client for a profile is already running (typically orphaned by a previous GUI session that exited without stopping it, or started manually), the GUI could not monitor the profile - the client failed with "The 'onedrive' application is already running" or the GUI showed no status at all. Before starting a monitor, OneDriveGUI now scans the process list for a client using the profile's configuration directory. If one is found, the user can stop it gracefully (SIGINT, killed after 10 s at the latest) and start monitoring in the GUI, or keep the external client and not start the monitor. Together with the stop-on-exit option this closes the reconnect gap after logouts and crashes. Fixes #267
This commit is contained in:
@@ -27,6 +27,7 @@ import re
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
|
||||
import requests
|
||||
import subprocess
|
||||
@@ -890,12 +891,78 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
if self.profile_status_pages[profile_name].pushButton_start.isEnabled() and global_config[profile_name]["auto_sync"] == "True":
|
||||
self.start_onedrive_monitor(profile_name)
|
||||
|
||||
@staticmethod
|
||||
def find_running_client(profile_name):
|
||||
"""
|
||||
PID of an OneDrive client process running with this profile's
|
||||
configuration directory, or None. Such a process is typically left
|
||||
behind by a previous OneDriveGUI run or started outside the GUI; the
|
||||
client refuses to start a second instance for the same configuration.
|
||||
"""
|
||||
config_file = global_config[profile_name]["config_file"].strip('"')
|
||||
config_dir = os.path.expanduser(re.search(r"(.+)/.+$", config_file).group(1))
|
||||
|
||||
for proc_entry in os.listdir("/proc"):
|
||||
if not proc_entry.isdigit():
|
||||
continue
|
||||
try:
|
||||
with open(f"/proc/{proc_entry}/cmdline", "rb") as cmdline_file:
|
||||
cmdline = cmdline_file.read()
|
||||
except OSError:
|
||||
continue
|
||||
if b"onedrive" in cmdline and os.fsencode(config_dir) in cmdline:
|
||||
return int(proc_entry)
|
||||
return None
|
||||
|
||||
def take_over_running_client(self, profile_name):
|
||||
"""
|
||||
A client for this profile is already running outside OneDriveGUI. Ask the
|
||||
user whether to stop it so the GUI can monitor the profile, and if
|
||||
agreed, stop it gracefully and wait for it to exit.
|
||||
Returns True when the caller can start monitoring.
|
||||
"""
|
||||
orphan_pid = self.find_running_client(profile_name)
|
||||
if orphan_pid is None:
|
||||
return True
|
||||
|
||||
answer = QMessageBox.question(
|
||||
self,
|
||||
f"OneDrive client already running for profile {profile_name}",
|
||||
f"An OneDrive client for profile <b>{profile_name}</b> is already running outside OneDriveGUI (PID {orphan_pid}).<br><br>"
|
||||
"It was probably left behind by a previous OneDriveGUI session. Only one client can use this profile.<br><br>"
|
||||
"Stop it and start monitoring it in OneDriveGUI?",
|
||||
buttons=QMessageBox.Yes | QMessageBox.No,
|
||||
defaultButton=QMessageBox.Yes,
|
||||
)
|
||||
if answer != QMessageBox.Yes:
|
||||
logging.info(f"[{profile_name}] Keeping the external OneDrive client (PID {orphan_pid}); not starting the monitor")
|
||||
return False
|
||||
|
||||
logging.info(f"[{profile_name}] Stopping external OneDrive client (PID {orphan_pid})")
|
||||
self.profile_status_pages[profile_name].label_onedrive_status.setText("Stopping external OneDrive client...")
|
||||
try:
|
||||
os.kill(orphan_pid, signal.SIGINT)
|
||||
for _ in range(50): # Wait up to 10 s for a graceful shutdown
|
||||
try:
|
||||
os.kill(orphan_pid, 0)
|
||||
except ProcessLookupError:
|
||||
break
|
||||
time.sleep(0.2)
|
||||
else:
|
||||
logging.warning(f"[{profile_name}] External client did not exit within 10s - killing it")
|
||||
os.kill(orphan_pid, signal.SIGKILL)
|
||||
except ProcessLookupError:
|
||||
pass # Exited on its own in the meantime
|
||||
return True
|
||||
|
||||
def start_onedrive_monitor(self, profile_name, options=""):
|
||||
# Clear any previous error messages when starting sync
|
||||
self.profile_status_pages[profile_name].label_error_icon.clear()
|
||||
self.profile_status_pages[profile_name].label_error_icon.setToolTip("")
|
||||
|
||||
if profile_name not in workers:
|
||||
if not self.take_over_running_client(profile_name):
|
||||
return
|
||||
workers[profile_name] = WorkerThread(profile_name, options)
|
||||
workers[profile_name].start()
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user