diff --git a/app/main.py b/app/main.py index cfdc77e2..70b5229a 100644 --- a/app/main.py +++ b/app/main.py @@ -4,8 +4,10 @@ import argparse import asyncio import base64 from datetime import datetime +import errno import json import os +import pty import random import shlex import time @@ -603,46 +605,80 @@ class Main: await self.sio.emit('cli_close', {'exitcode': 0}) return - proc = None try: - async def _read_stream(streamType, stream): - while True: - line = await stream.readline() - if line: - await self.sio.emit('cli_output', { - 'type': streamType, - 'line': line.decode('utf-8') - }) - else: - break - LOG.info(f'CLI: {data}') - args = ['yt-dlp', *shlex.split(data)] + args = ['yt-dlp'] + shlex.split(data) + _env = os.environ.copy() + _env.update({ + "TERM": "xterm-256color", + "LANG": "en_US.UTF-8", + "SHELL": "/bin/bash", + "LC_ALL": "en_US.UTF-8", + "PWD": self.config.download_path, + "FORCE_COLOR": "1", + "PYTHONUNBUFFERED": "1", + }) + + master_fd, slave_fd = pty.openpty() + proc = await asyncio.create_subprocess_exec( *args, cwd=self.config.download_path, stdin=asyncio.subprocess.DEVNULL, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - env=os.environ.copy().update({ - "TERM": "xterm-256color", - "LANG": "en_US.UTF-8", - "SHELL": "/bin/bash", - "LC_ALL": "en_US.UTF-8", - "PWD": self.config.download_path, - }), + stdout=slave_fd, + stderr=slave_fd, + env=_env, ) - await asyncio.gather( - _read_stream('stdout', proc.stdout), - _read_stream('stderr', proc.stderr), - ) + try: + os.close(slave_fd) + except Exception as e: + LOG.error(f'Error closing PTY: {str(e)}') - while proc.returncode is None: - await asyncio.sleep(0.1) + async def read_pty(): + loop = asyncio.get_running_loop() + buffer = b'' + while True: + try: + chunk = await loop.run_in_executor(None, lambda: os.read(master_fd, 1024)) + except OSError as e: + if e.errno == errno.EIO: + break + else: + raise - await self.sio.emit('cli_close', {'exitcode': proc.returncode}) + if not chunk: + # No more output + if buffer: + # Emit any remaining partial line + await self.sio.emit('cli_output', { + 'type': 'stdout', + 'line': buffer.decode('utf-8', errors='replace') + }) + break + buffer += chunk + *lines, buffer = buffer.split(b'\n') + for line in lines: + await self.sio.emit('cli_output', { + 'type': 'stdout', + 'line': line.decode('utf-8', errors='replace') + }) + try: + os.close(master_fd) + except Exception as e: + LOG.error(f'Error closing PTY: {str(e)}') + + # Start reading output from PTY + read_task = asyncio.create_task(read_pty()) + + # Wait until process finishes + returncode = await proc.wait() + + # Ensure reading is done + await read_task + + await self.sio.emit('cli_close', {'exitcode': returncode}) except Exception as e: LOG.error(f'CLI Error: {str(e)}') await self.sio.emit('cli_output', { diff --git a/frontend/src/components/CLI-Console.vue b/frontend/src/components/CLI-Console.vue index dd74d621..30605f10 100644 --- a/frontend/src/components/CLI-Console.vue +++ b/frontend/src/components/CLI-Console.vue @@ -15,7 +15,8 @@ Terminal

- +

@@ -23,16 +24,23 @@
-
+
+ + + yt-dlp + +
+
-

@@ -132,7 +140,8 @@ const runCommand = async () => { } emitter('runCommand', command.value) - terminal.value.writeln(`~ ${command.value}`) + terminal.value.writeln(`user@YTPTube ~`) + terminal.value.writeln(`$ yt-dlp ${command.value}`) } const clearOutput = async (withCommand = false) => { diff --git a/frontend/src/main.js b/frontend/src/main.js index 8b77b952..5358a14e 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -7,12 +7,7 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' import { faCog, faTrash, faLink, faPlus, faTrashCan, faCircleXmark, faCircleCheck, faRotateRight, faDownload, faUpRightFromSquare, faSpinner, faArrowUp, faArrowDown, faTasks, faCalendar, faArrowUpAZ, faArrowDownAZ, faEject, faGlobe, faMoon, faSun, - faTerminal, - faBroom, - faServer, - faPowerOff, - faPaperPlane, - faBoxArchive + faTerminal, faBroom, faServer, faPowerOff, faPaperPlane, faBoxArchive, } from '@fortawesome/free-solid-svg-icons' import { faSquare, faSquareCheck } from '@fortawesome/free-regular-svg-icons'