コード例 #1
0
        protected override ExitCode RunCommand(Preprocessor preprocessor)
        {
            // Get the standard input stream
            _configOptions.Stdin = StandardInputReader.Read();

            // Fix the root folder and other files
            DirectoryPath currentDirectory = Environment.CurrentDirectory;

            _configOptions.RootPath       = _configOptions.RootPath == null ? currentDirectory : currentDirectory.Combine(_configOptions.RootPath);
            _logFilePath                  = _logFilePath == null ? null : _configOptions.RootPath.CombineFile(_logFilePath);
            _configOptions.ConfigFilePath = _configOptions.RootPath.CombineFile(_configOptions.ConfigFilePath ?? "config.wyam");

            // Set up the log file
            if (_logFilePath != null)
            {
                // Delete an exiting log file if one exists
                if (File.Exists(_logFilePath.FullPath))
                {
                    try
                    {
                        File.Delete(_logFilePath.FullPath);
                    }
                    catch (Exception)
                    {
                    }
                }

                Trace.AddListener(new SimpleFileTraceListener(_logFilePath.FullPath));
            }

            // Get the engine and configurator
            EngineManager engineManager = EngineManager.Get(preprocessor, _configOptions);

            if (engineManager == null)
            {
                return(ExitCode.CommandLineError);
            }

            // Configure and execute
            if (!engineManager.Configure())
            {
                return(ExitCode.ConfigurationError);
            }

            if (_verifyConfig)
            {
                Trace.Information("No errors. Exiting.");
                return(ExitCode.Normal);
            }

            TraceEnviornment(engineManager);

            if (!engineManager.Execute())
            {
                return(ExitCode.ExecutionError);
            }

            bool messagePump = false;

            // Start the preview server
            Server previewServer = null;

            if (_preview)
            {
                messagePump = true;
                DirectoryPath previewPath = _previewRoot == null
                    ? engineManager.Engine.FileSystem.GetOutputDirectory().Path
                    : engineManager.Engine.FileSystem.GetOutputDirectory(_previewRoot).Path;

                previewServer = PreviewServer.Start(previewPath, _previewPort, _previewForceExtension, _previewVirtualDirectory, _watch && !_noReload, _contentTypes);
            }

            // Start the watchers
            IDisposable inputFolderWatcher = null;
            IDisposable configFileWatcher  = null;

            if (_watch)
            {
                messagePump = true;

                Trace.Information("Watching paths(s) {0}", string.Join(", ", engineManager.Engine.FileSystem.InputPaths));
                inputFolderWatcher = new ActionFileSystemWatcher(
                    engineManager.Engine.FileSystem.GetOutputDirectory().Path,
                    engineManager.Engine.FileSystem.GetInputDirectories().Select(x => x.Path),
                    true,
                    "*.*",
                    path =>
                {
                    _changedFiles.Enqueue(path);
                    _messageEvent.Set();
                });

                if (_configOptions.ConfigFilePath != null)
                {
                    Trace.Information("Watching configuration file {0}", _configOptions.ConfigFilePath);
                    configFileWatcher = new ActionFileSystemWatcher(
                        engineManager.Engine.FileSystem.GetOutputDirectory().Path,
                        new[] { _configOptions.ConfigFilePath.Directory },
                        false,
                        _configOptions.ConfigFilePath.FileName.FullPath,
                        path =>
                    {
                        FilePath filePath = new FilePath(path);
                        if (_configOptions.ConfigFilePath.Equals(filePath))
                        {
                            _newEngine.Set();
                            _messageEvent.Set();
                        }
                    });
                }
            }

            // Start the message pump if an async process is running
            ExitCode exitCode = ExitCode.Normal;

            if (messagePump)
            {
                // Only wait for a key if console input has not been redirected, otherwise it's on the caller to exit
                if (!Console.IsInputRedirected)
                {
                    // Start the key listening thread
                    Thread thread = new Thread(() =>
                    {
                        Trace.Information("Hit Ctrl-C to exit");
                        Console.TreatControlCAsInput = true;
                        while (true)
                        {
                            // Would have prefered to use Console.CancelKeyPress, but that bubbles up to calling batch files
                            // The (ConsoleKey)3 check is to support a bug in VS Code: https://github.com/Microsoft/vscode/issues/9347
                            ConsoleKeyInfo consoleKey = Console.ReadKey(true);
                            if (consoleKey.Key == (ConsoleKey)3 || (consoleKey.Key == ConsoleKey.C && (consoleKey.Modifiers & ConsoleModifiers.Control) != 0))
                            {
                                _exit.Set();
                                _messageEvent.Set();
                                break;
                            }
                        }
                    })
                    {
                        IsBackground = true
                    };
                    thread.Start();
                }

                // Wait for activity
                while (true)
                {
                    _messageEvent.WaitOne(); // Blocks the current thread until a signal
                    if (_exit)
                    {
                        break;
                    }

                    // See if we need a new engine
                    if (_newEngine)
                    {
                        // Get a new engine
                        Trace.Information("Configuration file {0} has changed, re-running", _configOptions.ConfigFilePath);
                        engineManager.Dispose();
                        engineManager = EngineManager.Get(preprocessor, _configOptions);

                        // Configure and execute
                        if (!engineManager.Configure())
                        {
                            exitCode = ExitCode.ConfigurationError;
                            break;
                        }

                        TraceEnviornment(engineManager);

                        if (!engineManager.Execute())
                        {
                            exitCode = ExitCode.ExecutionError;
                        }

                        // Clear the changed files since we just re-ran
                        string changedFile;
                        while (_changedFiles.TryDequeue(out changedFile))
                        {
                        }

                        _newEngine.Unset();
                    }
                    else
                    {
                        // Execute if files have changed
                        HashSet <string> changedFiles = new HashSet <string>();
                        string           changedFile;
                        while (_changedFiles.TryDequeue(out changedFile))
                        {
                            if (changedFiles.Add(changedFile))
                            {
                                Trace.Verbose("{0} has changed", changedFile);
                            }
                        }
                        if (changedFiles.Count > 0)
                        {
                            Trace.Information("{0} files have changed, re-executing", changedFiles.Count);
                            if (!engineManager.Execute())
                            {
                                exitCode = ExitCode.ExecutionError;
                            }
                            previewServer?.TriggerReload();
                        }
                    }

                    // Check one more time for exit
                    if (_exit)
                    {
                        break;
                    }
                    Trace.Information("Hit Ctrl-C to exit");
                    _messageEvent.Reset();
                }

                // Shutdown
                Trace.Information("Shutting down");
                engineManager.Dispose();
                inputFolderWatcher?.Dispose();
                configFileWatcher?.Dispose();
                previewServer?.Dispose();
            }

            return(exitCode);
        }
