diff --git a/ytdl_subscribe/entries/entry.py b/ytdl_subscribe/entries/entry.py index 10219a3c..edffac3c 100644 --- a/ytdl_subscribe/entries/entry.py +++ b/ytdl_subscribe/entries/entry.py @@ -80,6 +80,13 @@ class Entry: """Returns the entry's upload month as an int""" return int(self.upload_day_padded.lstrip("0")) + @property + def standardized_upload_date(self) -> str: + """ + :return: upload date as YYYY-MM-DD + """ + return f"{self.upload_year}-{self.upload_month_padded}-{self.upload_day_padded}" + @property def description(self) -> str: return self.kwargs("description") @@ -126,6 +133,7 @@ class Entry: "upload_month_padded": self.upload_month_padded, "upload_day": self.upload_day, "upload_day_padded": self.upload_day_padded, + "standardized_upload_date": self.standardized_upload_date, "thumbnail": self.thumbnail, "thumbnail_ext": self.thumbnail_ext, } diff --git a/ytdl_subscribe/subscriptions/subscription.py b/ytdl_subscribe/subscriptions/subscription.py index 06d624b1..dd2c7fba 100644 --- a/ytdl_subscribe/subscriptions/subscription.py +++ b/ytdl_subscribe/subscriptions/subscription.py @@ -1,3 +1,4 @@ +import contextlib import os import shutil from abc import ABC @@ -126,6 +127,10 @@ class Subscription(Generic[S], ABC): def _post_process_nfo(self, nfo_options: NFOValidator, entry: Optional[Entry] = None): """ Creates an entry's NFO file using values defined in the metadata options + + :param nfo_options: Options for the NFO + :param entry: Optional. Will pass entry values to nfo string formatters. If None, will only + use override variables that must resolve. """ nfo = {} @@ -151,21 +156,27 @@ class Subscription(Generic[S], ABC): with open(nfo_file_path, "wb") as nfo_file: nfo_file.write(xml) + @contextlib.contextmanager + def _maintain_archive_file(self): + if not self.output_options.maintain_download_archive: + return + + existing_download_archive = Path(self.output_directory) / self.download_archive_file_name + working_directory_archive = Path(self.working_directory) / self.download_archive_file_name + + if os.path.exists(existing_download_archive): + shutil.copy(existing_download_archive, working_directory_archive) + + yield + + shutil.copy(working_directory_archive, existing_download_archive) + def download(self): """ Performs the subscription download. """ - existing_download_archive = Path(self.output_directory) / self.download_archive_file_name - working_directory_archive = Path(self.working_directory) / self.download_archive_file_name - - if self.output_options.maintain_download_archive: - if os.path.exists(existing_download_archive): - shutil.copy(existing_download_archive, working_directory_archive) - - self._extract_info() - - if self.output_options.maintain_download_archive: - shutil.copy(working_directory_archive, existing_download_archive) + with self._maintain_archive_file(): + self._extract_info() def _extract_info(self): """ diff --git a/ytdl_subscribe/ytdl_additions/__init__.py b/ytdl_subscribe/ytdl_additions/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ytdl_subscribe/ytdl_additions/enhanced_download_archive.py b/ytdl_subscribe/ytdl_additions/enhanced_download_archive.py new file mode 100644 index 00000000..ad45ad71 --- /dev/null +++ b/ytdl_subscribe/ytdl_additions/enhanced_download_archive.py @@ -0,0 +1,92 @@ +import json +from dataclasses import dataclass +from pathlib import Path +from typing import Dict +from typing import List + +from ytdl_subscribe.entries.entry import Entry + + +@dataclass +class DownloadMapping: + upload_date: str + file_paths: List[str] + + +class DownloadMappings: + def __init__(self): + self._entry_mappings: Dict[str, DownloadMapping] = {} + + @classmethod + def from_json(cls, file_path: str) -> "DownloadMappings": + entry_mappings = DownloadMappings() + entry_mappings._entry_mappings = json.load(open(file_path, "r", encoding="utf8")) + return entry_mappings + + def to_json(self) -> str: + return json.dumps( + { + uid: mapping + for uid, mapping in sorted( + self._entry_mappings.items(), + key=lambda item: item[1]["upload_date"], + reverse=True, + ) + } + ) + + +class DownloadArchive: + """ + Class to handle any operations to the ytdl download archive. Try to keep it as barebones as + possible in case of future changes. + """ + + def __init__( + self, + ): + self._download_archive_lines: List[str] = [] + + @classmethod + def from_file(cls, file_path: str) -> "DownloadArchive": + lines = open(file_path, "r", encoding="utf8").readlines() + download_archive = DownloadArchive() + download_archive._download_archive_lines = lines + return download_archive + + +class EnhancedDownloadArchive: + """ + Maintains ytdl's download archive file as well as create an additional mapping file to map + ytdl ids to multiple files + """ + + def __init__(self, name: str): + self.name = name + self._download_archive = None + self._download_mapping = None + self._appended_entries: List[Entry] = [] + + @property + def download_archive_file_name(self): + return f".ytdl-subscribe-{self.name}-download-archive.txt" + + @property + def download_mapping_file_name(self): + return f".ytdl-subscribe-{self.name}-download-mapping.json" + + def load(self, directory: str) -> "EnhancedDownloadArchive": + self._download_archive = DownloadArchive.from_file( + file_path=str(Path(directory) / self.download_archive_file_name) + ) + + self._download_mapping = DownloadMappings.from_json( + file_path=str(Path(directory) / self.download_mapping_file_name) + ) + + return self + + # def append(self, entry: Entry, relative_file_path: str): + # self._appended_entries.append( + # EntryMapping(entry=entry, relative_file_path=relative_file_path)) + #