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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/

Expand Down
12 changes: 6 additions & 6 deletions src/BinaryPatrick.Prune.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<Nullable>enable</Nullable>
<AssemblyName>prune</AssemblyName>
<Authors>BinaryPatrick</Authors>
<Copyright>Copyright 2023-2024</Copyright>
<Copyright>Copyright 2023-2025</Copyright>
<Version>1.1.0</Version>
</PropertyGroup>

Expand All @@ -18,11 +18,11 @@

<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.10" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.10" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Abstractions" Version="9.0.10" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="9.0.10" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.10" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Interfaces/IDirectoryService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public interface IDirectoryService
{
/// <summary>Retrieves files using the arguments provided for <see cref="PruneOptions.Path"/>, <see cref="PruneOptions.FilePrefix"/>, and <see cref="PruneOptions.FileExtension"/></summary>
/// <returns>Files matching the search path and pattern</returns>
IEnumerable<IFileInfo> GetFiles();
ICollection<IFileInfo> GetFiles();

/// <summary>Deletes the given files when <see cref="PruneOptions.IsDryRun"/> is <see langword="false"/></summary>
/// <param name="files">Files to be deleted</param>
Expand Down
8 changes: 7 additions & 1 deletion src/Models/PruneOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ public class PruneOptions
/// </summary>
[Option('s', "silent", Required = false, HelpText = "Disable all logging", SetName = OptionsSetName.LoggingSilent, Default = false)]
public bool IsSilent { get; set; } = false;

/// <summary>
/// Disables logging
/// </summary>
[Option('z', "utc", Required = false, HelpText = "Use UTC Timezone for evaluating file time", Default = false)]
public bool UseUtc { get; set; } = false;

/// <summary>
/// File name prefix to use when matching archives
Expand All @@ -43,7 +49,7 @@ public class PruneOptions
/// <summary>
/// File extension to use when matching archives
/// </summary>
[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; }

/// <summary>
Expand Down
32 changes: 17 additions & 15 deletions src/Models/RetentionSorter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IFileInfo> enumerator;

private DateTimeOffset lastTimestamp;

/// <inheritdoc/>
/// <inheritdoc cref="Result"/>
public IRetentionSortResult Result { get; } = new RetentionSortResult();

/// <summary>Initializes a new instance of the <see cref="RetentionSorter"/> class</summary>
public RetentionSorter(IConsoleLogger logger, IEnumerable<IFileInfo> files)
public RetentionSorter(IConsoleLogger logger, IEnumerable<IFileInfo> files, TimeSpan offset)
{
logger.LogTrace($"Constructing {nameof(RetentionSorter)}");

this.logger = logger;
this.offset = offset;
lastTimestamp = DateTimeOffset.MinValue;
enumerator = files
.OrderByDescending(x => x.LastModified)
.GetEnumerator();
}

/// <inheritdoc/>
/// <inheritdoc cref="KeepLast"/>
public ILastSortedRetentionSorter KeepLast(uint count)
{
logger.LogTrace($"Entering {nameof(RetentionSorter)}.{nameof(KeepLast)}");
Expand All @@ -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;
}

/// <inheritdoc/>
/// <inheritdoc cref="KeepHourly"/>
public IHourlySortedRetentionSorter KeepHourly(uint count)
{
logger.LogTrace($"Entering {nameof(RetentionSorter)}.{nameof(KeepHourly)}");
Expand All @@ -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);
Expand All @@ -78,7 +80,7 @@ public IHourlySortedRetentionSorter KeepHourly(uint count)
return this;
}

/// <inheritdoc/>
/// <inheritdoc cref="KeepDaily"/>
public IDailySortedRetentionSorter KeepDaily(uint count)
{
logger.LogTrace($"Entering {nameof(RetentionSorter)}.{nameof(KeepDaily)}");
Expand All @@ -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);
Expand All @@ -109,7 +111,7 @@ public IDailySortedRetentionSorter KeepDaily(uint count)
return this;
}

/// <inheritdoc/>
/// <inheritdoc cref="KeepWeekly"/>
public IWeeklySortedRetentionSorter KeepWeekly(uint count)
{
logger.LogTrace($"Entering {nameof(RetentionSorter)}.{nameof(KeepWeekly)}");
Expand All @@ -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);
Expand All @@ -141,7 +143,7 @@ public IWeeklySortedRetentionSorter KeepWeekly(uint count)
}


