diff --git a/src/music_kraken/cli/download/shell.py b/src/music_kraken/cli/download/shell.py index 1db4e6d..91f546f 100644 --- a/src/music_kraken/cli/download/shell.py +++ b/src/music_kraken/cli/download/shell.py @@ -2,7 +2,7 @@ from typing import Set, Type, Dict, List from pathlib import Path import re -from ...utils.shared import MUSIC_DIR, NOT_A_GENRE_REGEX +from ...utils.shared import MUSIC_DIR, NOT_A_GENRE_REGEX, ENABLE_RESULT_HISTORY, HISTORY_LENGTH from ...utils.regex import URL_PATTERN from ...utils.string_processing import fit_to_file_system from ...utils.support_classes import Query, DownloadResult @@ -168,6 +168,7 @@ class Shell: self.option_digits: int = option_digits self.current_results: Results = SearchResults + self._result_history: List[Results] = [] self.genre = genre or get_genre() @@ -197,7 +198,26 @@ class Shell: print() def set_current_options(self, current_options: Results): + if ENABLE_RESULT_HISTORY: + self._result_history.append(current_options) + + if HISTORY_LENGTH != -1: + if len(self._result_history) > HISTORY_LENGTH: + self._result_history.pop(0) + self.current_results = current_options + + def previous_option(self) -> bool: + if not ENABLE_RESULT_HISTORY: + print("History is turned of.\nGo to settings, and change the value at 'result_history' to 'true'.") + return False + + if len(self._result_history) <= 1: + print(f"No results in history.") + return False + self._result_history.pop() + self.current_results = self._result_history[-1] + return True def _process_parsed(self, key_text: Dict[str, str], query: str) -> Query: song = None if not "t" in key_text else Song(title=key_text["t"], dynamic=True) @@ -340,6 +360,11 @@ class Shell: self.print_current_options() return False + if processed_input == "..": + if self.previous_option(): + self.print_current_options() + return False + if processed_input.startswith("s: "): self.search(input_str[3:]) return False diff --git a/src/music_kraken/download/__init__.py b/src/music_kraken/download/__init__.py index 327a571..e69de29 100644 --- a/src/music_kraken/download/__init__.py +++ b/src/music_kraken/download/__init__.py @@ -1 +0,0 @@ -from .search import Search diff --git a/src/music_kraken/download/multiple_options.py b/src/music_kraken/download/multiple_options.py deleted file mode 100644 index 4585952..0000000 --- a/src/music_kraken/download/multiple_options.py +++ /dev/null @@ -1,100 +0,0 @@ -from collections import defaultdict -from typing import Tuple, List, Dict, Type - -from . import page_attributes -from ..pages import Page -from ..objects import Options, DatabaseObject, Source - - -class MultiPageOptions: - def __init__( - self, - max_displayed_options: int = 10, - option_digits: int = 3, - derived_from: DatabaseObject = None - ) -> None: - self.max_displayed_options = max_displayed_options - self.option_digits: int = option_digits - - self._length = 0 - self._current_option_dict: Dict[Type[Page], Options] = defaultdict(lambda: Options()) - - self._derive_from = derived_from - - def __getitem__(self, key: Type[Page]): - return self._current_option_dict[key] - - def __setitem__(self, key: Type[Page], value: Options): - self._current_option_dict[key] = value - - self._length = 0 - for key in self._current_option_dict: - self._length += 1 - - def __len__(self) -> int: - return self._length - - def get_page_str(self, page: Page) -> str: - page_name_fill = "-" - max_page_len = 21 - - return f"({page_attributes.PAGE_NAME_MAP[page]}) ------------------------{type(page).__name__:{page_name_fill}<{max_page_len}}------------" - - def string_from_all_pages(self) -> str: - if self._length == 1: - for key in self._current_option_dict: - return self.string_from_single_page(key) - - lines: List[str] = [] - - j = 0 - for page, options in self._current_option_dict.items(): - lines.append(self.get_page_str(page)) - - i = -1 - - option_obj: DatabaseObject - for i, option_obj in enumerate(options): - if i >= self.max_displayed_options: - lines.append("...") - break - - lines.append(f"{j + i:0{self.option_digits}} {option_obj.option_string}") - - j += i + 1 - - return "\n".join(lines) - - def choose_from_all_pages(self, index: int) -> Tuple[DatabaseObject, Type[Page]]: - if self._length == 1: - for key in self._current_option_dict: - return self.choose_from_single_page(key, index), key - - sum_of_length = 0 - for page, options in self._current_option_dict.items(): - option_len = min((len(options), self.max_displayed_options)) - - index_of_list = index - sum_of_length - - if index_of_list < option_len: - return options[index_of_list], page - - sum_of_length += option_len - - raise IndexError("index is out of range") - - def string_from_single_page(self, page: Type[Page]) -> str: - lines: List[str] = [self.get_page_str(page)] - - option_obj: DatabaseObject - for i, option_obj in enumerate(self._current_option_dict[page]): - lines.append(f"{i:0{self.option_digits}} {option_obj.option_string}") - - return "\n".join(lines) - - def choose_from_single_page(self, page: Type[Page], index: int) -> DatabaseObject: - return self._current_option_dict[page][index] - - def __repr__(self) -> str: - return self.string_from_all_pages() - \ No newline at end of file diff --git a/src/music_kraken/download/search.py b/src/music_kraken/download/search.py deleted file mode 100644 index ce667a2..0000000 --- a/src/music_kraken/download/search.py +++ /dev/null @@ -1,203 +0,0 @@ -from typing import Tuple, List, Set, Type, Optional, Dict - -from .page_attributes import Pages -from .multiple_options import MultiPageOptions -from ..pages.abstract import Page -from ..utils.support_classes import DownloadResult, Query -from ..objects import DatabaseObject, Source, Artist, Song, Album -from ..utils.enums.source import SourcePages - - -class Search: - def __init__( - self, - exclude_pages: Set[Type[Page]] = None, - exclude_shady: bool = False, - max_displayed_options: int = 10, - option_digits: int = 3, - ) -> None: - self.pages: Pages = Pages(exclude_pages=exclude_pages, exclude_shady=exclude_shady) - - self.max_displayed_options = max_displayed_options - self.option_digits: int = option_digits - - self._option_history: List[MultiPageOptions] = [] - - self._current_option: MultiPageOptions = self.next_options() - - - def __repr__(self): - return self._current_option.__repr__() - - def next_options(self, derive_from: DatabaseObject = None) -> MultiPageOptions: - mpo = MultiPageOptions( - max_displayed_options=self.max_displayed_options, - option_digits=self.option_digits, - derived_from=derive_from - ) - - self._option_history.append(mpo) - self._current_option = mpo - - return mpo - - def _previous_options(self) -> MultiPageOptions: - self._option_history.pop() - self._current_option = self._option_history[-1] - - return self._option_history[-1] - - def _process_parsed(self, key_text: Dict[str, str], query: str) -> Query: - song = None if not "t" in key_text else Song(title=key_text["t"], dynamic=True) - album = None if not "r" in key_text else Album(title=key_text["r"], dynamic=True) - artist = None if not "a" in key_text else Artist(name=key_text["a"], dynamic=True) - - if song is not None: - song.album_collection.append(album) - song.main_artist_collection.append(artist) - return Query(raw_query=query, music_object=song) - - if album is not None: - album.artist_collection.append(artist) - return Query(raw_query=query, music_object=album) - - if artist is not None: - return Query(raw_query=query, music_object=artist) - - - def search(self, query: str): - """ - # The Query - - You can define a new parameter with "#", - the letter behind it defines the *type* of parameter, - followed by a space "#a Psychonaut 4 #r Tired, Numb and #t Drop by Drop" - if no # is in the query it gets treated as "unspecified query" - - doesn't set derived_from thus, - can't download right after - """ - - special_characters = "#\\" - query = query + " " - - key_text = {} - - skip_next = False - escape_next = False - new_text = "" - latest_key: str = None - for i in range(len(query) - 1): - current_char = query[i] - next_char = query[i+1] - - if skip_next: - skip_next = False - continue - - if escape_next: - new_text += current_char - escape_next = False - - # escaping - if current_char == "\\": - if next_char in special_characters: - escape_next = True - continue - - if current_char == "#": - if latest_key is not None: - key_text[latest_key] = new_text - new_text = "" - - latest_key = next_char - skip_next = True - continue - - new_text += current_char - - if latest_key is not None: - key_text[latest_key] = new_text - - - parsed_query: Query = self._process_parsed(key_text, query) - - - for page in self.pages: - self._current_option[page].extend(page.search(parsed_query)) - - def choose_page(self, page: Type[Page]): - """ - doesn't set derived_from thus, - can't download right after - """ - - if page not in page_attributes.ALL_PAGES: - raise ValueError(f"Page \"{page.__name__}\" does not exist in page_attributes.ALL_PAGES") - - prev_mpo = self._current_option - mpo = self.next_options() - - mpo[page] = prev_mpo[page] - - def get_page_from_query(self, query: str) -> Optional[Type[Page]]: - """ - query can be for example: - "a" or "EncyclopaediaMetallum" to choose a page - """ - - page = page_attributes.NAME_PAGE_MAP.get(query.lower().strip()) - - if page in self.pages: - return page - - def _get_page_from_source(self, source: Source) -> Optional[Type[Page]]: - return page_attributes.SOURCE_PAGE_MAP.get(source.page_enum) - - def choose_index(self, index: int): - db_object, page = self._current_option.choose_from_all_pages(index=index) - - music_object = self.fetch_details(db_object) - mpo = self.next_options(derive_from=music_object) - - mpo[page] = music_object.options - - def goto_previous(self): - try: - self._previous_options() - except IndexError: - pass - - def search_url(self, url: str) -> bool: - """ - sets derived_from, thus - can download directly after - """ - - source = Source.match_url(url=url, referer_page=SourcePages.MANUAL) - if source is None: - return False - - new_object = self.fetch_source(source) - if new_object is None: - return False - - page = page_attributes.SOURCE_PAGE_MAP[source.page_enum] - mpo = self.next_options(derive_from=new_object) - mpo[page] = new_object.options - - return True - - def download_chosen(self, genre: str = None, download_all: bool = False, **kwargs) -> DownloadResult: - if self._current_option._derive_from is None: - return DownloadResult(error_message="No option has been chosen yet.") - - source: Source - for source in self._current_option._derive_from.source_collection: - page = self._get_page_from_source(source=source) - - if page in self.audio_pages: - return page.download(music_object=self._current_option._derive_from, genre=genre, download_all=download_all) - - return DownloadResult(error_message=f"Didn't find a source for {self._current_option._derive_from.option_string}.") - diff --git a/src/music_kraken/utils/config/misc.py b/src/music_kraken/utils/config/misc.py index 021f154..3f5f24d 100644 --- a/src/music_kraken/utils/config/misc.py +++ b/src/music_kraken/utils/config/misc.py @@ -3,6 +3,21 @@ from .base_classes import Section, IntAttribute, ListAttribute, BoolAttribute class MiscSection(Section): def __init__(self): + self.ENABLE_RESULT_HISTORY = BoolAttribute( + name="result_history", + description="If enabled, you can go back to the previous results.\n" + "The consequence is a higher meory consumption, because every result is saved.", + value="false" + ) + + self.HISTORY_LENGTH = IntAttribute( + name="history_length", + description="You can choose how far back you can go in the result history.\n" + "The further you choose to be able to go back, the higher the memory usage.\n" + "'-1' removes the Limit entirely.", + value="8" + ) + self.HAPPY_MESSAGES = ListAttribute( name="happy_messages", description="Just some nice and wholesome messages.\n" @@ -37,6 +52,8 @@ class MiscSection(Section): ) self.attribute_list = [ + self.ENABLE_RESULT_HISTORY, + self.HISTORY_LENGTH, self.HAPPY_MESSAGES, self.MODIFY_GC, self.ID_BITS diff --git a/src/music_kraken/utils/shared.py b/src/music_kraken/utils/shared.py index 9d1574f..addcca7 100644 --- a/src/music_kraken/utils/shared.py +++ b/src/music_kraken/utils/shared.py @@ -99,3 +99,6 @@ SORT_BY_ALBUM_TYPE = AUDIO_SECTION.SORT_BY_ALBUM_TYPE.object_from_value ALBUM_TYPE_BLACKLIST: Set[AlbumType] = set(AUDIO_SECTION.ALBUM_TYPE_BLACKLIST.object_from_value) THREADED = False + +ENABLE_RESULT_HISTORY: bool = MISC_SECTION.ENABLE_RESULT_HISTORY.object_from_value +HISTORY_LENGTH: int = MISC_SECTION.HISTORY_LENGTH.object_from_value \ No newline at end of file