Skip to content
Open
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
25 changes: 25 additions & 0 deletions checkAmount/checkAmount.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.13.35806.99 d17.13
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CheckAmount", "checkAmount\CheckAmount.csproj", "{1AB54304-E937-42B8-929C-533823038651}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1AB54304-E937-42B8-929C-533823038651}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1AB54304-E937-42B8-929C-533823038651}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1AB54304-E937-42B8-929C-533823038651}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1AB54304-E937-42B8-929C-533823038651}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {06282FD8-DD37-410B-853D-14580665948A}
EndGlobalSection
EndGlobal
21 changes: 21 additions & 0 deletions checkAmount/checkAmount/CheckAmount.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<AdditionalFiles Include="stylecop.json" />
</ItemGroup>

</Project>
168 changes: 168 additions & 0 deletions checkAmount/checkAmount/CheckAmountСalculator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// <copyright file="CheckAmountСalculator.cs" company="Kalinin Andrew">
// Copyright (c) Kalinin Andrew. All rights reserved.
// </copyright>

namespace CheckAmount;

using System.Security.Cryptography;
using System.Text;

/// <summary>
/// Calculates the checksum (hash amount) for a given file or directory structure.
/// </summary>
public class CheckAmountСalculator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вроде как у него нет никаких причин быть не static

