feat: auto-detect iPod connection via QTimer polling + fix AlreadyMounted in _mount_linux

This commit is contained in:
Maksim Totmin 2026-06-01 12:29:43 +07:00
parent 71d162520c
commit 25bb8105ac
5 changed files with 97 additions and 1 deletions

View File

@ -16,6 +16,7 @@ from PyQt6.QtWidgets import (
from PyQt6.QtCore import Qt
from config_loader import ConfigLoader
from device_monitor import DeviceMonitor
from hotkeys import HotkeyManager
from ui.library_tab import LibraryTab
from ui.ipod_tab import iPodTab
@ -36,6 +37,7 @@ class MainWindow(QMainWindow):
self.config_loader = ConfigLoader()
self.hotkey_manager: Optional[HotkeyManager] = None
self.device_monitor: Optional[DeviceMonitor] = None
self.ipod_tab_index = 1
self._setup_ui()
@ -46,6 +48,7 @@ class MainWindow(QMainWindow):
self.library_tab._scan_library()
self.library_tab._resume_playback_if_saved()
self.ipod_tab._on_refresh_devices_clicked()
self._start_device_monitor()
def _setup_ui(self):
main_widget = QWidget()
@ -74,6 +77,7 @@ class MainWindow(QMainWindow):
self.settings_tab.hide_library_buttons_changed.connect(
self.library_tab.set_buttons_visible
)
self.settings_tab.auto_detect_changed.connect(self._on_auto_detect_toggled)
def _on_device_mounted(self, mount_point: str):
self.library_tab.on_device_mounted(mount_point)
@ -85,6 +89,23 @@ class MainWindow(QMainWindow):
self.tab_widget.setTabVisible(self.ipod_tab_index, False)
self.statusBar().showMessage("iPod disconnected")
def _start_device_monitor(self):
auto_detect = self.config_loader.get_boolean("Device", "auto_detect", fallback=True)
self.device_monitor = DeviceMonitor()
if auto_detect:
self.ipod_tab.start_monitoring(self.device_monitor)
self.device_monitor.start()
def _on_auto_detect_toggled(self, enabled: bool):
if not self.device_monitor:
return
if enabled:
self.ipod_tab.start_monitoring(self.device_monitor)
self.device_monitor.start()
else:
self.device_monitor.stop()
self.ipod_tab.stop_monitoring(self.device_monitor)
def _load_settings(self):
config = self.config_loader
self.library_tab.load_player_settings(config)
@ -128,6 +149,8 @@ class MainWindow(QMainWindow):
self.settings_tab.refresh_shortcuts_table()
def closeEvent(self, event):
if self.device_monitor:
self.device_monitor.stop()
self._save_settings()
event.accept()

38
src/device_monitor.py Normal file
View File

@ -0,0 +1,38 @@
import logging
from typing import Set
from PyQt6.QtCore import QObject, QTimer, pyqtSignal
from ipod_device import IPodDevice
logger = logging.getLogger(__name__)
class DeviceMonitor(QObject):
state_changed = pyqtSignal(list)
def __init__(self, parent=None):
super().__init__(parent)
self.timer = QTimer(self)
self.timer.timeout.connect(self._poll)
self._device_ids: Set[str] = set()
def start(self, interval_ms: int = 3000):
self._device_ids.clear()
self._poll()
self.timer.start(interval_ms)
def stop(self):
self.timer.stop()
self._device_ids.clear()
def _poll(self):
try:
devices = IPodDevice().detect_devices()
current_ids = {d["id"] for d in devices}
if current_ids != self._device_ids:
self._device_ids = current_ids
self.state_changed.emit(devices)
except Exception:
logger.exception("DeviceMonitor poll failed")

View File

@ -294,6 +294,24 @@ class IPodDevice:
self.logger.error(f"Device {device_id} does not exist")
return None
# Check if already mounted via findmnt (avoids udisksctl AlreadyMounted error)
try:
fm_result = subprocess.run(
["findmnt", "-n", "-o", "TARGET", device_id],
capture_output=True, check=False, text=True
)
if fm_result.returncode == 0 and fm_result.stdout.strip():
mount_point = fm_result.stdout.strip()
if os.path.exists(mount_point):
self.mount_point = mount_point
self._block_device = device_id
name = device_id[5:]
self._parent_drive = f"/dev/{name.rstrip('0123456789')}"
self.logger.info(f"Device already mounted at: {mount_point}")
return mount_point
except Exception:
self.logger.warning("findmnt check failed, falling through to udisksctl")
try:
result = subprocess.run(
["udisksctl", "mount", "-b", device_id],

View File

@ -11,6 +11,7 @@ from PyQt6.QtWidgets import (
from PyQt6.QtCore import Qt, pyqtSignal
from ipod_device import IPodDevice
from device_monitor import DeviceMonitor
from worker import WorkerThread
logger = logging.getLogger(__name__)
@ -486,6 +487,20 @@ class iPodTab(QWidget):
QMessageBox.information(self, "Duplicates" if success else "Error", message)
self._cleanup_worker()
def start_monitoring(self, monitor: DeviceMonitor):
monitor.state_changed.connect(self._on_auto_state_changed)
def stop_monitoring(self, monitor: DeviceMonitor):
try:
monitor.state_changed.disconnect(self._on_auto_state_changed)
except TypeError:
pass
def _on_auto_state_changed(self, devices):
if self.worker_thread and self.worker_thread.isRunning():
return
self._on_device_detection_finished(True, f"Found {len(devices)} devices", devices)
def _cleanup_worker(self):
if self.worker_thread:
self.worker_thread.wait(3000)

View File

@ -20,6 +20,7 @@ class SettingsTab(QWidget):
"""Settings tab widget."""
hide_library_buttons_changed = pyqtSignal(bool)
auto_detect_changed = pyqtSignal(bool)
def __init__(self, parent=None):
super().__init__(parent)
@ -80,8 +81,9 @@ class SettingsTab(QWidget):
self.clean_temp_check.setChecked(True)
advanced_layout.addWidget(self.clean_temp_check)
self.auto_detect_check = QCheckBox("Auto-Detect iPod on Startup")
self.auto_detect_check = QCheckBox("Auto-Detect iPod")
self.auto_detect_check.setChecked(True)
self.auto_detect_check.toggled.connect(self.auto_detect_changed.emit)
advanced_layout.addWidget(self.auto_detect_check)
self.hide_library_buttons_check = QCheckBox("Hide Library Toolbar Buttons")