Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions IoListTestingWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
<Border Background="{StaticResource SoftBlue}" CornerRadius="14" Padding="12,7" Margin="0,0,8,0">
<TextBlock Text="{Binding ProjectSummary}" FontSize="12" Foreground="{StaticResource Ink}"/>
</Border>
<Button Content="Save Progress" Style="{StaticResource SoftButton}" Padding="11,7"
Click="SaveProgress_Click" Margin="0,0,6,0"/>
<Button Content="Export Handover" Style="{StaticResource PrimaryButton}" Padding="11,7"
Click="ExportHandover_Click" Margin="0,0,6,0"/>
<Button Content="Return to Engineering" Style="{StaticResource SoftButton}" Click="ReturnToEngineering_Click"/>
</StackPanel>
</Grid>
Expand Down Expand Up @@ -176,20 +180,30 @@
BorderThickness="1" CornerRadius="14" Padding="12,8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="1.35*"/>
<ColumnDefinition Width="14"/>
<ColumnDefinition Width="1.1*"/>
<ColumnDefinition Width="14"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel>
<StackPanel Grid.Column="0">
<TextBlock Text="{Binding Session.StatusText}" Style="{StaticResource Caption}"/>
<TextBlock Text="{Binding Session.JournalIntegrityText}" Style="{StaticResource MicroLabel}" Margin="0,3,0,0"/>
<TextBlock Text="{Binding Session.JournalPath}" Style="{StaticResource Caption}" Margin="0,2,0,0"
TextTrimming="CharacterEllipsis" ToolTip="{Binding Session.JournalPath}"/>
</StackPanel>
<StackPanel Grid.Column="1" HorizontalAlignment="Right">
<StackPanel Grid.Column="2">
<TextBlock Text="{Binding Storage.StatusText}" Style="{StaticResource Caption}"/>
<TextBlock Text="{Binding Storage.LastSavedText}" Style="{StaticResource MicroLabel}" Margin="0,3,0,0"/>
<TextBlock Text="{Binding Storage.SnapshotPath}" Style="{StaticResource Caption}" Margin="0,2,0,0"
TextTrimming="CharacterEllipsis" ToolTip="{Binding Storage.SnapshotPath}"/>
</StackPanel>
<StackPanel Grid.Column="4" HorizontalAlignment="Right">
<TextBlock Text="{Binding Session.ProgressText}" Style="{StaticResource MicroLabel}" Foreground="{StaticResource Ink}" HorizontalAlignment="Right"/>
<TextBlock Style="{StaticResource Caption}" HorizontalAlignment="Right" Margin="0,3,0,0">
<Run Text="Evidence records "/><Run Text="{Binding Session.EvidenceRecordCount, Mode=OneWay}"/>
</TextBlock>
<TextBlock Text="Autosave enabled" Style="{StaticResource Caption}" HorizontalAlignment="Right" Margin="0,2,0,0"/>
</StackPanel>
</Grid>
</Border>
Expand Down
148 changes: 128 additions & 20 deletions IoListTestingWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Windows;
using ArIED61850Tester.Models.IoTesting;
using ArIED61850Tester.Services.IoTesting;
using Microsoft.Win32;

namespace ArIED61850Tester;

Expand All @@ -11,14 +12,18 @@ public partial class IoListTestingWindow : Window, INotifyPropertyChanged
private IoTestIedPlan? _selectedIed;

public IoListTestingWindow()
: this(CreateEmptyProject(), CreateEmptyController())
: this(CreateEmptyProject(), CreateEmptyController(), null)
{
}

