diff --git a/.gitignore b/.gitignore index aa33348..b85a546 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,8 @@ bld/ # Visual Studio 2015/2017 cache/options directory .vs/ +.idea/ +.vscode/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ diff --git a/src/BinaryPatrick.Prune.csproj b/src/BinaryPatrick.Prune.csproj index 055a062..9351a66 100644 --- a/src/BinaryPatrick.Prune.csproj +++ b/src/BinaryPatrick.Prune.csproj @@ -8,7 +8,7 @@ enable prune BinaryPatrick - Copyright 2023-2024 + Copyright 2023-2025 1.1.0 @@ -18,11 +18,11 @@ - - - - - + + + + + diff --git a/src/Interfaces/IDirectoryService.cs b/src/Interfaces/IDirectoryService.cs index 57326a2..3223c2a 100644 --- a/src/Interfaces/IDirectoryService.cs +++ b/src/Interfaces/IDirectoryService.cs @@ -8,7 +8,7 @@ public interface IDirectoryService { /// Retrieves files using the arguments provided for , , and /// Files matching the search path and pattern - IEnumerable GetFiles(); + ICollection GetFiles(); /// Deletes the given files when is /// Files to be deleted diff --git a/src/Models/PruneOptions.cs b/src/Models/PruneOptions.cs index b5f786e..58c168f 100644 --- a/src/Models/PruneOptions.cs +++ b/src/Models/PruneOptions.cs @@ -33,6 +33,12 @@ public class PruneOptions /// [Option('s', "silent", Required = false, HelpText = "Disable all logging", SetName = OptionsSetName.LoggingSilent, Default = false)] public bool IsSilent { get; set; } = false; + + /// + /// Disables logging + /// + [Option('z', "utc", Required = false, HelpText = "Use UTC Timezone for evaluating file time", Default = false)] + public bool UseUtc { get; set; } = false; /// /// File name prefix to use when matching archives @@ -43,7 +49,7 @@ public class PruneOptions /// /// File extension to use when matching archives /// - [Option('e', "ext", Required = false, HelpText = "File extension to use when matching archives, i.e. img, txt, tar.gz (do not include dot)")] + [Option('e', "ext", Required = false, HelpText = "File extension to use when matching archives, i.e. img, txt, tar.gz (do not include leading dot)")] public string? FileExtension { get; set; } /// diff --git a/src/Models/RetentionSorter.cs b/src/Models/RetentionSorter.cs index af1b84b..480e315 100644 --- a/src/Models/RetentionSorter.cs +++ b/src/Models/RetentionSorter.cs @@ -8,26 +8,28 @@ namespace BinaryPatrick.Prune.Models; public class RetentionSorter : IRetentionSorter, IInitializedRetentionSorter, ILastSortedRetentionSorter, IHourlySortedRetentionSorter, IDailySortedRetentionSorter, IWeeklySortedRetentionSorter, IMonthlySortedRetentionSorter, ISortedRetentionSorter { private readonly IConsoleLogger logger; + private readonly TimeSpan offset; private readonly IEnumerator enumerator; private DateTimeOffset lastTimestamp; - /// + /// public IRetentionSortResult Result { get; } = new RetentionSortResult(); /// Initializes a new instance of the class - public RetentionSorter(IConsoleLogger logger, IEnumerable files) + public RetentionSorter(IConsoleLogger logger, IEnumerable files, TimeSpan offset) { logger.LogTrace($"Constructing {nameof(RetentionSorter)}"); this.logger = logger; + this.offset = offset; lastTimestamp = DateTimeOffset.MinValue; enumerator = files .OrderByDescending(x => x.LastModified) .GetEnumerator(); } - /// + /// public ILastSortedRetentionSorter KeepLast(uint count) { logger.LogTrace($"Entering {nameof(RetentionSorter)}.{nameof(KeepLast)}"); @@ -40,14 +42,14 @@ public ILastSortedRetentionSorter KeepLast(uint count) while (Result.Last.Count < count && enumerator.MoveNext()) { Result.Last.Add(enumerator.Current); - lastTimestamp = enumerator.Current.LastModified; + lastTimestamp = enumerator.Current.LastModified.ToOffset(offset); LogKeeping(LabelConstant.KeepLast, enumerator.Current.Name); } return this; } - /// + /// public IHourlySortedRetentionSorter KeepHourly(uint count) { logger.LogTrace($"Entering {nameof(RetentionSorter)}.{nameof(KeepHourly)}"); @@ -59,7 +61,7 @@ public IHourlySortedRetentionSorter KeepHourly(uint count) while (Result.Hourly.Count < count && enumerator.MoveNext()) { - DateTimeOffset timestamp = enumerator.Current.LastModified; + DateTimeOffset timestamp = enumerator.Current.LastModified.ToOffset(offset); if (timestamp.Year != lastTimestamp.Year || timestamp.Month != lastTimestamp.Month || timestamp.Day != lastTimestamp.Day || timestamp.Hour != lastTimestamp.Hour) { Result.Hourly.Add(enumerator.Current); @@ -78,7 +80,7 @@ public IHourlySortedRetentionSorter KeepHourly(uint count) return this; } - /// + /// public IDailySortedRetentionSorter KeepDaily(uint count) { logger.LogTrace($"Entering {nameof(RetentionSorter)}.{nameof(KeepDaily)}"); @@ -90,7 +92,7 @@ public IDailySortedRetentionSorter KeepDaily(uint count) while (Result.Daily.Count < count && enumerator.MoveNext()) { - DateTimeOffset timestamp = enumerator.Current.LastModified; + DateTimeOffset timestamp = enumerator.Current.LastModified.ToOffset(offset); if (timestamp.Year != lastTimestamp.Year || timestamp.Month != lastTimestamp.Month || timestamp.Day != lastTimestamp.Day) { Result.Daily.Add(enumerator.Current); @@ -109,7 +111,7 @@ public IDailySortedRetentionSorter KeepDaily(uint count) return this; } - /// + /// public IWeeklySortedRetentionSorter KeepWeekly(uint count) { logger.LogTrace($"Entering {nameof(RetentionSorter)}.{nameof(KeepWeekly)}"); @@ -121,7 +123,7 @@ public IWeeklySortedRetentionSorter KeepWeekly(uint count) while (Result.Weekly.Count < count && enumerator.MoveNext()) { - DateTimeOffset timestamp = enumerator.Current.LastModified; + DateTimeOffset timestamp = enumerator.Current.LastModified.ToOffset(offset); if (ISOWeek.GetYear(timestamp.DateTime) != ISOWeek.GetYear(lastTimestamp.DateTime) || ISOWeek.GetWeekOfYear(timestamp.DateTime) != ISOWeek.GetWeekOfYear(lastTimestamp.DateTime)) { Result.Weekly.Add(enumerator.Current); @@ -141,7 +143,7 @@ public IWeeklySortedRetentionSorter KeepWeekly(uint count) } - /// + /// public IMonthlySortedRetentionSorter KeepMonthly(uint count) { logger.LogTrace($"Entering {nameof(RetentionSorter)}.{nameof(KeepMonthly)}"); @@ -153,7 +155,7 @@ public IMonthlySortedRetentionSorter KeepMonthly(uint count) while (Result.Monthly.Count < count && enumerator.MoveNext()) { - DateTimeOffset timestamp = enumerator.Current.LastModified; + DateTimeOffset timestamp = enumerator.Current.LastModified.ToOffset(offset); if (timestamp.Year != lastTimestamp.Year || timestamp.Month != lastTimestamp.Month) { Result.Monthly.Add(enumerator.Current); @@ -172,7 +174,7 @@ public IMonthlySortedRetentionSorter KeepMonthly(uint count) return this; } - /// + /// public ISortedRetentionSorter KeepYearly(uint count) { logger.LogTrace($"Entering {nameof(RetentionSorter)}.{nameof(KeepYearly)}"); @@ -184,7 +186,7 @@ public ISortedRetentionSorter KeepYearly(uint count) while (Result.Yearly.Count < count && enumerator.MoveNext()) { - DateTimeOffset timestamp = enumerator.Current.LastModified; + DateTimeOffset timestamp = enumerator.Current.LastModified.ToOffset(offset); if (timestamp.Year != lastTimestamp.Year) { Result.Yearly.Add(enumerator.Current); @@ -204,7 +206,7 @@ public ISortedRetentionSorter KeepYearly(uint count) return this; } - /// + /// public ISortedRetentionSorter PruneRemaining() { logger.LogTrace($"Entering {nameof(RetentionSorter)}.{nameof(PruneRemaining)}"); diff --git a/src/Services/DirectoryService.cs b/src/Services/DirectoryService.cs index 246c443..fd4066d 100644 --- a/src/Services/DirectoryService.cs +++ b/src/Services/DirectoryService.cs @@ -22,7 +22,7 @@ public DirectoryService(IConsoleLogger logger, PruneOptions options) } /// - public IEnumerable GetFiles() + public ICollection GetFiles() { logger.LogTrace($"Entering {nameof(DirectoryService)}.{nameof(GetFiles)}"); diff --git a/src/Services/PruneService.cs b/src/Services/PruneService.cs index 0448ed2..caa8d48 100644 --- a/src/Services/PruneService.cs +++ b/src/Services/PruneService.cs @@ -34,8 +34,8 @@ public void PruneFiles() return; } - IEnumerable files = directoryService.GetFiles(); - if (!files.Any()) + ICollection files = directoryService.GetFiles(); + if (files.Count == 0) { return; } diff --git a/src/Services/RetentionSorterFactory.cs b/src/Services/RetentionSorterFactory.cs index f7e9881..6e8e547 100644 --- a/src/Services/RetentionSorterFactory.cs +++ b/src/Services/RetentionSorterFactory.cs @@ -7,12 +7,14 @@ namespace BinaryPatrick.Prune.Services; public class RetentionSorterFactory : IRetentionSorterFactory { private readonly IConsoleLogger logger; + private readonly PruneOptions options; /// Initializes a new instance of the class - public RetentionSorterFactory(IConsoleLogger logger) + public RetentionSorterFactory(IConsoleLogger logger, PruneOptions options) { logger.LogTrace($"Constructing {nameof(RetentionSorterFactory)}"); this.logger = logger; + this.options = options; } /// @@ -20,6 +22,7 @@ public IInitializedRetentionSorter CreateRetentionSorter(IEnumerable { logger.LogTrace($"Entering {nameof(RetentionSorterFactory)}.{nameof(CreateRetentionSorter)}"); - return new RetentionSorter(logger, files); + TimeSpan offset = options.UseUtc ? TimeZoneInfo.Utc.BaseUtcOffset : TimeZoneInfo.Local.BaseUtcOffset; + return new RetentionSorter(logger, files, offset); } } diff --git a/tests/BinaryPatrick.Prune.Unit.csproj b/tests/BinaryPatrick.Prune.Unit.csproj index de16109..4b589c1 100644 --- a/tests/BinaryPatrick.Prune.Unit.csproj +++ b/tests/BinaryPatrick.Prune.Unit.csproj @@ -10,16 +10,16 @@ - + - + - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/tests/Tests/RetentionSorterTests/RetentionSorterTests.KeepDaily.cs b/tests/Tests/RetentionSorterTests/RetentionSorterTests.KeepDaily.cs index 08c2b2f..c675d18 100644 --- a/tests/Tests/RetentionSorterTests/RetentionSorterTests.KeepDaily.cs +++ b/tests/Tests/RetentionSorterTests/RetentionSorterTests.KeepDaily.cs @@ -15,7 +15,7 @@ public void KeepDaily_WithZero_ShouldNotTake() DateTimeOffset startTime = new DateTimeOffset(2023, 1, 1, 0, 0, 0, TimeSpan.Zero); IEnumerable files = FileInfoMockFactory.GetFileInfoCollectionFake(15, startTime, TimeSpan.FromMinutes(15)); - RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files); + RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files, TimeSpan.Zero); int keep = 0; // Act @@ -41,7 +41,7 @@ public void KeepDaily_With1_ShouldTake1() DateTimeOffset startTime = new DateTimeOffset(2023, 1, 1, 0, 0, 0, TimeSpan.Zero); IEnumerable files = FileInfoMockFactory.GetFileInfoCollectionFake(15, startTime, TimeSpan.FromMinutes(15)); - RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files); + RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files, TimeSpan.Zero); int keep = 1; // Act @@ -72,7 +72,7 @@ public void KeepDaily_WithN_ShouldTakeN(int keep) DateTimeOffset startTime = new DateTimeOffset(2023, 1, 1, 23, 59, 59, TimeSpan.Zero); IEnumerable files = FileInfoMockFactory.GetFileInfoCollectionFake(fileCount, startTime, TimeSpan.FromHours(6)); - RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files); + RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files, TimeSpan.Zero); // Act retentionSorter.KeepDaily((uint)keep); @@ -90,15 +90,38 @@ public void KeepDaily_WithN_ShouldTakeN(int keep) result.Unmatched.Should().NotBeEmpty(); } - [Fact] - public void KeepDaily_WithMinuteIncrements_ShouldTake2() + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + [InlineData(5)] + [InlineData(6)] + [InlineData(7)] + [InlineData(8)] + [InlineData(9)] + [InlineData(10)] + [InlineData(11)] + [InlineData(12)] + [InlineData(13)] + [InlineData(14)] + [InlineData(15)] + [InlineData(16)] + [InlineData(17)] + [InlineData(18)] + [InlineData(19)] + [InlineData(20)] + [InlineData(21)] + [InlineData(23)] + public void KeepDaily_WithMinuteIncrements_ShouldTake2(int hours) { + TimeSpan offset = TimeSpan.FromHours(8); // Arrange - int hours = Random.Shared.Next(0, 23); - DateTimeOffset startTime = new DateTimeOffset(2023, 1, 1, hours, 0, 0, TimeSpan.Zero); + DateTimeOffset startTime = new DateTimeOffset(2023, 1, 1, hours, 0, 0, offset); IEnumerable files = FileInfoMockFactory.GetFileInfoCollectionFake(1500, startTime, TimeSpan.FromMinutes(1)); - RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files); + RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files, offset); // Act retentionSorter.KeepDaily(2); @@ -109,9 +132,7 @@ public void KeepDaily_WithMinuteIncrements_ShouldTake2() result.Daily.Should().HaveCount(2); result.Daily[0].LastModified.Should().BeExactly(startTime); - result.Daily[1].LastModified.Should().BeExactly(startTime.AddHours(-hours).AddMinutes(-1)); - - result.Unmatched.Should().NotBeEmpty(); + result.Daily[1].LastModified.Should().BeExactly(startTime.AddHours(-startTime.Hour).AddMinutes(-1)); } [Fact] @@ -122,7 +143,7 @@ public void KeepDaily_WithPrevious_ShouldTake1() DateTimeOffset startTime = new DateTimeOffset(2023, 1, 1, 1, minutesPastHour, 0, TimeSpan.Zero); IEnumerable files = FileInfoMockFactory.GetFileInfoCollectionFake(70, startTime, TimeSpan.FromMinutes(1)); - RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files); + RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files, TimeSpan.Zero); // Act retentionSorter.KeepLast(1).KeepHourly(1); diff --git a/tests/Tests/RetentionSorterTests/RetentionSorterTests.KeepHourly.cs b/tests/Tests/RetentionSorterTests/RetentionSorterTests.KeepHourly.cs index f97e003..393534a 100644 --- a/tests/Tests/RetentionSorterTests/RetentionSorterTests.KeepHourly.cs +++ b/tests/Tests/RetentionSorterTests/RetentionSorterTests.KeepHourly.cs @@ -15,7 +15,7 @@ public void KeepHourly_WithZero_ShouldNotTake() DateTimeOffset startTime = new DateTimeOffset(2023, 1, 1, 0, 0, 0, TimeSpan.Zero); IEnumerable files = FileInfoMockFactory.GetFileInfoCollectionFake(15, startTime, TimeSpan.FromMinutes(15)); - RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files); + RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files, TimeSpan.Zero); int keep = 0; // Act @@ -41,7 +41,7 @@ public void KeepHourly_With1_ShouldTake1() DateTimeOffset startTime = new DateTimeOffset(2023, 1, 1, 0, 0, 0, TimeSpan.Zero); IEnumerable files = FileInfoMockFactory.GetFileInfoCollectionFake(15, startTime, TimeSpan.FromMinutes(15)); - RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files); + RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files, TimeSpan.Zero); int keep = 1; // Act @@ -72,7 +72,7 @@ public void KeepHourly_WithN_ShouldTakeN(int keep) DateTimeOffset startTime = new DateTimeOffset(2023, 1, 1, 23, 59, 59, TimeSpan.Zero); IEnumerable files = FileInfoMockFactory.GetFileInfoCollectionFake(fileCount, startTime, TimeSpan.FromMinutes(15)); - RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files); + RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files, TimeSpan.Zero); // Act retentionSorter.KeepHourly((uint)keep); @@ -98,7 +98,7 @@ public void KeepHourly_WithMinuteIncrements_ShouldTake2() DateTimeOffset startTime = new DateTimeOffset(2023, 1, 1, 1, minutes, 0, TimeSpan.Zero); IEnumerable files = FileInfoMockFactory.GetFileInfoCollectionFake(70, startTime, TimeSpan.FromMinutes(1)); - RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files); + RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files, TimeSpan.Zero); // Act retentionSorter.KeepHourly(2); @@ -122,7 +122,7 @@ public void KeepHourly_WithPrevious_ShouldTake1() DateTimeOffset startTime = new DateTimeOffset(2023, 1, 1, 1, minutesPastHour, 0, TimeSpan.Zero); IEnumerable files = FileInfoMockFactory.GetFileInfoCollectionFake(70, startTime, TimeSpan.FromMinutes(1)); - RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files); + RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files, TimeSpan.Zero); // Act retentionSorter.KeepLast(1).KeepHourly(1); diff --git a/tests/Tests/RetentionSorterTests/RetentionSorterTests.KeepLast.cs b/tests/Tests/RetentionSorterTests/RetentionSorterTests.KeepLast.cs index e67240c..be7d5ff 100644 --- a/tests/Tests/RetentionSorterTests/RetentionSorterTests.KeepLast.cs +++ b/tests/Tests/RetentionSorterTests/RetentionSorterTests.KeepLast.cs @@ -15,7 +15,7 @@ public void KeepLast_WithZero_ShouldNotTake() DateTimeOffset startTime = new DateTimeOffset(2023, 1, 1, 0, 0, 0, TimeSpan.Zero); IEnumerable files = FileInfoMockFactory.GetFileInfoCollectionFake(15, startTime, TimeSpan.FromMinutes(15)); - RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files); + RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files, TimeSpan.Zero); int keep = 0; // Act @@ -41,7 +41,7 @@ public void KeepLast_With1_ShouldTake1() DateTimeOffset startTime = new DateTimeOffset(2023, 1, 1, 0, 0, 0, TimeSpan.Zero); IEnumerable files = FileInfoMockFactory.GetFileInfoCollectionFake(15, startTime, TimeSpan.FromMinutes(15)); - RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files); + RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files, TimeSpan.Zero); int keep = 1; // Act @@ -73,7 +73,7 @@ public void KeepLast_WithN_ShouldTakeN(int keep) TimeSpan timeBetween = TimeSpan.FromMinutes(15); IEnumerable files = FileInfoMockFactory.GetFileInfoCollectionFake(fileCount, startTime, timeBetween); - RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files); + RetentionSorter retentionSorter = new RetentionSorter(consoleLoggerMock.Object, files, TimeSpan.Zero); // Act retentionSorter.KeepLast((uint)keep); diff --git a/tests/Tests/RetentionSorterTests/RetentionSorterTests.cs b/tests/Tests/RetentionSorterTests/RetentionSorterTests.cs index ea45b92..4a1be5f 100644 --- a/tests/Tests/RetentionSorterTests/RetentionSorterTests.cs +++ b/tests/Tests/RetentionSorterTests/RetentionSorterTests.cs @@ -112,7 +112,7 @@ private void RunSimulation(int keepLast, int keepHourly, int keepDaily, int keep .Concat(result.Daily).Concat(result.Weekly) .Concat(result.Monthly).Concat(result.Yearly); - RetentionSorter sorter = new RetentionSorter(consoleLoggerMock.Object, retained); + RetentionSorter sorter = new RetentionSorter(consoleLoggerMock.Object, retained, TimeSpan.Zero); sorter.KeepLast((uint)keepLast).KeepHourly((uint)keepHourly) .KeepDaily((uint)keepDaily).KeepWeekly((uint)keepWeekly) .KeepMonthly((uint)keepMonthly).KeepYearly((uint)keepYearly);