Latest Development Copy
This commit is contained in:
37
src/#__main__.py#
Executable file
37
src/#__main__.py#
Executable file
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import curses
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
from curses import wrapper
|
||||||
|
from .libs.config import Config
|
||||||
|
from .libs.absapi import ABSApi, Endpoint, ABSResponse, Library
|
||||||
|
from .libs.terminal import Terminal, ExitTerminal
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
_: argparse.Action = parser.add_argument("--debug", help="Debug the program", action="store_true")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
config: Config = Config()
|
||||||
|
|
||||||
|
# stdscr: curses.window = curses.initscr()
|
||||||
|
|
||||||
|
def main(stdscr: curses.window) -> None:
|
||||||
|
terminal: Terminal = Terminal(stdscr)
|
||||||
|
Terminal.startup(terminal)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if args.debug:
|
||||||
|
_api = ABSApi()
|
||||||
|
_response: list[Library]|None = _api.get_libraries()
|
||||||
|
_id = _response[0].id if _response else ""
|
||||||
|
_books = _api.get_books(_id)
|
||||||
|
for book in _books if _books else []:
|
||||||
|
breakpoint()
|
||||||
|
print(book.media.metadata.title)
|
||||||
|
sys.exit(0)
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
wrapper(main)
|
||||||
|
except ExitTerminal:
|
||||||
|
pass
|
||||||
|
|
||||||
@@ -9,12 +9,21 @@ from .libs.terminal import Terminal, ExitTerminal
|
|||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
_: argparse.Action = parser.add_argument("--debug", help="Debug the program", action="store_true")
|
_: argparse.Action = parser.add_argument("--debug", help="Debug the program", action="store_true")
|
||||||
|
_: argparse.Action = parser.add_argument("--remote-debug", help="Enable remote debugging with pudb", action="store_true")
|
||||||
|
_: argparse.Action = parser.add_argument("--debug-port", help="Port for remote debugging", type=int, default=6899)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
config: Config = Config()
|
config: Config = Config()
|
||||||
# stdscr: curses.window = curses.initscr()
|
# stdscr: curses.window = curses.initscr()
|
||||||
def main(stdscr: curses.window) -> None:
|
def main(stdscr: curses.window) -> None:
|
||||||
terminal: Terminal = Terminal(stdscr)
|
if args.remote_debug:
|
||||||
|
import os
|
||||||
|
from pudb.remote import set_trace
|
||||||
|
_term_size: os.terminal_size = os.get_terminal_size()
|
||||||
|
# set_trace(term_size=(_term_size.columns, _term_size.lines), host='0.0.0.0', port=args.debug_port)
|
||||||
|
terminal: Terminal = Terminal(stdscr, remote_debug=args.remote_debug, debug_port=args.debug_port)
|
||||||
|
else:
|
||||||
|
terminal: Terminal = Terminal(stdscr)
|
||||||
Terminal.startup(terminal)
|
Terminal.startup(terminal)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ class ExitTerminal(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
class Terminal:
|
class Terminal:
|
||||||
def __init__(self, stdscr: curses.window, running: bool = True) -> None:
|
def __init__(self, stdscr: curses.window, running: bool = True, remote_debug: bool = False, debug_port: int = 6899) -> None:
|
||||||
self.stdscr: curses.window = stdscr
|
self.stdscr: curses.window = stdscr
|
||||||
self.running: bool = running
|
self.running: bool = running
|
||||||
|
self.remote_debug: bool = remote_debug
|
||||||
|
self.debug_port: int = debug_port
|
||||||
self.api: ABSApi = ABSApi()
|
self.api: ABSApi = ABSApi()
|
||||||
self.height: int = self.stdscr.getmaxyx()[0]
|
self.height: int = self.stdscr.getmaxyx()[0]
|
||||||
self.width: int = self.stdscr.getmaxyx()[1]
|
self.width: int = self.stdscr.getmaxyx()[1]
|
||||||
@@ -28,6 +30,11 @@ class Terminal:
|
|||||||
]
|
]
|
||||||
self.active_window: str = "library"
|
self.active_window: str = "library"
|
||||||
self.playing: bool = False
|
self.playing: bool = False
|
||||||
|
self.row: int = 0
|
||||||
|
self.book_count: int = 0
|
||||||
|
self.libraries: list[Library]|None = None
|
||||||
|
self.books: list[Book]|None = None
|
||||||
|
self.library_pad: curses.window|None = None
|
||||||
|
|
||||||
def alignC(self, text: str) -> int:
|
def alignC(self, text: str) -> int:
|
||||||
return (self.width // 2) - (len(text) // 2)
|
return (self.width // 2) - (len(text) // 2)
|
||||||
@@ -35,9 +42,41 @@ class Terminal:
|
|||||||
def alignR(self, text: str) -> int:
|
def alignR(self, text: str) -> int:
|
||||||
return (self.height // 2) - (len(text) // 2)
|
return (self.height // 2) - (len(text) // 2)
|
||||||
|
|
||||||
|
def pad_content(self, strings: list[str], columns: int) -> list[str]:
|
||||||
|
col_width = self.width // columns
|
||||||
|
padded_strings = []
|
||||||
|
for i, s in enumerate(strings):
|
||||||
|
if i < len(strings) - 1: # Don't pad the last column
|
||||||
|
padded_strings.append(s.ljust(col_width))
|
||||||
|
else:
|
||||||
|
padded_strings.append(s)
|
||||||
|
return padded_strings
|
||||||
|
|
||||||
|
def initialize_colors(self) -> None:
|
||||||
|
if curses.has_colors() and curses.can_change_color():
|
||||||
|
curses.start_color()
|
||||||
|
curses.init_color(17, 500, 500, 900)
|
||||||
|
curses.init_pair(1, curses.COLOR_WHITE, 17)
|
||||||
|
curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_CYAN)
|
||||||
|
curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_WHITE)
|
||||||
|
curses.init_pair(4, curses.COLOR_WHITE, curses.COLOR_BLACK)
|
||||||
|
|
||||||
|
elif curses.has_colors():
|
||||||
|
curses.start_color()
|
||||||
|
curses.use_default_colors()
|
||||||
|
curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE)
|
||||||
|
curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_CYAN)
|
||||||
|
curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_WHITE)
|
||||||
|
curses.init_pair(4, curses.COLOR_WHITE, curses.COLOR_BLACK)
|
||||||
|
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def startup(self) -> None:
|
def startup(self) -> None:
|
||||||
self.stdscr.clear()
|
self.stdscr.clear()
|
||||||
self.menu()
|
self.menu()
|
||||||
|
self.initialize_colors()
|
||||||
self.stdscr.refresh()
|
self.stdscr.refresh()
|
||||||
self.library_window()
|
self.library_window()
|
||||||
self.footer()
|
self.footer()
|
||||||
@@ -76,20 +115,46 @@ class Terminal:
|
|||||||
return curses.newwin(self.main_content_height, self.main_content_width,
|
return curses.newwin(self.main_content_height, self.main_content_width,
|
||||||
self.main_content_height + 3, 0)
|
self.main_content_height + 3, 0)
|
||||||
|
|
||||||
|
def highlight_line(self, pad: curses.window, line: int, content: str) -> None:
|
||||||
|
if curses.has_colors() and curses.can_change_color():
|
||||||
|
pad.addstr(line, 0, "".join(content), curses.color_pair(17))
|
||||||
|
elif curses.has_colors():
|
||||||
|
pad.addstr(line, 0, "".join(content), curses.color_pair(2))
|
||||||
|
else:
|
||||||
|
pad.addstr(line, 0, "".join(content), curses.A_STANDOUT)
|
||||||
|
|
||||||
|
|
||||||
def library_window(self) -> None:
|
def library_window(self) -> None:
|
||||||
_y: int = 0
|
# Load data if needed
|
||||||
libraries: list[Library]|None = self.api.get_libraries()
|
if self.libraries is None:
|
||||||
books: list[Book]|None = self.api.get_books(libraries[0].id) if libraries else None
|
self.libraries = self.api.get_libraries()
|
||||||
if books is not None:
|
if self.books is None:
|
||||||
_pad: curses.window = self.new_main_pad(len(books) if libraries else 1)
|
self.books = self.api.get_books(self.libraries[0].id) if self.libraries else None
|
||||||
for book in books:
|
self.book_count = self.books.__len__() if self.books is not None else 0
|
||||||
_pad.addstr(_y, 0, f"- {book.media.metadata.title}\t {book.media.metadata.authorName} \t {book.media.duration}")
|
|
||||||
|
if self.books is not None:
|
||||||
|
# Always recreate the pad to update highlighting
|
||||||
|
self.library_pad = self.new_main_pad(len(self.books))
|
||||||
|
_y: int = 0
|
||||||
|
for book in self.books:
|
||||||
|
_row_contents: list[str] = [f"{_y} :: {book.media.metadata.title}", f"{book.media.metadata.seriesName}", f"{book.media.metadata.authorName}", f"{book.media.duration}"]
|
||||||
|
_padded_contents = self.pad_content(_row_contents, 4)
|
||||||
|
# Highlight the current row
|
||||||
|
if _y == self.row:
|
||||||
|
self.highlight_line(self.library_pad, _y, "".join(_padded_contents))
|
||||||
|
# self.library_pad.addstr(_y, 0, "".join(_padded_contents), curses.A_REVERSE)
|
||||||
|
else:
|
||||||
|
self.library_pad.addstr(_y, 0, "".join(_padded_contents), curses.A_STANDOUT if _y % 2 == 0 else curses.A_NORMAL)
|
||||||
_y += 1
|
_y += 1
|
||||||
_pad.refresh(0, 0, self.main_content_begin_y, self.main_content_begin_x,
|
|
||||||
self.main_content_begin_y + self.main_content_height - 1,
|
|
||||||
self.main_content_begin_x + self.main_content_width - 1)
|
|
||||||
else:
|
else:
|
||||||
self.help_window("No books found in the library.")
|
self.help_window("No books found in the library.")
|
||||||
|
|
||||||
|
if self.library_pad is not None:
|
||||||
|
# Always scroll by one line to create smooth scrolling effect
|
||||||
|
scroll_offset = max(0, self.row - (self.main_content_height // 2))
|
||||||
|
self.library_pad.refresh(scroll_offset, 0, self.main_content_begin_y, self.main_content_begin_x,
|
||||||
|
self.main_content_begin_y + self.main_content_height - 1,
|
||||||
|
self.main_content_begin_x + self.main_content_width - 1)
|
||||||
self.active_window = "library"
|
self.active_window = "library"
|
||||||
|
|
||||||
def search_window(self) -> None:
|
def search_window(self) -> None:
|
||||||
@@ -140,6 +205,22 @@ class Terminal:
|
|||||||
self.playing = True
|
self.playing = True
|
||||||
self.footer(f"Playing {self.playing} - Feature not implemented yet.")
|
self.footer(f"Playing {self.playing} - Feature not implemented yet.")
|
||||||
|
|
||||||
|
def set_remote_breakpoint(self) -> None:
|
||||||
|
if self.remote_debug:
|
||||||
|
import os
|
||||||
|
from pudb.remote import set_trace
|
||||||
|
_term_size: os.terminal_size = os.get_terminal_size()
|
||||||
|
set_trace(term_size=(_term_size.columns, _term_size.lines), host='0.0.0.0', port=self.debug_port)
|
||||||
|
self.stdscr.clear()
|
||||||
|
self.menu()
|
||||||
|
self.stdscr.refresh()
|
||||||
|
self.library_window() if self.active_window == "library" else None
|
||||||
|
self.search_window() if self.active_window == "search" else None
|
||||||
|
self.now_playing_window() if self.active_window == "now_playing" else None
|
||||||
|
self.settings_window() if self.active_window == "settings" else None
|
||||||
|
self.help_window(None) if self.active_window == "help" else None
|
||||||
|
self.footer()
|
||||||
|
|
||||||
def key_handler(self) -> None:
|
def key_handler(self) -> None:
|
||||||
while True:
|
while True:
|
||||||
key = self.stdscr.getch()
|
key = self.stdscr.getch()
|
||||||
@@ -158,3 +239,14 @@ class Terminal:
|
|||||||
raise ExitTerminal()
|
raise ExitTerminal()
|
||||||
elif key == ord(' '):
|
elif key == ord(' '):
|
||||||
self.play_toggle()
|
self.play_toggle()
|
||||||
|
|
||||||
|
elif key == ord("j") and self.active_window == "library":
|
||||||
|
if self.row < self.book_count - 1: self.row = self.row + 1
|
||||||
|
else: self.row = 0
|
||||||
|
self.library_window()
|
||||||
|
elif key == ord("k") and self.active_window == "library":
|
||||||
|
if self.row > 0: self.row = self.row - 1
|
||||||
|
else: self.row = self.book_count - 1
|
||||||
|
self.library_window()
|
||||||
|
elif key == ord("d") and self.remote_debug:
|
||||||
|
self.set_remote_breakpoint()
|
||||||
|
|||||||
Reference in New Issue
Block a user