Add log parser to output added file list sorted by size
This commit is contained in:
parent
54decd3526
commit
afcfc0d8c7
31
ResticLogParser.sln
Normal file
31
ResticLogParser.sln
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.13.35931.197 d17.13
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResticLogParser", "ResticLogParser\ResticLogParser.csproj", "{F688B5E5-B5BD-43D2-96E9-A7286A0EB440}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResticLogParserTests", "ResticLogParserTests\ResticLogParserTests.csproj", "{B4D864B6-35C1-4890-A89E-4C13152B569C}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{F688B5E5-B5BD-43D2-96E9-A7286A0EB440}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{F688B5E5-B5BD-43D2-96E9-A7286A0EB440}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{F688B5E5-B5BD-43D2-96E9-A7286A0EB440}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{F688B5E5-B5BD-43D2-96E9-A7286A0EB440}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B4D864B6-35C1-4890-A89E-4C13152B569C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B4D864B6-35C1-4890-A89E-4C13152B569C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B4D864B6-35C1-4890-A89E-4C13152B569C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B4D864B6-35C1-4890-A89E-4C13152B569C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {D90F1BCC-4C6C-48FC-8920-2B87B5019AAB}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
3
ResticLogParser/Program.cs
Normal file
3
ResticLogParser/Program.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
using ResticLogParser.src;
|
||||||
|
|
||||||
|
new LogParser(@"C:\Users\warrence\Documents\My Documents\restic\restic-logfile.txt").Run();
|
10
ResticLogParser/ResticLogParser.csproj
Normal file
10
ResticLogParser/ResticLogParser.csproj
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
36
ResticLogParser/src/FileLogEntry.cs
Normal file
36
ResticLogParser/src/FileLogEntry.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
namespace ResticLogParser.src
|
||||||
|
{
|
||||||
|
internal readonly struct FileLogEntry : IComparable<FileLogEntry>
|
||||||
|
{
|
||||||
|
public FileLogEntry(string fileName, FileLogEntryType type, long addedSize)
|
||||||
|
: this(fileName, type, addedSize, addedSize)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileLogEntry(string fileName, FileLogEntryType type, long addedSize, long storedSize)
|
||||||
|
{
|
||||||
|
FileName = fileName;
|
||||||
|
Type = type;
|
||||||
|
AddedSize = addedSize;
|
||||||
|
StoredSize = storedSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FileName { get; init; }
|
||||||
|
|
||||||
|
public FileLogEntryType Type { get; init; }
|
||||||
|
|
||||||
|
public long AddedSize { get; init; }
|
||||||
|
|
||||||
|
public long StoredSize { get; init; }
|
||||||
|
|
||||||
|
public int CompareTo(FileLogEntry other)
|
||||||
|
{
|
||||||
|
return AddedSize.CompareTo(other.AddedSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{Type}, added {AddedSize} bytes, {FileName}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
ResticLogParser/src/FileLogEntryType.cs
Normal file
8
ResticLogParser/src/FileLogEntryType.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace ResticLogParser.src
|
||||||
|
{
|
||||||
|
internal enum FileLogEntryType
|
||||||
|
{
|
||||||
|
New,
|
||||||
|
Modified
|
||||||
|
}
|
||||||
|
}
|
46
ResticLogParser/src/FileSize.cs
Normal file
46
ResticLogParser/src/FileSize.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace ResticLogParser.src
|
||||||
|
{
|
||||||
|
public class FileSize
|
||||||
|
{
|
||||||
|
public static long GetBytesFromString(string fileSizeText)
|
||||||
|
{
|
||||||
|
string[] split = fileSizeText.Split(" ", StringSplitOptions.TrimEntries);
|
||||||
|
if (split.Length < 2 || split[0].Length < 1 || split[1].Length < 1 || split[1].Length > 3
|
||||||
|
|| split[1][split[1].Length - 1] != 'B')
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
var value = double.Parse(split[0], CultureInfo.InvariantCulture.NumberFormat);
|
||||||
|
var power = split[1].Length > 1
|
||||||
|
? split[1][0] switch
|
||||||
|
{
|
||||||
|
'K' => 1,
|
||||||
|
'M' => 2,
|
||||||
|
'G' => 3,
|
||||||
|
'T' => 4,
|
||||||
|
_ => 0
|
||||||
|
}
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
int valueBase;
|
||||||
|
if (split[1].Length < 3)
|
||||||
|
{
|
||||||
|
valueBase = 1000;
|
||||||
|
}
|
||||||
|
else if (split[1][1] == 'i')
|
||||||
|
{
|
||||||
|
valueBase = 1024;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var s = Math.Pow(valueBase, power);
|
||||||
|
var t = value * Math.Pow(valueBase, power);
|
||||||
|
return (long)Math.Round(value * Math.Pow(valueBase, power));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
91
ResticLogParser/src/LogParser.cs
Normal file
91
ResticLogParser/src/LogParser.cs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace ResticLogParser.src
|
||||||
|
{
|
||||||
|
internal class LogParser
|
||||||
|
{
|
||||||
|
// Example match:
|
||||||
|
// new /E/Projects/Unity/SpriteTransfer/Images/Equipment/Belts/Belt 3 Up.png, saved in 0.000s (7.201 KiB added)
|
||||||
|
private Regex _newFileLogEntryRegex = new(@"new\s+(.+), saved in \d+\.\d+s \((\d+(?:\.\d+)? .+) added\)");
|
||||||
|
|
||||||
|
// Example match:
|
||||||
|
// modified /C/Users/warrence/AppData/Local/Log/Desktop.log, saved in 0.009s (1.135 MiB added, 1.135 MiB stored)
|
||||||
|
private Regex _modifiedFileLogEntryRegex =
|
||||||
|
new(@"modified\s+(.+), saved in \d+\.\d+s \((\d+(?:\.\d+)? .+) added, (\d+(?:\.\d+)? .+) stored\)");
|
||||||
|
|
||||||
|
private string _logFileName;
|
||||||
|
private readonly List<FileLogEntry> _files = new();
|
||||||
|
|
||||||
|
public LogParser(string logFileName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(logFileName))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(logFileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
_logFileName = logFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Run()
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Running restic log parser for '{_logFileName}'...");
|
||||||
|
ParseLogFile();
|
||||||
|
_files.Sort();
|
||||||
|
WriteFileInformation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseLogFile()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using StreamReader reader = new(_logFileName);
|
||||||
|
while (!reader.EndOfStream)
|
||||||
|
{
|
||||||
|
string line = reader.ReadLine()!;
|
||||||
|
TryParseLogEntry(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
Console.WriteLine("The file could not be read:");
|
||||||
|
Console.WriteLine(e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryParseLogEntry(string logLine)
|
||||||
|
{
|
||||||
|
Match match = _newFileLogEntryRegex.Match(logLine);
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
var size = FileSize.GetBytesFromString(match.Groups[2].Value);
|
||||||
|
_files.Add(new FileLogEntry(match.Groups[1].Value, FileLogEntryType.New, size));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
match = _modifiedFileLogEntryRegex.Match(logLine);
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
var addedSize = FileSize.GetBytesFromString(match.Groups[2].Value);
|
||||||
|
var storedSize = FileSize.GetBytesFromString(match.Groups[3].Value);
|
||||||
|
_files.Add(new FileLogEntry(match.Groups[1].Value, FileLogEntryType.Modified, addedSize, storedSize));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteFileInformation()
|
||||||
|
{
|
||||||
|
string outputFileName = Path.Combine(Path.GetDirectoryName(_logFileName)!, "restic-added-data.txt");
|
||||||
|
Console.WriteLine($"Writing output to '{outputFileName}'...");
|
||||||
|
|
||||||
|
using (StreamWriter outputFile = new StreamWriter(outputFileName))
|
||||||
|
{
|
||||||
|
for (int i = _files.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
outputFile.WriteLine(_files[i].ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
ResticLogParserTests/FileSizeTests.cs
Normal file
18
ResticLogParserTests/FileSizeTests.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using ResticLogParser.src;
|
||||||
|
|
||||||
|
namespace ResticLogParserTests
|
||||||
|
{
|
||||||
|
public class FileSizeTests
|
||||||
|
{
|
||||||
|
[TestCase("0 B", 0)]
|
||||||
|
[TestCase("739 B", 739)]
|
||||||
|
[TestCase("17.601 KiB", 18023)]
|
||||||
|
[TestCase("17.601 KB", 17601)]
|
||||||
|
[TestCase("107.135 MiB", 112339190)]
|
||||||
|
[TestCase("107.135 MB", 107135000)]
|
||||||
|
public void Test(string fileSizeText, long expected)
|
||||||
|
{
|
||||||
|
Assert.That(FileSize.GetBytesFromString(fileSizeText), Is.EqualTo(expected));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
ResticLogParserTests/ResticLogParserTests.csproj
Normal file
28
ResticLogParserTests/ResticLogParserTests.csproj
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||||
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
|
<PackageReference Include="NUnit.Analyzers" Version="3.9.0" />
|
||||||
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\ResticLogParser\ResticLogParser.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="NUnit.Framework" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
Loading…
x
Reference in New Issue
Block a user