diff --git a/src/Progress.Samples.Bar.App/Program.cs b/src/Progress.Samples.Bar.App/Program.cs
index e8bd44c..a86fe9b 100644
--- a/src/Progress.Samples.Bar.App/Program.cs
+++ b/src/Progress.Samples.Bar.App/Program.cs
@@ -1,6 +1,7 @@
using Progress;
using Progress.Descriptors;
using Progress.Samples;
+using Progress.Settings;
var onProgress = (Stats stats) =>
{
@@ -21,6 +22,7 @@
.DisplayingItemsOverview()
.NotifyingProgress(onProgress)
.NotifyingCompletion(onCompletion)
+ .ExportingTo("output.json", FileType.Json)
.UsingReportingFrequency(TimeSpan.FromMilliseconds(50))
.UsingComponentDescriptor(BarDescriptor.Default)
.Build(Worker.AllItems);
diff --git a/src/Progress.Samples.HearthBeat.App/Program.cs b/src/Progress.Samples.HearthBeat.App/Program.cs
index 5a0e56d..caff2cb 100644
--- a/src/Progress.Samples.HearthBeat.App/Program.cs
+++ b/src/Progress.Samples.HearthBeat.App/Program.cs
@@ -1,6 +1,7 @@
using Progress;
using Progress.Descriptors;
using Progress.Samples;
+using Progress.Settings;
var onProgress = (Stats stats) =>
{
@@ -21,6 +22,7 @@
.DisplayingItemsOverview()
.NotifyingProgress(onProgress)
.NotifyingCompletion(onCompletion)
+ .ExportingTo("output.csv", FileType.Csv)
.UsingReportingFrequency(TimeSpan.FromMilliseconds(50))
.UsingComponentDescriptor(HearthBeatDescriptor.Default)
.Build(Worker.AllItems);
diff --git a/src/Progress.Samples.Pulse.App/Program.cs b/src/Progress.Samples.Pulse.App/Program.cs
index 3bb2dab..20b1d64 100644
--- a/src/Progress.Samples.Pulse.App/Program.cs
+++ b/src/Progress.Samples.Pulse.App/Program.cs
@@ -1,6 +1,7 @@
using Progress;
using Progress.Descriptors;
using Progress.Samples;
+using Progress.Settings;
var onProgress = (Stats stats) =>
{
@@ -22,6 +23,7 @@
.DisplayingItemsOverview()
.NotifyingProgress(onProgress)
.NotifyingCompletion(onCompletion)
+ .ExportingTo("output.txt", FileType.Text)
.UsingReportingFrequency(TimeSpan.FromMilliseconds(50))
.UsingComponentDescriptor(PulseDescriptor.Default)
.Build(Worker.AllItems);
diff --git a/src/Progress.Samples.Spinner.App/Program.cs b/src/Progress.Samples.Spinner.App/Program.cs
index d593589..6879109 100644
--- a/src/Progress.Samples.Spinner.App/Program.cs
+++ b/src/Progress.Samples.Spinner.App/Program.cs
@@ -22,6 +22,7 @@
.DisplayingItemsOverview()
.NotifyingProgress(onProgress)
.NotifyingCompletion(onCompletion)
+ .ExportingTo("output.xml", Progress.Settings.FileType.Xml)
.UsingReportingFrequency(TimeSpan.FromMilliseconds(50))
.UsingComponentDescriptor(SpinnerDescriptor.Default)
.Build(Worker.AllItems);
diff --git a/src/Progress.UnitTest/ExporterTests.cs b/src/Progress.UnitTest/ExporterTests.cs
new file mode 100644
index 0000000..ae72d62
--- /dev/null
+++ b/src/Progress.UnitTest/ExporterTests.cs
@@ -0,0 +1,38 @@
+using Progress.Settings;
+
+namespace Progress.UnitTest;
+
+public class ExporterTests
+{
+ private readonly Stats _stats = new()
+ {
+ StartedOn = DateTimeOffset.Now.AddMinutes(-1),
+ CurrentCount = 1000,
+ CurrentPercent = 100,
+ ElapsedTime = TimeSpan.FromMinutes(1),
+ EstTimeOfArrival = DateTimeOffset.UtcNow,
+ ExpectedItems = 1000,
+ FailureCount = 30,
+ SuccessCount = 970,
+ RemainingTime = TimeSpan.Zero
+ };
+
+
+ [Theory]
+ [InlineData("output.csv", FileType.Csv)]
+ [InlineData("output.json", FileType.Json)]
+ [InlineData("output.txt", FileType.Text)]
+ [InlineData("output.xml", FileType.Xml)]
+ public void GivenStats_WhenExporting_ThenExportsSuccessfully(string fileName, FileType fileType)
+ {
+ // Arrange
+ ExportSettings settings = new(fileName, fileType);
+ Exporter exporter = new(settings);
+
+ // Act
+ exporter.Export(_stats);
+
+ // Assert
+ File.Exists(fileName).Should().BeTrue();
+ }
+}
diff --git a/src/Progress.UnitTest/PrinterTests.cs b/src/Progress.UnitTest/PrinterTests.cs
new file mode 100644
index 0000000..c1e5582
--- /dev/null
+++ b/src/Progress.UnitTest/PrinterTests.cs
@@ -0,0 +1,20 @@
+using Progress.Descriptors;
+
+namespace Progress.UnitTest;
+
+public class PrinterTests
+{
+ [Fact]
+ public void GivenDefaults_WhenPrinting_ThenGetsOutput()
+ {
+ // Arrange
+ Printer printer = new(new Settings.ReportingOptions(), BarDescriptor.Default.Build());
+ Stats stats = new();
+
+ // Act
+ var actual = printer.Print(stats);
+
+ // Assert
+ actual.Should().NotBeNullOrEmpty();
+ }
+}
diff --git a/src/Progress.UnitTest/ReporterBuilderTests.cs b/src/Progress.UnitTest/ReporterBuilderTests.cs
index 8c7340c..fc39528 100644
--- a/src/Progress.UnitTest/ReporterBuilderTests.cs
+++ b/src/Progress.UnitTest/ReporterBuilderTests.cs
@@ -1,4 +1,6 @@
-namespace Progress.UnitTest;
+using Progress.Settings;
+
+namespace Progress.UnitTest;
public class ReporterBuilderTests
{
@@ -37,7 +39,7 @@ public void GivenReportingFrequency_WhenBuilding_ThenReporterIsSetUp()
.UsingReportingFrequency(expectedFrequency);
// Act
- var actual = builder.Build(100);
+ var actual = builder.Build(100).Configuration;
// Assert
actual.ReportFrequency.Should().Be(expectedFrequency);
@@ -51,7 +53,7 @@ public void GivenElapsedTime_WhenBuilding_ThenReporterIsSetUp()
.DisplayingElapsedTime();
// Act
- var actual = builder.Build(100);
+ var actual = builder.Build(100).Configuration.Options;
// Assert
actual.DisplayElapsedTime.Should().BeTrue();
@@ -65,7 +67,7 @@ public void GivenStartingTime_WhenBuilding_ThenReporterIsSetUp()
.DisplayingStartingTime();
// Act
- var actual = builder.Build(100);
+ var actual = builder.Build(100).Configuration.Options;
// Assert
actual.DisplayStartingTime.Should().BeTrue();
@@ -79,7 +81,7 @@ public void GivenItemsOverview_WhenBuilding_ThenReporterIsSetUp()
.DisplayingItemsOverview();
// Act
- var actual = builder.Build(100);
+ var actual = builder.Build(100).Configuration.Options;
// Assert
actual.DisplayItemsOverview.Should().BeTrue();
@@ -93,7 +95,7 @@ public void GivenSummary_WhenBuilding_ThenReporterIsSetUp()
.DisplayingItemsSummary();
// Act
- var actual = builder.Build(100);
+ var actual = builder.Build(100).Configuration.Options;
// Assert
actual.DisplayItemsSummary.Should().BeTrue();
@@ -113,7 +115,7 @@ public void GivenProgressNotifications_WhenBuilding_ThenReporterIsSetUp()
// Arrange
actual.OnProgress.Should().NotBeNull();
actual.OnProgress.Should().Be(callback);
- actual.StatsFrequency.Should().Be(TimeSpan.FromSeconds(5));
+ actual.Configuration.StatsFrequency.Should().Be(TimeSpan.FromSeconds(5));
}
[Fact]
@@ -131,7 +133,8 @@ public void GivenProgressNotificationsWithFrequency_WhenBuilding_ThenReporterIsS
// Arrange
actual.OnProgress.Should().NotBeNull();
actual.OnProgress.Should().Be(callback);
- actual.StatsFrequency.Should().Be(frequency);
+ actual.Configuration.StatsFrequency.Should().Be(frequency);
+ actual.Configuration.Options.NotifyProgressStats.Should().BeTrue();
}
[Fact]
@@ -148,6 +151,25 @@ public void GivenCompletionNotifications_WhenBuilding_ThenReporterIsSetUp()
// Arrange
actual.OnCompletion.Should().NotBeNull();
actual.OnCompletion.Should().Be(callback);
+ actual.Configuration.Options.NotifyCompletionStats.Should().BeTrue();
}
+ [Fact]
+ public void GivenExporting_WhenBuilding_ThenReporterIsSetUp()
+ {
+ // Arrange
+ const string fileName = "output.txt";
+ const FileType type = FileType.Text;
+ var builder = new ReporterBuilder()
+ .ExportingTo(fileName, type);
+
+ // Act
+ var actual = builder.Build(100);
+
+ // Assert
+ actual.Configuration.ExportSettings.Should().NotBeNull();
+ actual.Configuration.ExportSettings.FileName.Should().Be(fileName);
+ actual.Configuration.ExportSettings.FileType.Should().Be(FileType.Text);
+ actual.Configuration.Options.ExportCompletionStats.Should().BeTrue();
+ }
}
diff --git a/src/Progress.UnitTest/ReporterTests.cs b/src/Progress.UnitTest/ReporterTests.cs
index 6b0ddd1..8d760af 100644
--- a/src/Progress.UnitTest/ReporterTests.cs
+++ b/src/Progress.UnitTest/ReporterTests.cs
@@ -19,16 +19,18 @@ public void GivenDefaults_WhenInitializing_ThenExpectedSettings()
var reporter = new Reporter(100, BarDescriptor.Default.Build());
// Assert
- reporter.DisplayStartingTime.Should().BeTrue();
- reporter.DisplayRemainingTime.Should().BeTrue();
- reporter.DisplayElapsedTime.Should().BeTrue();
- reporter.DisplayEstimatedTimeOfArrival.Should().BeTrue();
- reporter.DisplayItemsOverview.Should().BeTrue();
- reporter.DisplayItemsSummary.Should().BeTrue();
- reporter.NotifyProgressStats.Should().BeTrue();
- reporter.NotifyCompletionStats.Should().BeTrue();
- reporter.ReportFrequency.Should().Be(TimeSpan.FromSeconds(1));
- reporter.StatsFrequency.Should().Be(TimeSpan.FromSeconds(5));
+ reporter.Configuration.Options.DisplayStartingTime.Should().BeTrue();
+ reporter.Configuration.Options.DisplayRemainingTime.Should().BeTrue();
+ reporter.Configuration.Options.DisplayElapsedTime.Should().BeTrue();
+ reporter.Configuration.Options.DisplayEstimatedTimeOfArrival.Should().BeTrue();
+ reporter.Configuration.Options.DisplayItemsOverview.Should().BeTrue();
+ reporter.Configuration.Options.DisplayItemsSummary.Should().BeTrue();
+ reporter.Configuration.Options.NotifyProgressStats.Should().BeFalse();
+ reporter.Configuration.Options.NotifyCompletionStats.Should().BeFalse();
+ reporter.Configuration.Options.ExportCompletionStats.Should().BeFalse();
+ reporter.Configuration.ReportFrequency.Should().Be(TimeSpan.FromSeconds(1));
+ reporter.Configuration.StatsFrequency.Should().Be(TimeSpan.FromSeconds(5));
+ reporter.Configuration.ExportSettings.Should().BeNull();
reporter.OnProgress.Should().BeNull();
reporter.OnCompletion.Should().BeNull();
}
@@ -123,10 +125,11 @@ public async Task GivenProgressNotifications_WhenRunning_ThenCallbackIsCalled()
bool isCalled = false;
var reporter = new Reporter(100, BarDescriptor.Default.Build())
{
- NotifyProgressStats = true,
- StatsFrequency = TimeSpan.FromSeconds(1),
OnProgress = (stats) => isCalled = true
};
+
+ reporter.Configuration.Options.NotifyProgressStats = true;
+ reporter.Configuration.StatsFrequency = TimeSpan.FromSeconds(1);
// Act
@@ -144,10 +147,11 @@ public async Task GivenCompletionNotifications_WhenFinished_ThenCallbackIsCalled
bool isCalled = false;
var reporter = new Reporter(1, BarDescriptor.Default.Build())
{
- NotifyCompletionStats = true,
- ReportFrequency = TimeSpan.FromMicroseconds(500),
OnCompletion = (stats) => isCalled = true
};
+
+ reporter.Configuration.Options.NotifyCompletionStats = true;
+ reporter.Configuration.ReportFrequency = TimeSpan.FromMicroseconds(500);
reporter.Start();
diff --git a/src/Progress/Components/Component.cs b/src/Progress/Components/Component.cs
index 0182df2..5d53f5d 100644
--- a/src/Progress/Components/Component.cs
+++ b/src/Progress/Components/Component.cs
@@ -6,7 +6,7 @@ internal abstract class Component
{
private static readonly CultureInfo Culture = new("en-US");
- public Percent CurrentPercent { get; protected set; } = default!;
+ public Percent CurrentPercent { get; protected set; } = Percent.Zero;
public abstract Component Next(ulong availableItems, ulong currentCount);
@@ -14,6 +14,8 @@ internal abstract class Component
internal class Percent
{
+ public static Percent Zero => new(0, 0);
+
private readonly double _percent;
public Percent(ulong items, ulong count)
diff --git a/src/Progress/Exporter.cs b/src/Progress/Exporter.cs
new file mode 100644
index 0000000..7bd9b5b
--- /dev/null
+++ b/src/Progress/Exporter.cs
@@ -0,0 +1,26 @@
+using Progress.Exporters;
+using Progress.Settings;
+using System.Text;
+
+namespace Progress;
+
+///
+/// Exports the operation result in different formats
+///
+internal class Exporter(ExportSettings settings)
+{
+ private readonly ExportSettings _settings = settings;
+ private readonly IEnumerable _exporters = [ new CsvExporter(), new TextExporter(), new JsonExporter(), new XmlExporter() ];
+
+ public void Export(Stats stats)
+ {
+ using FileStream stream = File.Exists(_settings.FileName)
+ ? File.OpenWrite(_settings.FileName)
+ : File.Create(_settings.FileName);
+
+ IContentExporter contentExporter = _exporters.FirstOrDefault(e => e.FileType == _settings.FileType) ?? throw new NotSupportedException("Not supported export type");
+ string content = contentExporter.Export(stats);
+ byte[] result = Encoding.Default.GetBytes(content);
+ stream.Write(result, 0, result.Length);
+ }
+}
diff --git a/src/Progress/Exporters/CsvExporter.cs b/src/Progress/Exporters/CsvExporter.cs
new file mode 100644
index 0000000..44d3b61
--- /dev/null
+++ b/src/Progress/Exporters/CsvExporter.cs
@@ -0,0 +1,30 @@
+using Progress.Settings;
+using System.Text;
+
+namespace Progress.Exporters
+{
+ internal class CsvExporter : IContentExporter
+ {
+ private const char Separator = ';';
+
+ public FileType FileType => FileType.Csv;
+
+ public string Export(Stats stats)
+ {
+ var type = stats.GetType();
+ string[] properyNames = type.GetProperties().Select(p => p.Name).Order().ToArray();
+
+ StringBuilder sBuilder = new();
+ sBuilder.AppendLine(string.Join(Separator, properyNames));
+
+ foreach(string properyName in properyNames)
+ {
+ object value = type.GetProperty(properyName)!.GetValue(stats)!;
+ sBuilder.Append(value.ToString());
+ sBuilder.Append(Separator);
+ }
+
+ return sBuilder.ToString();
+ }
+ }
+}
diff --git a/src/Progress/Exporters/IContentExporter.cs b/src/Progress/Exporters/IContentExporter.cs
new file mode 100644
index 0000000..4e9d433
--- /dev/null
+++ b/src/Progress/Exporters/IContentExporter.cs
@@ -0,0 +1,10 @@
+using Progress.Settings;
+
+namespace Progress.Exporters
+{
+ internal interface IContentExporter
+ {
+ FileType FileType { get; }
+ string Export(Stats stats);
+ }
+}
diff --git a/src/Progress/Exporters/JsonExporter.cs b/src/Progress/Exporters/JsonExporter.cs
new file mode 100644
index 0000000..ab256d8
--- /dev/null
+++ b/src/Progress/Exporters/JsonExporter.cs
@@ -0,0 +1,12 @@
+using Progress.Settings;
+using System.Text.Json;
+
+namespace Progress.Exporters
+{
+ internal class JsonExporter : IContentExporter
+ {
+ public FileType FileType => FileType.Json;
+
+ public string Export(Stats stats) => JsonSerializer.Serialize(stats);
+ }
+}
diff --git a/src/Progress/Exporters/TextExporter.cs b/src/Progress/Exporters/TextExporter.cs
new file mode 100644
index 0000000..32db359
--- /dev/null
+++ b/src/Progress/Exporters/TextExporter.cs
@@ -0,0 +1,31 @@
+using Progress.Settings;
+using System.Text;
+
+namespace Progress.Exporters
+{
+ internal class TextExporter : IContentExporter
+ {
+ private const int LeftPadding = 30;
+ private const int RightPadding = 20;
+
+ public FileType FileType => FileType.Text;
+
+ public string Export(Stats stats)
+ {
+ var type = stats.GetType();
+ string[] properyNames = type.GetProperties().Select(p => p.Name).Order().ToArray();
+
+ StringBuilder sBuilder = new();
+
+ foreach (string properyName in properyNames)
+ {
+ object value = type.GetProperty(properyName)!.GetValue(stats)!;
+ sBuilder.Append($"{properyName}:".PadRight(RightPadding));
+ sBuilder.Append(value.ToString()!.PadLeft(LeftPadding));
+ sBuilder.AppendLine();
+ }
+
+ return sBuilder.ToString();
+ }
+ }
+}
diff --git a/src/Progress/Exporters/XmlExporter.cs b/src/Progress/Exporters/XmlExporter.cs
new file mode 100644
index 0000000..a413f5c
--- /dev/null
+++ b/src/Progress/Exporters/XmlExporter.cs
@@ -0,0 +1,29 @@
+using Progress.Settings;
+using System.Xml;
+
+namespace Progress.Exporters;
+
+internal class XmlExporter : IContentExporter
+{
+ public FileType FileType => FileType.Xml;
+
+ public string Export(Stats stats)
+ {
+ var type = stats.GetType();
+ string[] properyNames = type.GetProperties().Select(p => p.Name).Order().ToArray();
+
+ XmlDocument xmlDoc = new();
+ var root = xmlDoc.CreateElement("Stats");
+
+ foreach (string properyName in properyNames)
+ {
+ object value = type.GetProperty(properyName)!.GetValue(stats)!;
+ var xmlEntry = xmlDoc.CreateElement(properyName);
+ xmlEntry.InnerText = value.ToString()!;
+ root.AppendChild(xmlEntry);
+ }
+
+ xmlDoc.AppendChild(root);
+ return xmlDoc.OuterXml;
+ }
+}
diff --git a/src/Progress/Printer.cs b/src/Progress/Printer.cs
new file mode 100644
index 0000000..d859abb
--- /dev/null
+++ b/src/Progress/Printer.cs
@@ -0,0 +1,69 @@
+using Progress.Components;
+using Progress.Settings;
+using System.Text;
+
+namespace Progress;
+
+internal class Printer(ReportingOptions options, Component component)
+{
+ private const int Left_Padding = 30;
+ private const int Right_Padding = 20;
+
+ private readonly ReportingOptions _options = options;
+ private readonly Component _component = component;
+
+ public string Print(Stats stats)
+ {
+ StringBuilder sBuilder = new();
+
+ if (_options.DisplayStartingTime)
+ {
+ string label = "Process started at:".PadRight(Right_Padding);
+ sBuilder.AppendLine($"{label} {stats.StartedOn.ToString().PadLeft(Left_Padding)}");
+ }
+
+ if (_options.DisplayEstimatedTimeOfArrival)
+ {
+ string label = "ETA:".PadRight(Right_Padding);
+ string value = stats.CurrentPercent == 0
+ ? Timer.Unknowm
+ : stats.EstTimeOfArrival.ToString();
+
+ sBuilder.AppendLine($"{label} {value.PadLeft(Left_Padding)}");
+ }
+
+ if (_options.DisplayElapsedTime)
+ {
+ string label = "Elapsed time:".PadRight(Right_Padding);
+ sBuilder.AppendLine($"{label} {stats.ElapsedTime.ToString().PadLeft(Left_Padding)}");
+ }
+
+ if (_options.DisplayRemainingTime)
+ {
+ string label = "Remaining time:".PadRight(Right_Padding);
+ string value = stats.CurrentPercent == 0
+ ? Timer.Unknowm
+ : stats.RemainingTime.ToString();
+
+ sBuilder.AppendLine($"{label} {value.PadLeft(Left_Padding)}");
+ }
+
+ if (_options.DisplayItemsSummary)
+ {
+ string successLabel = "Successful items:".PadRight(Right_Padding);
+ string unsuccessLabel = "Unsuccessful items:".PadRight(Right_Padding);
+ sBuilder.AppendLine($"{successLabel} {stats.SuccessCount.ToString().PadLeft(Left_Padding)}");
+ sBuilder.AppendLine($"{unsuccessLabel} {stats.FailureCount.ToString().PadLeft(Left_Padding)}");
+ }
+
+ if (_options.DisplayItemsOverview)
+ {
+ string label = "Total items:".PadRight(Right_Padding);
+ sBuilder.AppendLine($"{label} {stats.CurrentCount.ToString().PadLeft(Left_Padding)}");
+ }
+
+ sBuilder.AppendLine();
+ sBuilder.AppendLine(_component.ToString());
+ return sBuilder.ToString();
+ }
+}
diff --git a/src/Progress/Progress.csproj b/src/Progress/Progress.csproj
index 4827469..78a594e 100644
--- a/src/Progress/Progress.csproj
+++ b/src/Progress/Progress.csproj
@@ -9,7 +9,7 @@
Progress
- 1.3.0
+ 1.4.0
MIT
Gerard Castello
Report progress with ease
diff --git a/src/Progress/Reporter.cs b/src/Progress/Reporter.cs
index 5b446f7..9906f3a 100644
--- a/src/Progress/Reporter.cs
+++ b/src/Progress/Reporter.cs
@@ -1,4 +1,5 @@
using Progress.Components;
+using Progress.Settings;
using System.Text;
namespace Progress;
@@ -8,16 +9,15 @@ namespace Progress;
///
public class Reporter : IDisposable
{
- private const int LEFT_PADDING = 30;
- private const int RIGHT_PADDING = 20;
-
private readonly ulong _itemsCount;
+ private readonly Configuration _configuration;
private bool _isDisposed;
private ulong _successCount;
private ulong _failureCount;
private Component _component;
private Timer _timer;
+ private Printer _printer;
private Thread _reportingThread = default!;
private Thread _statsThread = default!;
private CancellationTokenSource _cancellationTokenSource = default!;
@@ -29,31 +29,10 @@ public class Reporter : IDisposable
///
public bool IsFinished => CurrentCount == _itemsCount;
- ///
- /// Gets or sets the progress notification hook which invocation happens during the operation.
- ///
- public Action? OnProgress { get; set; } = null!;
+ internal Configuration Configuration => _configuration;
+ internal Action? OnProgress { get; set; } = null!;
+ internal Action? OnCompletion { get; set; } = null!;
- ///
- /// Gets or sets the completion hook which invocation happens at the end of the operation.
- ///
- public Action? OnCompletion { get; set; } = null!;
-
- internal bool DisplayEstimatedTimeOfArrival { get; set; } = true;
- internal bool DisplayRemainingTime { get; set; } = true;
- internal bool DisplayElapsedTime { get; set; } = true;
- internal bool DisplayStartingTime { get; set; } = true;
- internal bool DisplayItemsOverview { get; set; } = true;
- internal bool DisplayItemsSummary { get; set; } = true;
- internal bool NotifyProgressStats { get; set; } = true;
- internal bool NotifyCompletionStats { get; set; } = true;
-
- internal TimeSpan ReportFrequency { get; set; } = TimeSpan.FromSeconds(1);
- internal TimeSpan StatsFrequency { get; set; } = TimeSpan.FromSeconds(5);
-
- private string AllItems => (_successCount + _failureCount).ToString().PadLeft(10);
- private string SuccessfulItems => _successCount.ToString().PadLeft(10);
- private string UnsuccessfulItems => _failureCount.ToString().PadLeft(10);
private ulong CurrentCount => _successCount + _failureCount;
@@ -68,6 +47,8 @@ internal Reporter(ulong itemsCount, Component component)
_itemsCount = itemsCount;
_component = component;
_timer = Timer.Start();
+ _configuration = new Configuration();
+ _printer = new Printer(Configuration.Options, component);
}
///
@@ -97,7 +78,7 @@ public void Start()
_reportingThread = DoWork();
_reportingThread.Start();
- if (NotifyProgressStats)
+ if (Configuration.Options.NotifyProgressStats)
{
_statsThread = DoStats();
_statsThread.Start();
@@ -138,7 +119,7 @@ public void Resume()
_reportingThread = DoWork();
_reportingThread.Start();
- if (NotifyProgressStats)
+ if (Configuration.Options.NotifyProgressStats)
{
_statsThread = DoStats();
_statsThread.Start();
@@ -151,57 +132,8 @@ public void Resume()
///
public override string ToString()
{
- StringBuilder sBuilder = new();
-
- if (DisplayStartingTime)
- {
- string label = "Process started at:".PadRight(RIGHT_PADDING);
- sBuilder.AppendLine($"{label} {_timer.StartedOn.ToString().PadLeft(LEFT_PADDING)}");
- }
-
- if (DisplayEstimatedTimeOfArrival)
- {
- string label = "ETA:".PadRight(RIGHT_PADDING);
- string value = _component.CurrentPercent.Value == 0
- ? Timer.Unknowm
- : _timer.GetEstimatedTimeOfArrival(_component.CurrentPercent.Value).ToString();
-
- sBuilder.AppendLine($"{label} {value.PadLeft(LEFT_PADDING)}");
- }
-
- if (DisplayElapsedTime)
- {
- string label = "Elapsed time:".PadRight(RIGHT_PADDING);
- sBuilder.AppendLine($"{label} {_timer.ElapsedTime.ToString().PadLeft(LEFT_PADDING)}");
- }
-
- if (DisplayRemainingTime)
- {
- string label = "Remaining time:".PadRight(RIGHT_PADDING);
- string value = _component.CurrentPercent.Value == 0
- ? Timer.Unknowm
- : _timer.GetRemainingTime(_component.CurrentPercent.Value).ToString();
-
- sBuilder.AppendLine($"{label} {value.PadLeft(LEFT_PADDING)}");
- }
-
- if (DisplayItemsSummary)
- {
- string successLabel = "Successful items:".PadRight(RIGHT_PADDING);
- string unsuccessLabel = "Unsuccessful items:".PadRight(RIGHT_PADDING);
- sBuilder.AppendLine($"{successLabel} {SuccessfulItems.ToString().PadLeft(LEFT_PADDING)}");
- sBuilder.AppendLine($"{unsuccessLabel} {UnsuccessfulItems.ToString().PadLeft(LEFT_PADDING)}");
- }
-
- if (DisplayItemsOverview)
- {
- string label = "Total items:".PadRight(RIGHT_PADDING);
- sBuilder.AppendLine($"{label} {AllItems.ToString().PadLeft(LEFT_PADDING)}");
- }
-
- sBuilder.AppendLine();
- sBuilder.AppendLine(_component.ToString());
- return sBuilder.ToString();
+ var stats = CollectStats();
+ return _printer.Print(stats);
}
///
@@ -248,7 +180,7 @@ private void Display()
private void ReportStats()
{
- if (OnProgress == null || DateTimeOffset.UtcNow - _lastStatsNotification < StatsFrequency)
+ if (OnProgress == null || DateTimeOffset.UtcNow - _lastStatsNotification < Configuration.StatsFrequency)
return;
var stats = CollectStats();
@@ -263,16 +195,19 @@ private Thread DoWork()
do
{
Display();
- Thread.Sleep(ReportFrequency);
+ Thread.Sleep(Configuration.ReportFrequency);
}
while (!IsFinished && !_cancellationToken.IsCancellationRequested);
Display();
- if (IsFinished && NotifyCompletionStats)
+ if (IsFinished && (Configuration.Options.NotifyCompletionStats || Configuration.Options.ExportCompletionStats))
{
var stats = CollectStats();
OnCompletion?.Invoke(stats);
+
+ if (Configuration.ExportSettings != null)
+ new Exporter(Configuration.ExportSettings).Export(stats);
}
});
@@ -286,7 +221,7 @@ private Thread DoStats()
do
{
ReportStats();
- Thread.Sleep(StatsFrequency);
+ Thread.Sleep(Configuration.StatsFrequency);
}
while (!IsFinished && !_cancellationToken.IsCancellationRequested);
});
diff --git a/src/Progress/ReporterBuilder.cs b/src/Progress/ReporterBuilder.cs
index 83547bb..392dbd4 100644
--- a/src/Progress/ReporterBuilder.cs
+++ b/src/Progress/ReporterBuilder.cs
@@ -1,4 +1,5 @@
using Progress.Descriptors;
+using Progress.Settings;
namespace Progress;
@@ -17,6 +18,7 @@ public class ReporterBuilder
private TimeSpan _reportFrequency = TimeSpan.FromSeconds(1);
private TimeSpan _statsFrequency = TimeSpan.FromSeconds(5);
private ComponentDescriptor _componentDescriptor = BarDescriptor.Default;
+ private ExportSettings _exportSettings = default!;
private Action _onProgressNotified = default!;
private Action _onCompletionNotified = default!;
@@ -138,6 +140,18 @@ public ReporterBuilder NotifyingCompletion(Action callback)
return this;
}
+ ///
+ /// Sets and tells the reporter how and where to export the final stats.
+ ///
+ ///
+ ///
+ ///
+ public ReporterBuilder ExportingTo(string fileName, FileType fileType)
+ {
+ _exportSettings = new ExportSettings(fileName, fileType);
+ return this;
+ }
+
///
/// Builds the reporte getting an instance of .
///
@@ -150,21 +164,26 @@ public Reporter Build(ulong itemsCount)
throw new ArgumentException("Nothing to do! Set the initial items count for completion.");
var component = _componentDescriptor.Build();
-
- return new Reporter(itemsCount, component)
+
+ var reporter = new Reporter(itemsCount, component)
{
- DisplayRemainingTime = _displayRemainingTime,
- DisplayEstimatedTimeOfArrival = _displayEstTimeOfArrival,
- DisplayElapsedTime = _displayElapsedTime,
- DisplayStartingTime = _displayStartingTime,
- DisplayItemsOverview = _displayItemsOverview,
- DisplayItemsSummary = _displayItemsSummary,
- NotifyProgressStats = _onProgressNotified != null,
- NotifyCompletionStats = _onCompletionNotified != null,
- ReportFrequency = _reportFrequency,
- StatsFrequency = _statsFrequency,
OnProgress = _onProgressNotified,
OnCompletion = _onCompletionNotified
};
+
+ reporter.Configuration.ReportFrequency = _reportFrequency;
+ reporter.Configuration.StatsFrequency = _statsFrequency;
+ reporter.Configuration.ExportSettings = _exportSettings;
+ reporter.Configuration.Options.DisplayRemainingTime = _displayRemainingTime;
+ reporter.Configuration.Options.DisplayEstimatedTimeOfArrival = _displayEstTimeOfArrival;
+ reporter.Configuration.Options.DisplayElapsedTime = _displayElapsedTime;
+ reporter.Configuration.Options.DisplayStartingTime = _displayStartingTime;
+ reporter.Configuration.Options.DisplayItemsOverview = _displayItemsOverview;
+ reporter.Configuration.Options.DisplayItemsSummary = _displayItemsSummary;
+ reporter.Configuration.Options.NotifyProgressStats = _onProgressNotified != null;
+ reporter.Configuration.Options.NotifyCompletionStats = _onCompletionNotified != null;
+ reporter.Configuration.Options.ExportCompletionStats = _exportSettings != null;
+
+ return reporter;
}
}
diff --git a/src/Progress/Settings/Configuration.cs b/src/Progress/Settings/Configuration.cs
new file mode 100644
index 0000000..0f4825b
--- /dev/null
+++ b/src/Progress/Settings/Configuration.cs
@@ -0,0 +1,9 @@
+namespace Progress.Settings;
+
+internal class Configuration
+{
+ public ReportingOptions Options { get; init; } = new();
+ public ExportSettings ExportSettings { get; set; } = default!;
+ public TimeSpan ReportFrequency { get; set; } = TimeSpan.FromSeconds(1);
+ public TimeSpan StatsFrequency { get; set; } = TimeSpan.FromSeconds(5);
+}
diff --git a/src/Progress/Settings/ExportSettings.cs b/src/Progress/Settings/ExportSettings.cs
new file mode 100644
index 0000000..c6778e9
--- /dev/null
+++ b/src/Progress/Settings/ExportSettings.cs
@@ -0,0 +1,26 @@
+namespace Progress.Settings;
+
+///
+/// Supported export file types
+///
+public enum FileType
+{
+ ///
+ /// CSV
+ ///
+ Csv,
+ ///
+ /// TXT
+ ///
+ Text,
+ ///
+ /// JSON
+ ///
+ Json,
+ ///
+ /// XML
+ ///
+ Xml
+}
+
+internal record ExportSettings(string FileName, FileType FileType);
\ No newline at end of file
diff --git a/src/Progress/Settings/ReportingOptions.cs b/src/Progress/Settings/ReportingOptions.cs
new file mode 100644
index 0000000..91a5a5b
--- /dev/null
+++ b/src/Progress/Settings/ReportingOptions.cs
@@ -0,0 +1,14 @@
+namespace Progress.Settings;
+
+internal class ReportingOptions
+{
+ internal bool DisplayEstimatedTimeOfArrival { get; set; } = true;
+ internal bool DisplayRemainingTime { get; set; } = true;
+ internal bool DisplayElapsedTime { get; set; } = true;
+ internal bool DisplayStartingTime { get; set; } = true;
+ internal bool DisplayItemsOverview { get; set; } = true;
+ internal bool DisplayItemsSummary { get; set; } = true;
+ internal bool NotifyProgressStats { get; set; } = false;
+ internal bool NotifyCompletionStats { get; set; } = false;
+ internal bool ExportCompletionStats { get; set; } = false;
+}