Add: Archiver
This commit is contained in:
parent
e8b7b8408c
commit
5520b26658
2
Archiver/.gitignore
vendored
Normal file
2
Archiver/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
bin
|
||||||
|
obj
|
18
Archiver/Archiver.csproj
Normal file
18
Archiver/Archiver.csproj
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\ProtocolLib\ProtocolLib.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
25
Archiver/Archiver.sln
Normal file
25
Archiver/Archiver.sln
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.002.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Archiver", "Archiver.csproj", "{EF1E4119-D2B7-420D-9D4B-3EBC40FCE68C}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{EF1E4119-D2B7-420D-9D4B-3EBC40FCE68C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{EF1E4119-D2B7-420D-9D4B-3EBC40FCE68C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{EF1E4119-D2B7-420D-9D4B-3EBC40FCE68C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{EF1E4119-D2B7-420D-9D4B-3EBC40FCE68C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {92B64C8C-A729-4720-8CCF-F1A1BE5FB4D1}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
85
Archiver/Lib/ArchiveDecoder.cs
Normal file
85
Archiver/Lib/ArchiveDecoder.cs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
using PacketIO;
|
||||||
|
|
||||||
|
namespace Archiver.Lib;
|
||||||
|
|
||||||
|
public static class ArchiveDecoder
|
||||||
|
{
|
||||||
|
public static async ValueTask Decode(string inputPath, string outputPath)
|
||||||
|
{
|
||||||
|
if (!File.Exists(inputPath))
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException($"{inputPath} does not exist", inputPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileAttributes attributes = File.GetAttributes(inputPath);
|
||||||
|
if (attributes.HasFlag(FileAttributes.Directory))
|
||||||
|
{
|
||||||
|
throw new IOException("Target does not file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
using (FileStream fs = new FileStream(inputPath, FileMode.Open))
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[ArchiveEncoder.CreateFileHeader().Length];
|
||||||
|
await fs.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
|
||||||
|
if (!buffer.SequenceEqual(ArchiveEncoder.CreateFileHeader()))
|
||||||
|
{
|
||||||
|
throw new IOException("Not supported file type.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await ReadFile(fs, outputPath).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async ValueTask ReadFile(Stream stream, string outputPath)
|
||||||
|
{
|
||||||
|
IPacketFile? file = await TreePacketFile.ReadTreeFile(stream).ConfigureAwait(false);
|
||||||
|
if (file is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await OutputFile(file, outputPath).ConfigureAwait(false);
|
||||||
|
await ReadFile(stream, outputPath).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async ValueTask OutputFile(IPacketFile file, string outputPath)
|
||||||
|
{
|
||||||
|
switch (file.GetFileType())
|
||||||
|
{
|
||||||
|
case PacketFileType.Directory:
|
||||||
|
{
|
||||||
|
string dirPath = Path.Combine(outputPath, file.GetFileName());
|
||||||
|
if (!Directory.Exists(dirPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(dirPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (IPacketFile inFile in file.EnumerableFiles())
|
||||||
|
{
|
||||||
|
await OutputFile(inFile, dirPath).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PacketFileType.File:
|
||||||
|
{
|
||||||
|
string filePath = Path.Combine(outputPath, file.GetFileName());
|
||||||
|
using (FileStream outputStream = new FileStream(filePath, FileMode.OpenOrCreate))
|
||||||
|
{
|
||||||
|
PacketFile pfile = (PacketFile) file;
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
int c;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
c = await pfile.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
|
||||||
|
await outputStream.WriteAsync(buffer, 0, c).ConfigureAwait(false);
|
||||||
|
} while (c > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file.Dispose();
|
||||||
|
}
|
||||||
|
}
|
186
Archiver/Lib/ArchiveEncoder.cs
Normal file
186
Archiver/Lib/ArchiveEncoder.cs
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
using PacketIO;
|
||||||
|
|
||||||
|
namespace Archiver.Lib;
|
||||||
|
|
||||||
|
public static class ArchiveEncoder
|
||||||
|
{
|
||||||
|
public static async ValueTask Encode(string inputPath, string outputPath)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(inputPath))
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException($"{inputPath} does not exist", inputPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (FileStream fs = new FileStream(outputPath, FileMode.OpenOrCreate))
|
||||||
|
{
|
||||||
|
byte[] buffer = CreateFileHeader();
|
||||||
|
await fs.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
|
||||||
|
|
||||||
|
FileAttributes attributes = File.GetAttributes(inputPath);
|
||||||
|
if (attributes.HasFlag(FileAttributes.Directory))
|
||||||
|
{
|
||||||
|
DirectoryInfo dirInfo = new DirectoryInfo(inputPath);
|
||||||
|
PacketDirectoryModel model = await CreatePacketDirectory(dirInfo).ConfigureAwait(false);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await TreePacketFile.WriteTreeFiles(fs, model.PacketDirectory).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
DisposeFiles(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FileInfo fileInfo = new FileInfo(inputPath);
|
||||||
|
PacketFileModel model = await CreatePacketFile(fileInfo).ConfigureAwait(false);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await TreePacketFile.WriteTreeFiles(fs, model.PacketFile).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
DisposeFiles(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async ValueTask Encode(IEnumerable<string> inputFiles, string outputPath)
|
||||||
|
{
|
||||||
|
using (FileStream fs = new FileStream(outputPath, FileMode.OpenOrCreate))
|
||||||
|
{
|
||||||
|
byte[] buffer = CreateFileHeader();
|
||||||
|
await fs.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
|
||||||
|
|
||||||
|
foreach (string inputPath in inputFiles)
|
||||||
|
{
|
||||||
|
FileAttributes attributes = File.GetAttributes(inputPath);
|
||||||
|
if (attributes.HasFlag(FileAttributes.Directory))
|
||||||
|
{
|
||||||
|
DirectoryInfo dirInfo = new DirectoryInfo(inputPath);
|
||||||
|
PacketDirectoryModel model = await CreatePacketDirectory(dirInfo).ConfigureAwait(false);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await TreePacketFile.WriteTreeFiles(fs, model.PacketDirectory).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
DisposeFiles(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FileInfo fileInfo = new FileInfo(inputPath);
|
||||||
|
PacketFileModel model = await CreatePacketFile(fileInfo).ConfigureAwait(false);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await TreePacketFile.WriteTreeFiles(fs, model.PacketFile).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
DisposeFiles(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsArchiveFile(string inputPath)
|
||||||
|
{
|
||||||
|
FileAttributes attributes = File.GetAttributes(inputPath);
|
||||||
|
if (attributes.HasFlag(FileAttributes.Directory))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (FileStream fs = File.OpenRead(inputPath))
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[CreateFileHeader().Length];
|
||||||
|
int c = fs.Read(buffer, 0, buffer.Length);
|
||||||
|
if (c <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.SequenceEqual(CreateFileHeader());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static byte[] CreateFileHeader()
|
||||||
|
{
|
||||||
|
byte[] header = new byte[]
|
||||||
|
{
|
||||||
|
0xCC, 0xDE, 0xFF, 0x00,
|
||||||
|
};
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DisposeFiles(PacketDirectoryModel dirModel)
|
||||||
|
{
|
||||||
|
dirModel.PacketDirectory.Dispose();
|
||||||
|
foreach (PacketDirectoryModel subDirModel in dirModel.Directories)
|
||||||
|
{
|
||||||
|
DisposeFiles(subDirModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (PacketFileModel fileModel in dirModel.Files)
|
||||||
|
{
|
||||||
|
DisposeFiles(fileModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DisposeFiles(PacketFileModel fileModel)
|
||||||
|
{
|
||||||
|
fileModel.PacketFile.Dispose();
|
||||||
|
fileModel.TempFile.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async ValueTask<PacketDirectoryModel> CreatePacketDirectory(DirectoryInfo dirInfo)
|
||||||
|
{
|
||||||
|
PacketDirectory packetDirectory = new PacketDirectory(dirInfo.Name);
|
||||||
|
PacketDirectoryModel dirModel = new PacketDirectoryModel()
|
||||||
|
{
|
||||||
|
PacketDirectory = packetDirectory,
|
||||||
|
Files = new List<PacketFileModel>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (FileInfo fileInfo in dirInfo.EnumerateFiles())
|
||||||
|
{
|
||||||
|
PacketFileModel model = await CreatePacketFile(fileInfo).ConfigureAwait(false);
|
||||||
|
dirModel.Files.Add(model);
|
||||||
|
packetDirectory.AddFile(model.PacketFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (DirectoryInfo subInfo in dirInfo.EnumerateDirectories())
|
||||||
|
{
|
||||||
|
PacketDirectoryModel subDirModel = await CreatePacketDirectory(subInfo).ConfigureAwait(false);
|
||||||
|
dirModel.Directories.Add(subDirModel);
|
||||||
|
packetDirectory.AddFile(subDirModel.PacketDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dirModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async ValueTask<PacketFileModel> CreatePacketFile(FileInfo fileInfo)
|
||||||
|
{
|
||||||
|
TempFile tempFile = new TempFile();
|
||||||
|
Stream outputStream = tempFile.Open(FileMode.Open);
|
||||||
|
|
||||||
|
using (FileStream sourceStream = fileInfo.OpenRead())
|
||||||
|
{
|
||||||
|
await sourceStream.CopyToAsync(outputStream).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
outputStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
PacketFile file = new PacketFile(fileInfo.Name, outputStream);
|
||||||
|
PacketFileModel model = new PacketFileModel()
|
||||||
|
{
|
||||||
|
TempFile = tempFile,
|
||||||
|
PacketFile = file,
|
||||||
|
};
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
10
Archiver/Lib/PacketDirectoryModel.cs
Normal file
10
Archiver/Lib/PacketDirectoryModel.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using PacketIO;
|
||||||
|
|
||||||
|
namespace Archiver.Lib;
|
||||||
|
|
||||||
|
public class PacketDirectoryModel
|
||||||
|
{
|
||||||
|
public PacketDirectory PacketDirectory { get; set; }
|
||||||
|
public List<PacketFileModel> Files { get; set; } = new List<PacketFileModel>();
|
||||||
|
public List<PacketDirectoryModel> Directories { get; set; } = new List<PacketDirectoryModel>();
|
||||||
|
}
|
9
Archiver/Lib/PacketFileModel.cs
Normal file
9
Archiver/Lib/PacketFileModel.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using PacketIO;
|
||||||
|
|
||||||
|
namespace Archiver.Lib;
|
||||||
|
|
||||||
|
public class PacketFileModel
|
||||||
|
{
|
||||||
|
public TempFile TempFile { get; set;}
|
||||||
|
public PacketFile PacketFile { get; set; }
|
||||||
|
}
|
21
Archiver/Lib/TempFile.cs
Normal file
21
Archiver/Lib/TempFile.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
namespace Archiver.Lib;
|
||||||
|
|
||||||
|
public class TempFile : IDisposable
|
||||||
|
{
|
||||||
|
public string FileName { get; }
|
||||||
|
|
||||||
|
public TempFile()
|
||||||
|
{
|
||||||
|
FileName = Path.GetTempFileName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream Open(FileMode mode)
|
||||||
|
{
|
||||||
|
return new FileStream(FileName, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
File.Delete(FileName);
|
||||||
|
}
|
||||||
|
}
|
12
Archiver/Options.cs
Normal file
12
Archiver/Options.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using CommandLine;
|
||||||
|
|
||||||
|
namespace Archiver;
|
||||||
|
|
||||||
|
public class Options
|
||||||
|
{
|
||||||
|
[Option('i', "input", Required = true)]
|
||||||
|
public IEnumerable<string> InputFiles { get; set; }
|
||||||
|
|
||||||
|
[Option('o', "output", Required = true)]
|
||||||
|
public string OutputPath { get; set; }
|
||||||
|
}
|
56
Archiver/Program.cs
Normal file
56
Archiver/Program.cs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
using Archiver.Lib;
|
||||||
|
|
||||||
|
namespace Archiver;
|
||||||
|
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Options options = CommandLine.Parser.Default.ParseArguments<Options>(args).Value;
|
||||||
|
if (options is null || options.InputFiles is null || string.IsNullOrEmpty(options.OutputPath))
|
||||||
|
{
|
||||||
|
Console.WriteLine("[Archiver] Usage: archiver -i <input files> -o <output file>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int c = options.InputFiles.Count();
|
||||||
|
if (c == 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine("[Archiver] Empty input files.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c > 1)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[Archiver] Encode");
|
||||||
|
Console.WriteLine($"[Archiver] Input : {string.Join(", ", options.InputFiles)}");
|
||||||
|
Console.WriteLine($"[Archiver] Output: {options.OutputPath}");
|
||||||
|
ArchiveEncoder.Encode(options.InputFiles, options.OutputPath).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string inputFile = options.InputFiles.ToArray()[0];
|
||||||
|
if (ArchiveEncoder.IsArchiveFile(inputFile))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[Archiver] Decode");
|
||||||
|
Console.WriteLine($"[Archiver] Input : {inputFile}");
|
||||||
|
Console.WriteLine($"[Archiver] Output: {options.OutputPath}");
|
||||||
|
ArchiveDecoder.Decode(inputFile, options.OutputPath).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[Archiver] Encode");
|
||||||
|
Console.WriteLine($"[Archiver] Input : {string.Join(',', options.InputFiles)}");
|
||||||
|
Console.WriteLine($"[Archiver] Output: {options.OutputPath}");
|
||||||
|
ArchiveEncoder.Encode(inputFile, options.OutputPath).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[Archiver] Error: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user