Import rewrite WIP
This commit is contained in:
parent
3b71041b66
commit
4a4058d211
@ -126,5 +126,5 @@ class Config(object):
|
||||
|
||||
# These rely on earlier definitions
|
||||
HIDE_PLAYED_MODE = HIDE_PLAYED_MODE_SECTIONS
|
||||
IMPORT_DESTINATION = os.path.join(ROOT, "Singles")
|
||||
IMPORT_DESTINATION = "/tmp/mm" # os.path.join(ROOT, "Singles")
|
||||
REPLACE_FILES_DEFAULT_DESTINATION = os.path.dirname(REPLACE_FILES_DEFAULT_SOURCE)
|
||||
|
||||
@ -17,6 +17,7 @@ from PyQt6.QtCore import (
|
||||
from PyQt6.QtWidgets import (
|
||||
QButtonGroup,
|
||||
QDialog,
|
||||
QFileDialog,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
@ -36,7 +37,7 @@ from config import Config
|
||||
from helpers import (
|
||||
file_is_unreadable,
|
||||
get_tags,
|
||||
show_warning,
|
||||
show_OK,
|
||||
)
|
||||
from log import log
|
||||
from models import db, Tracks
|
||||
@ -88,6 +89,9 @@ class DoTrackImport(QObject):
|
||||
shutil.move(self.destination_track_path, temp_file)
|
||||
# Move file to destination
|
||||
shutil.move(self.import_file_path, self.destination_track_path)
|
||||
# Clean up
|
||||
if temp_file and os.path.exists(temp_file):
|
||||
os.unlink(temp_file)
|
||||
|
||||
with db.Session() as session:
|
||||
self.signals.status_message_signal.emit(
|
||||
@ -143,10 +147,10 @@ class FileImporter:
|
||||
# Create ModelData
|
||||
if not row_number:
|
||||
row_number = base_model.rowCount()
|
||||
self.model_data = ModelData(base_model=base_model, row_number=row_number)
|
||||
self.model_data = ThreadData(base_model=base_model, row_number=row_number)
|
||||
|
||||
# Place to keep reference to importer threads and data
|
||||
self.thread_data: dict[QThread, ModelData] = {}
|
||||
self.thread_data: dict[QThread, ThreadData] = {}
|
||||
|
||||
# Data structure to track files to import
|
||||
self.import_files_data: dict[str, TrackFileData] = {}
|
||||
@ -187,7 +191,7 @@ class FileImporter:
|
||||
self.import_files_data[path].tags = get_tags(path)
|
||||
except ApplicationError as e:
|
||||
self.import_files_data[path].import_this_file = False
|
||||
self.import_files_data[path].error = f"{path} tag errors ({str(e)})"
|
||||
self.import_files_data[path].error = f"Tag errors ({str(e)})"
|
||||
continue
|
||||
|
||||
# Get track match data
|
||||
@ -200,26 +204,17 @@ class FileImporter:
|
||||
# Process user choices
|
||||
self.process_user_choices(path)
|
||||
|
||||
# Tell users about files that won't be imported
|
||||
for path in [
|
||||
a
|
||||
for a in self.import_files_data.keys()
|
||||
if not self.import_files_data[a].import_this_file
|
||||
]:
|
||||
msg = (
|
||||
f"{os.path.basename(path)} will not be imported because "
|
||||
f"{self.import_files_data[path].error}"
|
||||
)
|
||||
show_warning(None, "File not imported", msg)
|
||||
|
||||
# Import files once we have data for all of them (to avoid long
|
||||
# drawn-out user interaction while we import files)
|
||||
for path in [
|
||||
a
|
||||
for a in self.import_files_data.keys()
|
||||
if self.import_files_data[a].import_this_file
|
||||
]:
|
||||
self._start_thread(path)
|
||||
# Import files and tell users about files that won't be imported
|
||||
msgs: list[str] = []
|
||||
for (path, entry) in self.import_files_data.items():
|
||||
if entry.import_this_file:
|
||||
self._start_thread(path)
|
||||
else:
|
||||
msgs.append(
|
||||
f"{os.path.basename(path)} will not be imported because {entry.error}"
|
||||
)
|
||||
if msgs:
|
||||
show_OK("File not imported", "\r\r".join(msgs))
|
||||
|
||||
def _start_thread(self, path: str) -> None:
|
||||
"""
|
||||
@ -230,27 +225,36 @@ class FileImporter:
|
||||
|
||||
# Create thread and worker
|
||||
thread = QThread()
|
||||
self.worker = DoTrackImport(
|
||||
worker = DoTrackImport(
|
||||
associated_thread=thread,
|
||||
import_file_path=path,
|
||||
tags=self.import_files_data[path].tags,
|
||||
destination_path=self.import_files_data[path].destination_path,
|
||||
track_id=self.import_files_data[path].track_id
|
||||
track_id=self.import_files_data[path].track_id,
|
||||
)
|
||||
|
||||
# Associate data with the thread
|
||||
self.model_data.worker = worker
|
||||
self.thread_data[thread] = self.model_data
|
||||
|
||||
# Move self.worker to thread
|
||||
self.worker.moveToThread(thread)
|
||||
# Move worker to thread
|
||||
worker.moveToThread(thread)
|
||||
log.debug(f"_start_thread_worker started ({path=}, {id(thread)=}, {id(worker)=})")
|
||||
|
||||
# Connect signals
|
||||
thread.started.connect(self.worker.run)
|
||||
self.worker.import_finished.connect(self._thread_finished)
|
||||
self.worker.import_finished.connect(thread.quit)
|
||||
self.worker.import_finished.connect(self.worker.deleteLater)
|
||||
thread.started.connect(lambda: log.debug(f"Thread {thread} started"))
|
||||
thread.started.connect(worker.run)
|
||||
|
||||
thread.finished.connect(lambda: log.debug(f"Thread {thread} finished"))
|
||||
thread.finished.connect(thread.deleteLater)
|
||||
|
||||
worker.import_finished.connect(
|
||||
lambda: log.debug(f"Worker task finished for thread {thread}")
|
||||
)
|
||||
worker.import_finished.connect(self._thread_finished)
|
||||
worker.import_finished.connect(thread.quit)
|
||||
worker.import_finished.connect(worker.deleteLater)
|
||||
|
||||
# Start thread
|
||||
thread.start()
|
||||
|
||||
@ -259,6 +263,8 @@ class FileImporter:
|
||||
If track already in playlist, refresh it else insert it
|
||||
"""
|
||||
|
||||
log.debug(f" Ending thread {thread}")
|
||||
|
||||
model_data = self.thread_data.pop(thread, None)
|
||||
if model_data:
|
||||
if model_data.base_model:
|
||||
@ -355,22 +361,29 @@ class FileImporter:
|
||||
# enough
|
||||
default = 1 # default choice is import as new
|
||||
track_match_data = self.import_files_data[path].track_match_data
|
||||
if (
|
||||
track_match_data[0].artist_match >= Config.FUZZYMATCH_MINIMUM_SELECT_ARTIST
|
||||
and track_match_data[0].title_match
|
||||
>= Config.FUZZYMATCH_MINIMUM_SELECT_TITLE
|
||||
):
|
||||
default = 2
|
||||
try:
|
||||
if track_match_data:
|
||||
if (
|
||||
track_match_data[0].artist_match
|
||||
>= Config.FUZZYMATCH_MINIMUM_SELECT_ARTIST
|
||||
and track_match_data[0].title_match
|
||||
>= Config.FUZZYMATCH_MINIMUM_SELECT_TITLE
|
||||
):
|
||||
default = 2
|
||||
|
||||
for rec in track_match_data:
|
||||
existing_track_description = (f"{rec.title} ({rec.artist})")
|
||||
if Config.FUZZYMATCH_SHOW_SCORES:
|
||||
existing_track_description += f" ({rec.title_match:.0f}%)"
|
||||
existing_track_path = self._get_existing_track(rec.track_id).path
|
||||
choices.append(
|
||||
(existing_track_description, rec.track_id, existing_track_path)
|
||||
)
|
||||
for rec in track_match_data:
|
||||
existing_track_description = f"{rec.title} ({rec.artist})"
|
||||
if Config.FUZZYMATCH_SHOW_SCORES:
|
||||
existing_track_description += f" ({rec.title_match:.0f}%)"
|
||||
existing_track_path = self._get_existing_track(rec.track_id).path
|
||||
choices.append(
|
||||
(existing_track_description, rec.track_id, existing_track_path)
|
||||
)
|
||||
except IndexError:
|
||||
import pdb
|
||||
|
||||
pdb.set_trace()
|
||||
print(2)
|
||||
dialog = PickMatch(
|
||||
new_track_description=importing_track_description,
|
||||
choices=choices,
|
||||
@ -383,6 +396,32 @@ class FileImporter:
|
||||
elif dialog.selected_track_id > 0:
|
||||
self.replace_file(path=path, track_id=dialog.selected_track_id)
|
||||
else:
|
||||
# Import as new, but check destination path doesn't
|
||||
# already exists
|
||||
while os.path.exists(self.import_files_data[path].destination_path):
|
||||
msg = (
|
||||
"New import requested but default destination path ({path}) "
|
||||
"already exists. Click OK and choose where to save this track"
|
||||
)
|
||||
import pdb
|
||||
|
||||
pdb.set_trace()
|
||||
show_OK(None, title="Desintation path exists", msg=msg)
|
||||
# Get output filename
|
||||
pathspec = QFileDialog.getSaveFileName(
|
||||
None,
|
||||
"Save imported track",
|
||||
directory=Config.IMPORT_DESTINATION,
|
||||
)
|
||||
if not pathspec:
|
||||
self.import_files_data[path].import_this_file = False
|
||||
self.import_files_data[
|
||||
path
|
||||
].error = "destination file already exists"
|
||||
return
|
||||
|
||||
self.import_files_data[path].destination_path = pathspec[0]
|
||||
|
||||
self.import_as_new(path=path)
|
||||
else:
|
||||
# User cancelled dialog
|
||||
@ -398,12 +437,12 @@ class FileImporter:
|
||||
|
||||
tfd = self.import_files_data[path]
|
||||
|
||||
destination_path = os.path.join(Config.IMPORT_DESTINATION, os.path.basename(path))
|
||||
destination_path = os.path.join(
|
||||
Config.IMPORT_DESTINATION, os.path.basename(path)
|
||||
)
|
||||
if os.path.exists(destination_path):
|
||||
tfd.import_this_file = False
|
||||
tfd.error = (
|
||||
f"this is a new import but destination file already exists ({destination_path})"
|
||||
)
|
||||
tfd.error = f"this is a new import but destination file already exists ({destination_path})"
|
||||
return
|
||||
|
||||
tfd.destination_path = destination_path
|
||||
@ -435,9 +474,10 @@ class FileImporter:
|
||||
|
||||
|
||||
@dataclass
|
||||
class ModelData:
|
||||
class ThreadData:
|
||||
base_model: PlaylistModel
|
||||
row_number: int
|
||||
worker: Optional[DoTrackImport] = None
|
||||
|
||||
|
||||
class PickMatch(QDialog):
|
||||
|
||||
@ -10,7 +10,7 @@ import ssl
|
||||
import tempfile
|
||||
|
||||
# PyQt imports
|
||||
from PyQt6.QtWidgets import QMainWindow, QMessageBox
|
||||
from PyQt6.QtWidgets import QMainWindow, QMessageBox, QWidget
|
||||
|
||||
# Third party imports
|
||||
from mutagen.flac import FLAC # type: ignore
|
||||
@ -204,14 +204,14 @@ def get_tags(path: str) -> Tags:
|
||||
except TinyTagException:
|
||||
raise ApplicationError(f"Can't read tags: get_tags({path=})")
|
||||
|
||||
if not tag.title:
|
||||
tag.title = ""
|
||||
if not tag.artist:
|
||||
tag.artist = ""
|
||||
if not tag.bitrate:
|
||||
tag.bitrate = 0.0
|
||||
if not tag.duration:
|
||||
tag.duration = 0.0
|
||||
if (
|
||||
tag.title is None
|
||||
or tag.artist is None
|
||||
or tag.bitrate is None
|
||||
or tag.duration is None
|
||||
):
|
||||
raise ApplicationError(f"Missing tags: get_tags({path=})")
|
||||
|
||||
return Tags(
|
||||
title=tag.title,
|
||||
artist=tag.artist,
|
||||
@ -400,10 +400,16 @@ def set_track_metadata(track: Tracks) -> None:
|
||||
setattr(track, tag_key, getattr(tags, tag_key))
|
||||
|
||||
|
||||
def show_OK(parent: QMainWindow, title: str, msg: str) -> None:
|
||||
def show_OK(title: str, msg: str, parent: Optional[QWidget] = None) -> None:
|
||||
"""Display a message to user"""
|
||||
|
||||
QMessageBox.information(parent, title, msg, buttons=QMessageBox.StandardButton.Ok)
|
||||
dlg = QMessageBox(parent)
|
||||
dlg.setIcon(QMessageBox.Icon.Information)
|
||||
dlg.setWindowTitle(title)
|
||||
dlg.setText(msg)
|
||||
dlg.setStandardButtons(QMessageBox.StandardButton.Ok)
|
||||
|
||||
_ = dlg.exec()
|
||||
|
||||
|
||||
def show_warning(parent: Optional[QMainWindow], title: str, msg: str) -> None:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user