コード例 #2
0
    public static void Preview(Engine engine, NukeBuild build)
    {
        var _messageEvent = new AutoResetEvent(false);
        var _changedFiles = new ConcurrentQueue <string>();
        var _exit         = new InterlockedBool(false);
        // Start the preview server
        DirectoryPath previewPath = (NukeBuild.RootDirectory / "output").ToString();

        engine.Execute();
        var previewServer = PreviewServer.Start(previewPath, 5080, true, null, true, new Dictionary <string, string>());

        Trace.Information("Watching paths(s) {0}", string.Join(", ", engine.FileSystem.InputPaths));
        var inputFolderWatcher = new ActionFileSystemWatcher(
            engine.FileSystem.GetOutputDirectory().Path,
            engine.FileSystem.GetInputDirectories().Select(x => x.Path),
            true,
            "*.*",
            path =>
        {
            _changedFiles.Enqueue(path);
            _messageEvent.Set();
        });

        // Start the message pump if an async process is running
        ExitCode exitCode = ExitCode.Normal;

        // Only wait for a key if console input has not been redirected, otherwise it's on the caller to exit
        if (!Console.IsInputRedirected)
        {
            // Start the key listening thread
            Thread thread = new Thread(() =>
            {
                Trace.Information("Hit Ctrl-C to exit");
                Console.TreatControlCAsInput = true;
                while (true)
                {
                    // Would have prefered to use Console.CancelKeyPress, but that bubbles up to calling batch files
                    // The (ConsoleKey)3 check is to support a bug in VS Code: https://github.com/Microsoft/vscode/issues/9347
                    ConsoleKeyInfo consoleKey = Console.ReadKey(true);
                    if (consoleKey.Key == (ConsoleKey)3 || (consoleKey.Key == ConsoleKey.C && (consoleKey.Modifiers & ConsoleModifiers.Control) != 0))
                    {
                        _exit.Set();
                        _messageEvent.Set();
                        break;
                    }
                }
            })
            {
                IsBackground = true
            };
            thread.Start();
        }

        // Wait for activity
        while (true)
        {
            _messageEvent.WaitOne(); // Blocks the current thread until a signal
            if (_exit)
            {
                break;
            }

            // Execute if files have changed
            HashSet <string> changedFiles = new HashSet <string>();
            string           changedFile;
            while (_changedFiles.TryDequeue(out changedFile))
            {
                if (changedFiles.Add(changedFile))
                {
                    Trace.Verbose("{0} has changed", changedFile);
                }
            }
            if (changedFiles.Count > 0)
            {
                Trace.Information("{0} files have changed, re-executing", changedFiles.Count);
                engine.Execute();
                previewServer?.TriggerReloadAsync().GetAwaiter().GetResult();
            }

            // Check one more time for exit
            if (_exit)
            {
                break;
            }
            Trace.Information("Hit Ctrl-C to exit");
            _messageEvent.Reset();
        }

        // Shutdown
        Trace.Information("Shutting down");
        inputFolderWatcher?.Dispose();
        previewServer?.Dispose();
    }
