From a9c863526767f1ddcce3742fa5da39d2b3a1bd51 Mon Sep 17 00:00:00 2001 From: Justin Donofrio Date: Fri, 22 Nov 2024 10:29:46 -0500 Subject: [PATCH] Add ability to download duplicates --- src/onthespot/cli.py | 7 +-- src/onthespot/downloader.py | 4 +- src/onthespot/gui/dl_progressbtn.py | 24 +++++----- src/onthespot/gui/mainui.py | 72 ++++++++++++++--------------- src/onthespot/parse_item.py | 59 +++++++++++++++++------ src/onthespot/post_download.py | 6 +-- 6 files changed, 102 insertions(+), 70 deletions(-) diff --git a/src/onthespot/cli.py b/src/onthespot/cli.py index efd6478..886baff 100644 --- a/src/onthespot/cli.py +++ b/src/onthespot/cli.py @@ -28,13 +28,14 @@ def __init__(self): def run(self): while True: if pending: - item_id = next(iter(pending)) - item = pending.pop(item_id) + local_id = next(iter(pending)) + item = pending.pop(local_id) token = get_account_token() item_metadata = globals()[f"{item['item_service']}_get_{item['item_type']}_metadata"](token, item['item_id']) with download_queue_lock: - download_queue[item['item_id']] = { + download_queue[local_id] = { + 'local_id': local_id, "item_service": item["item_service"], "item_type": item["item_type"], 'item_id': item['item_id'], diff --git a/src/onthespot/downloader.py b/src/onthespot/downloader.py index 4fe84ed..77fda60 100644 --- a/src/onthespot/downloader.py +++ b/src/onthespot/downloader.py @@ -34,8 +34,8 @@ def start(self): def readd_item_to_download_queue(self, item): with download_queue_lock: try: - del download_queue[item['item_id']] - download_queue[item['item_id']] = item + del download_queue[item['local_id']] + download_queue[item['local_id']] = item except (KeyError): # Item likely cleared from queue return diff --git a/src/onthespot/gui/dl_progressbtn.py b/src/onthespot/gui/dl_progressbtn.py index 017a142..12f496d 100644 --- a/src/onthespot/gui/dl_progressbtn.py +++ b/src/onthespot/gui/dl_progressbtn.py @@ -10,9 +10,9 @@ class DownloadActionsButtons(QWidget): - def __init__(self, item_id, item_metadata, pbar, copy_btn, cancel_btn, retry_btn, open_btn, locate_btn, delete_btn, parent=None): + def __init__(self, local_id, item_metadata, pbar, copy_btn, cancel_btn, retry_btn, open_btn, locate_btn, delete_btn, parent=None): super(DownloadActionsButtons, self).__init__(parent) - self.item_id = item_id + self.local_id = local_id self.item_metadata = item_metadata layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) @@ -46,34 +46,34 @@ def copy_link(self): pyperclip.copy(self.item_metadata['item_url']) def cancel_item(self): - download_queue[self.item_id]['item_status'] = "Cancelled" - download_queue[self.item_id]['gui']['status_label'].setText(self.tr("Cancelled")) - download_queue[self.item_id]['gui']['progress_bar'].setValue(0) + download_queue[self.local_id]['item_status'] = "Cancelled" + download_queue[self.local_id]['gui']['status_label'].setText(self.tr("Cancelled")) + download_queue[self.local_id]['gui']['progress_bar'].setValue(0) self.cancel_btn.hide() self.retry_btn.show() def retry_item(self): - download_queue[self.item_id]['item_status'] = "Waiting" - download_queue[self.item_id]['gui']['status_label'].setText(self.tr("Waiting")) - download_queue[self.item_id]['gui']['progress_bar'].setValue(0) + download_queue[self.local_id]['item_status'] = "Waiting" + download_queue[self.local_id]['gui']['status_label'].setText(self.tr("Waiting")) + download_queue[self.local_id]['gui']['progress_bar'].setValue(0) self.retry_btn.hide() self.cancel_btn.show() def open_file(self): - file_path = download_queue[self.item_id]['file_path'] + file_path = download_queue[self.local_id]['file_path'] file = os.path.abspath(file_path) open_item(file) def locate_file(self): - file_path = download_queue[self.item_id]['file_path'] + file_path = download_queue[self.local_id]['file_path'] file_dir = os.path.dirname(file_path) open_item(file_dir) def delete_file(self): - file_path = download_queue[self.item_id]['file_path'] + file_path = download_queue[self.local_id]['file_path'] file = os.path.abspath(file_path) os.remove(file) - download_queue[self.item_id]["gui"]["status_label"].setText(self.tr("Deleted")) + download_queue[self.local_id]["gui"]["status_label"].setText(self.tr("Deleted")) self.open_btn.hide() self.locate_btn.hide() self.delete_btn.hide() diff --git a/src/onthespot/gui/mainui.py b/src/onthespot/gui/mainui.py index 5f01698..afa5546 100644 --- a/src/onthespot/gui/mainui.py +++ b/src/onthespot/gui/mainui.py @@ -31,15 +31,16 @@ def __init__(self): def run(self): while True: if pending: - - item_id = next(iter(pending)) - item = pending.pop(item_id) - token = get_account_token() - item_metadata = globals()[f"{item['item_service']}_get_{item['item_type']}_metadata"](token, item['item_id']) - if item_id not in download_queue: + try: + local_id = next(iter(pending)) + item = pending.pop(local_id) + token = get_account_token() + item_metadata = globals()[f"{item['item_service']}_get_{item['item_type']}_metadata"](token, item['item_id']) self.add_item_to_download_list.emit(item, item_metadata) - continue - + continue + except Exception as e: + logger.error(f"Unknown Exception for {item}: {str(e)}") + pending[local_id] = item else: time.sleep(0.2) @@ -374,7 +375,7 @@ def add_item_to_download_list(self, item, item_metadata): status_label = QLabel(self.tbl_dl_progress) status_label.setText(self.tr("Waiting")) - actions = DownloadActionsButtons(item['item_id'], item_metadata, pbar, copy_btn, cancel_btn, retry_btn, open_btn, locate_btn, delete_btn) + actions = DownloadActionsButtons(item['local_id'], item_metadata, pbar, copy_btn, cancel_btn, retry_btn, open_btn, locate_btn, delete_btn) rows = self.tbl_dl_progress.rowCount() self.tbl_dl_progress.insertRow(rows) @@ -389,7 +390,7 @@ def add_item_to_download_list(self, item, item_metadata): item_label = QLabel(self.tbl_dl_progress) item_label.setText(title) # Add To List - self.tbl_dl_progress.setItem(rows, 0, QTableWidgetItem(str(item['item_id']))) + self.tbl_dl_progress.setItem(rows, 0, QTableWidgetItem(str(item['local_id']))) self.tbl_dl_progress.setCellWidget(rows, 1, item_label) self.tbl_dl_progress.setItem(rows, 2, QTableWidgetItem(item_metadata['artists'])) self.tbl_dl_progress.setItem(rows, 3, QTableWidgetItem(item_category)) @@ -401,7 +402,8 @@ def add_item_to_download_list(self, item, item_metadata): self.update_table_visibility() with download_queue_lock: - download_queue[item['item_id']] = { + download_queue[item['local_id']] = { + 'local_id': item['local_id'], "item_service": item["item_service"], "item_type": item["item_type"], 'item_id': item['item_id'], @@ -472,21 +474,17 @@ def remove_completed_from_download_list(self): with download_queue_lock: check_row = 0 while check_row < self.tbl_dl_progress.rowCount(): - item_id = self.tbl_dl_progress.item(check_row, 0).text() - try: - item_id = int(item_id) - except ValueError: - pass - logger.info(f'Removing Row : {check_row} and mediaid: {item_id}') - if item_id in download_queue: - if download_queue[item_id]['item_status'] in ( + local_id = self.tbl_dl_progress.item(check_row, 0).text() + logger.info(f'Removing Row : {check_row} and mediaid: {local_id}') + if local_id in download_queue: + if download_queue[local_id]['item_status'] in ( "Cancelled", "Downloaded", "Already Exists" ): - logger.info(f'Removing Row : {check_row} and mediaid: {item_id}') + logger.info(f'Removing Row : {check_row} and mediaid: {local_id}') self.tbl_dl_progress.removeRow(check_row) - download_queue.pop(item_id) + download_queue.pop(local_id) else: check_row = check_row + 1 else: @@ -496,15 +494,15 @@ def cancel_all_downloads(self): with download_queue_lock: row_count = self.tbl_dl_progress.rowCount() while row_count > 0: - for item_id in download_queue.keys(): - logger.info(f'Trying to cancel : {item_id}') - if download_queue[item_id]['item_status'] == "Waiting": - download_queue[item_id]['item_status'] = "Cancelled" - download_queue[item_id]['gui']['status_label'].setText(self.tr("Cancelled")) - download_queue[item_id]['gui']['status_label'].setText(self.tr("Cancelled")) - download_queue[item_id]['gui']['progress_bar'].setValue(0) - download_queue[item_id]['gui']["btn"]['cancel'].hide() - download_queue[item_id]['gui']["btn"]['retry'].show() + for local_id in download_queue.keys(): + logger.info(f'Trying to cancel : {local_id}') + if download_queue[local_id]['item_status'] == "Waiting": + download_queue[local_id]['item_status'] = "Cancelled" + download_queue[local_id]['gui']['status_label'].setText(self.tr("Cancelled")) + download_queue[local_id]['gui']['status_label'].setText(self.tr("Cancelled")) + download_queue[local_id]['gui']['progress_bar'].setValue(0) + download_queue[local_id]['gui']["btn"]['cancel'].hide() + download_queue[local_id]['gui']["btn"]['retry'].show() row_count -= 1 self.update_table_visibility() @@ -512,13 +510,13 @@ def retry_all_failed_downloads(self): with download_queue_lock: row_count = self.tbl_dl_progress.rowCount() while row_count > 0: - for item_id in download_queue.keys(): - logger.info(f'Trying to cancel : {item_id}') - if download_queue[item_id]['item_status'] == "Failed": - download_queue[item_id]['item_status'] = "Waiting" - download_queue[item_id]['gui']['status_label'].setText(self.tr("Waiting")) - download_queue[item_id]['gui']["btn"]['cancel'].show() - download_queue[item_id]['gui']["btn"]['retry'].hide() + for local_id in download_queue.keys(): + logger.info(f'Trying to cancel : {local_id}') + if download_queue[local_id]['item_status'] == "Failed": + download_queue[local_id]['item_status'] = "Waiting" + download_queue[local_id]['gui']['status_label'].setText(self.tr("Waiting")) + download_queue[local_id]['gui']["btn"]['cancel'].show() + download_queue[local_id]['gui']["btn"]['retry'].hide() row_count -= 1 self.update_table_visibility() diff --git a/src/onthespot/parse_item.py b/src/onthespot/parse_item.py index 7720e5f..3f85406 100755 --- a/src/onthespot/parse_item.py +++ b/src/onthespot/parse_item.py @@ -73,7 +73,9 @@ def parsingworker(): if current_service == "spotify": if current_type == "track": - pending[item_id] = { + local_id = format_item_id(item_id) + pending[local_id] = { + 'local_id': local_id, 'item_service': current_service, 'item_type': current_type, 'item_id': item_id, @@ -85,7 +87,9 @@ def parsingworker(): tracks = spotify_get_album_tracks(token, current_id) for index, track in enumerate(tracks): item_id = track['id'] - pending[item_id] = { + local_id = format_item_id(item_id) + pending[local_id] = { + 'local_id': local_id, 'item_service': 'spotify', 'item_type': 'track', 'item_id': item_id, @@ -100,7 +104,9 @@ def parsingworker(): try: item_id = item['track']['id'] item_type = item['track']['type'] - pending[item_id] = { + local_id = format_item_id(item_id) + pending[local_id] = { + 'local_id': local_id, 'item_service': 'spotify', 'item_type': item_type, 'item_id': item_id, @@ -120,7 +126,9 @@ def parsingworker(): continue elif current_type == "episode": - pending[item_id] = { + local_id = format_item_id(item_id) + pending[local_id] = { + 'local_id': local_id, 'item_service': current_service, 'item_type': current_type, 'item_id': item_id, @@ -131,7 +139,9 @@ def parsingworker(): elif current_type in ['show', 'audiobook']: episode_ids = spotify_get_show_episodes(token, current_id) for index, episode_id in enumerate(episode_ids): - pending[episode_id] = { + local_id = format_item_id(episode_id) + pending[local_id] = { + 'local_id': local_id, 'item_service': 'spotify', 'item_type': 'episode', 'item_id': episode_id, @@ -143,7 +153,9 @@ def parsingworker(): tracks = spotify_get_liked_songs(token) for index, track in enumerate(tracks): item_id = track['track']['id'] - pending[item_id] = { + local_id = format_item_id(item_id) + pending[local_id] = { + 'local_id': local_id, 'item_service': 'spotify', 'item_type': 'track', 'item_id': item_id, @@ -158,7 +170,9 @@ def parsingworker(): tracks = spotify_get_your_episodes(token) for index, track in enumerate(tracks): item_id = track['show']['id'] - pending[item_id] = { + local_id = format_item_id(item_id) + pending[local_id] = { + 'local_id': local_id, 'item_service': 'spotify', 'item_type': 'episode', 'item_id': item_id, @@ -172,7 +186,9 @@ def parsingworker(): elif current_service == "soundcloud": if current_type == "track": - pending[item_id] = { + local_id = format_item_id(item_id) + pending[local_id] = { + 'local_id': local_id, 'item_service': current_service, 'item_type': current_type, 'item_id': item_id, @@ -184,11 +200,14 @@ def parsingworker(): # Items are added to pending in function to avoid complexity set_data = soundcloud_get_set_items(token, item['item_url']) for index, track in enumerate(set_data['tracks']): - pending[track['id']] = { + item_id = track['id'] + local_id = format_item_id(item_id) + pending[local_id] = { + 'local_id': local_id, 'item_url': track.get('permalink_url', ''), 'item_service': 'soundcloud', 'item_type': 'track', - 'item_id': track['id'], + 'item_id': item_id, 'parent_category': 'playlist' if not set_data['is_album'] else 'album', 'playlist_name': set_data['title'], 'playlist_by': set_data['user']['username'], @@ -198,7 +217,9 @@ def parsingworker(): elif current_service == "deezer": if current_type == "track": - pending[item_id] = { + local_id = format_item_id(item_id) + pending[local_id] = { + 'local_id': local_id, 'item_service': current_service, 'item_type': current_type, 'item_id': item_id, @@ -209,7 +230,9 @@ def parsingworker(): tracks = deezer_get_album_items(current_id) for index, track in enumerate(tracks): item_id = track['id'] - pending[item_id] = { + local_id = format_item_id(item_id) + pending[local_id] = { + 'local_id': local_id, 'item_service': 'deezer', 'item_type': 'track', 'item_id': item_id, @@ -221,7 +244,9 @@ def parsingworker(): playlist_name, playlist_by = deezer_get_playlist_data(current_id) for index, track in enumerate(tracks): item_id = track['id'] - pending[item_id] = { + local_id = format_item_id(item_id) + pending[local_id] = { + 'local_id': local_id, 'item_service': 'deezer', 'item_type': 'track', 'item_id': item_id, @@ -238,3 +263,11 @@ def parsingworker(): continue else: time.sleep(0.2) + +def format_item_id(item_id): + suffix = 0 + local_id = f"{item_id}-{suffix}" + while local_id in download_queue or local_id in pending: + suffix += 1 + local_id = f"{item_id}-{suffix}" + return local_id \ No newline at end of file diff --git a/src/onthespot/post_download.py b/src/onthespot/post_download.py index 8e701ec..5c269cf 100644 --- a/src/onthespot/post_download.py +++ b/src/onthespot/post_download.py @@ -390,11 +390,11 @@ def add_to_m3u_file(item, item_metadata): # Check if the item_path is already in the M3U file with open(m3u_path, 'r') as m3u_file: + m3u_item_header = f"#EXTINF:{round(int(item_metadata['length'])/1000)}, {item['playlist_number']}. {item_metadata['artists']} - {item_metadata['title']}" m3u_contents = m3u_file.readlines() - - if item['file_path'] not in [line.strip() for line in m3u_contents]: + if m3u_item_header not in [line.strip() for line in m3u_contents]: with open(m3u_path, 'a') as m3u_file: - m3u_file.write(f"#EXTINF:{round(int(item_metadata['length'])/1000)}, {item_metadata['artists']} - {item_metadata['title']}\n{item['file_path']}\n") + m3u_file.write(f"{m3u_item_header}\n{item['file_path']}\n") else: logger.info(f"{item['file_path']} already exists in the M3U file.")