Fix UI freeze on iPod mount/track-load and add content_hash/sync-metadata

- Move mount_device, get_device_info, and track scanning to background
  WorkerThread (new 'mount_and_load' task type) so the UI stays responsive
- Fix DeviceMonitor auto-poll to run detect_devices() in a background thread
- Refactor _on_device_detection_finished and _on_mount_clicked to use the
  new worker; remove synchronous _set_device_mounted
- Avoid double scan (get_track_count + get_all_tracks) — scan once in worker
- Add content_hash fingerprint to _scan_ipod_files and library_cache for
  track matching
- Add update_track_metadata / _write_databases_from_tracks with artwork
  override support in Nano7Database
- Add 'Sync Metadata' button and context menu action in LibraryTab
This commit is contained in:
Maksim Totmin
2026-06-01 12:37:49 +07:00
parent 25bb8105ac
commit 3bfb0ebbfe
6 changed files with 437 additions and 74 deletions
+91 -1
View File
@@ -214,6 +214,12 @@ class LibraryTab(QWidget):
self.library_transfer_btn.clicked.connect(self._on_library_transfer_clicked)
toolbar.addWidget(self.library_transfer_btn)
self.library_sync_btn = QPushButton("\uD83D\uDD04 Sync Metadata")
self.library_sync_btn.setEnabled(False)
self.library_sync_btn.setToolTip("Sync metadata and artwork for all tracks on iPod")
self.library_sync_btn.clicked.connect(self._on_sync_metadata_clicked)
toolbar.addWidget(self.library_sync_btn)
self.library_remove_btn = QPushButton("\uD83D\uDDD1\uFE0F Remove")
self.library_remove_btn.setEnabled(False)
self.library_remove_btn.clicked.connect(self._on_library_remove_selected)
@@ -233,6 +239,7 @@ class LibraryTab(QWidget):
self.library_add_btn,
self.library_convert_btn,
self.library_transfer_btn,
self.library_sync_btn,
self.library_remove_btn,
self.library_edit_btn,
refresh_btn,
@@ -717,7 +724,9 @@ class LibraryTab(QWidget):
has_ready = ready_count > 0
has_source = source_count > 0
self.library_transfer_btn.setEnabled(has_ready and self._current_mount_point is not None)
has_mount = self._current_mount_point is not None
self.library_transfer_btn.setEnabled(has_ready and has_mount)
self.library_sync_btn.setEnabled(has_ready and has_mount)
self.library_convert_btn.setEnabled(has_source)
self.library_remove_btn.setEnabled(len(all_tracks) > 0)
self.library_edit_btn.setEnabled(len(all_tracks) > 0)
@@ -984,6 +993,8 @@ class LibraryTab(QWidget):
play_action.triggered.connect(lambda: self._play_track_from_index(row))
if is_ready and self._current_mount_point is not None:
sync_action = menu.addAction("\uD83D\uDD04 Sync Metadata to iPod")
sync_action.triggered.connect(self._on_sync_metadata_clicked)
transfer_action = menu.addAction("\uD83D\uDCE4 Transfer to iPod")
transfer_action.triggered.connect(self._on_library_transfer_clicked)
else:
@@ -1129,6 +1140,84 @@ class LibraryTab(QWidget):
self._cleanup_worker()
def _on_sync_metadata_clicked(self):
selected_tracks = self._get_selected_ready_tracks()
if not selected_tracks:
QMessageBox.warning(self, "Error", "No tracks selected.")
return
if not self._current_mount_point:
QMessageBox.warning(self, "Error", "No iPod device mounted.")
return
if self.worker_thread and self.worker_thread.isRunning():
QMessageBox.information(self, "Info", "Please wait for the current task to finish")
return
self.library_sync_btn.setEnabled(False)
self.library_status.setText("Matching tracks on iPod...")
self.library_progress.setValue(0)
from ipod_nano7_db import Nano7Database, content_hash as compute_content_hash
db = Nano7Database(self._current_mount_point)
ipod_tracks = db.get_all_tracks()
ipod_by_hash = {t.get("content_hash", ""): t for t in ipod_tracks if t.get("content_hash")}
sync_batch = []
errors = []
for track_info, local_path in selected_tracks:
ch = compute_content_hash(local_path)
ipod_track = ipod_by_hash.get(ch)
if ipod_track is None:
ipod_track = db.find_track_by_metadata(
track_info.title, track_info.artist, track_info.album or "",
)
if ipod_track is None:
errors.append(f"'{track_info.title}' — not found on iPod")
continue
sync_batch.append({
"ipod_pid": ipod_track["pid"],
"local_path": local_path,
"new_metadata": {
"title": track_info.title,
"artist": track_info.artist,
"album": track_info.album or "",
"track_number": track_info.track_number or 0,
"genre": track_info.genre or "",
},
})
if not sync_batch:
msg = "No matching tracks found on iPod"
if errors:
msg += ":\n" + "\n".join(errors[:5])
QMessageBox.warning(self, "Sync Error", msg)
return
if errors:
logger.warning("Sync metadata skipped for %d unmatched track(s)", len(errors))
self.worker_thread = WorkerThread(
task_type="sync_metadata",
sync_batch=sync_batch,
mount_point=self._current_mount_point,
)
self.worker_thread.progress_signal.connect(self._on_library_progress)
self.worker_thread.finished_signal.connect(self._on_sync_metadata_finished)
self.worker_thread.start()
def _on_sync_metadata_finished(self, success, message, result):
self.library_sync_btn.setEnabled(True)
if success:
self.library_status.setText(message)
QMessageBox.information(self, "Sync Complete", message)
else:
self.library_status.setText(f"Error: {message}")
QMessageBox.warning(self, "Sync Error", message)
self._cleanup_worker()
def _cleanup_worker(self):
if self.worker_thread:
self.worker_thread.wait(3000)
@@ -1168,6 +1257,7 @@ class LibraryTab(QWidget):
def on_device_unmounted(self):
self._current_mount_point = None
self.library_transfer_btn.setEnabled(False)
self.library_sync_btn.setEnabled(False)
self.library_progress.setVisible(False)
self.library_status.setVisible(False)