public IoListTestingWindow(IoTestProject project, IoTestSessionController session)
public IoListTestingWindow(
IoTestProject project,
IoTestSessionController session,
IoTestWorkspacePersistence? persistence)
{
Project = project ?? throw new ArgumentNullException(nameof(project));
Session = session ?? throw new ArgumentNullException(nameof(session));
Storage = persistence;
Project.InitializeRuntimeNotifications();
InitializeComponent();
DataContext = this;
Expand All @@ -27,6 +32,7 @@ public IoListTestingWindow(IoTestProject project, IoTestSessionController sessio

public IoTestProject Project { get; }
public IoTestSessionController Session { get; }
public IoTestWorkspacePersistence? Storage { get; }

public IoTestIedPlan? SelectedIed
{
Expand Down Expand Up @@ -59,40 +65,135 @@ private void StartSession_Click(object sender, RoutedEventArgs e)
return;
}

ShowActionResult(Session.Start(SelectedIed), "FAT session could not start");
var result = Session.Start(SelectedIed);
ShowActionResult(result, "FAT session could not start");
if (result.Succeeded)
Storage?.ScheduleSave();
}

private void PauseSession_Click(object sender, RoutedEventArgs e)
=> ShowActionResult(Session.Pause(), "FAT session could not pause");
{
var result = Session.Pause();
ShowActionResult(result, "FAT session could not pause");
if (result.Succeeded)
Storage?.SaveNow();
}

private void ResumeSession_Click(object sender, RoutedEventArgs e)
=> ShowActionResult(Session.Resume(), "FAT session could not resume");
{
var result = Session.Resume();
ShowActionResult(result, "FAT session could not resume");
if (result.Succeeded)
Storage?.ScheduleSave();
}

private void StopSession_Click(object sender, RoutedEventArgs e)
=> ShowActionResult(Session.Stop(), "FAT session could not stop");
{
var result = Session.Stop();
ShowActionResult(result, "FAT session could not stop");
if (result.Succeeded)
Storage?.SaveNow();
}

private void ReturnToEngineering_Click(object sender, RoutedEventArgs e)
=> Close();
private void SaveProgress_Click(object sender, RoutedEventArgs e)
{
try
{
Storage?.SaveNow();
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or InvalidOperationException)
{
MessageBox.Show(this, ex.Message, "Progress save failed", MessageBoxButton.OK, MessageBoxImage.Error);
}
}

private void Window_Closing(object? sender, CancelEventArgs e)
private async void ExportHandover_Click(object sender, RoutedEventArgs e)
{
if (!Session.IsSessionActive)
if (Storage == null)
return;
if (Session.IsSessionActive)
{
MessageBox.Show(
this,
"Stop the active IED session before exporting. This seals and verifies the evidence journal before it is transferred to another laptop.",
"Stop session before export",
MessageBoxButton.OK,
MessageBoxImage.Information);
return;
}

var answer = MessageBox.Show(
this,
"A FAT session is active. Returning to Engineering will stop the session and seal the current evidence journal.\n\nStop the session and return?",
"Stop active FAT session",
MessageBoxButton.YesNo,
MessageBoxImage.Warning,
MessageBoxResult.No);
if (answer != MessageBoxResult.Yes)
var dialog = new SaveFileDialog
{
e.Cancel = true;
Title = "Export portable ARSAS IO FAT handover",
Filter = $"ARSAS IO FAT handover (*{IoTestWorkspacePersistence.PackageExtension})|*{IoTestWorkspacePersistence.PackageExtension}",
FileName = $"{SafeFileName(Project.ProjectId)}_{DateTime.Now:yyyyMMdd_HHmm}{IoTestWorkspacePersistence.PackageExtension}",
AddExtension = true,
DefaultExt = IoTestWorkspacePersistence.PackageExtension,
OverwritePrompt = true
};
if (dialog.ShowDialog(this) != true)
return;

try
{
IsEnabled = false;
await Storage.ExportPackageAsync(dialog.FileName);
MessageBox.Show(
this,
$"Portable FAT handover created successfully.\n\n{Storage.LastExportPath}\n\nThe package can be opened in ARSAS on another laptop. It also contains report/IO-FAT-Report.html for browser Print to PDF.",
"FAT handover exported",
MessageBoxButton.OK,
MessageBoxImage.Information);
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or InvalidDataException or InvalidOperationException)
{
MessageBox.Show(this, ex.Message, "FAT handover export failed", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
IsEnabled = true;
}
}

Session.Stop("Workspace closed by operator; evidence journal sealed.");
private void ReturnToEngineering_Click(object sender, RoutedEventArgs e)
=> Close();

private void Window_Closing(object? sender, CancelEventArgs e)
{
if (Session.IsSessionActive)
{
var answer = MessageBox.Show(
this,
"A FAT session is active. Returning to Engineering will stop the session, seal the evidence journal, and save the current project progress.\n\nStop the session and return?",
"Stop active FAT session",
MessageBoxButton.YesNo,
MessageBoxImage.Warning,
MessageBoxResult.No);
if (answer != MessageBoxResult.Yes)
{
e.Cancel = true;
return;
}

Session.Stop("Workspace closed by operator; evidence journal sealed.");
}

try
{
Storage?.SaveNow();
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or InvalidOperationException)
{
var answer = MessageBox.Show(
this,
$"ARSAS could not save the latest IO FAT progress.\n\n{ex.Message}\n\nClose the workspace anyway?",
"Progress save failed",
MessageBoxButton.YesNo,
MessageBoxImage.Error,
MessageBoxResult.No);
if (answer != MessageBoxResult.Yes)
e.Cancel = true;
}
}

private void ShowActionResult(IoTestSessionActionResult result, string title)
Expand All @@ -102,6 +203,13 @@ private void ShowActionResult(IoTestSessionActionResult result, string title)
MessageBox.Show(this, result.Message, title, MessageBoxButton.OK, MessageBoxImage.Warning);
}

private static string SafeFileName(string value)
{
var invalid = Path.GetInvalidFileNameChars().ToHashSet();
var result = new string((value ?? "IO-FAT").Select(ch => invalid.Contains(ch) ? '_' : ch).ToArray()).Trim();
return result.Length == 0 ? "IO-FAT" : result;
}

private void Raise([CallerMemberName] string? propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

Expand Down
Loading
Loading