/// <inheritdoc/>
/// <inheritdoc cref="KeepMonthly"/>
public IMonthlySortedRetentionSorter KeepMonthly(uint count)
{
logger.LogTrace($"Entering {nameof(RetentionSorter)}.{nameof(KeepMonthly)}");
Expand All @@ -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);
Expand All @@ -172,7 +174,7 @@ public IMonthlySortedRetentionSorter KeepMonthly(uint count)
return this;
}

/// <inheritdoc/>
/// <inheritdoc cref="KeepYearly"/>
public ISortedRetentionSorter KeepYearly(uint count)
{
logger.LogTrace($"Entering {nameof(RetentionSorter)}.{nameof(KeepYearly)}");
Expand All @@ -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);
Expand All @@ -204,7 +206,7 @@ public ISortedRetentionSorter KeepYearly(uint count)
return this;
}

/// <inheritdoc/>
/// <inheritdoc cref="PruneRemaining"/>
public ISortedRetentionSorter PruneRemaining()
{
logger.LogTrace($"Entering {nameof(RetentionSorter)}.{nameof(PruneRemaining)}");
Expand Down
2 changes: 1 addition & 1 deletion src/Services/DirectoryService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public DirectoryService(IConsoleLogger logger, PruneOptions options)
}

/// <inheritdoc/>
public IEnumerable<IFileInfo> GetFiles()
public ICollection<IFileInfo> GetFiles()
{
logger.LogTrace($"Entering {nameof(DirectoryService)}.{nameof(GetFiles)}");

Expand Down
4 changes: 2 additions & 2 deletions src/Services/PruneService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ public void PruneFiles()
return;
}

IEnumerable<IFileInfo> files = directoryService.GetFiles();
if (!files.Any())
ICollection<IFileInfo> files = directoryService.GetFiles();
if (files.Count == 0)
{
return;
}
Expand Down
7 changes: 5 additions & 2 deletions src/Services/RetentionSorterFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,22 @@ namespace BinaryPatrick.Prune.Services;
public class RetentionSorterFactory : IRetentionSorterFactory
{
private readonly IConsoleLogger logger;
private readonly PruneOptions options;

/// <summary>Initializes a new instance of the <see cref="RetentionSorterFactory"/> class</summary>
public RetentionSorterFactory(IConsoleLogger logger)
public RetentionSorterFactory(IConsoleLogger logger, PruneOptions options)
{
logger.LogTrace($"Constructing {nameof(RetentionSorterFactory)}");
this.logger = logger;
this.options = options;
}

/// <inheritdoc/>
public IInitializedRetentionSorter CreateRetentionSorter(IEnumerable<IFileInfo> files)
{
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);
}
}
10 changes: 5 additions & 5 deletions tests/BinaryPatrick.Prune.Unit.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="FluentAssertions" Version="8.8.0" />

<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PackageReference Include="coverlet.collector" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
45 changes: 33 additions & 12 deletions tests/Tests/RetentionSorterTests/RetentionSorterTests.KeepDaily.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public void KeepDaily_WithZero_ShouldNotTake()
DateTimeOffset startTime = new DateTimeOffset(2023, 1, 1, 0, 0, 0, TimeSpan.Zero);
IEnumerable<IFileInfo> 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
Expand All @@ -41,7 +41,7 @@ public void KeepDaily_With1_ShouldTake1()
DateTimeOffset startTime = new DateTimeOffset(2023, 1, 1, 0, 0, 0, TimeSpan.Zero);
IEnumerable<IFileInfo> 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
Expand Down Expand Up @@ -72,7 +72,7 @@ public void KeepDaily_WithN_ShouldTakeN(int keep)
DateTimeOffset startTime = new DateTimeOffset(2023, 1, 1, 23, 59, 59, TimeSpan.Zero);
IEnumerable<IFileInfo> 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);
Expand All @@ -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<IFileInfo> 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);
Expand All @@ -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]
Expand All @@ -122,7 +143,7 @@ public void KeepDaily_WithPrevious_ShouldTake1()
DateTimeOffset startTime = new DateTimeOffset(2023, 1, 1, 1, minutesPastHour, 0, TimeSpan.Zero);
IEnumerable<IFileInfo> 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);
Expand Down
Loading