コード例 #3
0
ファイル: BuildCommand.cs プロジェクト: ibebbs/Wyam
        protected override ExitCode RunCommand(Preprocessor preprocessor)
        {
            // Get the standard input stream
            _configOptions.Stdin = StandardInputReader.Read();
            
            // Fix the root folder and other files
            DirectoryPath currentDirectory = Environment.CurrentDirectory;
            _configOptions.RootPath = _configOptions.RootPath == null ? currentDirectory : currentDirectory.Combine(_configOptions.RootPath);
            _logFilePath = _logFilePath == null ? null : _configOptions.RootPath.CombineFile(_logFilePath);
            _configOptions.ConfigFilePath = _configOptions.RootPath.CombineFile(_configOptions.ConfigFilePath ?? "config.wyam");

            // Set up the log file         
            if (_logFilePath != null)
            {
                Trace.AddListener(new SimpleFileTraceListener(_logFilePath.FullPath));
            }

            // Get the engine and configurator
            EngineManager engineManager = EngineManager.Get(preprocessor, _configOptions);
            if (engineManager == null)
            {
                return ExitCode.CommandLineError;
            }

            // Configure and execute
            if (!engineManager.Configure())
            {
                return ExitCode.ConfigurationError;
            }

            if (_verifyConfig)
            {
                Trace.Information("No errors. Exiting.");
                return ExitCode.Normal;
            }

            Trace.Information($"Root path:{Environment.NewLine}  {engineManager.Engine.FileSystem.RootPath}");
            Trace.Information($"Input path(s):{Environment.NewLine}  {string.Join(Environment.NewLine + "  ", engineManager.Engine.FileSystem.InputPaths)}");
            Trace.Information($"Output path:{Environment.NewLine}  {engineManager.Engine.FileSystem.OutputPath}");
            if (!engineManager.Execute())
            {
                return ExitCode.ExecutionError;
            }

            bool messagePump = false;

            // Start the preview server
            IDisposable previewServer = null;
            if (_preview)
            {
                messagePump = true;
                DirectoryPath previewPath = _previewRoot == null
                    ? engineManager.Engine.FileSystem.GetOutputDirectory().Path
                    : engineManager.Engine.FileSystem.GetOutputDirectory(_previewRoot).Path;
                previewServer = PreviewServer.Start(previewPath, _previewPort, _previewForceExtension);
            }

            // Start the watchers
            IDisposable inputFolderWatcher = null;
            IDisposable configFileWatcher = null;
            if (_watch)
            {
                messagePump = true;

                Trace.Information("Watching paths(s) {0}", string.Join(", ", engineManager.Engine.FileSystem.InputPaths));
                inputFolderWatcher = new ActionFileSystemWatcher(engineManager.Engine.FileSystem.GetOutputDirectory().Path,
                    engineManager.Engine.FileSystem.GetInputDirectories().Select(x => x.Path), true, "*.*", path =>
                    {
                        _changedFiles.Enqueue(path);
                        _messageEvent.Set();
                    });

                if (_configOptions.ConfigFilePath != null)
                {
                    Trace.Information("Watching configuration file {0}", _configOptions.ConfigFilePath);
                    configFileWatcher = new ActionFileSystemWatcher(engineManager.Engine.FileSystem.GetOutputDirectory().Path,
                        new[] { _configOptions.ConfigFilePath.Directory }, false, _configOptions.ConfigFilePath.FileName.FullPath, path =>
                        {
                            FilePath filePath = new FilePath(path);
                            if (_configOptions.ConfigFilePath.Equals(filePath))
                            {
                                _newEngine.Set();
                                _messageEvent.Set();
                            }
                        });
                }
            }

            // Start the message pump if an async process is running
            ExitCode exitCode = ExitCode.Normal;
            if (messagePump)
            {
                // Only wait for a key if console input has not been redirected, otherwise it's on the caller to exit
                if (!Console.IsInputRedirected)
                {
                    // Start the key listening thread
                    var thread = new Thread(() =>
                    {
                        Trace.Information("Hit any key to exit");
                        Console.ReadKey();
                        _exit.Set();
                        _messageEvent.Set();
                    })
                    {
                        IsBackground = true
                    };
                    thread.Start();
                }

                // Wait for activity
                while (true)
                {
                    _messageEvent.WaitOne();  // Blocks the current thread until a signal
                    if (_exit)
                    {
                        break;
                    }

                    // See if we need a new engine
                    if (_newEngine)
                    {
                        // Get a new engine
                        Trace.Information("Configuration file {0} has changed, re-running", _configOptions.ConfigFilePath);
                        engineManager.Dispose();
                        engineManager = EngineManager.Get(preprocessor, _configOptions);

                        // Configure and execute
                        if (!engineManager.Configure())
                        {
                            exitCode = ExitCode.ConfigurationError;
                            break;
                        }
                        Console.WriteLine($"Root path:{Environment.NewLine}  {engineManager.Engine.FileSystem.RootPath}");
                        Console.WriteLine($"Input path(s):{Environment.NewLine}  {string.Join(Environment.NewLine + "  ", engineManager.Engine.FileSystem.InputPaths)}");
                        Console.WriteLine($"Root path:{Environment.NewLine}  {engineManager.Engine.FileSystem.OutputPath}");
                        if (!engineManager.Execute())
                        {
                            exitCode = ExitCode.ExecutionError;
                            break;
                        }

                        // Clear the changed files since we just re-ran
                        string changedFile;
                        while (_changedFiles.TryDequeue(out changedFile))
                        {
                        }

                        _newEngine.Unset();
                    }
                    else
                    {
                        // Execute if files have changed
                        HashSet<string> changedFiles = new HashSet<string>();
                        string changedFile;
                        while (_changedFiles.TryDequeue(out changedFile))
                        {
                            if (changedFiles.Add(changedFile))
                            {
                                Trace.Verbose("{0} has changed", changedFile);
                            }
                        }
                        if (changedFiles.Count > 0)
                        {
                            Trace.Information("{0} files have changed, re-executing", changedFiles.Count);
                            if (!engineManager.Execute())
                            {
                                exitCode = ExitCode.ExecutionError;
                                break;
                            }
                        }
                    }

                    // Check one more time for exit
                    if (_exit)
                    {
                        break;
                    }
                    Trace.Information("Hit any key to exit");
                    _messageEvent.Reset();
                }

                // Shutdown
                Trace.Information("Shutting down");
                engineManager.Dispose();
                inputFolderWatcher?.Dispose();
                configFileWatcher?.Dispose();
                previewServer?.Dispose();
            }

            return exitCode;
        }
