diff --git a/Provider/HIFI.py b/Provider/HIFI.py index db1f41e..5ea5b92 100644 --- a/Provider/HIFI.py +++ b/Provider/HIFI.py @@ -70,11 +70,11 @@ class HIFI(Provider): "hifi.track": ["download-file"], } QUERY_ARG_CHOICES = { - "album": (), "artist": (), - "playlist": (), + "album": (), "track": (), "title": (), + "playlist": (), "video": (), } INLINE_QUERY_FIELD_CHOICES = QUERY_ARG_CHOICES @@ -272,7 +272,7 @@ class HIFI(Provider): title = self._stringify(detail.get("title")) or title return SearchResult( - table="hifi", + table="hifi.track", title=title, path=f"hifi://track/{track_id}", detail=f"id:{track_id}", @@ -792,7 +792,7 @@ class HIFI(Provider): md["_artist_name"] = artist_name return SearchResult( - table="hifi", + table="hifi.album", title=title, path=path, detail="album", @@ -1384,7 +1384,7 @@ class HIFI(Provider): if not isinstance(hit, dict): continue hit_type = str(hit.get("type") or "").upper() - if hit_type != "TRACKS": + if hit_type != "TRACKS" and hit_type != "TRACK": continue value = hit.get("value") if isinstance(value, dict): @@ -1510,7 +1510,7 @@ class HIFI(Provider): if not isinstance(hit, dict): continue hit_type = str(hit.get("type") or "").upper() - if hit_type != "ARTISTS": + if hit_type != "ARTISTS" and hit_type != "ARTIST": continue value = hit.get("value") if isinstance(value, dict): @@ -1557,10 +1557,10 @@ class HIFI(Provider): columns.append(("Popularity", popularity)) return SearchResult( - table="hifi", + table="hifi.artist", title=name, path=path, - detail="artist", + detail="hifi.artist", annotations=["tidal", "artist"], media_kind="audio", columns=columns, @@ -1656,11 +1656,11 @@ class HIFI(Provider): tags = self._build_track_tags(full_md) result = SearchResult( - table="hifi", + table="hifi.track", title=title, path=path, - detail=detail, - annotations=["tidal"], + detail="hifi.track", + annotations=["tidal", "track"], media_kind="audio", tag=tags, columns=columns, @@ -1816,6 +1816,28 @@ class HIFI(Provider): def _build_track_tags(self, metadata: Dict[str, Any]) -> set[str]: return build_track_tags(metadata) + def selection_auto_stage( + self, + table_type: str, + stage_args: Optional[Sequence[str]] = None, + ) -> Optional[List[str]]: + """Determine if selection should auto-run download-file.""" + t = str(table_type or "").strip().lower() + + # Explicit track tables always auto-download. + if t == "hifi.track": + return ["download-file"] + + # For the generic "hifi" table (first-stage search results), + # only auto-download if we're selecting track items. + # Otherwise, let selector() handle navigation (artist -> album -> track). + if t == "hifi": + # If we can't see the items yet, we have to guess. + # Default to None so selector() gets a chance to run first. + return None + + return super().selection_auto_stage(table_type, stage_args) + def selector( self, selected_items: List[Any], @@ -1836,11 +1858,11 @@ class HIFI(Provider): current_table = ctx.get_last_result_table() except Exception: current_table = None - table_type = ( + table_type = str( current_table.table if current_table and hasattr(current_table, "table") - else None - ) + else "" + ).strip().lower() try: debug( @@ -1849,8 +1871,12 @@ class HIFI(Provider): except Exception: pass + # Unified selection logic: detect artist/album/track by inspecting path or metadata + # when the table name is just the generic "hifi" (from search-file). + is_generic_hifi = (table_type == "hifi") + # Artist selection: selecting @N should open an albums list. - if isinstance(table_type, str) and table_type.strip().lower() == "hifi.artist": + if table_type == "hifi.artist" or (is_generic_hifi and any(str(get_field(i, "path")).startswith("hifi://artist/") for i in selected_items)): contexts = self._extract_artist_selection_context(selected_items) try: debug(f"[hifi.selector] artist contexts={len(contexts)}") @@ -1911,7 +1937,7 @@ class HIFI(Provider): return True # Album selection: selecting @N should open the track list for that album. - if isinstance(table_type, str) and table_type.strip().lower() == "hifi.album": + if table_type == "hifi.album" or (is_generic_hifi and any(str(get_field(i, "path")).startswith("hifi://album/") for i in selected_items)): contexts = self._extract_album_selection_context(selected_items) try: debug(f"[hifi.selector] album contexts={len(contexts)}") @@ -1978,7 +2004,7 @@ class HIFI(Provider): return True - if isinstance(table_type, str) and table_type.strip().lower() == "hifi.track": + if table_type == "hifi.track" or (is_generic_hifi and any(str(get_field(i, "path")).startswith("hifi://track/") for i in selected_items)): try: meta = ( current_table.get_table_metadata() @@ -2051,7 +2077,7 @@ class HIFI(Provider): url_value = self._stringify(detail.get("url")) result = SearchResult( - table="hifi", + table="hifi.track", title=title, path=resolved_path, detail=f"id:{track_id}", diff --git a/scripts/bootstrap.py b/scripts/bootstrap.py index 721608c..ae2f839 100644 --- a/scripts/bootstrap.py +++ b/scripts/bootstrap.py @@ -593,38 +593,61 @@ def main() -> int: return 1 return 0 + def _update_config_value(root: Path, key: str, value: str) -> bool: + config_path = root / "config.conf" + if not config_path.exists(): + fallback = root / "config.conf.remove" + if fallback.exists(): + shutil.copy(fallback, config_path) + else: + return False + try: + content = config_path.read_text(encoding="utf-8") + pattern = rf'^(\s*{re.escape(key)}\s*=\s*)(.*)$' + if re.search(pattern, content, flags=re.MULTILINE): + new_content = re.sub(pattern, rf'\1"{value}"', content, flags=re.MULTILINE) + else: + section_pattern = r'\[store=hydrusnetwork\]' + if re.search(section_pattern, content): + new_content = re.sub(section_pattern, f'[store=hydrusnetwork]\n{key}="{value}"', content, count=1) + else: + new_content = content + f'\n\n[store=hydrusnetwork]\nname="hydrus"\n{key}="{value}"' + config_path.write_text(new_content, encoding="utf-8") + return True + except Exception as e: + print(f"Error updating config: {e}") + return False + def _interactive_menu() -> str | int: """Show a simple interactive menu to choose install/uninstall or delegate.""" try: installed = _is_installed() while True: - print("\nMedeia-Macina bootstrap - interactive menu") + os.system("cls" if os.name == "nt" else "clear") + print("====================================") + print(" MEDEIA MACINA BOOTSTRAP MENU") + print("====================================") print("1) Install / Reinstall") - print("2) Extras") + print("2) Extras > HydrusNetwork") if installed: print("3) Uninstall") print("4) Status") print("q) Quit") - choice = input("Choose an option: ").strip().lower() + choice = input("\nChoose an option: ").strip().lower() if choice in ("1", "install", "reinstall"): return "install" - if choice in ("2", "extras"): - print("\nExtras Menu:") - print(" 1) HydrusNetwork (Setup & Clone)") - print(" b) Back") - extra_choice = input("Choose an extra: ").strip().lower() - if extra_choice == "1": - return "extras_hydrus" - continue # back to main menu + if choice in ("2", "extras", "hydrus"): + return "extras_hydrus" if installed and choice in ("3", "uninstall"): return "uninstall" if installed and choice in ("4", "status"): - print("Installation detected." if installed else "Not installed.") + print("\nInstallation detected." if installed else "\nNot installed.") + input("\nPress Enter to continue...") continue if choice in ("q", "quit", "exit"): @@ -795,6 +818,15 @@ def main() -> int: if hydrus_script.exists(): try: subprocess.check_call([sys.executable, str(hydrus_script)]) + + # New: Prompt for location as requested + print("\n" + "="*40) + print(" HYDRUS CONFIGURATION") + print("="*40) + location = input("\nEnter the absolute path to your Hydrus git clone\n(to link it with Medios-Macina config): ").strip() + if location: + if _update_config_value(repo_root, "gitclone", location): + print(f"✅ Updated config.conf with gitclone=\"{location}\"") except subprocess.CalledProcessError: print("\nHydrusNetwork setup exited with an error.") except Exception as e: