diff --git a/pretext/cli.py b/pretext/cli.py index 49480449..69ed7d48 100644 --- a/pretext/cli.py +++ b/pretext/cli.py @@ -612,7 +612,6 @@ def view( log.debug(e, exc_info=True) return - port = port or 8128 # Call generate if flag is set if generate: try: @@ -620,6 +619,7 @@ def view( except Exception as e: log.info(f"Failed to generate assets: {e}") log.debug("", exc_info=True) + # Call build if flag is set if build: try: target.build() @@ -627,15 +627,16 @@ def view( log.info(f"Failed to build: {e}") log.debug("Exception info:\n##################\n", exc_info=True) # Start server if there isn't one running already: - if not utils.server_running(port=port) or restart_server: + used_port = utils.server_is_running() + if port or restart_server or not used_port: # First terminate any server that might be running - utils.stop_server() + utils.stop_server(port) # Start the server log.info("Starting server.") server = project.server_process( output_dir=target.output_dir_abspath(), access=access, - port=port, + port=port or 8128, launch=not no_launch, ) server.start() @@ -646,15 +647,15 @@ def view( log.info("Stopping server.") server.terminate() return - - url = ( - "http://localhost:" - + str(port) - + target.output_dir_abspath() - .as_posix() - .replace(project.abspath().as_posix(), "") - ) - log.info(f"Viewing output for {target.name} at {url}") + else: + url = ( + "http://localhost:" + + str(used_port) + + target.output_dir_abspath() + .as_posix() + .replace(project.abspath().as_posix(), "") + ) + log.info(f"Viewing output for {target.name} at {url}") # pretext deploy diff --git a/pretext/project/__init__.py b/pretext/project/__init__.py index 958af8d4..0cd608c3 100644 --- a/pretext/project/__init__.py +++ b/pretext/project/__init__.py @@ -835,33 +835,6 @@ def generate_assets( self.save_asset_table(saved_asset_table) log.debug(f"Saved asset table to disk: {saved_asset_table}") - def view( - self, - access: t.Literal["public", "private"], - port: int = 8000, - watch: bool = False, - no_launch: bool = False, - ) -> None: - directory = self.output_dir_abspath() - - if watch: - watch_directory = self.source_abspath() - else: - watch_directory = None - if not self.output_dir_abspath().exists(): - log.error(f"The directory `{self.output_dir_abspath()}` does not exist.") - log.error( - f"Run `pretext view {self.name} -b` to build your project before viewing." - ) - return - - def watch_callback() -> None: - self.build() - - utils.run_server( - directory, access, port, watch_directory, watch_callback, no_launch - ) - class Project(pxml.BaseXmlModel, tag="project", search_mode="unordered"): """ diff --git a/pretext/utils.py b/pretext/utils.py index 7d467da5..4cad7db4 100644 --- a/pretext/utils.py +++ b/pretext/utils.py @@ -15,7 +15,6 @@ import tempfile import threading import psutil -import urllib.request import watchdog.events import watchdog.observers import time @@ -297,34 +296,6 @@ class TCPServer(socketserver.TCPServer): log.warning(f"Trying port {port} instead.\n") -def run_server( - directory: Path, - access: t.Literal["public", "private"], - port: int, - watch_directory: t.Optional[Path] = None, - watch_callback: Callable[[], None] = lambda: None, - no_launch: bool = False, -) -> None: - threading.Thread( - target=lambda: serve_forever(directory, access, port, no_launch), daemon=True - ).start() - if watch_directory is not None: - log.info(f"\nWatching for changes in `{watch_directory}` ...\n") - event_handler = HTMLRebuildHandler(watch_callback) - observer = watchdog.observers.Observer() - observer.schedule(event_handler, watch_directory, recursive=True) - observer.start() - try: - while True: - time.sleep(1) - except KeyboardInterrupt: - log.info("\nClosing server...") - if watch_directory is not None: - observer.stop() - if watch_directory is not None: - observer.join() - - # Info on namespaces: http://lxml.de/tutorial.html#namespaces NSMAP = { "xi": "http://www.w3.org/2001/XInclude", @@ -714,39 +685,49 @@ def retrieve(temp: Path, folder: Path) -> None: shutil.rmtree(temp) -def server_running(port: int) -> bool: +def server_is_running() -> t.Optional[int]: """ - Check if a pretext-view server is running already + Check if a pretext-view server is running already, and if so, return its port number. """ # We look at all currently running processes and check if any are a pretext process that is a child of a pretext process. This would only happen if we have run a `pretext view` command to start the server, so we can assume that this is the server we are looking for. for proc in psutil.process_iter(): if proc.name() == "pretext" and proc.parent().name() == "pretext": log.debug(f"Found pretext server running with pid {proc.pid}") - if proc.status() == psutil.STATUS_STOPPED: + # Sometimes the process stops but doesn't get removed from the process list. We check if the process is still running by checking its status. + if proc.status() not in [psutil.STATUS_RUNNING, psutil.STATUS_SLEEPING]: log.debug("Server is stopped.") - return False - log.debug(f"Checking if port {port} is an html host:") + return None + # To get the port number, we look at the connections the process has open. We assume that the server is running on the first port that is open. try: - with urllib.request.urlopen(f"http://localhost:{port}") as response: - log.debug(f"Found html host running on port {port}") - # We have found the right server if one of the files shown is project.ptx - if "project.ptx" in response.read().decode("utf-8"): - log.debug("Found project.ptx in html host.") - return True + port = proc.connections()[0].laddr.port + log.debug(f"Server is running on port {port}") + return port except Exception as e: - log.debug(f"Did not find html: {e}") - log.debug(f"No html host running on port {port}") - return False + log.debug(f"Could not get port number. Exception: {e}", exc_info=True) log.debug("No pretext server found running.") - return False + return None -def stop_server() -> None: +def stop_server(port: t.Optional[int] = None) -> None: """ Terminate a server running in the background. """ - # As before, we look for a pretext process that is a child of a pretext process. This time we terminate that process. - for proc in psutil.process_iter(): - if proc.name() == "pretext" and proc.parent().name() == "pretext": - log.debug(f"Terminating process with PID {proc.pid}") - proc.terminate() + # If a port was passed, we look for a "pretext" process that has a connection on that port, and kill it if we find it. + if port is not None: + log.debug(f"Terminating server running on port {port}") + for proc in psutil.process_iter(): + if len(proc.connections()) > 0: + if ( + proc.name() == "pretext" + and proc.connections()[0].laddr.port == port + ): + log.debug( + f"Terminating process with PID {proc.pid} hosting on port {port}" + ) + proc.terminate() + else: + # As before, we look for a pretext process that is a child of a pretext process. This time we terminate that process. + for proc in psutil.process_iter(): + if proc.name() == "pretext" and proc.parent().name() == "pretext": + log.debug(f"Terminating process with PID {proc.pid}") + proc.terminate()