diff --git a/FFUDevelopment/FFU.Common/FFU.Common.Parallel.psm1 b/FFUDevelopment/FFU.Common/FFU.Common.Parallel.psm1 index c62ce96..bb76e1b 100644 --- a/FFUDevelopment/FFU.Common/FFU.Common.Parallel.psm1 +++ b/FFUDevelopment/FFU.Common/FFU.Common.Parallel.psm1 @@ -36,6 +36,7 @@ function Invoke-ParallelProcessing { $jobs = @() $totalItems = $ItemsToProcess.Count $processedCount = 0 + $completedIdentifiers = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) # Create a thread-safe queue for intermediate progress updates $progressQueue = New-Object System.Collections.Concurrent.ConcurrentQueue[hashtable] @@ -319,17 +320,24 @@ function Invoke-ParallelProcessing { # Continue while jobs are running OR queue has messages # 1. Process intermediate status updates from the queue - $statusUpdate = $null + $statusUpdate = $null while ($progressQueue.TryDequeue([ref]$statusUpdate)) { if ($null -ne $statusUpdate) { + WriteLog "Dequeued progress update: $($statusUpdate | ConvertTo-Json -Compress)" $intermediateIdentifier = $statusUpdate.Identifier + # If this item has already been marked as complete, skip this stale intermediate update + if ($completedIdentifiers.Contains($intermediateIdentifier)) { + WriteLog "Skipping stale intermediate status for already completed item: $intermediateIdentifier" + continue + } $intermediateStatus = $statusUpdate.Status if ($isUiMode) { # Use the new $isUiMode flag # Update the UI with the intermediate status try { - $WindowObject.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [Action] { - Update-ListViewItemStatus -WindowObject $WindowObject -ListView $ListViewControl -IdentifierProperty $IdentifierProperty -IdentifierValue $intermediateIdentifier -StatusProperty $StatusProperty -StatusValue $intermediateStatus + WriteLog "Dispatching INTERMEDIATE status for '$intermediateIdentifier': '$intermediateStatus'" + $WindowObject.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [Action] { + Update-ListViewItemStatus -WindowObject $WindowObject -ListView $ListViewControl -IdentifierProperty $IdentifierProperty -IdentifierValue $intermediateIdentifier -StatusProperty $StatusProperty -StatusValue $intermediateStatus }) } catch { @@ -348,79 +356,80 @@ function Invoke-ParallelProcessing { if ($completedJobs) { foreach ($completedJob in $completedJobs) { - $finalIdentifier = "UnknownJob" # Placeholder if we can't get result - $finalStatus = "$ErrorStatusPrefix Job $($completedJob.Id) ended unexpectedly" - $finalResultCode = 1 # Assume error - + $jobHandled = $false if ($completedJob.State -eq 'Failed') { + $jobHandled = $true + $finalIdentifier = "UnknownJob" # Placeholder WriteLog "Job $($completedJob.Id) failed: $($completedJob.Error)" - # Try to get identifier from job name if possible (less reliable) - # $finalIdentifier = ... logic to parse job name or map ID ... $finalStatus = "$ErrorStatusPrefix Job Failed" - $processedCount++ # Count failed job as processed + $finalResultCode = 1 + $processedCount++ + + # --- DISPATCH FOR FAILED JOB --- + $completedIdentifiers.Add($finalIdentifier) | Out-Null + if ($isUiMode) { + try { + WriteLog "Dispatching FINAL status for '$finalIdentifier': '$finalStatus'" + $WindowObject.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [Action] { Update-ListViewItemStatus -WindowObject $WindowObject -ListView $ListViewControl -IdentifierProperty $IdentifierProperty -IdentifierValue $finalIdentifier -StatusProperty $StatusProperty -StatusValue $finalStatus }) + $WindowObject.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [Action] { Update-OverallProgress -WindowObject $WindowObject -CompletedCount $processedCount -TotalCount $totalItems -StatusText "Processed $processedCount of $totalItems..." -ProgressBarName "progressBar" -StatusLabelName "txtStatus" }) + } + catch { WriteLog "Error setting FINAL status for item '$finalIdentifier': $($_.Exception.Message)" } + } + else { WriteLog "Final Status for '$finalIdentifier': $finalStatus (ResultCode: $finalResultCode)" } } elseif ($completedJob.HasMoreData) { - # Receive final results specifically from the completed job + $jobHandled = $true $jobResults = $completedJob | Receive-Job foreach ($result in $jobResults) { - # Should only be one result per job in this setup + WriteLog "Received FINAL job result: $($result | ConvertTo-Json -Compress -Depth 3)" if ($null -ne $result -and $result -is [hashtable] -and $result.ContainsKey('Identifier')) { $finalIdentifier = $result.Identifier - $status = $result.Status # This is the FINAL status returned by the task + $status = $result.Status $finalResultCode = $result.ResultCode - - # Determine final status text based on the result code - if ($finalResultCode -eq 0) { - # Assuming 0 means success - # Use the specific status returned by the successful job - # This handles cases like "Already downloaded" correctly - $finalStatus = $status - } - else { - $finalStatus = "$($ErrorStatusPrefix)$($status)" # Use status from result for error message - } + $finalStatus = if ($finalResultCode -eq 0) { $status } else { "$($ErrorStatusPrefix)$($status)" } $processedCount++ } else { + $finalIdentifier = "UnknownResult" WriteLog "Warning: Received unexpected final job result format: $($result | Out-String)" $finalStatus = "$ErrorStatusPrefix Invalid Result Format" - $processedCount++ # Count as processed to avoid loop issues + $finalResultCode = 1 + $processedCount++ } - # Add the received result (even if format was unexpected, for logging) - if ($null -ne $result) { $resultsCollection.Add($result) } + if ($null -ne $result) { $resultsCollection.Add($result) } + + # --- DISPATCH PER RESULT --- + $completedIdentifiers.Add($finalIdentifier) | Out-Null + if ($isUiMode) { + try { + WriteLog "Dispatching FINAL status for '$finalIdentifier': '$finalStatus'" + $WindowObject.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [Action] { Update-ListViewItemStatus -WindowObject $WindowObject -ListView $ListViewControl -IdentifierProperty $IdentifierProperty -IdentifierValue $finalIdentifier -StatusProperty $StatusProperty -StatusValue $finalStatus }) + $WindowObject.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [Action] { Update-OverallProgress -WindowObject $WindowObject -CompletedCount $processedCount -TotalCount $totalItems -StatusText "Processed $processedCount of $totalItems..." -ProgressBarName "progressBar" -StatusLabelName "txtStatus" }) + } + catch { WriteLog "Error setting FINAL status for item '$finalIdentifier': $($_.Exception.Message)" } } - } - else { - # Job completed but had no data - if ($completedJob.State -ne 'Failed') { - WriteLog "Job $($completedJob.Id) completed with state '$($completedJob.State)' but had no data." - # $finalIdentifier = ... logic to parse job name or map ID ... - $finalStatus = "$ErrorStatusPrefix No Result Data" - $processedCount++ + else { WriteLog "Final Status for '$finalIdentifier': $finalStatus (ResultCode: $finalResultCode)" } } - # If it was 'Failed', it was handled above } + + if (-not $jobHandled) { # Catches 'Completed' with no data + $finalIdentifier = "UnknownJob" + WriteLog "Job $($completedJob.Id) completed with state '$($completedJob.State)' but had no data." + $finalStatus = "$ErrorStatusPrefix No Result Data" + $finalResultCode = 1 + $processedCount++ - # Update the specific item in the ListView with its FINAL status - if ($isUiMode) { - # Use the new $isUiMode flag - try { - $WindowObject.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [Action] { - Update-ListViewItemStatus -WindowObject $WindowObject -ListView $ListViewControl -IdentifierProperty $IdentifierProperty -IdentifierValue $finalIdentifier -StatusProperty $StatusProperty -StatusValue $finalStatus - }) + # --- DISPATCH FOR NO-DATA JOB --- + $completedIdentifiers.Add($finalIdentifier) | Out-Null + if ($isUiMode) { + try { + WriteLog "Dispatching FINAL status for '$finalIdentifier': '$finalStatus'" + $WindowObject.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [Action] { Update-ListViewItemStatus -WindowObject $WindowObject -ListView $ListViewControl -IdentifierProperty $IdentifierProperty -IdentifierValue $finalIdentifier -StatusProperty $StatusProperty -StatusValue $finalStatus }) + $WindowObject.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [Action] { Update-OverallProgress -WindowObject $WindowObject -CompletedCount $processedCount -TotalCount $totalItems -StatusText "Processed $processedCount of $totalItems..." -ProgressBarName "progressBar" -StatusLabelName "txtStatus" }) + } + catch { WriteLog "Error setting FINAL status for item '$finalIdentifier': $($_.Exception.Message)" } } - catch { - WriteLog "Error setting FINAL status for item '$finalIdentifier': $($_.Exception.Message)" - } - - # Update overall progress after processing a job's results - $WindowObject.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [Action] { - Update-OverallProgress -WindowObject $WindowObject -CompletedCount $processedCount -TotalCount $totalItems -StatusText "Processed $processedCount of $totalItems..." -ProgressBarName "progressBar" -StatusLabelName "txtStatus" - }) - } - else { - # Log final status if not in UI mode - WriteLog "Final Status for '$finalIdentifier': $finalStatus (ResultCode: $finalResultCode)" + else { WriteLog "Final Status for '$finalIdentifier': $finalStatus (ResultCode: $finalResultCode)" } } # Remove the completed/failed job from the list and clean it up diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Applications.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Applications.psm1 index db19b7d..2a307a1 100644 --- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Applications.psm1 +++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Applications.psm1 @@ -234,7 +234,7 @@ function Invoke-CopyBYOApps { $allAppsWithSource = $State.Controls.lstApplications.Items | Where-Object { -not [string]::IsNullOrWhiteSpace($_.Source) } if (-not $allAppsWithSource) { - [System.Windows.MessageBox]::Show("UserAppList.json has been updated. No applications with a source path were found to copy.", "Copy BYO Apps", "OK", "Information") + [System.Windows.MessageBox]::Show("No applications with a source path were found to copy.", "Copy BYO Apps", "OK", "Information") return } @@ -341,7 +341,7 @@ function Start-CopyBYOApplicationTask { } if (-not (Test-Path -Path $sourcePath -PathType Container)) { - $status = "Error: Source path not found" + $status = "Source path not found" Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $appName -Status $status WriteLog "Copy error for $($appName): Source path '$sourcePath' not found." return [PSCustomObject]@{ Name = $appName; Status = $status; Success = $success } @@ -381,12 +381,7 @@ function Start-CopyBYOApplicationTask { # Enqueue error status Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $appName -Status $status } - - # Enqueue final success status if applicable - if ($success) { - Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $appName -Status $status - } - + # Return the final status return [PSCustomObject]@{ Name = $appName; Status = $status; Success = $success } } diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Shared.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Shared.psm1 index f8b21b0..f5baebd 100644 --- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Shared.psm1 +++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Shared.psm1 @@ -113,7 +113,16 @@ function Update-ListViewItemStatus { if ($WindowObject -is [System.Windows.Window] -and $ListView -is [System.Windows.Controls.ListView]) { # Directly update UI elements as this function is now called on the UI thread try { - $itemToUpdate = $ListView.ItemsSource | Where-Object { $_.$IdentifierProperty -eq $IdentifierValue } | Select-Object -First 1 + # Determine which collection to search: ItemsSource (preferred) or Items. + $collectionToSearch = $null + if ($null -ne $ListView.ItemsSource) { + $collectionToSearch = $ListView.ItemsSource + } + else { + $collectionToSearch = $ListView.Items + } + + $itemToUpdate = $collectionToSearch | Where-Object { $_.$IdentifierProperty -eq $IdentifierValue } | Select-Object -First 1 if ($null -ne $itemToUpdate) { $itemToUpdate.$StatusProperty = $StatusValue $ListView.Items.Refresh() # Refresh the view to show the change