added result history, with the appropriate setting.
This commit is contained in:
parent
c513759f1e
commit
b20d7fcc76
@ -2,7 +2,7 @@ from typing import Set, Type, Dict, List
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
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.regex import URL_PATTERN
|
||||||
from ...utils.string_processing import fit_to_file_system
|
from ...utils.string_processing import fit_to_file_system
|
||||||
from ...utils.support_classes import Query, DownloadResult
|
from ...utils.support_classes import Query, DownloadResult
|
||||||
@ -168,6 +168,7 @@ class Shell:
|
|||||||
self.option_digits: int = option_digits
|
self.option_digits: int = option_digits
|
||||||
|
|
||||||
self.current_results: Results = SearchResults
|
self.current_results: Results = SearchResults
|
||||||
|
self._result_history: List[Results] = []
|
||||||
|
|
||||||
self.genre = genre or get_genre()
|
self.genre = genre or get_genre()
|
||||||
|
|
||||||
@ -197,7 +198,26 @@ class Shell:
|
|||||||
print()
|
print()
|
||||||
|
|
||||||
def set_current_options(self, current_options: Results):
|
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
|
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:
|
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)
|
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()
|
self.print_current_options()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if processed_input == "..":
|
||||||
|
if self.previous_option():
|
||||||
|
self.print_current_options()
|
||||||
|
return False
|
||||||
|
|
||||||
if processed_input.startswith("s: "):
|
if processed_input.startswith("s: "):
|
||||||
self.search(input_str[3:])
|
self.search(input_str[3:])
|
||||||
return False
|
return False
|
||||||
|
@ -1 +0,0 @@
|
|||||||
from .search import Search
|
|
@ -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()
|
|
||||||
|
|
@ -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}.")
|
|
||||||
|
|
@ -3,6 +3,21 @@ from .base_classes import Section, IntAttribute, ListAttribute, BoolAttribute
|
|||||||
|
|
||||||
class MiscSection(Section):
|
class MiscSection(Section):
|
||||||
def __init__(self):
|
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(
|
self.HAPPY_MESSAGES = ListAttribute(
|
||||||
name="happy_messages",
|
name="happy_messages",
|
||||||
description="Just some nice and wholesome messages.\n"
|
description="Just some nice and wholesome messages.\n"
|
||||||
@ -37,6 +52,8 @@ class MiscSection(Section):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.attribute_list = [
|
self.attribute_list = [
|
||||||
|
self.ENABLE_RESULT_HISTORY,
|
||||||
|
self.HISTORY_LENGTH,
|
||||||
self.HAPPY_MESSAGES,
|
self.HAPPY_MESSAGES,
|
||||||
self.MODIFY_GC,
|
self.MODIFY_GC,
|
||||||
self.ID_BITS
|
self.ID_BITS
|
||||||
|
@ -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)
|
ALBUM_TYPE_BLACKLIST: Set[AlbumType] = set(AUDIO_SECTION.ALBUM_TYPE_BLACKLIST.object_from_value)
|
||||||
|
|
||||||
THREADED = False
|
THREADED = False
|
||||||
|
|
||||||
|
ENABLE_RESULT_HISTORY: bool = MISC_SECTION.ENABLE_RESULT_HISTORY.object_from_value
|
||||||
|
HISTORY_LENGTH: int = MISC_SECTION.HISTORY_LENGTH.object_from_value
|
Loading…
Reference in New Issue
Block a user