diff --git a/components/AddToPlaylistWindow.py b/components/AddToPlaylistWindow.py index 1f78124..66848e8 100644 --- a/components/AddToPlaylistWindow.py +++ b/components/AddToPlaylistWindow.py @@ -7,25 +7,34 @@ from PyQt5.QtWidgets import ( QLabel, QLineEdit, QPushButton, + QListWidgetItem, ) from PyQt5.QtGui import QFont +import DBA +import logging class AddToPlaylistWindow(QDialog): - def __init__(self, list_options: dict): + def __init__(self, list_options: dict, song_db_ids: list): super(AddToPlaylistWindow, self).__init__() - self.setWindowTitle("Choose") + self.song_db_ids = song_db_ids + self.setWindowTitle("Add songs to playlist:") self.setMinimumSize(400, 400) layout = QVBoxLayout() - listWidget = QListWidget(self) - for k, v in list_options: - listWidget.addItem(f"{k} | {v}") + + self.item_dict = {} + self.listWidget = QListWidget(self) + for i, (k, v) in enumerate(list_options.items()): + item_text = f"{i} | {v}" + item = QListWidgetItem(item_text) + self.listWidget.addItem(item) + self.item_dict[item_text] = k # add ui elements to window label = QLabel("Playlists") label.setFont(QFont("Sans", weight=QFont.Bold)) layout.addWidget(label) - layout.addWidget(listWidget) + layout.addWidget(self.listWidget) # Save button save_button = QPushButton("Add") @@ -35,5 +44,19 @@ class AddToPlaylistWindow(QDialog): self.show() def save(self): - print(self.listWidget.selectedItems()) + selected_items = [item.text() for item in self.listWidget.selectedItems()] + selected_db_ids = [self.item_dict[item] for item in selected_items] + for song in self.song_db_ids: + for playlist in selected_db_ids: + try: + with DBA.DBAccess() as db: + db.execute( + "INSERT INTO song_playlist (playlist_id, song_id) VALUES (?, ?);", + (playlist, song), + ) + except Exception as e: + logging.error( + "AddToPlaylistWindow.py save() | could not insert song into playlist: {e}" + ) + self.close() diff --git a/components/CreatePlaylistWindow.py b/components/CreatePlaylistWindow.py index 323fd00..29cc715 100644 --- a/components/CreatePlaylistWindow.py +++ b/components/CreatePlaylistWindow.py @@ -1,19 +1,45 @@ -from PyQt5.QtWidgets import QInputDialog +import logging +from PyQt5.QtWidgets import QDialog, QHBoxLayout, QLineEdit, QPushButton, QVBoxLayout +import DBA -class CreatePlaylistWindow(QInputDialog): - def __init__(self, list_options: dict): +class CreatePlaylistWindow(QDialog): + def __init__(self): super(CreatePlaylistWindow, self).__init__() - self.setWindowTitle("Choose") - self.setLabelText("Enter playlist name:") - self.exec() + self.setWindowTitle("Create new playlist") + layout = QVBoxLayout() + button_layout = QHBoxLayout() - def done(self, result: int) -> None: - value = self.textValue() - if result: - print(value) + self.input = QLineEdit() + layout.addWidget(self.input) + + ok_button = QPushButton("OK") + ok_button.clicked.connect(self.save) + button_layout.addWidget(ok_button) + + cancel_button = QPushButton("Cancel") + cancel_button.clicked.connect(self.cancel) + button_layout.addWidget(cancel_button) + + layout.addLayout(button_layout) + self.setLayout(layout) + + def save(self) -> None: + """Creates a playlist in the database with a specific name""" + value = self.input.text() + if value == "" or value is None: + self.close() + return else: - print("NOPE") - # FIXME: dialog box doesn't close on OK or Cancel buttons pressed... - # do i have to manually implement the accept and reject when i override the done() func? + try: + with DBA.DBAccess() as db: + db.execute("INSERT INTO playlist (name) VALUES (?);", (value,)) + except Exception as e: + logging.error( + f"CreatePlaylistWindow.py save() | Could not create playlist: {e}" + ) + self.close() + + def cancel(self) -> None: self.close() + return diff --git a/components/MusicTable.py b/components/MusicTable.py index bb101de..b50f74b 100644 --- a/components/MusicTable.py +++ b/components/MusicTable.py @@ -19,6 +19,7 @@ from PyQt5.QtWidgets import ( from PyQt5.QtCore import QAbstractItemModel, QModelIndex, Qt, pyqtSignal, QTimer from components.LyricsWindow import LyricsWindow from components.AddToPlaylistWindow import AddToPlaylistWindow +from utils import delete_song_id_from_database from utils import add_files_to_library from utils import update_song_in_library from utils import get_id3_tags @@ -66,6 +67,8 @@ class MusicTable(QTableView): "TDRC", None, ] + # hide the id column + self.hideColumn(0) # db names of headers self.database_columns = str(self.config["table"]["columns"]).split(",") self.vertical_scroll_position = 0 @@ -124,13 +127,18 @@ class MusicTable(QTableView): selected_indices = self.get_selected_rows() for file in selected_filepaths: with DBA.DBAccess() as db: - db.execute("DELETE FROM song WHERE filepath = ?", (file,)) + song_id = db.query( + "SELECT id FROM song WHERE filepath = ?", (file,) + )[0][0] + delete_song_id_from_database(song_id) + self.model.dataChanged.disconnect(self.on_cell_data_changed) for index in selected_indices: try: model.removeRow(index) except Exception as e: logging.info(f"MusicTable.py delete_songs() failed | {e}") self.fetch_library() + self.model.dataChanged.connect(self.on_cell_data_changed) def open_directory(self): """Opens the currently selected song in the system file manager""" @@ -157,11 +165,14 @@ class MusicTable(QTableView): def add_selected_files_to_playlist(self): """Opens a playlist choice menu and adds the currently selected files to the chosen playlist""" playlist_dict = {} + print(type(playlist_dict)) with DBA.DBAccess() as db: - data = db.query("SELECT id, name from playlist", ()) + data = db.query("SELECT id, name from playlist;", ()) for row in data: - playlist_dict[row[0][0]] = row[0][1] - playlist_choice_window = AddToPlaylistWindow(playlist_dict) + playlist_dict[row[0]] = row[1] + playlist_choice_window = AddToPlaylistWindow( + playlist_dict, self.get_selected_songs_db_ids() + ) playlist_choice_window.exec_() def show_lyrics_menu(self): @@ -387,6 +398,23 @@ class MusicTable(QTableView): """Returns the selected song's ID3 tags""" return get_id3_tags(self.selected_song_filepath) + def get_selected_songs_db_ids(self) -> list: + """Returns a list of id's for the selected songs""" + indexes = self.selectedIndexes() + if not indexes: + return [] + selected_rows = set(index.row() for index in indexes) + id_list = [ + self.model.data(self.model.index(row, 0), Qt.UserRole) + for row in selected_rows + ] + # selected_ids = [] + # for index in indexes: + # model_item = self.model.item(index.row()) + # id_data = model_item.data(Qt.UserRole) + # selected_ids.append(id_data) + return id_list + def get_current_song_filepath(self) -> str: """Returns the currently playing song filepath""" return self.current_song_filepath diff --git a/main.py b/main.py index a1e7f24..23a67e4 100644 --- a/main.py +++ b/main.py @@ -573,7 +573,7 @@ class MainWindow(QMainWindow): def create_playlist(self) -> None: """Creates a database record for a playlist, given a name""" - create_playlist_window = CreatePlaylistWindow(self) + create_playlist_window = CreatePlaylistWindow() create_playlist_window.exec_() def open_preferences(self) -> None: @@ -651,6 +651,7 @@ if __name__ == "__main__": for statement in lines.split(";"): print(f"executing [{statement}]") db.execute(statement, ()) + # logging setup logging.basicConfig(filename="musicpom.log", encoding="utf-8", level=logging.DEBUG) # Allow for dynamic imports of my custom classes and utilities diff --git a/utils/__init__.py b/utils/__init__.py index 39387ec..e1618e1 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -5,6 +5,7 @@ from .safe_get import safe_get from .get_album_art import get_album_art from .get_id3_tags import get_id3_tags from .set_id3_tag import set_id3_tag +from .delete_song_id_from_database import delete_song_id_from_database from .delete_and_create_library_database import delete_and_create_library_database from .update_song_in_library import update_song_in_library from .scan_for_music import scan_for_music diff --git a/utils/delete_song_id_from_database.py b/utils/delete_song_id_from_database.py new file mode 100644 index 0000000..a878069 --- /dev/null +++ b/utils/delete_song_id_from_database.py @@ -0,0 +1,23 @@ +import DBA +from components.ErrorDialog import ErrorDialog + + +def delete_song_id_from_database(song_id: int): + """ + Handles deleting a song from the database by ID + Accounts for playlists and other dependencies + + Returns True on success + False on failure/error + """ + try: + with DBA.DBAccess() as db: + db.execute("DELETE FROM song_playlist WHERE song_id = ?", (song_id,)) + db.execute("DELETE FROM song WHERE id = ?", (song_id,)) + except Exception as e: + dialog = ErrorDialog( + f"delete_song_id_from_database.py | could not delete song id {song_id} from database: {e}" + ) + dialog.exec_() + return False + return True