diff --git a/checkAmount/checkAmount.sln b/checkAmount/checkAmount.sln new file mode 100644 index 0000000..528815a --- /dev/null +++ b/checkAmount/checkAmount.sln @@ -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 diff --git a/checkAmount/checkAmount/CheckAmount.csproj b/checkAmount/checkAmount/CheckAmount.csproj new file mode 100644 index 0000000..d764014 --- /dev/null +++ b/checkAmount/checkAmount/CheckAmount.csproj @@ -0,0 +1,21 @@ + + + + Exe + net9.0 + enable + enable + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git "a/checkAmount/checkAmount/CheckAmount\320\241alculator.cs" "b/checkAmount/checkAmount/CheckAmount\320\241alculator.cs" new file mode 100644 index 0000000..f7ae808 --- /dev/null +++ "b/checkAmount/checkAmount/CheckAmount\320\241alculator.cs" @@ -0,0 +1,168 @@ +// +// Copyright (c) Kalinin Andrew. All rights reserved. +// + +namespace CheckAmount; + +using System.Security.Cryptography; +using System.Text; + +/// +/// Calculates the checksum (hash amount) for a given file or directory structure. +/// +public class CheckAmountСalculator +{ + private const int BufferSize = 4096; + + /// + /// Initiates the single-threaded checksum calculation. + /// + /// The path to the file or directory. + /// The MD5 hash. + /// Thrown if the path does not exist. + public string Calculate(string path) + { + 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(); + } + + /// + /// Initiates the multi-threaded checksum calculation for directories. + /// + /// The path to the file or directory. + /// The MD5 hash. + /// Thrown if the path does not exist. + 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(); + } + + 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)) + { + 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!; + } + } + + 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); + } + } + } + + 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> tasks = new List>(); + + 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 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); + } + } + } +} \ No newline at end of file diff --git a/checkAmount/checkAmount/Program.cs b/checkAmount/checkAmount/Program.cs new file mode 100644 index 0000000..807a042 --- /dev/null +++ b/checkAmount/checkAmount/Program.cs @@ -0,0 +1,27 @@ +// +// Copyright (c) Kalinin Andrew. All rights reserved. +// + +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}"); \ No newline at end of file diff --git a/checkAmount/checkAmount/TestDir/SubDir/TestFileInSubDir.txt b/checkAmount/checkAmount/TestDir/SubDir/TestFileInSubDir.txt new file mode 100644 index 0000000..53c33d5 --- /dev/null +++ b/checkAmount/checkAmount/TestDir/SubDir/TestFileInSubDir.txt @@ -0,0 +1 @@ +dajskdhasjkdhfsdkfnkdsngkdfnfk;fksdfnkjsdfhkjdsnfksdanfskl \ No newline at end of file diff --git a/checkAmount/checkAmount/TestDir/TestFileInTestDir.txt b/checkAmount/checkAmount/TestDir/TestFileInTestDir.txt new file mode 100644 index 0000000..bda685a --- /dev/null +++ b/checkAmount/checkAmount/TestDir/TestFileInTestDir.txt @@ -0,0 +1 @@ +dasdasfgfdgsddsfbskdfns \ No newline at end of file diff --git a/checkAmount/checkAmount/stylecop.json b/checkAmount/checkAmount/stylecop.json new file mode 100644 index 0000000..c7ed419 --- /dev/null +++ b/checkAmount/checkAmount/stylecop.json @@ -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" + } + } +}