0
1
mirror of https://github.com/radio95-rnt/RadioPlayer.git synced 2026-02-27 14:13:55 +01:00

idk if this works

This commit is contained in:
2025-11-15 21:12:56 +01:00
parent 2966285aa3
commit 760f13fabe

View File

@@ -10,11 +10,12 @@ import libcache
import argparse import argparse
from datetime import datetime from datetime import datetime
from typing import List, Dict, Set, Tuple, Optional from typing import List, Dict, Set, Tuple, Optional
from dataclasses import dataclass from dataclasses import dataclass, field
from pathlib import Path
# Configuration # Configuration
FILES_DIR = "/home/user/mixes/" FILES_DIR = Path("/home/user/mixes/")
PLAYLISTS_DIR = "/home/user/playlists/" PLAYLISTS_DIR = Path("/home/user/playlists/")
POLISH_INDICATORS = ("Polskie", "Dzem") POLISH_INDICATORS = ("Polskie", "Dzem")
@dataclass @dataclass
@@ -39,8 +40,9 @@ class Config:
class FileItem: class FileItem:
"""Represents either a single file or a folder containing files.""" """Represents either a single file or a folder containing files."""
name: str name: str
path: Path
is_folder: bool is_folder: bool
files: List[str] _all_files_cache: Optional[Set[str]] = field(default=None, init=False, repr=False)
@property @property
def display_name(self) -> str: def display_name(self) -> str:
@@ -49,52 +51,43 @@ class FileItem:
return self.name return self.name
@property @property
def all_files(self) -> List[str]: def all_files(self) -> Set[str]:
if self.is_folder: return [os.path.join(self.name, f) for f in self.files] """Return set of relative paths for all files in this item."""
return self.files if self._all_files_cache is not None:
return self._all_files_cache
if self.is_folder:
# For folders, get all files inside
folder_path = self.path.parent
files = set()
if folder_path.exists():
for file in sorted(folder_path.glob("*")):
if file.is_file():
rel_path = str(file.relative_to(FILES_DIR))
files.add(rel_path)
self._all_files_cache = files
return files
else:
# For single files
rel_path = str(self.path.relative_to(FILES_DIR))
self._all_files_cache = {rel_path}
return self._all_files_cache
class FileManager: class FileManager:
@staticmethod @staticmethod
def get_audio_files(directory: str) -> List[str]: def get_file_items(directory: Path) -> List[FileItem]:
"""Get all audio files from the specified directory (legacy method for compatibility)."""
audio_files = []
try:
for file in os.listdir(directory):
audio_files.append(file)
return sorted(audio_files)
except FileNotFoundError:
print(f"Error: Directory '{directory}' not found.")
return []
except PermissionError:
print(f"Error: Permission denied for directory '{directory}'.")
return []
@staticmethod
def get_file_items(directory: str) -> List[FileItem]:
"""Get all audio files and folders containing audio files as FileItem objects.""" """Get all audio files and folders containing audio files as FileItem objects."""
items = [] items = []
try: try:
entries = sorted(os.listdir(directory)) for entry in sorted(directory.glob("*")):
if entry.is_dir():
for entry in entries: # Create folder item
full_path = os.path.join(directory, entry) item = FileItem(name=entry.name, path=entry / "*", is_folder=True)
items.append(item)
if os.path.isfile(full_path): elif entry.is_file():
# Single audio file # Create file item
items.append(FileItem(name=entry, is_folder=False, files=[entry])) item = FileItem(name=entry.name, path=entry, is_folder=False)
items.append(item)
elif os.path.isdir(full_path):
# Directory - check for audio files inside
audio_files = []
try:
for file in os.listdir(full_path):
audio_files.append(file)
except (PermissionError, FileNotFoundError): continue
if audio_files:
# Folder contains audio files
items.append(FileItem(name=entry, is_folder=True, files=["*"]))
return items return items
except FileNotFoundError: except FileNotFoundError:
print(f"Error: Directory '{directory}' not found.") print(f"Error: Directory '{directory}' not found.")
@@ -106,7 +99,8 @@ class FileManager:
class SearchManager: class SearchManager:
@staticmethod @staticmethod
def filter_file_items(items: List[FileItem], search_term: str) -> List[FileItem]: def filter_file_items(items: List[FileItem], search_term: str) -> List[FileItem]:
if not search_term: return items if not search_term:
return items
search_lower = search_term.lower() search_lower = search_term.lower()
@@ -118,9 +112,12 @@ class SearchManager:
for item in items: for item in items:
item_name_lower = item.name.lower() item_name_lower = item.name.lower()
if item_name_lower.startswith(search_lower): starts_with.append(item) if item_name_lower.startswith(search_lower):
elif search_lower in item_name_lower: contains.append(item) starts_with.append(item)
elif SearchManager._has_matching_chars(item_name_lower, search_lower): has_chars.append(item) elif search_lower in item_name_lower:
contains.append(item)
elif SearchManager._has_matching_chars(item_name_lower, search_lower):
has_chars.append(item)
return starts_with + contains + has_chars return starts_with + contains + has_chars
@@ -131,7 +128,6 @@ class SearchManager:
search_lower = search_term.lower() search_lower = search_term.lower()
# Group files by match type
starts_with = [] starts_with = []
contains = [] contains = []
has_chars = [] has_chars = []
@@ -139,9 +135,12 @@ class SearchManager:
for file in files: for file in files:
file_lower = file.lower() file_lower = file.lower()
if file_lower.startswith(search_lower): starts_with.append(file) if file_lower.startswith(search_lower):
elif search_lower in file_lower: contains.append(file) starts_with.append(file)
elif SearchManager._has_matching_chars(file_lower, search_lower): has_chars.append(file) elif search_lower in file_lower:
contains.append(file)
elif SearchManager._has_matching_chars(file_lower, search_lower):
has_chars.append(file)
return starts_with + contains + has_chars return starts_with + contains + has_chars
@@ -158,10 +157,10 @@ class PlaylistManager:
self.config = config self.config = config
self.custom_playlist_files = set() self.custom_playlist_files = set()
def ensure_playlist_dir(self, day: str) -> str: def ensure_playlist_dir(self, day: str) -> Path:
"""Ensure playlist directory exists for the given day.""" """Ensure playlist directory exists for the given day."""
playlist_dir = os.path.expanduser(os.path.join(PLAYLISTS_DIR, day)) playlist_dir = PLAYLISTS_DIR / day
if not os.path.exists(playlist_dir): os.makedirs(playlist_dir) playlist_dir.mkdir(parents=True, exist_ok=True)
return playlist_dir return playlist_dir
def load_playlists(self, days: List[str]) -> Dict[str, Dict[str, Set[str]]]: def load_playlists(self, days: List[str]) -> Dict[str, Dict[str, Set[str]]]:
@@ -170,15 +169,21 @@ class PlaylistManager:
# In custom mode, we only need one "day" entry # In custom mode, we only need one "day" entry
playlists = {"custom": {period: set() for period in self.periods}} playlists = {"custom": {period: set() for period in self.periods}}
# Load existing custom playlist if it exists # Load existing custom playlist if it exists
if os.path.exists(self.config.custom_playlist_file): custom_path = Path(self.config.custom_playlist_file)
with open(self.config.custom_playlist_file, 'r') as f: if custom_path.exists():
with open(custom_path, 'r') as f:
for line in f: for line in f:
line = line.strip() line = line.strip()
if line: if line:
# Store relative path for comparison # Convert to relative path
rel_path = os.path.relpath(line, FILES_DIR) if line.startswith('/') else line abs_path = Path(line)
try:
rel_path = str(abs_path.relative_to(FILES_DIR))
except ValueError:
# If it's already relative, use as is
rel_path = line
self.custom_playlist_files.add(rel_path) self.custom_playlist_files.add(rel_path)
# In custom mode, we'll use 'day' as the default period for display
playlists["custom"]["day"].add(rel_path) playlists["custom"]["day"].add(rel_path)
return playlists return playlists
else: else:
@@ -186,18 +191,23 @@ class PlaylistManager:
playlists = {} playlists = {}
for day in days: for day in days:
playlists[day] = {period: set() for period in self.periods} playlists[day] = {period: set() for period in self.periods}
playlist_dir = os.path.expanduser(os.path.join(PLAYLISTS_DIR, day)) playlist_dir = PLAYLISTS_DIR / day
if os.path.exists(playlist_dir): if playlist_dir.exists():
for period in self.periods: for period in self.periods:
playlist_file = os.path.join(playlist_dir, period) playlist_file = playlist_dir / period
if os.path.exists(playlist_file): if playlist_file.exists():
with open(playlist_file, 'r') as f: with open(playlist_file, 'r') as f:
for line in f: for line in f:
line = line.strip() line = line.strip()
if line: if line:
# Store relative path for comparison # Convert to relative path
rel_path = os.path.relpath(line, FILES_DIR) if line.startswith('/') else line abs_path = Path(line)
try:
rel_path = str(abs_path.relative_to(FILES_DIR))
except ValueError:
# If it's already relative, use as is
rel_path = line
playlists[day][period].add(rel_path) playlists[day][period].add(rel_path)
return playlists return playlists
@@ -210,58 +220,77 @@ class PlaylistManager:
def _update_custom_playlist(self, file_item: FileItem, add: bool): def _update_custom_playlist(self, file_item: FileItem, add: bool):
"""Update the custom playlist file.""" """Update the custom playlist file."""
if not self.config.custom_playlist_file: raise Exception if not self.config.custom_playlist_file:
# Ensure the directory exists raise Exception("No custom playlist file specified")
os.makedirs(os.path.dirname(self.config.custom_playlist_file), exist_ok=True)
custom_path = Path(self.config.custom_playlist_file)
custom_path.parent.mkdir(parents=True, exist_ok=True)
# Read existing content # Read existing content
lines = [] lines = []
if os.path.exists(self.config.custom_playlist_file): if custom_path.exists():
with open(self.config.custom_playlist_file, 'r') as f: with open(custom_path, 'r') as f:
lines = f.read().splitlines() lines = f.read().splitlines()
# Get full paths for all files in the item # Get all files in this item as absolute paths
full_filepaths = [os.path.join(FILES_DIR, filepath) for filepath in file_item.all_files] files_to_process = set()
for rel_path in file_item.all_files:
abs_path = str(FILES_DIR / rel_path)
files_to_process.add(abs_path)
if add: if add:
for full_filepath in full_filepaths: # Add new files that aren't already in the list
if full_filepath not in lines: for filepath in files_to_process:
lines.append(full_filepath) if filepath not in lines:
# Update tracking set with relative paths lines.append(filepath)
self.custom_playlist_files.update(file_item.all_files) # Also update the tracking set with relative path
try:
rel_path = str(Path(filepath).relative_to(FILES_DIR))
self.custom_playlist_files.add(rel_path)
except ValueError:
pass
else: else:
for full_filepath in full_filepaths: # Remove files
while full_filepath in lines: for filepath in files_to_process:
lines.remove(full_filepath) while filepath in lines:
# Remove from tracking set lines.remove(filepath)
for filepath in file_item.all_files: # Also update the tracking set
self.custom_playlist_files.discard(filepath) try:
rel_path = str(Path(filepath).relative_to(FILES_DIR))
self.custom_playlist_files.discard(rel_path)
except ValueError:
pass
# Write back to file with open(custom_path, 'w') as f:
with open(self.config.custom_playlist_file, 'w') as f:
f.write('\n'.join(lines) + ('\n' if lines else '')) f.write('\n'.join(lines) + ('\n' if lines else ''))
def _update_weekly_playlist(self, day: str, period: str, file_item: FileItem, add: bool): def _update_weekly_playlist(self, day: str, period: str, file_item: FileItem, add: bool):
"""Update a weekly playlist file (original functionality).""" """Update a weekly playlist file (original functionality)."""
playlist_dir = self.ensure_playlist_dir(day) playlist_dir = self.ensure_playlist_dir(day)
playlist_file = os.path.join(playlist_dir, period) playlist_file = playlist_dir / period
if not os.path.exists(playlist_file): if not playlist_file.exists():
with open(playlist_file, 'w') as f: pass playlist_file.touch()
with open(playlist_file, 'r') as f: lines = f.read().splitlines() with open(playlist_file, 'r') as f:
lines = f.read().splitlines()
# Get full paths for all files in the item # Get all files in this item as absolute paths
full_filepaths = [os.path.join(FILES_DIR, filepath) for filepath in file_item.all_files] files_to_process = set()
for rel_path in file_item.all_files:
abs_path = str(FILES_DIR / rel_path)
files_to_process.add(abs_path)
if add: if add:
for full_filepath in full_filepaths: # Add new files that aren't already in the list
if full_filepath not in lines: for filepath in files_to_process:
lines.append(full_filepath) if filepath not in lines:
lines.append(filepath)
else: else:
for full_filepath in full_filepaths: # Remove files
while full_filepath in lines: for filepath in files_to_process:
lines.remove(full_filepath) while filepath in lines:
lines.remove(filepath)
with open(playlist_file, 'w', encoding='utf-8', errors='strict') as f: with open(playlist_file, 'w', encoding='utf-8', errors='strict') as f:
for line in lines: for line in lines:
@@ -273,11 +302,9 @@ class PlaylistManager:
exit() exit()
def is_file_item_in_playlist(self, file_item: FileItem, day: str, period: str, playlists: Dict) -> bool: def is_file_item_in_playlist(self, file_item: FileItem, day: str, period: str, playlists: Dict) -> bool:
"""Check if ALL files from a FileItem are in the specified playlist.""" """Check if ALL files in the item are in the playlist."""
if not file_item.all_files: return False
playlist_set = playlists.get(day, {}).get(period, set()) playlist_set = playlists.get(day, {}).get(period, set())
return all(filepath in playlist_set for filepath in file_item.all_files) return all(rel_path in playlist_set for rel_path in file_item.all_files)
def copy_day_to_all(self, playlists: Dict, source_day: str, days: List[str]) -> Dict: def copy_day_to_all(self, playlists: Dict, source_day: str, days: List[str]) -> Dict:
"""Copy all playlists from source day to all other days.""" """Copy all playlists from source day to all other days."""
@@ -290,10 +317,11 @@ class PlaylistManager:
for period in self.periods: for period in self.periods:
target_dir = self.ensure_playlist_dir(target_day) target_dir = self.ensure_playlist_dir(target_day)
target_file = os.path.join(target_dir, period) target_file = target_dir / period
filepaths = [os.path.join(FILES_DIR, filename) # Convert relative paths to absolute paths
for filename in playlists[source_day][period]] filepaths = [str(FILES_DIR / rel_path)
for rel_path in playlists[source_day][period]]
with open(target_file, 'w') as f: with open(target_file, 'w') as f:
f.write('\n'.join(filepaths) + ('\n' if filepaths else '')) f.write('\n'.join(filepaths) + ('\n' if filepaths else ''))
@@ -321,34 +349,38 @@ class PlaylistManager:
for period, is_present in source_periods.items(): for period, is_present in source_periods.items():
target_set = playlists[target_day][period] target_set = playlists[target_day][period]
# Get all relative paths for this item
item_rel_paths = current_item.all_files
if is_present: if is_present:
# Add all files from the item # Add all files from the item
target_set.update(current_item.all_files) target_set.update(item_rel_paths)
else: else:
# Remove all files from the item # Remove all files from the item
for filepath in current_item.all_files: for rel_path in item_rel_paths:
target_set.discard(filepath) target_set.discard(rel_path)
# Update the playlist file # Update the playlist file
playlist_dir = self.ensure_playlist_dir(target_day) playlist_dir = self.ensure_playlist_dir(target_day)
playlist_file = os.path.join(playlist_dir, period) playlist_file = playlist_dir / period
if os.path.exists(playlist_file): if playlist_file.exists():
with open(playlist_file, 'r') as f: with open(playlist_file, 'r') as f:
lines = [line.strip() for line in f.readlines()] lines = [line.strip() for line in f.readlines()]
else: else:
lines = [] lines = []
full_filepaths = [os.path.join(FILES_DIR, filepath) for filepath in current_item.all_files] # Convert relative paths to absolute for file storage
abs_paths = [str(FILES_DIR / rel_path) for rel_path in item_rel_paths]
if is_present: if is_present:
for full_filepath in full_filepaths: for abs_path in abs_paths:
if full_filepath not in lines: if abs_path not in lines:
lines.append(full_filepath) lines.append(abs_path)
else: else:
for full_filepath in full_filepaths: for abs_path in abs_paths:
while full_filepath in lines: while abs_path in lines:
lines.remove(full_filepath) lines.remove(abs_path)
with open(playlist_file, 'w') as f: with open(playlist_file, 'w') as f:
f.write('\n'.join(lines) + ('\n' if lines else '')) f.write('\n'.join(lines) + ('\n' if lines else ''))
@@ -360,12 +392,12 @@ class TerminalUtils:
def get_char() -> str: def get_char() -> str:
"""Get a single character from stdin.""" """Get a single character from stdin."""
fd = sys.stdin.fileno() fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd) # type: ignore old_settings = termios.tcgetattr(fd)
try: try:
tty.setraw(sys.stdin.fileno()) # type: ignore tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1) ch = sys.stdin.read(1)
finally: finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) # type: ignore termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch return ch
@staticmethod @staticmethod
@@ -409,7 +441,8 @@ class DisplayManager:
days: List[str], term_width: int, days: List[str], term_width: int,
force_redraw: bool = False, state: InterfaceState | None = None): force_redraw: bool = False, state: InterfaceState | None = None):
"""Draw the header, only if content has changed.""" """Draw the header, only if content has changed."""
if not state: raise Exception if not state:
raise Exception("State required")
if self.config.is_custom_mode: if self.config.is_custom_mode:
# Custom mode header # Custom mode header
@@ -428,7 +461,8 @@ class DisplayManager:
def draw_search_bar(self, search_term: str, force_redraw: bool = False, def draw_search_bar(self, search_term: str, force_redraw: bool = False,
state: InterfaceState | None = None): state: InterfaceState | None = None):
"""Draw the search bar, only if the search term has changed.""" """Draw the search bar, only if the search term has changed."""
if not state: raise Exception if not state:
raise Exception("State required")
# Optimization: Only redraw if search term changes # Optimization: Only redraw if search term changes
if force_redraw or state.last_search != search_term: if force_redraw or state.last_search != search_term:
self.terminal.move_cursor(4) self.terminal.move_cursor(4)
@@ -441,7 +475,8 @@ class DisplayManager:
current_day: str, scroll_offset: int, term_width: int, term_height: int, current_day: str, scroll_offset: int, term_width: int, term_height: int,
force_redraw: bool = False, state: InterfaceState | None = None): force_redraw: bool = False, state: InterfaceState | None = None):
"""Draw the files list, optimized to only redraw when necessary.""" """Draw the files list, optimized to only redraw when necessary."""
if not state: raise Exception if not state:
raise Exception("State required")
available_lines = term_height - 6 available_lines = term_height - 6
start_idx = scroll_offset start_idx = scroll_offset
@@ -460,8 +495,10 @@ class DisplayManager:
self.terminal.move_cursor(6) self.terminal.move_cursor(6)
self.terminal.clear_line() self.terminal.clear_line()
if start_idx > 0: print("", end="") if start_idx > 0:
else: print(" ", end="") print("", end="")
else:
print(" ", end="")
if self.config.is_custom_mode: if self.config.is_custom_mode:
position_info = f" Custom | Item {selected_idx + 1}/{len(file_items)} " position_info = f" Custom | Item {selected_idx + 1}/{len(file_items)} "
@@ -484,8 +521,8 @@ class DisplayManager:
if self.config.is_custom_mode: if self.config.is_custom_mode:
# In custom mode, only show 'C' for custom playlist # In custom mode, only show 'C' for custom playlist
in_custom = all(filepath in playlists.get("custom", {}).get("day", set()) in_custom = all(rel_path in playlists.get("custom", {}).get("day", set())
for filepath in item.all_files) for rel_path in item.all_files)
c_color = "\033[1;32m" if in_custom else "\033[1;30m" c_color = "\033[1;32m" if in_custom else "\033[1;30m"
row_highlight = "\033[1;44m" if idx == selected_idx else "" row_highlight = "\033[1;44m" if idx == selected_idx else ""
@@ -497,14 +534,14 @@ class DisplayManager:
print(f"{row_highlight}[{c_color}C\033[0m{row_highlight}] {display_name}\033[0m", end="", flush=True) print(f"{row_highlight}[{c_color}C\033[0m{row_highlight}] {display_name}\033[0m", end="", flush=True)
else: else:
# Original weekly mode display # Original weekly mode display
in_late_night = all(filepath in playlists[current_day]['late_night'] in_late_night = all(rel_path in playlists[current_day]['late_night']
for filepath in item.all_files) for rel_path in item.all_files)
in_morning = all(filepath in playlists[current_day]['morning'] in_morning = all(rel_path in playlists[current_day]['morning']
for filepath in item.all_files) for rel_path in item.all_files)
in_day = all(filepath in playlists[current_day]['day'] in_day = all(rel_path in playlists[current_day]['day']
for filepath in item.all_files) for rel_path in item.all_files)
in_night = all(filepath in playlists[current_day]['night'] in_night = all(rel_path in playlists[current_day]['night']
for filepath in item.all_files) for rel_path in item.all_files)
l_color = "\033[1;32m" if in_late_night else "\033[1;30m" l_color = "\033[1;32m" if in_late_night else "\033[1;30m"
m_color = "\033[1;32m" if in_morning else "\033[1;30m" m_color = "\033[1;32m" if in_morning else "\033[1;30m"
@@ -532,14 +569,14 @@ class DisplayManager:
def _get_item_playlist_status(self, item: FileItem, playlists: Dict, current_day: str) -> Tuple: def _get_item_playlist_status(self, item: FileItem, playlists: Dict, current_day: str) -> Tuple:
"""Get playlist status for an item to use in display state comparison.""" """Get playlist status for an item to use in display state comparison."""
if self.config.is_custom_mode: if self.config.is_custom_mode:
return (all(filepath in playlists.get("custom", {}).get("day", set()) return (all(rel_path in playlists.get("custom", {}).get("day", set())
for filepath in item.all_files),) for rel_path in item.all_files),)
else: else:
return ( return (
all(filepath in playlists[current_day]['late_night'] for filepath in item.all_files), all(rel_path in playlists[current_day]['late_night'] for rel_path in item.all_files),
all(filepath in playlists[current_day]['morning'] for filepath in item.all_files), all(rel_path in playlists[current_day]['morning'] for rel_path in item.all_files),
all(filepath in playlists[current_day]['day'] for filepath in item.all_files), all(rel_path in playlists[current_day]['day'] for rel_path in item.all_files),
all(filepath in playlists[current_day]['night'] for filepath in item.all_files) all(rel_path in playlists[current_day]['night'] for rel_path in item.all_files)
) )
class Application: class Application:
@@ -695,8 +732,8 @@ class Application:
if is_in_playlist: if is_in_playlist:
# Remove all files from the item # Remove all files from the item
for filepath in file_item.all_files: for rel_path in file_item.all_files:
self.playlists["custom"]["day"].discard(filepath) self.playlists["custom"]["day"].discard(rel_path)
else: else:
# Add all files from the item # Add all files from the item
self.playlists["custom"]["day"].update(file_item.all_files) self.playlists["custom"]["day"].update(file_item.all_files)
@@ -708,8 +745,8 @@ class Application:
if is_in_playlist: if is_in_playlist:
# Remove all files from the item # Remove all files from the item
for filepath in file_item.all_files: for rel_path in file_item.all_files:
self.playlists[current_day][period].discard(filepath) self.playlists[current_day][period].discard(rel_path)
else: else:
# Add all files from the item # Add all files from the item
self.playlists[current_day][period].update(file_item.all_files) self.playlists[current_day][period].update(file_item.all_files)
@@ -767,6 +804,7 @@ class Application:
if needs_redraw: if needs_redraw:
self.draw_interface() self.draw_interface()
self.state.last_selected_idx = self.selected_idx
self.state.last_current_day_idx = self.current_day_idx self.state.last_current_day_idx = self.current_day_idx
self.state.last_scroll_offset = self.scroll_offset self.state.last_scroll_offset = self.scroll_offset
@@ -786,16 +824,20 @@ class Application:
continue continue
# Handle regular input # Handle regular input
if key == 'q': break if key == 'q':
elif key == '/': self.in_search_mode = True break
elif key == '/':
self.in_search_mode = True
elif key == '\x1b': # Escape sequences elif key == '\x1b': # Escape sequences
next_key = self.terminal.get_char() next_key = self.terminal.get_char()
if next_key == '[': if next_key == '[':
arrow_key = self.terminal.get_char() arrow_key = self.terminal.get_char()
self.handle_navigation_key(arrow_key) self.handle_navigation_key(arrow_key)
if arrow_key in ['5', '6', '1', '4']: if arrow_key in ['5', '6', '1', '4']:
try: self.terminal.get_char() # Consume the ~ character try:
except: pass self.terminal.get_char() # Consume the ~ character
except:
pass
elif key == ' ': elif key == ' ':
self.selected_idx = min(len(self.filtered_file_items) - 1, self.selected_idx + (term_height - 6)) self.selected_idx = min(len(self.filtered_file_items) - 1, self.selected_idx + (term_height - 6))
elif key.lower() == 'c': elif key.lower() == 'c':
@@ -808,10 +850,14 @@ class Application:
self.playlists = self.playlist_manager.copy_day_to_all(self.playlists, current_day, self.days_of_week) self.playlists = self.playlist_manager.copy_day_to_all(self.playlists, current_day, self.days_of_week)
self.flash_message = f"Playlists from {current_day} copied to all other days!" self.flash_message = f"Playlists from {current_day} copied to all other days!"
self.message_timer = 0 self.message_timer = 0
elif key.lower() == 'm' and not self.config.is_custom_mode: self.toggle_playlist('morning') elif key.lower() == 'm' and not self.config.is_custom_mode:
elif key.lower() == 'd' and not self.config.is_custom_mode: self.toggle_playlist('day') self.toggle_playlist('morning')
elif key.lower() == 'n' and not self.config.is_custom_mode: self.toggle_playlist('night') elif key.lower() == 'd' and not self.config.is_custom_mode:
elif key.lower() == 'l' and not self.config.is_custom_mode: self.toggle_playlist('late_night') self.toggle_playlist('day')
elif key.lower() == 'n' and not self.config.is_custom_mode:
self.toggle_playlist('night')
elif key.lower() == 'l' and not self.config.is_custom_mode:
self.toggle_playlist('late_night')
elif key.lower() == 'f' and not self.config.is_custom_mode: elif key.lower() == 'f' and not self.config.is_custom_mode:
if self.filtered_file_items: if self.filtered_file_items:
current_day = self.days_of_week[self.current_day_idx] current_day = self.days_of_week[self.current_day_idx]
@@ -822,7 +868,8 @@ class Application:
if success: if success:
item_name = current_item.display_name item_name = current_item.display_name
self.flash_message = f"Item '{item_name}' copied to all days!" self.flash_message = f"Item '{item_name}' copied to all days!"
else: self.flash_message = f"Item not in any playlist! Add it first." else:
self.flash_message = f"Item not in any playlist! Add it first."
self.message_timer = 0 self.message_timer = 0
elif key.isupper() and len(key) == 1 and key.isalpha(): elif key.isupper() and len(key) == 1 and key.isalpha():
# Jump to item starting with letter # Jump to item starting with letter
@@ -837,7 +884,8 @@ class Application:
if self.filtered_file_items[i].name.lower().startswith(target_letter): if self.filtered_file_items[i].name.lower().startswith(target_letter):
found_idx = i found_idx = i
break break
if found_idx != -1: self.selected_idx = found_idx if found_idx != -1:
self.selected_idx = found_idx
finally: finally:
self.terminal.show_cursor() self.terminal.show_cursor()
@@ -865,14 +913,18 @@ def main():
args = parse_arguments() args = parse_arguments()
fd = sys.stdin.fileno() fd = sys.stdin.fileno()
original_settings = termios.tcgetattr(fd) # type: ignore original_settings = termios.tcgetattr(fd)
new_settings = termios.tcgetattr(fd) # type: ignore new_settings = termios.tcgetattr(fd)
new_settings[3] = new_settings[3] & ~termios.ECHOCTL # type: ignore new_settings[3] = new_settings[3] & ~termios.ECHOCTL
termios.tcsetattr(fd, termios.TCSADRAIN, new_settings) # type: ignore termios.tcsetattr(fd, termios.TCSADRAIN, new_settings)
config = Config(custom_playlist_file=args.playlist) config = Config(custom_playlist_file=args.playlist)
app = Application(config) app = Application(config)
code = app.run() code = app.run()
termios.tcsetattr(fd, termios.TCSADRAIN, original_settings) # type: ignore termios.tcsetattr(fd, termios.TCSADRAIN, original_settings)
exit(code) exit(code)
if __name__ == "__main__":
main()