{
private const int BufferSize = 4096;

/// <summary>
/// Initiates the single-threaded checksum calculation.
/// </summary>
/// <param name="path">The path to the file or directory.</param>
/// <returns>The MD5 hash.</returns>
/// <exception cref="FileNotFoundException">Thrown if the path does not exist.</exception>
public string Calculate(string path)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Потенциально длительные операции в современном мире должны быть асинхронными, а считать хеш точно может быть долго.

{
byte[] hashBytes;

if (File.Exists(path))
{
hashBytes = this.CalculateFileCheckAmount(path);
}
else if (Directory.Exists(path))
{
hashBytes = this.CalculateDirectoryCheckAmount(path);
}
else
{
throw new FileNotFoundException("Путь не найден");
}

return BitConverter.ToString(hashBytes).Replace("-", string.Empty).ToLower();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не, всё-таки возвращать хешер должен был хеш, а не его строковое представление. Мы же не знаем, как он может быть использован в дальнейшем.

}

/// <summary>
/// Initiates the multi-threaded checksum calculation for directories.
/// </summary>
/// <param name="path">The path to the file or directory.</param>
/// <returns>The MD5 hash.</returns>
/// <exception cref="FileNotFoundException">Thrown if the path does not exist.</exception>
public string CalculateMultiThreaded(string path)
{
byte[] hashBytes;

if (File.Exists(path))
{
hashBytes = this.CalculateFileCheckAmount(path);
}
else if (Directory.Exists(path))
{
hashBytes = this.CalculateDirectoryAmountThreaded(path);
}
else
{
throw new FileNotFoundException("Путь не найден");
}

return BitConverter.ToString(hashBytes).Replace("-", string.Empty).ToLower();
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Некий копипаст с предыдущим методом. Можно было бы сделать один метод и параметризовать его лямбдой. И сделать два метода-обёртки, чтобы клиенту не надо было лямбду передавать.


private byte[] CalculateFileCheckAmount(string filePath)
{
string fileName = Path.GetFileName(filePath);
byte[] fileNameBytes = Encoding.UTF8.GetBytes(fileName);
using (MD5 md = MD5.Create())
using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
using (FileStream fileStream = new(filePath, FileMode.Open, FileAccess.Read))

Древний C#, значит генерено нейросетью :)

{
md.TransformBlock(fileNameBytes, 0, fileNameBytes.Length, fileNameBytes, 0);

byte[] buffer = new byte[BufferSize];
int bytesRead;

while ((bytesRead = fileStream.Read(buffer, 0, BufferSize)) > 0)
{
md.TransformBlock(buffer, 0, bytesRead, buffer, 0);
}

md.TransformFinalBlock(new byte[0], 0, 0);
return md.Hash!;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

! не используйте вовсе. А ну как там null?

}
}

private byte[] CalculateDirectoryCheckAmount(string dirPath)
{
string dirName = new DirectoryInfo(dirPath).Name;
byte[] dirNameBytes = Encoding.UTF8.GetBytes(dirName);

string[] subDirs = Directory.GetDirectories(dirPath);
string[] files = Directory.GetFiles(dirPath);

Array.Sort(subDirs);
Array.Sort(files);

using (MemoryStream stream = new MemoryStream())
{
stream.Write(dirNameBytes, 0, dirNameBytes.Length);

foreach (string subDir in subDirs)
{
byte[] subDirHash = this.CalculateDirectoryCheckAmount(subDir);
stream.Write(subDirHash, 0, subDirHash.Length);
}

foreach (string file in files)
{
byte[] fileHash = this.CalculateFileCheckAmount(file);
stream.Write(fileHash, 0, fileHash.Length);
}

stream.Position = 0;
using (MD5 md = MD5.Create())
{
return md.ComputeHash(stream);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Есть статическая версия ComputeHash (правда, называется HashData), которая позволила бы это записать в одну строчку. Объект-то Вам тут не нужен.

}
}

private byte[] CalculateDirectoryAmountThreaded(string dirPath)
{
string dirName = new DirectoryInfo(dirPath).Name;
byte[] dirNameBytes = Encoding.UTF8.GetBytes(dirName);

string[] subDirs = Directory.GetDirectories(dirPath);
string[] files = Directory.GetFiles(dirPath);

Array.Sort(subDirs);
Array.Sort(files);

List<Task<byte[]>> tasks = new List<Task<byte[]>>();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Уже давно в C# не надо писать один тип дважды. Тем более такой.


foreach (string subDir in subDirs)
{
tasks.Add(Task.Run(() => this.CalculateDirectoryAmountThreaded(subDir)));
}

foreach (string file in files)
{
tasks.Add(Task.Run(() => this.CalculateFileCheckAmount(file)));
}

Task.WaitAll(tasks.ToArray());

using (MemoryStream stream = new MemoryStream())
{
stream.Write(dirNameBytes, 0, dirNameBytes.Length);

foreach (Task<byte[]> task in tasks)
{
byte[] resultHash = task.Result;
stream.Write(resultHash, 0, resultHash.Length);
}

stream.Position = 0;
using (MD5 md = MD5.Create())
{
return md.ComputeHash(stream);
}
}
}
}
27 changes: 27 additions & 0 deletions checkAmount/checkAmount/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// <copyright file="Program.cs" company="Kalinin Andrew">
// Copyright (c) Kalinin Andrew. All rights reserved.
// </copyright>

using System.Diagnostics;
using CheckAmount;

string testDir = @"..\..\..\TestDir";

Stopwatch sw = new Stopwatch();
CheckAmountСalculator calculator = new CheckAmountСalculator();

sw.Start();
string checkAmountOneThread = calculator.Calculate(testDir);
sw.Stop();
long timeOne = sw.ElapsedMilliseconds;
Console.WriteLine($"Хеш: {checkAmountOneThread}");
Console.WriteLine($"Время(однопоточный запуск): {timeOne} мс");

sw.Restart();
string checkAmountMulti = calculator.CalculateMultiThreaded(testDir);
sw.Stop();
long timeMulti = sw.ElapsedMilliseconds;
Console.WriteLine($"Хеш: {checkAmountMulti}");
Console.WriteLine($"Время(однопоточный запуск): {timeMulti} мс");

Console.WriteLine($"Хеши совпадают {checkAmountMulti == checkAmountOneThread}");
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dajskdhasjkdhfsdkfnkdsngkdfnfk;fksdfnkjsdfhkjdsnfksdanfskl
1 change: 1 addition & 0 deletions checkAmount/checkAmount/TestDir/TestFileInTestDir.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dasdasfgfdgsddsfbskdfns
8 changes: 8 additions & 0 deletions checkAmount/checkAmount/stylecop.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
"settings": {
"documentationRules": {
"companyName": "Kalinin Andrew"
}
}
}