コード例 #4
0
        protected override ExitCode RunCommand(Preprocessor preprocessor)
        {
            // Get the standard input stream
            _configOptions.Stdin = StandardInputReader.Read();

            // Fix the root folder and other files
            DirectoryPath currentDirectory = Environment.CurrentDirectory;

            _configOptions.RootPath       = _configOptions.RootPath == null ? currentDirectory : currentDirectory.Combine(_configOptions.RootPath);
            _logFilePath                  = _logFilePath == null ? null : _configOptions.RootPath.CombineFile(_logFilePath);
            _configOptions.ConfigFilePath = _configOptions.RootPath.CombineFile(_configOptions.ConfigFilePath ?? "config.wyam");

            // Set up the log file
            if (_logFilePath != null)
            {
                Trace.AddListener(new SimpleFileTraceListener(_logFilePath.FullPath));
            }

            // Get the engine and configurator
            EngineManager engineManager = EngineManager.Get(preprocessor, _configOptions);

            if (engineManager == null)
            {
                return(ExitCode.CommandLineError);
            }

            // Configure and execute
            if (!engineManager.Configure())
            {
                return(ExitCode.ConfigurationError);
            }

            if (_verifyConfig)
            {
                Trace.Information("No errors. Exiting.");
                return(ExitCode.Normal);
            }

            Trace.Information($"Root path:{Environment.NewLine}    {engineManager.Engine.FileSystem.RootPath}");
            Trace.Information($"Input path(s):{Environment.NewLine}    {string.Join(Environment.NewLine + "    ", engineManager.Engine.FileSystem.InputPaths)}");
            Trace.Information($"Output path:{Environment.NewLine}    {engineManager.Engine.FileSystem.OutputPath}");
            Trace.Information($"Settings:{Environment.NewLine}    {string.Join(Environment.NewLine + "    ", engineManager.Engine.Settings.Select(x => $"{x.Key}: {x.Value?.ToString() ?? "null"}"))}");
            if (!engineManager.Execute())
            {
                return(ExitCode.ExecutionError);
            }

            bool messagePump = false;

            // Start the preview server
            Server previewServer = null;

            if (_preview)
            {
                messagePump = true;
                DirectoryPath previewPath = _previewRoot == null
                    ? engineManager.Engine.FileSystem.GetOutputDirectory().Path
                    : engineManager.Engine.FileSystem.GetOutputDirectory(_previewRoot).Path;

                previewServer = PreviewServer.Start(previewPath, _previewPort, _previewForceExtension, _previewVirtualDirectory, _watch && !_noReload);
            }

            // Start the watchers
            IDisposable inputFolderWatcher = null;
            IDisposable configFileWatcher  = null;

            if (_watch)
            {
                messagePump = true;

                Trace.Information("Watching paths(s) {0}", string.Join(", ", engineManager.Engine.FileSystem.InputPaths));
                inputFolderWatcher = new ActionFileSystemWatcher(
                    engineManager.Engine.FileSystem.GetOutputDirectory().Path,
                    engineManager.Engine.FileSystem.GetInputDirectories().Select(x => x.Path),
                    true,
                    "*.*",
                    path =>
                {
                    _changedFiles.Enqueue(path);
                    _messageEvent.Set();
                });

                if (_configOptions.ConfigFilePath != null)
                {
                    Trace.Information("Watching configuration file {0}", _configOptions.ConfigFilePath);
                    configFileWatcher = new ActionFileSystemWatcher(
                        engineManager.Engine.FileSystem.GetOutputDirectory().Path,
                        new[] { _configOptions.ConfigFilePath.Directory },
                        false,
                        _configOptions.ConfigFilePath.FileName.FullPath,
                        path =>
                    {
                        FilePath filePath = new FilePath(path);
                        if (_configOptions.ConfigFilePath.Equals(filePath))
                        {
                            _newEngine.Set();
                            _messageEvent.Set();
                        }
                    });
                }
            }

            // Start the message pump if an async process is running
            ExitCode exitCode = ExitCode.Normal;

            if (messagePump)
            {
                // Only wait for a key if console input has not been redirected, otherwise it's on the caller to exit
                if (!Console.IsInputRedirected)
                {
                    // Start the key listening thread
                    Thread thread = new Thread(() =>
                    {
                        Trace.Information("Hit any key to exit");
                        Console.ReadKey();
                        _exit.Set();
                        _messageEvent.Set();
                    })
                    {
                        IsBackground = true
                    };
                    thread.Start();
                }

                // Wait for activity
                while (true)
                {
                    _messageEvent.WaitOne(); // Blocks the current thread until a signal
                    if (_exit)
                    {
                        break;
                    }

                    // See if we need a new engine
                    if (_newEngine)
                    {
                        // Get a new engine
                        Trace.Information("Configuration file {0} has changed, re-running", _configOptions.ConfigFilePath);
                        engineManager.Dispose();
                        engineManager = EngineManager.Get(preprocessor, _configOptions);

                        // Configure and execute
                        if (!engineManager.Configure())
                        {
                            exitCode = ExitCode.ConfigurationError;
                            break;
                        }
                        Console.WriteLine($"Root path:{Environment.NewLine}  {engineManager.Engine.FileSystem.RootPath}");
                        Console.WriteLine($"Input path(s):{Environment.NewLine}  {string.Join(Environment.NewLine + "  ", engineManager.Engine.FileSystem.InputPaths)}");
                        Console.WriteLine($"Root path:{Environment.NewLine}  {engineManager.Engine.FileSystem.OutputPath}");
                        if (!engineManager.Execute())
                        {
                            exitCode = ExitCode.ExecutionError;
                        }

                        // Clear the changed files since we just re-ran
                        string changedFile;
                        while (_changedFiles.TryDequeue(out changedFile))
                        {
                        }

                        _newEngine.Unset();
                    }
                    else
                    {
                        // Execute if files have changed
                        HashSet <string> changedFiles = new HashSet <string>();
                        string           changedFile;
                        while (_changedFiles.TryDequeue(out changedFile))
                        {
                            if (changedFiles.Add(changedFile))
                            {
                                Trace.Verbose("{0} has changed", changedFile);
                            }
                        }
                        if (changedFiles.Count > 0)
                        {
                            Trace.Information("{0} files have changed, re-executing", changedFiles.Count);
                            if (!engineManager.Execute())
                            {
                                exitCode = ExitCode.ExecutionError;
                            }
                            previewServer?.TriggerReload();
                        }
                    }

                    // Check one more time for exit
                    if (_exit)
                    {
                        break;
                    }
                    Trace.Information("Hit any key to exit");
                    _messageEvent.Reset();
                }

                // Shutdown
                Trace.Information("Shutting down");
                engineManager.Dispose();
                inputFolderWatcher?.Dispose();
                configFileWatcher?.Dispose();
                previewServer?.Dispose();
            }

            return(exitCode);
        }
