From aca144028434bb401966c3e9cdc549e1bbcb24e4 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Tue, 17 Dec 2024 19:23:49 -0800 Subject: [PATCH] first round --- examples/OpenApi-TuttiFrutti.ps1 | 6 +- src/Locales/ar/Pode.psd1 | 6 +- src/Locales/de/Pode.psd1 | 4 - src/Locales/en-us/Pode.psd1 | 6 +- src/Locales/en/Pode.psd1 | 6 +- src/Locales/es/Pode.psd1 | 4 - src/Locales/fr/Pode.psd1 | 4 - src/Locales/it/Pode.psd1 | 4 - src/Locales/ja/Pode.psd1 | 16 +-- src/Locales/ko/Pode.psd1 | 4 - src/Locales/nl/Pode.psd1 | 4 - src/Locales/pl/Pode.psd1 | 4 - src/Locales/pt/Pode.psd1 | 4 - src/Locales/zh/Pode.psd1 | 4 - src/Pode.psd1 | 1 + src/Private/CancellationToken.ps1 | 143 +++++++++-------------- src/Private/Console.ps1 | 173 ++++++++-------------------- src/Private/Context.ps1 | 70 +++++------ src/Private/Gui.ps1 | 2 +- src/Private/Helpers.ps1 | 139 +++++++++++----------- src/Private/Runspaces.ps1 | 6 +- src/Private/Schedules.ps1 | 11 +- src/Private/Server.ps1 | 54 ++++----- src/Public/Core.ps1 | 66 +++++++++-- src/Public/Metrics.ps1 | 85 ++++++-------- src/Public/Middleware.ps1 | 12 +- src/Public/Utilities.ps1 | 87 ++++---------- tests/integration/OpenApi.Tests.ps1 | 2 +- tests/unit/Helpers.Tests.ps1 | 6 +- tests/unit/Server.Tests.ps1 | 10 +- 30 files changed, 394 insertions(+), 549 deletions(-) diff --git a/examples/OpenApi-TuttiFrutti.ps1 b/examples/OpenApi-TuttiFrutti.ps1 index 40ab1b3fe..3b437fdc9 100644 --- a/examples/OpenApi-TuttiFrutti.ps1 +++ b/examples/OpenApi-TuttiFrutti.ps1 @@ -19,7 +19,7 @@ .PARAMETER DisableTermination Prevents the server from being terminated. -.PARAMETER IgnoreServerPsConfig +.PARAMETER IgnoreServerConfig Ignores the server.psd1 configuration file when starting the server. This parameter ensures the server does not load or apply any settings defined in the server.psd1 file, allowing for a fully manual configuration at runtime. @@ -55,7 +55,7 @@ param( $DisableTermination, [switch] - $IgnoreServerPsConfig + $IgnoreServerConfig ) try { @@ -73,7 +73,7 @@ try { } catch { throw } -Start-PodeServer -Threads 1 -Quiet:$Quiet -DisableTermination:$DisableTermination -IgnoreServerPsConfig:$IgnoreServerPsConfig -ScriptBlock { +Start-PodeServer -Threads 1 -Quiet:$Quiet -DisableTermination:$DisableTermination -IgnoreServerConfig:$IgnoreServerConfig -ScriptBlock { Add-PodeEndpoint -Address localhost -Port $PortV3 -Protocol Http -Default -Name 'endpoint_v3' Add-PodeEndpoint -Address localhost -Port $PortV3_1 -Protocol Http -Default -Name 'endpoint_v3.1' New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging diff --git a/src/Locales/ar/Pode.psd1 b/src/Locales/ar/Pode.psd1 index a0f6cc2de..5083f5950 100644 --- a/src/Locales/ar/Pode.psd1 +++ b/src/Locales/ar/Pode.psd1 @@ -304,11 +304,7 @@ suspendedMessage = 'معلق' runningMessage = 'يعمل' openHttpEndpointMessage = 'افتح أول نقطة نهاية HTTP في المتصفح الافتراضي.' - suspendingRunspaceMessage = 'إيقاف تنفيذ المسارات' - resumingRunspaceMessage = 'استئناف تنفيذ المسارات' - waitingforSuspendingMessage = 'انتظار الإيقاف...' - waitingforResumingMessage = 'انتظار الاستئناف...' - terminatedMessage = 'تم الإنهاء' + terminatedMessage = 'تم الإنهاء' showMetricsMessage = 'عرض المقاييس' clearConsoleMessage = 'مسح وحدة التحكم' serverMetricsMessage = 'مقاييس الخادم' diff --git a/src/Locales/de/Pode.psd1 b/src/Locales/de/Pode.psd1 index 0306491f3..f38bced77 100644 --- a/src/Locales/de/Pode.psd1 +++ b/src/Locales/de/Pode.psd1 @@ -304,10 +304,6 @@ suspendedMessage = 'Angehalten' runningMessage = 'Läuft' openHttpEndpointMessage = 'Öffnen Sie den ersten HTTP-Endpunkt im Standardbrowser.' - suspendingRunspaceMessage = 'Suspension der Runspaces' - resumingRunspaceMessage = 'Fortsetzung der Runspaces' - waitingforSuspendingMessage = 'Warten auf das Suspendieren ...' - waitingforResumingMessage = 'Warten auf das Fortsetzen ...' terminatedMessage = 'Beendet' showMetricsMessage = 'Metriken anzeigen' clearConsoleMessage = 'Konsole löschen' diff --git a/src/Locales/en-us/Pode.psd1 b/src/Locales/en-us/Pode.psd1 index 2248e7772..a222fc726 100644 --- a/src/Locales/en-us/Pode.psd1 +++ b/src/Locales/en-us/Pode.psd1 @@ -304,15 +304,11 @@ suspendedMessage = 'Suspended' runningMessage = 'Running' openHttpEndpointMessage = 'Open the first HTTP endpoint in the default browser.' - suspendingRunspaceMessage = 'Suspending Runspaces' - resumingRunspaceMessage = 'Resuming Runspaces' - waitingforSuspendingMessage = 'Waiting for suspending ...' - waitingforResumingMessage = 'Waiting for resuming ...' terminatedMessage = 'Terminated' showMetricsMessage = 'Show Metrics' clearConsoleMessage = 'Clear the Console' serverMetricsMessage = 'Server Metrics' totalUptimeMessage = 'Total Uptime:' uptimeSinceLastRestartMessage = 'Uptime Since Last Restart:' - totalRestartMessage = 'Total Number of Restart:' + totalRestartMessage = 'Total Number of Restarts:' } \ No newline at end of file diff --git a/src/Locales/en/Pode.psd1 b/src/Locales/en/Pode.psd1 index d5d901537..39f54c6e9 100644 --- a/src/Locales/en/Pode.psd1 +++ b/src/Locales/en/Pode.psd1 @@ -304,15 +304,11 @@ suspendedMessage = 'Suspended' runningMessage = 'Running' openHttpEndpointMessage = 'Open the first HTTP endpoint in the default browser.' - suspendingRunspaceMessage = 'Suspending Runspaces' - resumingRunspaceMessage = 'Resuming Runspaces' - waitingforSuspendingMessage = 'Waiting for suspending ...' - waitingforResumingMessage = 'Waiting for resuming ...' terminatedMessage = 'Terminated' showMetricsMessage = 'Show Metrics' clearConsoleMessage = 'Clear the Console' serverMetricsMessage = 'Server Metrics' totalUptimeMessage = 'Total Uptime:' uptimeSinceLastRestartMessage = 'Uptime Since Last Restart:' - totalRestartMessage = 'Total Number of Restart:' + totalRestartMessage = 'Total Number of Restarts:' } \ No newline at end of file diff --git a/src/Locales/es/Pode.psd1 b/src/Locales/es/Pode.psd1 index 06530011f..5f88aa776 100644 --- a/src/Locales/es/Pode.psd1 +++ b/src/Locales/es/Pode.psd1 @@ -304,10 +304,6 @@ suspendedMessage = 'Suspendido' runningMessage = 'En ejecución' openHttpEndpointMessage = 'Abrir el primer endpoint HTTP en el navegador predeterminado.' - suspendingRunspaceMessage = 'Suspendiendo los espacios de ejecución' - resumingRunspaceMessage = 'Reanudando los espacios de ejecución' - waitingforSuspendingMessage = 'Esperando para suspender ...' - waitingforResumingMessage = 'Esperando para reanudar ...' terminatedMessage = 'Terminado' showMetricsMessage = 'Mostrar métricas' clearConsoleMessage = 'Limpiar la consola' diff --git a/src/Locales/fr/Pode.psd1 b/src/Locales/fr/Pode.psd1 index c4ff5cc12..778f61a44 100644 --- a/src/Locales/fr/Pode.psd1 +++ b/src/Locales/fr/Pode.psd1 @@ -304,10 +304,6 @@ suspendedMessage = 'Suspendu' runningMessage = "En cours d'exécution" openHttpEndpointMessage = 'Ouvrez le premier point de terminaison HTTP dans le navigateur par défaut.' - suspendingRunspaceMessage = 'Suspension des Runspaces' - resumingRunspaceMessage = 'Reprise des Runspaces' - waitingforSuspendingMessage = 'En attente de la suspension ...' - waitingforResumingMessage = 'En attente de la reprise ...' terminatedMessage = 'Terminé' showMetricsMessage = 'Afficher les métriques' clearConsoleMessage = 'Effacer la console' diff --git a/src/Locales/it/Pode.psd1 b/src/Locales/it/Pode.psd1 index 4e8036432..b70408aae 100644 --- a/src/Locales/it/Pode.psd1 +++ b/src/Locales/it/Pode.psd1 @@ -304,10 +304,6 @@ suspendedMessage = 'Sospeso' runningMessage = 'In esecuzione' openHttpEndpointMessage = 'Apri il primo endpoint HTTP nel browser predefinito.' - suspendingRunspaceMessage = 'Sospensione degli spazi di esecuzione' - resumingRunspaceMessage = 'Ripresa degli spazi di esecuzione' - waitingforSuspendingMessage = 'Attesa per sospensione ...' - waitingforResumingMessage = 'Attesa per ripresa ...' terminatedMessage = 'Terminato' showMetricsMessage = 'Mostra metriche' clearConsoleMessage = 'Cancella la console' diff --git a/src/Locales/ja/Pode.psd1 b/src/Locales/ja/Pode.psd1 index 9346f8552..e1e95b8a1 100644 --- a/src/Locales/ja/Pode.psd1 +++ b/src/Locales/ja/Pode.psd1 @@ -304,15 +304,11 @@ suspendedMessage = '一時停止中' runningMessage = '実行中' openHttpEndpointMessage = 'デフォルトのブラウザで最初の HTTP エンドポイントを開きます。' - suspendingRunspaceMessage = 'ランスペースの停止' - resumingRunspaceMessage = 'ランスペースの再開' - waitingforSuspendingMessage = '停止待機中...' - waitingforResumingMessage = '再開待機中...' terminatedMessage = '終了しました' - showMetricsMessage = 'メトリクスを表示' - clearConsoleMessage = 'コンソールをクリア' - serverMetricsMessage = 'サーバーメトリクス' - totalUptimeMessage = '総稼働時間:' - uptimeSinceLastRestartMessage = '最後の再起動からの稼働時間:' - totalRestartMessage = '再起動の総数:' + showMetricsMessage = 'メトリクスを表示' + clearConsoleMessage = 'コンソールをクリア' + serverMetricsMessage = 'サーバーメトリクス' + totalUptimeMessage = '総稼働時間:' + uptimeSinceLastRestartMessage = '最後の再起動からの稼働時間:' + totalRestartMessage = '再起動の総数:' } \ No newline at end of file diff --git a/src/Locales/ko/Pode.psd1 b/src/Locales/ko/Pode.psd1 index 830c13f39..b1434e1ff 100644 --- a/src/Locales/ko/Pode.psd1 +++ b/src/Locales/ko/Pode.psd1 @@ -304,10 +304,6 @@ suspendedMessage = '일시 중지됨' runningMessage = '실행 중' openHttpEndpointMessage = '기본 브라우저에서 첫 번째 HTTP 엔드포인트를 엽니다.' - suspendingRunspaceMessage = '런스페이스 일시 정지' - resumingRunspaceMessage = '런스페이스 재개' - waitingforSuspendingMessage = '일시 정지 대기 중...' - waitingforResumingMessage = '재개 대기 중...' terminatedMessage = '종료됨' showMetricsMessage = '메트릭 표시' clearConsoleMessage = '콘솔 지우기' diff --git a/src/Locales/nl/Pode.psd1 b/src/Locales/nl/Pode.psd1 index ab1920990..04e540034 100644 --- a/src/Locales/nl/Pode.psd1 +++ b/src/Locales/nl/Pode.psd1 @@ -304,10 +304,6 @@ suspendedMessage = 'Gepauzeerd' runningMessage = 'Actief' openHttpEndpointMessage = 'Open het eerste HTTP-eindpunt in de standaardbrowser.' - suspendingRunspaceMessage = 'Wstrzymywanie przestrzeni roboczych' - resumingRunspaceMessage = 'Wznawianie przestrzeni roboczych' - waitingforSuspendingMessage = 'Oczekiwanie na zawieszenie ...' - waitingforResumingMessage = 'Oczekiwanie na wznowienie ...' terminatedMessage = 'Beëindigd' showMetricsMessage = 'Toon statistieken' clearConsoleMessage = 'Console wissen' diff --git a/src/Locales/pl/Pode.psd1 b/src/Locales/pl/Pode.psd1 index 179f5b1af..58ed3185d 100644 --- a/src/Locales/pl/Pode.psd1 +++ b/src/Locales/pl/Pode.psd1 @@ -304,10 +304,6 @@ suspendedMessage = 'Wstrzymany' runningMessage = 'Działa' openHttpEndpointMessage = 'Otwórz pierwszy punkt końcowy HTTP w domyślnej przeglądarce.' - suspendingRunspaceMessage = 'Wstrzymywanie przestrzeni roboczych' - resumingRunspaceMessage = 'Wznawianie przestrzeni roboczych' - waitingforSuspendingMessage = 'Oczekiwanie na zawieszenie ...' - waitingforResumingMessage = 'Oczekiwanie na wznowienie ...' terminatedMessage = 'Zakończono' showMetricsMessage = 'Pokaż metryki' clearConsoleMessage = 'Wyczyść konsolę' diff --git a/src/Locales/pt/Pode.psd1 b/src/Locales/pt/Pode.psd1 index d6f0605fe..dde497b85 100644 --- a/src/Locales/pt/Pode.psd1 +++ b/src/Locales/pt/Pode.psd1 @@ -304,10 +304,6 @@ suspendedMessage = 'Suspenso' runningMessage = 'Executando' openHttpEndpointMessage = 'Abrir o primeiro endpoint HTTP no navegador padrão.' - suspendingRunspaceMessage = 'Suspensão dos Runspaces' - resumingRunspaceMessage = 'Retomada dos Runspaces' - waitingforSuspendingMessage = 'Esperando para suspender ...' - waitingforResumingMessage = 'Esperando para retomar ...' terminatedMessage = 'Terminado' showMetricsMessage = 'Mostrar métricas' clearConsoleMessage = 'Limpar o console' diff --git a/src/Locales/zh/Pode.psd1 b/src/Locales/zh/Pode.psd1 index 3bd5d61d1..a68ed60d8 100644 --- a/src/Locales/zh/Pode.psd1 +++ b/src/Locales/zh/Pode.psd1 @@ -304,10 +304,6 @@ suspendedMessage = '已暂停' runningMessage = '运行中' openHttpEndpointMessage = '在默认浏览器中打开第一个 HTTP 端点。' - suspendingRunspaceMessage = '暂停运行空间' - resumingRunspaceMessage = '恢复运行空间' - waitingforSuspendingMessage = '等待暂停...' - waitingforResumingMessage = '等待恢复...' terminatedMessage = '已终止' showMetricsMessage = '显示指标' clearConsoleMessage = '清除控制台' diff --git a/src/Pode.psd1 b/src/Pode.psd1 index f4e9014cc..f8929ec77 100644 --- a/src/Pode.psd1 +++ b/src/Pode.psd1 @@ -312,6 +312,7 @@ 'Suspend-PodeServer', 'Resume-PodeServer', 'Get-PodeServerState', + 'Test-PodeServerState', 'Enable-PodeServer', 'Disable-PodeServer', diff --git a/src/Private/CancellationToken.ps1 b/src/Private/CancellationToken.ps1 index df42cca65..080b7dd70 100644 --- a/src/Private/CancellationToken.ps1 +++ b/src/Private/CancellationToken.ps1 @@ -32,13 +32,13 @@ function Reset-PodeCancellationToken { [string[]] $Type ) - $type.ForEach({ - # Ensure cleanup of disposable tokens - Close-PodeDisposable -Disposable $PodeContext.Tokens[$_] + foreach ($item in $type) { + # Ensure cleanup of disposable tokens + Close-PodeDisposable -Disposable $PodeContext.Tokens[$item] - # Reinitialize the Token - $PodeContext.Tokens[$_] = [System.Threading.CancellationTokenSource]::new() - }) + # Reinitialize the Token + $PodeContext.Tokens[$item] = [System.Threading.CancellationTokenSource]::new() + } } <# @@ -146,7 +146,7 @@ function Test-PodeSuspensionToken { Creates a set of cancellation tokens for managing Pode application states. .DESCRIPTION - The `New-PodeSuspensionToken` function initializes and returns a hashtable containing + The `Initialize-PodeCancellationToken` function initializes and returns a hashtable containing multiple cancellation tokens used for managing various states in a Pode application. These tokens provide coordinated control over application operations, such as cancellation, restart, suspension, resumption, termination, and start operations. @@ -161,7 +161,7 @@ function Test-PodeSuspensionToken { - `Disable`: A token for denying web access. .EXAMPLE - $tokens = New-PodeSuspensionToken + $tokens = Initialize-PodeCancellationToken Initializes a set of cancellation tokens and stores them in the `$tokens` variable. .OUTPUTS @@ -171,7 +171,7 @@ function Test-PodeSuspensionToken { .NOTES This is an internal function and may change in future releases of Pode. #> -function New-PodeSuspensionToken { +function Initialize-PodeCancellationToken { # Initialize and return a hashtable containing various cancellation tokens. return @{ # A cancellation token specifically for managing endpoint cancellation tasks. @@ -219,7 +219,7 @@ function New-PodeSuspensionToken { function Set-PodeResumeToken { # Ensure the Resume token is in a cancellation requested state - Set-PodeCancellationTokenRequest -Type Resume + Close-PodeCancellationTokenRequest -Type Resume # If the Cancellation token is in a requested state, reset it (unexpected scenario) if ($PodeContext.Tokens.Cancellation.IsCancellationRequested) { @@ -252,7 +252,7 @@ function Set-PodeResumeToken { #> function Set-PodeSuspendToken { # Ensure the Suspend and Cancellation tokens is in a cancellation requested state - Set-PodeCancellationTokenRequest -Type Suspend, Cancellation + Close-PodeCancellationTokenRequest -Type Suspend, Cancellation } @@ -261,7 +261,7 @@ function Set-PodeSuspendToken { Sets the cancellation token(s) for the specified Pode server actions. .DESCRIPTION - The `Set-PodeCancellationTokenRequest` function cancels one or more specified tokens within the Pode server. + The `Close-PodeCancellationTokenRequest` function cancels one or more specified tokens within the Pode server. These tokens are used to manage the server's lifecycle actions, such as Restart, Suspend, Resume, or Terminate. The function takes a mandatory parameter `$Type`, which determines the token(s) to be canceled. Supported types include: `Cancellation`, `Restart`, `Suspend`, `Resume`, `Terminate`, `Start`, and `Disable`. @@ -271,19 +271,19 @@ function Set-PodeSuspendToken { Allowed values: `Cancellation`, `Restart`, `Suspend`, `Resume`, `Terminate`, `Start`, `Disable`. .EXAMPLE - Set-PodeCancellationTokenRequest -Type 'Restart' + Close-PodeCancellationTokenRequest -Type 'Restart' Cancels the Restart token for the Pode server. .EXAMPLE - Set-PodeCancellationTokenRequest -Type 'Suspend','Terminate' + Close-PodeCancellationTokenRequest -Type 'Suspend','Terminate' Cancels both the Suspend and Terminate tokens for the Pode server. .NOTES This function is an internal utility and may change in future releases of Pode. #> -function Set-PodeCancellationTokenRequest { +function Close-PodeCancellationTokenRequest { param( [Parameter(Mandatory = $true)] [ValidateSet('Cancellation', 'Restart', 'Suspend', 'Resume', 'Terminate', 'Start', 'Disable')] @@ -292,14 +292,14 @@ function Set-PodeCancellationTokenRequest { ) # Iterate over each provided type and cancel its corresponding token if not already canceled - $Type.ForEach({ - if ($PodeContext.Tokens.ContainsKey($_)) { - if (!$PodeContext.Tokens[$_].IsCancellationRequested) { - # Cancel the specified token - $PodeContext.Tokens[$_].Cancel() - } + foreach ($item in $Type) { + if ($PodeContext.Tokens.ContainsKey($item)) { + if (! $PodeContext.Tokens[$item].IsCancellationRequested) { + # Cancel the specified token + $PodeContext.Tokens[$item].Cancel() } - }) + } + } } <# @@ -337,102 +337,71 @@ function Wait-PodeCancellationTokenRequest { $Type ) - # Wait for the token to be reset + # Wait for the token to be reset, with exponential back-off + $count = 1 while ($PodeContext.Tokens[$Type].IsCancellationRequested) { - Start-Sleep -Seconds 1 + Start-Sleep -Milliseconds (100 * $count) + $count = [System.Math]::Min($count + 1, 20) } } - - <# .SYNOPSIS - Tests whether specified Pode server tokens have active cancellation requests using logical operations. + Evaluates whether a specified Pode server token has an active cancellation request. .DESCRIPTION - The `Test-PodeCancellationTokenRequest` function checks the cancellation state of one or more tokens - within the Pode server context based on the specified logical operation (`AND`, `OR`, `XOR`, `NAND`, `NOR`, `XNOR`). - - `AND` (default): Returns `$true` if all tokens have active cancellation requests. - - `OR`: Returns `$true` if at least one token has an active cancellation request. - - `XOR`: Returns `$true` if exactly one token has an active cancellation request. - - `NAND`: Returns `$true` if not all tokens have active cancellation requests. - - `NOR`: Returns `$true` if none of the tokens have active cancellation requests. - - `XNOR`: Returns `$true` if an even number of tokens have active cancellation requests. + The `Test-PodeCancellationTokenRequest` function checks the cancellation state of a given token + in the Pode server context. It determines whether the token has been marked for cancellation + and optionally waits for the cancellation to occur if the `-Wait` parameter is specified. .PARAMETER Type - Specifies the token(s) to check. This parameter accepts one or more values from a predefined set. - Allowed values: `Cancellation`, `Restart`, `Suspend`, `Resume`, `Terminate`, `Start`, `Disable`. + Specifies the token to check for an active cancellation request. + Acceptable values include predefined token types in Pode: + - `Cancellation` + - `Restart` + - `Suspend` + - `Resume` + - `Terminate` + - `Start` + - `Disable` -.PARAMETER Operation - Specifies the logical operation to apply when evaluating the token states. - Allowed values: `AND`, `OR`, `XOR`, `NAND`, `NOR`, `XNOR`. - Default is `AND`. +.PARAMETER Wait + If specified, waits until the token's cancellation request becomes active before returning the result. .OUTPUTS - [bool] The result of the logical operation on the token cancellation states. + [bool] Returns `$true` if the specified token has an active cancellation request, otherwise `$false`. .EXAMPLE Test-PodeCancellationTokenRequest -Type 'Restart' - Returns `$true` if the Restart token has an active cancellation request, otherwise `$false`. + Checks if the Restart token has an active cancellation request and returns `$true` or `$false`. .EXAMPLE - Test-PodeCancellationTokenRequest -Type 'Suspend', 'Terminate' -Operation 'NAND' + Test-PodeCancellationTokenRequest -Type 'Suspend' -Wait - Returns `$true` if not all of Suspend and Terminate tokens have active cancellation requests. - -.EXAMPLE - Test-PodeCancellationTokenRequest -Type 'Suspend', 'Terminate' - - Defaults to `AND` operation. Returns `$true` if both Suspend and Terminate tokens have active cancellation requests. + Waits until the Suspend token has an active cancellation request before returning `$true` or `$false`. .NOTES - This function is part of Pode's internal utilities and may change in future releases. + This function is an internal utility for Pode and may be subject to change in future releases. #> function Test-PodeCancellationTokenRequest { param( [Parameter(Mandatory = $true)] [ValidateSet('Cancellation', 'Restart', 'Suspend', 'Resume', 'Terminate', 'Start', 'Disable')] - [string[]] + [string] $Type, - [Parameter()] - [ValidateSet('AND', 'OR', 'XOR', 'NAND', 'NOR', 'XNOR')] - [string] - $Operation = 'AND' # Default operation is AND + [switch] + $Wait ) - # Collect the state of each token - $states = $Type | ForEach-Object { - $PodeContext.Tokens[$_].IsCancellationRequested - } + # Check if the specified token has an active cancellation request + $cancelled = $PodeContext.Tokens[$Type].IsCancellationRequested - # Evaluate based on the specified operation - switch ($Operation) { - 'AND' { - # Return true if all tokens have cancellation requests - return ($states -notcontains $false) - } - 'OR' { - # Return true if at least one token has a cancellation request - return ($states -contains $true) - } - 'XOR' { - # Return true if exactly one token has a cancellation request - return ($states | Where-Object { $_ -eq $true }).Count -eq 1 - } - 'NAND' { - # Return true if not all tokens have cancellation requests - return ($states -contains $false) - } - 'NOR' { - # Return true if none of the tokens have cancellation requests - return ($states -notcontains $true) - } - 'XNOR' { - # Return true if an even number of tokens have cancellation requests - return (($states | Where-Object { $_ -eq $true }).Count % 2) -eq 0 - } + # If -Wait is specified, block until the token's cancellation request becomes active + if ($Wait) { + Wait-PodeCancellationTokenRequest -Type $Type } -} + return $cancelled +} \ No newline at end of file diff --git a/src/Private/Console.ps1 b/src/Private/Console.ps1 index ce412c3d3..2e38630c7 100644 --- a/src/Private/Console.ps1 +++ b/src/Private/Console.ps1 @@ -27,25 +27,18 @@ function Show-PodeConsoleInfo { $ShowTopSeparator ) - + # Exit the function if PodeContext is not initialized + # or if the console is in quiet mode and the Force switch is not used + if (!$PodeContext -or ($PodeContext.Server.Console.Quiet -and !$Force)) { + return + } # Get the current server state and timestamp $serverState = Get-PodeServerState - $timestamp = if ($PodeContext.Server.Console.ShowTimeStamp ) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')]" } else { '' } - - if (!$PodeContext) { return } + $timestamp = if ($PodeContext.Server.Console.ShowTimeStamp ) { "[$([datetime]::Now.ToString('yyyy-MM-dd HH:mm:ss'))]" } else { '' } # Define color variables with fallback - $headerColor = if ($null -ne $PodeContext.Server.Console.Colors.Header) { - $PodeContext.Server.Console.Colors.Header - } - else { - [System.ConsoleColor]::White - } - - if ($PodeContext.Server.Console.Quiet -and !$Force) { - return - } + $headerColor = $PodeContext.Server.Console.Colors.Header switch ($serverState) { 'Suspended' { @@ -218,34 +211,11 @@ function Show-PodeConsoleHelp { # Retrieve centralized key mapping for keyboard shortcuts $KeyBindings = $PodeContext.Server.Console.KeyBindings - # Define help section color variables with fallback defaults - $helpHeaderColor = if ($null -ne $PodeContext.Server.Console.Colors.HelpHeader) { - $PodeContext.Server.Console.Colors.HelpHeader - } - else { - [System.ConsoleColor]::Yellow - } - - $helpKeyColor = if ($null -ne $PodeContext.Server.Console.Colors.HelpKey) { - $PodeContext.Server.Console.Colors.HelpKey - } - else { - [System.ConsoleColor]::Green - } - - $helpDescriptionColor = if ($null -ne $PodeContext.Server.Console.Colors.HelpDescription) { - $PodeContext.Server.Console.Colors.HelpDescription - } - else { - [System.ConsoleColor]::White - } - - $helpDividerColor = if ($null -ne $PodeContext.Server.Console.Colors.HelpDivider) { - $PodeContext.Server.Console.Colors.HelpDivider - } - else { - [System.ConsoleColor]::Gray - } + # Define help section color variables + $helpHeaderColor = $PodeContext.Server.Console.Colors.HelpHeader + $helpKeyColor = $PodeContext.Server.Console.Colors.HelpKey + $helpDescriptionColor = $PodeContext.Server.Console.Colors.HelpDescription + $helpDividerColor = $PodeContext.Server.Console.Colors.HelpDivider # Display the "Show Help" option if the $Hide parameter is specified if ($Hide) { @@ -579,29 +549,10 @@ function Show-PodeConsoleEndpointsInfo { #> function Show-PodeConsoleMetric { - # Determine the color for the metrics header - if ($null -ne $PodeContext.Server.Console.Colors.EndpointsHeader) { - $headerColor = $PodeContext.Server.Console.Colors.MetricsHeader - } - else { - $headerColor = [System.ConsoleColor]::White - } - - # Determine the color for the metrics labels - if ($null -ne $PodeContext.Server.Console.Colors.Endpoints) { - $labelColor = $PodeContext.Server.Console.Colors.MetricsLabel - } - else { - $labelColor = [System.ConsoleColor]::Yellow - } - - # Determine the color for the metrics values - if ($null -ne $PodeContext.Server.Console.Colors.Endpoints) { - $valueColor = $PodeContext.Server.Console.Colors.MetricsValue - } - else { - $valueColor = [System.ConsoleColor]::Green - } + # Determine the color for the labels + $headerColor = $PodeContext.Server.Console.Colors.MetricsHeader + $labelColor = $PodeContext.Server.Console.Colors.MetricsLabel + $valueColor = $PodeContext.Server.Console.Colors.MetricsValue # Write a horizontal divider line to separate the header Write-PodeHostDivider -Force $true @@ -614,20 +565,26 @@ function Show-PodeConsoleMetric { # Display the total uptime Write-PodeHost $Podelocale.totalUptimeMessage -ForegroundColor $labelColor -NoNewLine - Write-PodeHost (Get-PodeServerUptime -Total -Readable -OutputType Verbose -ExcludeMilliseconds) -ForegroundColor $valueColor + Write-PodeHost (Get-PodeServerUptime -Format Verbose -Total -ExcludeMilliseconds) -ForegroundColor $valueColor - # If uptime exceeds 1000 seconds, display uptime since last restart - if ((Get-PodeServerUptime) -gt 1000) { + # If the server restarted, display uptime since last restart + if ((Get-PodeServerRestartCount) -gt 0) { Write-PodeHost $Podelocale.uptimeSinceLastRestartMessage -ForegroundColor $labelColor -NoNewLine - Write-PodeHost (Get-PodeServerUptime -Readable -OutputType Verbose -ExcludeMilliseconds) -ForegroundColor $valueColor + Write-PodeHost (Get-PodeServerUptime -Format Verbose -ExcludeMilliseconds) -ForegroundColor $valueColor } # Display the total number of server restarts Write-PodeHost $Podelocale.totalRestartMessage -ForegroundColor $labelColor -NoNewLine Write-PodeHost (Get-PodeServerRestartCount) -ForegroundColor $valueColor - Write-PodeHost 'Active Requests' -ForegroundColor $labelColor -NoNewLine - Write-PodeHost (Get-PodeServerActiveRequestMetric) -ForegroundColor $valueColor + Write-PodeHost 'Requests' -ForegroundColor $labelColor + Write-PodeHost ' Total :' -ForegroundColor $labelColor -NoNewLine + Write-PodeHost (Get-PodeServerActiveRequestMetric -CountType Total) -ForegroundColor $valueColor + Write-PodeHost ' Queued :' -ForegroundColor $labelColor -NoNewLine + Write-PodeHost (Get-PodeServerActiveRequestMetric -CountType Queued) -ForegroundColor $valueColor + Write-PodeHost ' Processing :' -ForegroundColor $labelColor -NoNewLine + Write-PodeHost (Get-PodeServerActiveRequestMetric -CountType Processing) -ForegroundColor $valueColor + } @@ -662,35 +619,11 @@ function Show-PodeConsoleOAInfo { # Default header initialization $openAPIHeader = $false - # Fallback colors - - $headerColor = if ($null -ne $PodeContext.Server.Console.Colors.OpenApiHeaders) { - $PodeContext.Server.Console.Colors.OpenApiHeaders - } - else { - [System.ConsoleColor]::Yellow - } - - $titleColor = if ($null -ne $PodeContext.Server.Console.Colors.OpenApiTitles) { - $PodeContext.Server.Console.Colors.OpenApiTitles - } - else { - [System.ConsoleColor]::White - } - - $subtitleColor = if ($null -ne $PodeContext.Server.Console.Colors.OpenApiSubtitles) { - $PodeContext.Server.Console.Colors.OpenApiSubtitles - } - else { - [System.ConsoleColor]::Yellow - } - - $urlColor = if ($null -ne $PodeContext.Server.Console.Colors.OpenApiUrls) { - $PodeContext.Server.Console.Colors.OpenApiUrls - } - else { - [System.ConsoleColor]::Cyan - } + # Determine the color for the labels + $headerColor = $PodeContext.Server.Console.Colors.OpenApiHeaders + $titleColor = $PodeContext.Server.Console.Colors.OpenApiTitles + $subtitleColor = $PodeContext.Server.Console.Colors.OpenApiSubtitles + $urlColor = $PodeContext.Server.Console.Colors.OpenApiUrls # Iterate through OpenAPI definitions foreach ($key in $PodeContext.Server.OpenAPI.Definitions.Keys) { @@ -771,13 +704,9 @@ function Show-PodeConsoleOAInfo { #> function Clear-PodeKeyPressed { - if (!$PodeContext.Server.Console.DisableConsoleInput) { - - # Clear any remaining keys in the input buffer - while ([Console]::KeyAvailable) { - - [Console]::ReadKey($true) | Out-Null - } + # Clear any remaining keys in the input buffer + while ([Console]::KeyAvailable) { + $null = [Console]::ReadKey($true) } } @@ -838,11 +767,16 @@ function Test-PodeKeyPressed { This function is useful for scenarios requiring real-time console key handling. #> function Get-PodeConsoleKey { - if ([Console]::IsInputRedirected -or ![Console]::KeyAvailable) { - return $null - } + try { + if ([Console]::IsInputRedirected -or ![Console]::KeyAvailable) { + return $null + } - return [Console]::ReadKey($true) + return [Console]::ReadKey($true) + } + finally { + Clear-PodeKeyPressed + } } <# @@ -883,7 +817,6 @@ function Invoke-PodeConsoleAction { # Browser action if (Test-PodeKeyPressed -Key $Key -Character $KeyBindings.Browser) { - Clear-PodeKeyPressed $url = Get-PodeEndpointUrl if (![string]::IsNullOrWhitespace($url)) { Invoke-PodeEvent -Type Browser @@ -892,49 +825,41 @@ function Invoke-PodeConsoleAction { } # Toggle help display elseif (Test-PodeKeyPressed -Key $Key -Character $KeyBindings.Help) { - Clear-PodeKeyPressed $PodeContext.Server.Console.ShowHelp = !$PodeContext.Server.Console.ShowHelp Show-PodeConsoleInfo -ShowTopSeparator } # Toggle OpenAPI display elseif (Test-PodeKeyPressed -Key $Key -Character $KeyBindings.OpenAPI) { - Clear-PodeKeyPressed $PodeContext.Server.Console.ShowOpenAPI = !$PodeContext.Server.Console.ShowOpenAPI Show-PodeConsoleInfo -ShowTopSeparator } # Toggle endpoints display elseif (Test-PodeKeyPressed -Key $Key -Character $KeyBindings.Endpoints) { - Clear-PodeKeyPressed $PodeContext.Server.Console.ShowEndpoints = !$PodeContext.Server.Console.ShowEndpoints Show-PodeConsoleInfo -ShowTopSeparator } # Clear console elseif (Test-PodeKeyPressed -Key $Key -Character $KeyBindings.Clear) { - Clear-PodeKeyPressed Show-PodeConsoleInfo -ClearHost } # Toggle quiet mode elseif (Test-PodeKeyPressed -Key $Key -Character $KeyBindings.Quiet) { - Clear-PodeKeyPressed $PodeContext.Server.Console.Quiet = !$PodeContext.Server.Console.Quiet Show-PodeConsoleInfo -ClearHost -Force } # Terminate server elseif ((! $PodeContext.Server.Console.DisableTermination) -and (Test-PodeKeyPressed -Key $Key -Character $KeyBindings.Terminate)) { - Clear-PodeKeyPressed - Set-PodeCancellationTokenRequest -Type Terminate + Close-PodeCancellationTokenRequest -Type Terminate return } elseif ( (('Running', 'Suspended') -contains $serverState ) -and (Test-PodeKeyPressed -Key $Key -Character $KeyBindings.Metrics)) { - Clear-PodeKeyPressed Show-PodeConsoleMetric } # Handle restart actions if ($PodeContext.Server.AllowedActions.Restart) { if (Test-PodeKeyPressed -Key $Key -Character $KeyBindings.Restart) { - Clear-PodeKeyPressed - Set-PodeCancellationTokenRequest -Type Restart + Close-PodeCancellationTokenRequest -Type Restart Restart-PodeInternalServer } elseif (Test-PodeCancellationTokenRequest -Type Restart) { @@ -945,9 +870,8 @@ function Invoke-PodeConsoleAction { # Handle enable/disable server actions if ($PodeContext.Server.AllowedActions.Disable -and ($serverState -eq 'Running')) { if ((! $PodeContext.Server.Console.DisableTermination) -and (Test-PodeKeyPressed -Key $Key -Character $KeyBindings.Disable)) { - Clear-PodeKeyPressed if (Test-PodeServerIsEnabled) { - Set-PodeCancellationTokenRequest -Type Disable + Close-PodeCancellationTokenRequest -Type Disable Disable-PodeServerInternal } else { @@ -971,7 +895,6 @@ function Invoke-PodeConsoleAction { # Handle suspend/resume actions if ($PodeContext.Server.AllowedActions.Suspend) { if ((! $PodeContext.Server.Console.DisableTermination) -and (Test-PodeKeyPressed -Key $Key -Character $KeyBindings.Suspend)) { - Clear-PodeKeyPressed if ($serverState -eq 'Suspended') { Set-PodeResumeToken Resume-PodeServerInternal -Timeout $PodeContext.Server.AllowedActions.Timeout.Resume diff --git a/src/Private/Context.ps1 b/src/Private/Context.ps1 index e83406eed..3097e2abc 100644 --- a/src/Private/Context.ps1 +++ b/src/Private/Context.ps1 @@ -50,7 +50,7 @@ function New-PodeContext { $EnableBreakpoints, [switch] - $IgnoreServerPsConfig + $IgnoreServerConfig ) # set a random server name if one not supplied @@ -195,7 +195,22 @@ function New-PodeContext { } } - if (!$IgnoreServerPsConfig) { + $ctx.Server.AllowedActions = @{ + Suspend = $true + Restart = $true + Disable = $true + DisableSettings = @{ + RetryAfter = 3600 + MiddlewareName = '__Pode_Midleware_Code_503__' + } + Timeout = @{ + Suspend = 30 + Resume = 30 + } + } + + + if (!$IgnoreServerConfig) { # check if there is any global configuration $ctx.Server.Configuration = Open-PodeConfiguration -ServerRoot $ServerRoot -Context $ctx } @@ -383,20 +398,6 @@ function New-PodeContext { #OpenApi Definition Tag $ctx.Server.OpenAPI = Initialize-PodeOpenApiTable -DefaultDefinitionTag $ctx.Server.Web.OpenApi.DefaultDefinitionTag - $ctx.Server.AllowedActions = @{ - Suspend = $true - Restart = $true - Disable = $true - DisableSettings = @{ - RetryAfter = 3600 - MiddlewareName = '__Pode_Midleware_Code_503' - } - Timeout = @{ - Suspend = 30 - Resume = 30 - } - } - # server metrics $ctx.Metrics = @{ @@ -424,7 +425,7 @@ function New-PodeContext { } # create new cancellation tokens - $ctx.Tokens = New-PodeSuspensionToken + $ctx.Tokens = Initialize-PodeCancellationToken # requests that should be logged $ctx.LogsToProcess = [System.Collections.ArrayList]::new() @@ -957,20 +958,21 @@ function Set-PodeServerConfiguration { ShowTimeStamp = [bool](Protect-PodeValue -Value $Configuration.Console.ShowTimeStamp -Default $Context.Server.Console.ShowTimeStamp) DividerLength = [int](Protect-PodeValue -Value $Configuration.Console.DividerLength -Default $Context.Server.Console.DividerLength) Colors = @{ - Header = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.Header -Default $Context.Server.Console.Colors.Header), $true) - EndpointsHeader = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.EndpointsHeader -Default $Context.Server.Console.Colors.EndpointsHeader), $true) - Endpoints = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.Endpoints -Default $Context.Server.Console.Colors.Endpoints), $true) - OpenApiUrls = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.OpenApiUrls -Default $Context.Server.Console.Colors.OpenApiUrls), $true) - OpenApiHeaders = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.OpenApiHeaders -Default $Context.Server.Console.Colors.OpenApiHeaders), $true) - OpenApiTitles = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.OpenApiTitles -Default $Context.Server.Console.Colors.OpenApiTitles), $true) - HelpHeader = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.HelpHeader -Default $Context.Server.Console.Colors.HelpHeader), $true) - HelpKey = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.HelpKey -Default $Context.Server.Console.Colors.HelpKey), $true) - HelpDescription = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.HelpDescription -Default $Context.Server.Console.Colors.HelpDescription), $true) - HelpDivider = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.HelpDivider -Default $Context.Server.Console.Colors.HelpDivider), $true) - Divider = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.Divider -Default $Context.Server.Console.Colors.Divider), $true) - MetricsHeader = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.MetricsHeader -Default $Context.Server.Console.Colors.MetricsHeader), $true) - MetricsLabel = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.MetricsLabel -Default $Context.Server.Console.Colors.MetricsLabel), $true) - MetricsValue = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.MetricsValue -Default $Context.Server.Console.Colors.MetricsValue), $true) + Header = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.Header -Default $Context.Server.Console.Colors.Header), $true) + EndpointsHeader = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.EndpointsHeader -Default $Context.Server.Console.Colors.EndpointsHeader), $true) + Endpoints = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.Endpoints -Default $Context.Server.Console.Colors.Endpoints), $true) + OpenApiUrls = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.OpenApiUrls -Default $Context.Server.Console.Colors.OpenApiUrls), $true) + OpenApiHeaders = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.OpenApiHeaders -Default $Context.Server.Console.Colors.OpenApiHeaders), $true) + OpenApiTitles = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.OpenApiTitles -Default $Context.Server.Console.Colors.OpenApiTitles), $true) + OpenApiSubtitles = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.OpenApiSubtitles -Default $Context.Server.Console.Colors.OpenApiSubtitles), $true) + HelpHeader = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.HelpHeader -Default $Context.Server.Console.Colors.HelpHeader), $true) + HelpKey = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.HelpKey -Default $Context.Server.Console.Colors.HelpKey), $true) + HelpDescription = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.HelpDescription -Default $Context.Server.Console.Colors.HelpDescription), $true) + HelpDivider = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.HelpDivider -Default $Context.Server.Console.Colors.HelpDivider), $true) + Divider = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.Divider -Default $Context.Server.Console.Colors.Divider), $true) + MetricsHeader = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.MetricsHeader -Default $Context.Server.Console.Colors.MetricsHeader), $true) + MetricsLabel = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.MetricsLabel -Default $Context.Server.Console.Colors.MetricsLabel), $true) + MetricsValue = [System.ConsoleColor]::parse([System.ConsoleColor], (Protect-PodeValue -Value $Configuration.Console.Colors.MetricsValue -Default $Context.Server.Console.Colors.MetricsValue), $true) } KeyBindings = @{ Browser = [System.Enum]::Parse([System.ConsoleKey], (Protect-PodeValue -Value $Configuration.Console.KeyBindings.Browser -Default $Context.Server.Console.KeyBindings.Browser), $true) @@ -1077,7 +1079,7 @@ function New-PodeAutoRestartServer { $period = [int]$restart.period if ($period -gt 0) { Add-PodeTimer -Name '__pode_restart_period__' -Interval ($period * 60) -ScriptBlock { - Set-PodeCancellationTokenRequest -Type Restart + Close-PodeCancellationTokenRequest -Type Restart } } @@ -1092,7 +1094,7 @@ function New-PodeAutoRestartServer { } Add-PodeSchedule -Name '__pode_restart_times__' -Cron @($crons) -ScriptBlock { - Set-PodeCancellationTokenRequest -Type Restart + Close-PodeCancellationTokenRequest -Type Restart } } @@ -1100,7 +1102,7 @@ function New-PodeAutoRestartServer { $crons = @(@($restart.crons) -ne $null) if (($crons | Measure-Object).Count -gt 0) { Add-PodeSchedule -Name '__pode_restart_crons__' -Cron @($crons) -ScriptBlock { - Set-PodeCancellationTokenRequest -Type Restart + Close-PodeCancellationTokenRequest -Type Restart } } } diff --git a/src/Private/Gui.ps1 b/src/Private/Gui.ps1 index 2496b8332..d8135389c 100644 --- a/src/Private/Gui.ps1 +++ b/src/Private/Gui.ps1 @@ -135,7 +135,7 @@ function Start-PodeGuiRunspace { } finally { # invoke the cancellation token to close the server - Set-PodeCancellationTokenRequest -Type Cancellation + Close-PodeCancellationTokenRequest -Type Cancellation } } diff --git a/src/Private/Helpers.ps1 b/src/Private/Helpers.ps1 index e49cc8890..11cdd6f75 100644 --- a/src/Private/Helpers.ps1 +++ b/src/Private/Helpers.ps1 @@ -555,7 +555,7 @@ function Close-PodeServerInternal { try { # ensure the token is cancelled Write-Verbose 'Cancelling main cancellation token' - Set-PodeCancellationTokenRequest -Type Cancellation, Terminate + Close-PodeCancellationTokenRequest -Type Cancellation, Terminate # stop all current runspaces Write-Verbose 'Closing runspaces' @@ -2423,7 +2423,7 @@ function Find-PodeFileForContentType { .PARAMETER TestPath Verifies if the resolved path exists. Throws an exception if the path does not exist. -.PARAMETER NormalizeRelativePath +.PARAMETER NormaliseRelativePath (Optional) Removes any leading './' or '../' segments from the relative path when used with -JoinRoot. This ensures that the path is normalized before being joined with the root path. .OUTPUTS @@ -2436,7 +2436,7 @@ function Find-PodeFileForContentType { .EXAMPLE # Example 2: Resolve and normalize a relative path - Get-PodeRelativePath -Path '../example' -RootPath 'C:\Root' -JoinRoot -NormalizeRelativePath + Get-PodeRelativePath -Path '../example' -RootPath 'C:\Root' -JoinRoot -NormaliseRelativePath .EXAMPLE # Example 3: Test if a path exists @@ -2465,7 +2465,7 @@ function Get-PodeRelativePath { $TestPath, [switch] - $NormalizeRelativePath + $NormaliseRelativePath ) @@ -2475,7 +2475,7 @@ function Get-PodeRelativePath { $RootPath = $PodeContext.Server.Root } - if ($NormalizeRelativePath) { + if ($NormaliseRelativePath) { $Path = [System.IO.Path]::Combine($RootPath, ($Path -replace '^\.{1,2}([\\\/])?', '')) } else { @@ -3761,28 +3761,29 @@ function Copy-PodeObjectDeepClone { } } - <# .SYNOPSIS Converts a duration in milliseconds into a human-readable time format. .DESCRIPTION - This function takes an input duration in milliseconds and converts it into - a readable time format. It supports multiple output styles, such as verbose - (detailed text), compact (`dd:hh:mm:ss`), and concise (short notation). - Optionally, milliseconds can be excluded from the output. + The `Convert-PodeMillisecondsToReadable` function converts a specified duration in milliseconds into + a readable time format. The output can be formatted in three styles: + - `Concise`: A short and simple format (e.g., "1d 2h 3m"). + - `Compact`: A compact representation (e.g., "01:02:03:04"). + - `Verbose`: A detailed, descriptive format (e.g., "1 day, 2 hours, 3 minutes"). + The function also provides an option to exclude milliseconds from the output for all formats. .PARAMETER Milliseconds - The duration in milliseconds to be converted. - -.PARAMETER VerboseOutput - If specified, outputs a detailed, descriptive format (e.g., "1 day, 2 hours, 3 minutes"). + Specifies the duration in milliseconds to be converted into a human-readable format. -.PARAMETER CompactOutput - If specified, outputs a compact format (e.g., "dd:hh:mm:ss"). +.PARAMETER Format + Specifies the desired format for the output. Valid options are: + - `Concise` (default): Short and simple (e.g., "1d 2h 3m"). + - `Compact`: Condensed form (e.g., "01:02:03:04"). + - `Verbose`: Detailed description (e.g., "1 day, 2 hours, 3 minutes, 4 seconds"). .PARAMETER ExcludeMilliseconds - If specified, excludes milliseconds from the output. + If specified, milliseconds will be excluded from the output for all formats. .EXAMPLE Convert-PodeMillisecondsToReadable -Milliseconds 123456789 @@ -3791,13 +3792,13 @@ function Copy-PodeObjectDeepClone { 1d 10h 17m 36s .EXAMPLE - Convert-PodeMillisecondsToReadable -Milliseconds 123456789 -VerboseOutput + Convert-PodeMillisecondsToReadable -Milliseconds 123456789 -Format Verbose Output: 1 day, 10 hours, 17 minutes, 36 seconds, 789 milliseconds .EXAMPLE - Convert-PodeMillisecondsToReadable -Milliseconds 123456789 -CompactOutput -ExcludeMilliseconds + Convert-PodeMillisecondsToReadable -Milliseconds 123456789 -Format Compact -ExcludeMilliseconds Output: 01:10:17:36 @@ -3805,67 +3806,71 @@ function Copy-PodeObjectDeepClone { .NOTES This is an internal function and may change in future releases of Pode. #> - function Convert-PodeMillisecondsToReadable { - param ( - [Parameter(Mandatory)] - [long]$Milliseconds, + param( + # The duration in milliseconds to convert + [Parameter(Mandatory = $true)] + [long] + $Milliseconds, - [switch]$VerboseOutput, # Provide detailed descriptions - [switch]$CompactOutput, # Provide compact format like dd:hh:mm:ss or mm:ss:ms - [switch]$ExcludeMilliseconds # Exclude milliseconds from the output + # Specifies the desired output format + [Parameter()] + [ValidateSet('Concise', 'Compact', 'Verbose')] + [string] + $Format = 'Concise', + + # Omits milliseconds from the output + [switch] + $ExcludeMilliseconds ) + # Convert the milliseconds input into a TimeSpan object $timeSpan = [timespan]::FromMilliseconds($Milliseconds) - if ($CompactOutput) { - # Dynamically build compact format - $components = @() - - # Include days only if greater than 0 - if ($timeSpan.Days -gt 0) { $components += '{0:D2}' -f $timeSpan.Days } + # Generate the formatted output based on the selected format + switch ($Format.ToLower()) { + 'concise' { + # Concise format: "1d 2h 3m 4s" + $output = @() + if ($timeSpan.Days -gt 0) { $output += "$($timeSpan.Days)d" } + if ($timeSpan.Hours -gt 0) { $output += "$($timeSpan.Hours)h" } + if ($timeSpan.Minutes -gt 0) { $output += "$($timeSpan.Minutes)m" } + if ($timeSpan.Seconds -gt 0) { $output += "$($timeSpan.Seconds)s" } + + # Include milliseconds if they exist and are not excluded + if ((($timeSpan.Milliseconds -gt 0) -and !$ExcludeMilliseconds) -or ($output.Count -eq 0)) { + $output += "$($timeSpan.Milliseconds)ms" + } - # Include hours only if greater than 0 or days are included - if ($timeSpan.Hours -gt 0 -or $components.Count -gt 0) { $components += '{0:D2}' -f $timeSpan.Hours } + return $output -join ' ' + } - # Include minutes if relevant - if ($timeSpan.Minutes -gt 0 -or $components.Count -gt 0) { $components += '{0:D2}' -f $timeSpan.Minutes } + 'compact' { + # Compact format: "dd:hh:mm:ss" + $output = "{0:D2}:{1:D2}:{2:D2}:{3:D2}" -f $timeSpan.Days, $timeSpan.Hours, $timeSpan.Minutes, $timeSpan.Seconds - # Add seconds as the final required time component - $components += '{0:D2}' -f $timeSpan.Seconds + # Append milliseconds if not excluded + if (!$ExcludeMilliseconds) { + $output += ".{0:D3}" -f $timeSpan.Milliseconds + } - # Append milliseconds if not excluded - if (-not $ExcludeMilliseconds) { - $components[-1] += ':{0:D3}' -f $timeSpan.Milliseconds + return $output } - # Join with ":" and return - return $components -join ':' - } - - # Default or verbose format - if ($VerboseOutput) { - $verboseParts = @() - if ($timeSpan.Days -gt 0) { $verboseParts += "$($timeSpan.Days) day$(if ($timeSpan.Days -ne 1) { 's' })" } - if ($timeSpan.Hours -gt 0) { $verboseParts += "$($timeSpan.Hours) hour$(if ($timeSpan.Hours -ne 1) { 's' })" } - if ($timeSpan.Minutes -gt 0) { $verboseParts += "$($timeSpan.Minutes) minute$(if ($timeSpan.Minutes -ne 1) { 's' })" } - if ($timeSpan.Seconds -gt 0) { $verboseParts += "$($timeSpan.Seconds) second$(if ($timeSpan.Seconds -ne 1) { 's' })" } - if (-not $ExcludeMilliseconds -and $timeSpan.Milliseconds -gt 0) { - $verboseParts += "$($timeSpan.Milliseconds) millisecond$(if ($timeSpan.Milliseconds -ne 1) { 's' })" - } + 'verbose' { + # Verbose format: "1 day, 2 hours, 3 minutes, 4 seconds" + $output = @() + if ($timeSpan.Days -gt 0) { $output += "$($timeSpan.Days) day$(if ($timeSpan.Days -ne 1) { 's' })" } + if ($timeSpan.Hours -gt 0) { $output += "$($timeSpan.Hours) hour$(if ($timeSpan.Hours -ne 1) { 's' })" } + if ($timeSpan.Minutes -gt 0) { $output += "$($timeSpan.Minutes) minute$(if ($timeSpan.Minutes -ne 1) { 's' })" } + if ($timeSpan.Seconds -gt 0) { $output += "$($timeSpan.Seconds) second$(if ($timeSpan.Seconds -ne 1) { 's' })" } - return $verboseParts -join ' ' - } + # Include milliseconds if they exist and are not excluded + if ((($timeSpan.Milliseconds -gt 0) -and !$ExcludeMilliseconds) -or ($output.Count -eq 0)) { + $output += "$($timeSpan.Milliseconds) millisecond$(if ($timeSpan.Milliseconds -ne 1) { 's' })" + } - # Default concise format - $parts = @() - if ($timeSpan.Days -gt 0) { $parts += "$($timeSpan.Days)d" } - if ($timeSpan.Hours -gt 0 -or $parts.Count -gt 0) { $parts += "$($timeSpan.Hours)h" } - if ($timeSpan.Minutes -gt 0 -or $parts.Count -gt 0) { $parts += "$($timeSpan.Minutes)m" } - if ($timeSpan.Seconds -gt 0 -or $parts.Count -gt 0) { $parts += "$($timeSpan.Seconds)s" } - if (-not $ExcludeMilliseconds -and $timeSpan.Milliseconds -gt 0 -or $parts.Count -gt 0) { - $parts += "$($timeSpan.Milliseconds)ms" + return $output -join ', ' + } } - - return $parts -join ':' } diff --git a/src/Private/Runspaces.ps1 b/src/Private/Runspaces.ps1 index 68ff6eb44..6cf6dee5f 100644 --- a/src/Private/Runspaces.ps1 +++ b/src/Private/Runspaces.ps1 @@ -103,7 +103,7 @@ function Add-PodeRunspace { throw } } - + # Create a PowerShell pipeline. $ps = [powershell]::Create() $ps.RunspacePool = $PodeContext.RunspacePools[$Type].Pool @@ -347,10 +347,10 @@ function Reset-PodeRunspaceName { $currentRunspace = [System.Management.Automation.Runspaces.Runspace]::DefaultRunspace # Check if the runspace name starts with 'Pode_' - if (-not $currentRunspace.Name.StartsWith('Pode_')) { + if (! $currentRunspace.Name.StartsWith('Pode_')) { return } # Update the runspace name with the required format - $currentRunspace.Name = "_$($currentRunspace.Name -replace '(^[^_]*_[^_]*_)[^_]*_(\d+)$', '${1}waiting_${2}')" + $currentRunspace.Name = "_$($currentRunspace.Name -replace '^(Pode_[^_]+_).+?(_\d+)$', '${1}idle${2}')" } diff --git a/src/Private/Schedules.ps1 b/src/Private/Schedules.ps1 index bd81d4e62..5f2e32f60 100644 --- a/src/Private/Schedules.ps1 +++ b/src/Private/Schedules.ps1 @@ -96,15 +96,8 @@ function Start-PodeScheduleRunspace { # complete any schedules Complete-PodeInternalSchedule -Now $_now - # Calculate the remaining seconds to sleep until the next minute - $remainingSeconds = 60 - [DateTime]::Now.Second - - # Loop in 5-second intervals until the remaining seconds are covered - while ($remainingSeconds -gt 0) { - $sleepTime = [math]::Min(5, $remainingSeconds) # Sleep for 5 seconds or remaining time - Start-PodeSleep -Seconds $sleepTime - $remainingSeconds -= $sleepTime - } + # cron expression only goes down to the minute, so sleep for 1min + Start-PodeSleep -Seconds (60 - [DateTime]::Now.Second) } catch { $_ | Write-PodeErrorLog diff --git a/src/Private/Server.ps1 b/src/Private/Server.ps1 index 43397036b..621566a9a 100644 --- a/src/Private/Server.ps1 +++ b/src/Private/Server.ps1 @@ -143,7 +143,7 @@ function Start-PodeInternalServer { } # Trigger the start - Set-PodeCancellationTokenRequest -Type Start + Close-PodeCancellationTokenRequest -Type Start # set the start time of the server (start and after restart) $PodeContext.Metrics.Server.StartTime = [datetime]::UtcNow @@ -176,7 +176,7 @@ function Restart-PodeInternalServer { Invoke-PodeEvent -Type Restart # cancel the session token - Set-PodeCancellationTokenRequest -Type Cancellation, Terminate + Close-PodeCancellationTokenRequest -Type Cancellation, Terminate # close all current runspaces Close-PodeRunspace -ClosePool @@ -369,7 +369,7 @@ function Suspend-PodeServerInternal { ) # Exit early if no suspension request is pending or if the server is already suspended. - if (!(Test-PodeCancellationTokenRequest -Type Suspend) -or ((Get-PodeServerState) -eq 'Suspended')) { + if (!(Test-PodeCancellationTokenRequest -Type Suspend) -or (Test-PodeServerState -State Suspended)) { return } @@ -380,9 +380,8 @@ function Suspend-PodeServerInternal { # Trigger the 'Suspend' event for the server. Invoke-PodeEvent -Type Suspend - # Retrieve and sort all Pode-related runspaces. - # Main runspaces are suspended first to avoid potential hangs. - $runspaces = Get-Runspace | Where-Object { $_.Name -like 'Pode_Tasks_*' -or $_.Name -like 'Pode_Schedules_*' } | Sort-Object Name + # Retrieve all Pode-related runspaces for tasks and schedules. + $runspaces = Get-Runspace | Where-Object { $_.Name -like 'Pode_Tasks_*' -or $_.Name -like 'Pode_Schedules_*' } # Iterate over each runspace to initiate suspension. $runspaces | Foreach-Object { @@ -479,7 +478,7 @@ function Resume-PodeServerInternal { Start-Sleep -Seconds 1 # Retrieve all runspaces currently in a suspended (debug) state. - $runspaces = Get-Runspace | Where-Object { $_.Debugger.InBreakpoint } + $runspaces = Get-Runspace | Where-Object { ($_.Name -like 'Pode_Tasks_*' -or $_.Name -like 'Pode_Schedules_*') -and $_.Debugger.InBreakpoint } # Iterate over each suspended runspace to restore normal execution. $runspaces | ForEach-Object { @@ -514,7 +513,7 @@ function Resume-PodeServerInternal { $_ | Write-PodeErrorLog # Force a restart action to recover the server. - Set-PodeCancellationTokenRequest -Type Restart + Close-PodeCancellationTokenRequest -Type Restart } finally { # Reset the resume cancellation token for future suspension/resumption cycles. @@ -539,12 +538,13 @@ function Resume-PodeServerInternal { This function is used internally to manage Watchdog monitoring and may change in future releases of Pode. #> function Enable-PodeServerInternal { - if (Get-PodeServerState -eq 'Running') { - # Check if the Watchdog middleware exists and remove it if found to allow new requests - if (! (Test-PodeServerIsEnabled)) { - Remove-PodeMiddleware -Name $PodeContext.Server.AllowedActions.DisableSettings.MiddlewareName - } + + # Check if the Watchdog middleware exists and remove it if found to allow new requests + if (!(Test-PodeServerState -State Running) -or (Test-PodeServerIsEnabled) ) { + return } + + Remove-PodeMiddleware -Name $PodeContext.Server.AllowedActions.DisableSettings.MiddlewareName } <# @@ -559,20 +559,20 @@ function Enable-PodeServerInternal { This function is used internally to manage Watchdog monitoring and may change in future releases of Pode. #> function Disable-PodeServerInternal { - if (Get-PodeServerState -eq 'Running') { - if (Test-PodeServerIsEnabled) { - # Add middleware to block new requests and respond with 503 Service Unavailable - Add-PodeMiddleware -Name $PodeContext.Server.AllowedActions.DisableSettings.MiddlewareName -ScriptBlock { - # Set HTTP response header for retrying after a certain time (RFC7231) - Set-PodeHeader -Name 'Retry-After' -Value $PodeContext.Server.AllowedActions.DisableSettings.RetryAfter - - # Set HTTP status to 503 Service Unavailable - Set-PodeResponseStatus -Code 503 - - # Stop further processing - return $false - } - } + + if (!(Test-PodeServerState -State Running) -or (!( Test-PodeServerIsEnabled)) ) { + return + } + # Add middleware to block new requests and respond with 503 Service Unavailable + Add-PodeMiddleware -Name $PodeContext.Server.AllowedActions.DisableSettings.MiddlewareName -ScriptBlock { + # Set HTTP response header for retrying after a certain time (RFC7231) + Set-PodeHeader -Name 'Retry-After' -Value $PodeContext.Server.AllowedActions.DisableSettings.RetryAfter + + # Set HTTP status to 503 Service Unavailable + Set-PodeResponseStatus -Code 503 + + # Stop further processing + return $false } } diff --git a/src/Public/Core.ps1 b/src/Public/Core.ps1 index 3292498d4..b48a29aa8 100644 --- a/src/Public/Core.ps1 +++ b/src/Public/Core.ps1 @@ -77,7 +77,7 @@ .PARAMETER ShowHelp Displays a help menu in the console with control commands.. -.PARAMETER IgnoreServerPsConfig +.PARAMETER IgnoreServerConfig Ignores the server.psd1 configuration file when starting the server. This parameter ensures the server does not load or apply any settings defined in the server.psd1 file, allowing for a fully manual configuration at runtime. @@ -178,7 +178,7 @@ function Start-PodeServer { $ShowHelp, [switch] - $IgnoreServerPsConfig + $IgnoreServerConfig ) begin { $pipelineItemCount = 0 @@ -235,7 +235,7 @@ function Start-PodeServer { StatusPageExceptions = $StatusPageExceptions Console = Get-PodeDefaultConsole EnableBreakpoints = $EnableBreakpoints - IgnoreServerPsConfig = $IgnoreServerPsConfig + IgnoreServerConfig = $IgnoreServerConfig } $ContextParams.Console.DisableTermination = $DisableTermination.IsPresent $ContextParams.Console.DisableConsoleInput = $DisableConsoleInput.IsPresent @@ -284,7 +284,6 @@ function Start-PodeServer { } # Terminating... - # Write-PodeHost $PodeLocale.terminatingMessage -NoNewLine -ForegroundColor Yellow Invoke-PodeEvent -Type Terminate Close-PodeServer Show-PodeConsoleInfo @@ -337,7 +336,7 @@ function Close-PodeServer { [CmdletBinding()] param() - Set-PodeCancellationTokenRequest -Type Cancellation, Terminate + Close-PodeCancellationTokenRequest -Type Cancellation, Terminate } <# @@ -356,7 +355,7 @@ function Restart-PodeServer { # Only if the Restart feature is anabled if ($PodeContext.Server.AllowedActions.Restart) { - Set-PodeCancellationTokenRequest -Type Restart + Close-PodeCancellationTokenRequest -Type Restart } } @@ -389,7 +388,7 @@ function Resume-PodeServer { $PodeContext.Server.AllowedActions.Timeout.Resume = $Timeout } - if ((Get-PodeServerState) -eq 'Suspended') { + if ((Test-PodeServerState -State Suspended)) { Set-PodeResumeToken } } @@ -423,7 +422,7 @@ function Suspend-PodeServer { if ($Timeout) { $PodeContext.Server.AllowedActions.Timeout.Suspend = $Timeout } - if ((Get-PodeServerState) -ne 'Suspended') { + if (!(Test-PodeServerState -State Suspended)) { Set-PodeSuspendToken } } @@ -1028,6 +1027,55 @@ function Get-PodeServerState { return 'Running' } +<# +.SYNOPSIS + Tests whether the Pode server is in a specified state. + +.DESCRIPTION + The `Test-PodeServerState` function checks the current state of the Pode server + by calling `Get-PodeServerState` and comparing the result to the specified state. + The function returns `$true` if the server is in the specified state and `$false` otherwise. + +.PARAMETER State + Specifies the server state to test. Allowed values are: + - `Terminated`: The server is not running, and the context is null. + - `Terminating`: The server is in the process of shutting down. + - `Resuming`: The server is resuming from a suspended state. + - `Suspending`: The server is in the process of entering a suspended state. + - `Suspended`: The server is fully suspended. + - `Restarting`: The server is restarting. + - `Starting`: The server is in the process of starting up. + - `Running`: The server is actively running. + +.EXAMPLE + Test-PodeServerState -State 'Running' + + Returns `$true` if the server is currently running, otherwise `$false`. + +.EXAMPLE + Test-PodeServerState -State 'Suspended' + + Returns `$true` if the server is fully suspended, otherwise `$false`. + +.NOTES + This function is part of Pode's server state management utilities. + It relies on the `Get-PodeServerState` function to determine the current state. +#> +function Test-PodeServerState { + param( + [Parameter(Mandatory = $true)] + [ValidateSet('Terminated', 'Terminating', 'Resuming', 'Suspending', 'Suspended', 'Restarting', 'Starting', 'Running')] + [string] + $State + ) + + # Call Get-PodeServerState to retrieve the current server state + $currentState = Get-PodeServerState + + # Return true if the current state matches the provided state, otherwise false + return $currentState -eq $State +} + <# .SYNOPSIS Enables new incoming requests by removing the middleware that blocks requests when the Pode Watchdog client is active. @@ -1060,7 +1108,7 @@ function Disable-PodeServer { $PodeContext.Server.AllowedActions.DisableSettings.RetryAfter = $RetryAfter if (! (Test-PodeCancellationTokenRequest -Type Disable)) { - Set-PodeCancellationTokenRequest -Type Disable -RetryAfter $RetryAfter + Close-PodeCancellationTokenRequest -Type Disable } } diff --git a/src/Public/Metrics.ps1 b/src/Public/Metrics.ps1 index 2d42b13e4..2334f27c5 100644 --- a/src/Public/Metrics.ps1 +++ b/src/Public/Metrics.ps1 @@ -1,25 +1,29 @@ <# .SYNOPSIS - Returns the uptime of the server in milliseconds or in a human-readable format. + Retrieves the server uptime in milliseconds or a human-readable format. .DESCRIPTION - Returns the uptime of the server in milliseconds by default. You can optionally return the total uptime regardless of server restarts or convert the uptime to a human-readable format with selectable output styles (e.g., Verbose, Compact). - Additionally, milliseconds can be excluded from the output if desired. + The `Get-PodeServerUptime` function calculates the server's uptime since its last start or total uptime since initial load, depending on the `-Total` switch. + By default, the uptime is returned in milliseconds. When the `-Format` parameter is used, the uptime can be returned in various human-readable styles: + - `Milliseconds` (default): Raw uptime in milliseconds. + - `Concise`: A short format like "1d 2h 3m". + - `Compact`: A condensed format like "01:10:17:36". + - `Verbose`: A detailed format like "1 day, 2 hours, 3 minutes, 5 seconds, 200 milliseconds". + The `-ExcludeMilliseconds` switch allows removal of milliseconds from human-readable output. .PARAMETER Total - If supplied, the total uptime of the server will be returned, regardless of restarts. + Retrieves the total server uptime since the initial load, regardless of any restarts. -.PARAMETER Readable - If supplied, the uptime will be returned in a human-readable format instead of milliseconds. - -.PARAMETER OutputType - Specifies the format for the human-readable output. Valid options are: - - 'Verbose' for detailed descriptions (e.g., "1 day, 2 hours, 3 minutes"). - - 'Compact' for a compact format (e.g., "dd:hh:mm:ss"). - - Default is concise format (e.g., "1d 2h 3m"). +.PARAMETER Format + Specifies the desired output format for the uptime. + Allowed values: + - `Milliseconds` (default): Uptime in raw milliseconds. + - `Concise`: Human-readable in a short form (e.g., "1d 2h 3m"). + - `Compact`: Condensed form (e.g., "01:10:17:36"). + - `Verbose`: Detailed format (e.g., "1 day, 2 hours, 3 minutes, 5 seconds"). .PARAMETER ExcludeMilliseconds - If supplied, milliseconds will be excluded from the human-readable output. + Omits milliseconds from the human-readable output when `-Format` is not `Milliseconds`. .EXAMPLE $currentUptime = Get-PodeServerUptime @@ -30,47 +34,42 @@ # Output: 987654321 (milliseconds) .EXAMPLE - $readableUptime = Get-PodeServerUptime -Readable - # Output: "1d 10h 17m 36s" + $readableUptime = Get-PodeServerUptime -Format Concise + # Output: "1d 10h 17m" .EXAMPLE - $verboseUptime = Get-PodeServerUptime -Readable -OutputType Verbose + $verboseUptime = Get-PodeServerUptime -Format Verbose # Output: "1 day, 10 hours, 17 minutes, 36 seconds, 789 milliseconds" .EXAMPLE - $compactUptime = Get-PodeServerUptime -Readable -OutputType Compact + $compactUptime = Get-PodeServerUptime -Format Compact # Output: "01:10:17:36" .EXAMPLE - $compactUptimeNoMs = Get-PodeServerUptime -Readable -OutputType Compact -ExcludeMilliseconds + $compactUptimeNoMs = Get-PodeServerUptime -Format Compact -ExcludeMilliseconds # Output: "01:10:17:36" + +.NOTES + This function is part of Pode's utility metrics to monitor server uptime. #> function Get-PodeServerUptime { - [CmdletBinding(DefaultParameterSetName = 'Milliseconds')] + [CmdletBinding()] [OutputType([long], [string])] param( - # Common to all parameter sets [switch] $Total, - # Default set: Milliseconds output - [Parameter(ParameterSetName = 'Readable')] - [switch] - $Readable, - - # Available only when -Readable is specified - [Parameter(ParameterSetName = 'Readable')] - [ValidateSet("Verbose", "Compact", "Default")] + [Parameter()] + [ValidateSet('Milliseconds', 'Concise', 'Compact', 'Verbose')] [string] - $OutputType = "Default", - - # Available only when -Readable is specified - [Parameter(ParameterSetName = 'Readable')] + $Format = 'Milliseconds', + [switch] $ExcludeMilliseconds ) - # Determine the appropriate start time + # Determine the start time based on the -Total switch + # Default: Uses the last start time; -Total: Uses the initial load time $time = $PodeContext.Metrics.Server.StartTime if ($Total) { $time = $PodeContext.Metrics.Server.InitialLoadTime @@ -79,23 +78,13 @@ function Get-PodeServerUptime { # Calculate uptime in milliseconds $uptimeMilliseconds = [long]([datetime]::UtcNow - $time).TotalMilliseconds - # Handle readable output - if ($PSCmdlet.ParameterSetName -eq 'Readable') { - switch ($OutputType) { - "Verbose" { - return Convert-PodeMillisecondsToReadable -Milliseconds $uptimeMilliseconds -VerboseOutput -ExcludeMilliseconds:$ExcludeMilliseconds - } - "Compact" { - return Convert-PodeMillisecondsToReadable -Milliseconds $uptimeMilliseconds -CompactOutput -ExcludeMilliseconds:$ExcludeMilliseconds - } - "Default" { - return Convert-PodeMillisecondsToReadable -Milliseconds $uptimeMilliseconds -ExcludeMilliseconds:$ExcludeMilliseconds - } - } + # Return uptime in milliseconds if no readable format is requested + if ($Format -ieq 'Milliseconds') { + return $uptimeMilliseconds } - # Default to milliseconds if no readable output is requested - return $uptimeMilliseconds + # Convert uptime to a human-readable format + return Convert-PodeMillisecondsToReadable -Milliseconds $uptimeMilliseconds -Format $Format -ExcludeMilliseconds:$ExcludeMilliseconds } diff --git a/src/Public/Middleware.ps1 b/src/Public/Middleware.ps1 index a7c9e8720..e8320fefd 100644 --- a/src/Public/Middleware.ps1 +++ b/src/Public/Middleware.ps1 @@ -675,6 +675,12 @@ function Test-PodeMiddleware { $Name ) - # Check if the specified middleware exists in the Pode server's middleware collection - return (($PodeContext.Server.Middleware | Where-Object { $_.Name -ieq $Name } | Measure-Object).Count -gt 0) -} \ No newline at end of file + # Check if the middleware exists + foreach ($middleware in $PodeContext.Server.Middleware) { + if ($middleware.Name -ieq $Name) { + return $true + } + } + + return $false +} diff --git a/src/Public/Utilities.ps1 b/src/Public/Utilities.ps1 index 25743082d..ff1c5f178 100644 --- a/src/Public/Utilities.ps1 +++ b/src/Public/Utilities.ps1 @@ -1496,12 +1496,11 @@ function Invoke-PodeGC { <# .SYNOPSIS - A function to pause execution for a specified duration with an optional progress bar. + A function to pause execution for a specified duration. + This function should be used in Pode as replacement for Start-Sleep .DESCRIPTION The `Start-PodeSleep` function pauses script execution for a given duration specified in seconds, milliseconds, or a TimeSpan. - It includes an optional progress bar that displays the elapsed time and completion percentage. - The progress bar can also display a custom activity name and allows grouping with a ParentId. .PARAMETER Seconds Specifies the duration to pause execution in seconds. Default is 1 second. @@ -1525,19 +1524,9 @@ function Invoke-PodeGC { None. .EXAMPLE - Start-PodeSleep -Seconds 5 -ShowProgress + Start-PodeSleep -Seconds 5 - Pauses execution for 5 seconds and displays a progress bar. - -.EXAMPLE - Start-PodeSleep -Milliseconds 3000 -ShowProgress -Activity "Processing Task" -ParentId 1 - - Pauses execution for 3000 milliseconds, showing a progress bar with the custom activity grouped under ParentId 1. - -.EXAMPLE - Start-PodeSleep -Duration (New-TimeSpan -Seconds 10) -ShowProgress -Activity "Running Script" - - Pauses execution for 10 seconds using a TimeSpan object and displays a progress bar. + Pauses execution for 5 seconds. .NOTES This function is useful for scenarios where tracking the remaining wait time visually is helpful. @@ -1546,64 +1535,36 @@ function Start-PodeSleep { [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory = $false, ParameterSetName = 'Seconds')] - [int]$Seconds = 1, + [int] + $Seconds = 1, [Parameter(Position = 0, Mandatory = $false, ParameterSetName = 'Milliseconds')] - [int]$Milliseconds, + [int] + $Milliseconds, [Parameter(Position = 0, Mandatory = $false, ParameterSetName = 'Duration')] - [TimeSpan]$Duration, - - [Parameter(Position = 1, Mandatory = $false)] - [string]$Activity = 'Sleeping...', - - [Parameter(Mandatory = $false)] - [int]$ParentId, - - [Parameter(Mandatory = $false)] - [Switch]$ShowProgress + [TimeSpan] + $Duration ) - # Determine total duration and end time - switch ($PSCmdlet.ParameterSetName) { - 'Seconds' { - $totalDuration = [TimeSpan]::FromSeconds($Seconds) - } - 'Milliseconds' { - $totalDuration = [TimeSpan]::FromMilliseconds($Milliseconds) - } - 'Duration' { - $totalDuration = $Duration - } + # Determine the total duration + $totalDuration = switch ($PSCmdlet.ParameterSetName) { + 'Seconds' { [TimeSpan]::FromSeconds($Seconds) } + 'Milliseconds' { [TimeSpan]::FromMilliseconds($Milliseconds) } + 'Duration' { $Duration } } - $endTime = (Get-Date).Add($totalDuration) - - # Start the timer - $startTime = Get-Date - - while ((Get-Date) -lt $endTime) { - if ($ShowProgress) { - # Calculate progress and build Write-Progress parameters - $progressParams = @{ - Activity = $Activity - Status = "$([math]::Round((($(Get-Date) - $startTime).TotalMilliseconds / $totalDuration.TotalMilliseconds) * 100, 1))%" - PercentComplete = [math]::Min((($(Get-Date) - $startTime).TotalMilliseconds / $totalDuration.TotalMilliseconds) * 100, 100) - } - if ($ParentId) { - $progressParams.ParentId = $ParentId - } - # Write the progress with dynamic parameters - Write-Progress @progressParams - } + # Calculate end time + $startTime = [DateTime]::UtcNow + $endTime = $startTime.Add($totalDuration) - # Sleep for a short duration to prevent high CPU usage - Start-Sleep -Milliseconds 200 - } + # Precompute sleep interval (total duration divided by 100 - ie 100%) + $sleepInterval = [math]::Max($totalDuration.TotalMilliseconds / 100, 10) - # Clear the progress bar after completion - if ($ShowProgress) { - Write-Progress -Activity $Activity -Completed + # Main loop + while ([DateTime]::UtcNow -lt $endTime) { + # Sleep for the interval + Start-Sleep -Milliseconds $sleepInterval } } diff --git a/tests/integration/OpenApi.Tests.ps1 b/tests/integration/OpenApi.Tests.ps1 index e55c343f8..fa8987a11 100644 --- a/tests/integration/OpenApi.Tests.ps1 +++ b/tests/integration/OpenApi.Tests.ps1 @@ -19,7 +19,7 @@ Describe 'OpenAPI integration tests' { $PortV3 = 8080 $PortV3_1 = 8081 $scriptPath = "$($PSScriptRoot)\..\..\examples\OpenApi-TuttiFrutti.ps1" - Start-Process (Get-Process -Id $PID).Path -ArgumentList "-NoProfile -File `"$scriptPath`" -Quiet -PortV3 $PortV3 -PortV3_1 $PortV3_1 -DisableTermination -IgnoreServerPsConfig" -NoNewWindow + Start-Process (Get-Process -Id $PID).Path -ArgumentList "-NoProfile -File `"$scriptPath`" -Quiet -PortV3 $PortV3 -PortV3_1 $PortV3_1 -DisableTermination -IgnoreServerConfig" -NoNewWindow function Compare-StringRnLn { param ( diff --git a/tests/unit/Helpers.Tests.ps1 b/tests/unit/Helpers.Tests.ps1 index bc4114751..989d29de2 100644 --- a/tests/unit/Helpers.Tests.ps1 +++ b/tests/unit/Helpers.Tests.ps1 @@ -1062,7 +1062,7 @@ Describe 'Get-PodeRelativePath' { } It 'Returns path for a relative path joined to default root normalized' { - Get-PodeRelativePath -Path './path' -JoinRoot -NormalizeRelativePath | Should -Be 'c:/path' + Get-PodeRelativePath -Path './path' -JoinRoot -NormaliseRelativePath | Should -Be 'c:/path' } It 'Returns resolved path for a relative path joined to default root when resolving' { @@ -1080,7 +1080,7 @@ Describe 'Get-PodeRelativePath' { } It 'Returns path for a relative path joined to passed root normalized' { - Get-PodeRelativePath -Path './path' -JoinRoot -RootPath 'e:/' -NormalizeRelativePath | Should -Be 'e:/path' + Get-PodeRelativePath -Path './path' -JoinRoot -RootPath 'e:/' -NormaliseRelativePath | Should -Be 'e:/path' } It 'Throws error for path ot existing' { @@ -1151,7 +1151,7 @@ Describe 'Close-PodeServerInternal' { Mock Close-PodeDisposable {} Mock Remove-PodePSDrive {} Mock Write-PodeHost {} - Mock Set-PodeCancellationTokenRequest {} + Mock Close-PodeCancellationTokenRequest {} } diff --git a/tests/unit/Server.Tests.ps1 b/tests/unit/Server.Tests.ps1 index 1457cdc22..7dbaae4b5 100644 --- a/tests/unit/Server.Tests.ps1 +++ b/tests/unit/Server.Tests.ps1 @@ -45,7 +45,7 @@ Describe 'Start-PodeInternalServer' { It 'Calls one-off script logic' { $PodeContext.Server = @{ Types = ([string]::Empty); Logic = {}; Console = @{Quiet = $true } } - $PodeContext.Tokens = New-PodeSuspensionToken + $PodeContext.Tokens = Initialize-PodeCancellationToken Start-PodeInternalServer | Out-Null Assert-MockCalled Invoke-PodeScriptBlock -Times 1 -Scope It @@ -60,7 +60,7 @@ Describe 'Start-PodeInternalServer' { It 'Calls smtp server logic' { $PodeContext.Server = @{ Types = 'SMTP'; Logic = {}; Console = @{Quiet = $true } } - $PodeContext.Tokens = New-PodeSuspensionToken + $PodeContext.Tokens = Initialize-PodeCancellationToken Start-PodeInternalServer | Out-Null Assert-MockCalled Invoke-PodeScriptBlock -Times 1 -Scope It @@ -75,7 +75,7 @@ Describe 'Start-PodeInternalServer' { It 'Calls tcp server logic' { $PodeContext.Server = @{ Types = 'TCP'; Logic = {}; Console = @{Quiet = $true } } - $PodeContext.Tokens = New-PodeSuspensionToken + $PodeContext.Tokens = Initialize-PodeCancellationToken Start-PodeInternalServer | Out-Null Assert-MockCalled Invoke-PodeScriptBlock -Times 1 -Scope It @@ -90,7 +90,7 @@ Describe 'Start-PodeInternalServer' { It 'Calls http web server logic' { $PodeContext.Server = @{ Types = 'HTTP'; Logic = {}; Console = @{Quiet = $true } } - $PodeContext.Tokens = New-PodeSuspensionToken + $PodeContext.Tokens = Initialize-PodeCancellationToken Start-PodeInternalServer | Out-Null Assert-MockCalled Invoke-PodeScriptBlock -Times 1 -Scope It @@ -118,7 +118,7 @@ Describe 'Restart-PodeInternalServer' { It 'Resetting the server values' { $PodeContext = @{ - Tokens = New-PodeSuspensionToken + Tokens = Initialize-PodeCancellationToken Server = @{ Routes = @{ GET = @{ 'key' = 'value' }