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