コード例 #5
0
        protected override async Task <int> ExecuteEngineAsync(
            CommandContext commandContext,
            PreviewCommandSettings commandSettings,
            IEngineManager engineManager)
        {
            SetPipelines(commandContext, commandSettings, engineManager);

            ExitCode exitCode = ExitCode.Normal;
            ILogger  logger   = engineManager.Engine.Services.GetRequiredService <ILogger <Bootstrapper> >();

            // Execute the engine for the first time
            using (CancellationTokenSource cancellationTokenSource = new CancellationTokenSource())
            {
                exitCode = await engineManager.ExecuteAsync(cancellationTokenSource)
                    ? ExitCode.Normal
                    : ExitCode.ExecutionError;
            }

            // Start the preview server
            Dictionary <string, string> contentTypes = commandSettings.ContentTypes?.Length > 0
                ? GetContentTypes(commandSettings.ContentTypes)
                : new Dictionary <string, string>();
            ILoggerProvider loggerProvider  = engineManager.Engine.Services.GetRequiredService <ILoggerProvider>();
            IDirectory      outputDirectory = engineManager.Engine.FileSystem.GetOutputDirectory();
            Server          previewServer   = null;

            if (outputDirectory.Exists)
            {
                previewServer = await StartPreviewServerAsync(
                    outputDirectory.Path,
                    commandSettings.Port,
                    commandSettings.ForceExt,
                    commandSettings.VirtualDirectory,
                    !commandSettings.NoReload,
                    contentTypes,
                    loggerProvider,
                    logger);
            }

            // Start the watchers
            ActionFileSystemWatcher inputFolderWatcher = null;

            if (!commandSettings.NoWatch)
            {
                logger.LogInformation("Watching paths(s) {0}", string.Join(", ", engineManager.Engine.FileSystem.InputPaths));
                inputFolderWatcher = new ActionFileSystemWatcher(
                    outputDirectory.Path,
                    engineManager.Engine.FileSystem.GetInputDirectories().Select(x => x.Path),
                    true,
                    "*.*",
                    path =>
                {
                    _changedFiles.Enqueue(path);
                    _messageEvent.Set();
                });
            }

            // Start the message pump
            CommandUtilities.WaitForControlC(
                () =>
            {
                _exit.Set();
                _messageEvent.Set();
            },
                logger);

            // Wait for activity
            while (true)
            {
                _messageEvent.WaitOne(); // Blocks the current thread until a signal
                if (_exit)
                {
                    break;
                }

                // Execute if files have changed
                HashSet <string> changedFiles = new HashSet <string>();
                while (_changedFiles.TryDequeue(out string changedFile))
                {
                    if (changedFiles.Add(changedFile))
                    {
                        logger.LogDebug($"{changedFile} has changed");
                    }
                }
                if (changedFiles.Count > 0)
                {
                    logger.LogInformation($"{changedFiles.Count} files have changed, re-executing");

                    // Reset caches when an error occurs during the previous preview
                    string existingResetCacheSetting = null;
                    bool   setResetCacheSetting      = false;
                    if (exitCode == ExitCode.ExecutionError)
                    {
                        existingResetCacheSetting = engineManager.Engine.Settings.GetString(Keys.ResetCache);
                        setResetCacheSetting      = true;
                        ConfigurationSettings[Keys.ResetCache] = "true";
                    }

                    // If there was an execution error due to reload, keep previewing but clear the cache
                    using (CancellationTokenSource cancellationTokenSource = new CancellationTokenSource())
                    {
                        exitCode = await engineManager.ExecuteAsync(cancellationTokenSource)
                            ? ExitCode.Normal
                            : ExitCode.ExecutionError;
                    }

                    // Reset the reset cache setting after removing it
                    if (setResetCacheSetting)
                    {
                        if (existingResetCacheSetting == null)
                        {
                            ConfigurationSettings[Keys.ResetCache] = false;
                        }
                        {
                            ConfigurationSettings[Keys.ResetCache] = existingResetCacheSetting;
                        }
                    }

                    if (previewServer == null)
                    {
                        if (outputDirectory.Exists)
                        {
                            previewServer = await StartPreviewServerAsync(
                                outputDirectory.Path,
                                commandSettings.Port,
                                commandSettings.ForceExt,
                                commandSettings.VirtualDirectory,
                                !commandSettings.NoReload,
                                contentTypes,
                                loggerProvider,
                                logger);
                        }
                    }
                    else
                    {
                        await previewServer.TriggerReloadAsync();
                    }
                }

                // Check one more time for exit
                if (_exit)
                {
                    break;
                }
                logger.LogInformation("Hit Ctrl-C to exit");
                _messageEvent.Reset();
            }

            // Shutdown
            logger.LogInformation("Shutting down");
            inputFolderWatcher?.Dispose();
            previewServer?.Dispose();

            return((int)exitCode);
        }