38 Commits

Author SHA1 Message Date
7b00a87620 Merge pull request 'feature/NewHashingAlgorithms' (#6) from feature/NewHashingAlgorithms into main
Reviewed-on: #6
2023-07-03 22:34:15 +00:00
86111732c2 Merge pull request 'Runtime dependency' (#5) from feature/BackwardsCompatibility into feature/NewHashingAlgorithms
Reviewed-on: #5
2023-07-03 22:20:25 +00:00
d7aa7a6d98 Runtime dependency 2023-07-04 00:18:09 +02:00
336eb5b73d Fix logging 2023-07-03 23:56:27 +02:00
b011f08172 feature/progressBar (#4)
# Add progress bar
Added a progress bar so that one can see how far along the program is.

Reviewed-on: #4
Co-authored-by: ProfessionalUwU <andre.fuhry@hopeless-cloud.xyz>
Co-committed-by: ProfessionalUwU <andre.fuhry@hopeless-cloud.xyz>
2023-07-03 21:50:38 +00:00
02a0bddd7e Major advancments
New options
Switched to wal (Write ahead log)
Now using redis to cache filehashes
New option to dump redis data into sqlite database
2023-07-03 00:59:50 +02:00
d80a5f5e6b Add option for choosing hash algo 2023-07-02 15:32:46 +02:00
9d6b1385c8 Add new hashing algorithms
Add XxHash
Code cleanup
2023-07-02 15:20:13 +02:00
f65108425c New hashing algorithm 2023-06-29 19:53:00 +02:00
f9cdac5f92 Merge pull request 'feature/logging' (#3) from feature/logging into main
Reviewed-on: #3
2023-06-29 17:01:40 +00:00
e99ca810f6 Add instructions for verbose output 2023-06-29 03:58:28 +02:00
184e525adb Add logging 2023-06-29 03:49:13 +02:00
1ee5114dab Merge pull request 'feature/multi-threading' (#2) from feature/multi-threading into main
Reviewed-on: #2
2023-06-29 00:15:11 +00:00
f403b77864 Code cleanup 2023-06-29 02:03:41 +02:00
42320ebf8d Use ConcurrentDictionary 2023-06-28 13:18:33 +02:00
787721381d refactor doTheThing 2023-06-28 00:54:55 +02:00
7d7e9bac6c refactor checkIfFileWasDeleted 2023-06-28 00:54:30 +02:00
2b4019d7cf move return outside of using 2023-06-28 00:48:14 +02:00
be8180a60d refactor doTheThing 2023-06-28 00:41:38 +02:00
7f6f4c5253 Streamline code 2023-06-27 23:56:43 +02:00
e12117fba8 Small fix 2023-06-27 23:12:50 +02:00
763cde4e2d Use dictionary 2023-06-27 22:58:07 +02:00
531a4676e9 First implementation of parralel checksumming 2023-06-27 22:31:24 +02:00
bc42dd542a Merge pull request 'feature/SQlite' (#1) from feature/SQlite into main
Reviewed-on: #1
2023-06-26 16:22:27 +00:00
836b850f3f Refactor getFilehashesFromDatabase 2023-06-26 18:20:47 +02:00
944b61a1ac Refactor compareDatabases 2023-06-26 17:21:56 +02:00
6fbc53fa53 Add option to compare databases 2023-06-26 16:25:42 +02:00
b899c4c5b6 Add option to check for deleted files 2023-06-26 15:59:06 +02:00
2d42842fb2 Minor tweaks 2023-06-26 12:58:05 +02:00
8aec649781 code cleanup
removed unused methods
changed output of methods
2023-06-25 22:37:50 +02:00
c51e02fa05 Add library
Embed the library into the executable and extract at runtime
Cleanup after executing the program
2023-06-25 21:12:42 +02:00
132894e924 Minor fixes 2023-06-25 16:48:43 +02:00
b05ddd5e18 Fix formatting 2023-06-25 03:32:43 +02:00
64995434ab Check if file moved
If the file moved the path will be updated
2023-06-25 03:28:34 +02:00
5a3becb3b4 Major additons
Converted to new project structure
Check if db already exists
Add vacuum for db cleanup
Check if filehash already exits and write info to console
2023-06-25 02:45:33 +02:00
846b983caa Major changes
Now using an SQlite database to store the file hash plus a bunch of other information
2023-06-24 01:04:11 +02:00
951068c750 Add SQlite package 2023-06-23 22:38:32 +02:00
6d2880907b Update README.md 2023-06-23 22:34:00 +02:00
14 changed files with 658 additions and 157 deletions

View File

@ -10,7 +10,7 @@ indent_style = space
tab_width = 4
# Naming Conventions
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
dotnet_naming_style.camel_case.capitalization = camel_case
# New line preferences
csharp_new_line_before_open_brace = none

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.vscode/
obj/
bin/
Libraries/

View File

@ -2,12 +2,23 @@
Checksums every file under the current directory
## Runtime dependency
If you use version 2.0.0 or above you will need redis!
```bash
pacman -S redis && systemctl start redis
```
With this redis will be downloaded and started.
Don't forget to enable the service if you don't want to start it every time you run the program.
## Run Locally
Clone the project
```bash
git clone http://192.168.0.69:3000/ProfessionalUwU/chksum.git
git clone https://gitea.hopeless-cloud.xyz/ProfessionalUwU/chksum.git
```
Go to the project directory
@ -25,22 +36,37 @@ pacman -S dotnet-runtime dotnet-sdk
Build project
```bash
dotnet build chksum.csproj
just build
```
Publish project
```bash
dotnet publish --configuration Release chksum.csproj
just publish
```
Go to the publish folder
```bash
cd bin/Release/net7.0/linux-x64/publish
cd src/Chksum/bin/Release/net7.0/linux-x64/publish
```
Run executable
```bash
./chksum
./Chksum
```
## Enabling verbose output for troubleshooting
1. Open the file called chksum.cs with your editor of choice.
2. At the top there will be the logger configuration which you can change. Should look like this.
```cs
private ILogger logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Error)
.WriteTo.File("chksum.log")
.CreateLogger();
```
3. Change the minimum level of the logger to Verbose.
4. Compile the program
5. Profit. Now you will be able to see how what the program is doing in detail.

111
chksum.cs
View File

@ -1,111 +0,0 @@
// Go into folder
// Check if any file is in there
// If there is a file. Calculate md5sum > filename.md5
// If there is no file. Repeat
public class Chksum {
// int getDirectoryCount() {
// int folderCount = Directory.GetDirectories(Directory.GetCurrentDirectory()).Length; // Get folder count in current directory
// return folderCount;
// }
private static int getFileCount() {
int fileCount = Directory.GetFiles(Directory.GetCurrentDirectory()).Length; // Get file count in current directory
return fileCount;
}
// string getParentFolder() {
// string parentFolder = Directory.GetParent(Directory.GetCurrentDirectory()).ToString(); // Get parent folder of current directory
// return parentFolder;
// }
private static string CalculateMD5(string filename) {
using (var md5 = System.Security.Cryptography.MD5.Create()) {
using (var stream = File.OpenRead(filename)) {
var hash = md5.ComputeHash(stream);
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}
}
}
public static void doTheThing() {
foreach (var directory in Directory.GetDirectories(Directory.GetCurrentDirectory())) {
Directory.SetCurrentDirectory(directory); // Set new root
if (getFileCount() >= 1) {
DirectoryInfo dir = new DirectoryInfo(Directory.GetCurrentDirectory());
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files) {
string fileName = file.Name;
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
string checksumFile = Directory.GetCurrentDirectory() + "/" + fileNameWithoutExtension + ".md5";
File.AppendAllText(checksumFile, CalculateMD5(fileName) + " " + fileName);
Console.WriteLine(checksumFile);
}
}
doTheThing();
}
}
private static int getTotalFileCount() {
int totalFileCount = Directory.GetFiles(Directory.GetCurrentDirectory(), "*", SearchOption.AllDirectories).Length;
return totalFileCount - 1; // Remove the program from the totalFileCount
}
public static void countAllMd5Checksums() {
int totalMD5FileCount = Directory.GetFiles(Directory.GetCurrentDirectory(), "*.md5", SearchOption.AllDirectories).Length;
Console.WriteLine("There are " + totalMD5FileCount + " md5 checksums");
}
public static void deleteAllMd5Checksums() {
foreach (var directory in Directory.GetDirectories(Directory.GetCurrentDirectory())) {
Directory.SetCurrentDirectory(directory); // Set new root
if (getFileCount() >= 1) {
DirectoryInfo dir = new DirectoryInfo(Directory.GetCurrentDirectory());
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files) {
string fileName = file.Name;
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
string checksumFile = Directory.GetCurrentDirectory() + "/" + fileNameWithoutExtension + ".md5";
File.Delete(checksumFile);
Console.WriteLine("Deleted " + checksumFile);
}
}
deleteAllMd5Checksums();
}
}
public static void compareChecksums() {
foreach (var directory in Directory.GetDirectories(Directory.GetCurrentDirectory())) {
Directory.SetCurrentDirectory(directory); // Set new root
if (getFileCount() >= 1) {
DirectoryInfo dir = new DirectoryInfo(Directory.GetCurrentDirectory());
FileInfo[] files = dir.GetFiles();
// files.ToList().ForEach(i => Console.WriteLine(i.ToString())); // Print all files in files array
foreach (FileInfo file in files) {
string fileName = file.Name;
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
string checksumFile = Directory.GetCurrentDirectory() + "/" + fileNameWithoutExtension + ".md5";
string fileMd5Checksum = fileNameWithoutExtension + ".md5";
if (File.Exists(fileMd5Checksum)) {
string newFileChecksum = CalculateMD5(fileName) + " " + fileName;
string existingFileChecksum = File.ReadAllText(fileMd5Checksum);
string newFileName = newFileChecksum.Substring(34);
string existingFileName = existingFileChecksum.Substring(34);
if (newFileChecksum.Equals(existingFileChecksum)) {
Console.WriteLine(newFileName + " and " + existingFileName + " are the same.");
} else {
Console.WriteLine(newFileName + " and " + existingFileName + " are not the same.");
Console.WriteLine("The checksum of " + newFileName + " is " + newFileChecksum);
Console.WriteLine("The checksum of the already exting file " + existingFileName + " is " + existingFileChecksum);
// TODO Tell the user to check which file is the correct one
}
} else {
File.AppendAllText(checksumFile, CalculateMD5(fileName) + " " + fileName);
Console.WriteLine("Calculated checksum for: " + checksumFile);
}
}
}
compareChecksums();
}
}
}

View File

@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<PublishTrimmed>true</PublishTrimmed>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

View File

@ -1,2 +1,31 @@
default:
@just --list
project_name := `printf '%s\n' "${PWD##*/}"`
uppercase_project_name := capitalize(project_name)
setup:
@mkdir src
@dotnet new sln --name src/{{project_name}}
@dotnet new classlib -o src/{{uppercase_project_name}}
@dotnet new xunit -o src/{{uppercase_project_name}}.Tests
@dotnet sln add src/{{uppercase_project_name}}/{{uppercase_project_name}}.csproj
@dotnet sln add src/{{uppercase_project_name}}.Tests/{{uppercase_project_name}}.Tests.csproj
@dotnet add src/{{uppercase_project_name}}/{{uppercase_project_name}}.csproj reference src/{{uppercase_project_name}}.Tests/{{uppercase_project_name}}.Tests.csproj
run:
@dotnet run
build:
@dotnet build src/{{uppercase_project_name}}/{{uppercase_project_name}}.csproj
@dotnet build src/{{uppercase_project_name}}.Tests/{{uppercase_project_name}}.Tests.csproj
publish:
@dotnet publish --configuration Release chksum.csproj
@dotnet publish --configuration Release src/{{uppercase_project_name}}/{{uppercase_project_name}}.csproj
format:
@dotnet format src/{{uppercase_project_name}}
@dotnet format src/{{uppercase_project_name}}.Tests
test: build
@dotnet test src/{{uppercase_project_name}}.Tests

View File

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,2 @@
global using Xunit;
global using FluentAssertions;

View File

@ -0,0 +1,8 @@
namespace Chksum.Tests;
public class doTheThingTest {
[Fact]
public void doTheThing_willNotThrowAnException() {
}
}

29
src/Chksum/Chksum.csproj Normal file
View File

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Chksum.Tests\Chksum.Tests.csproj" />
<EmbeddedResource Include="Libraries/libe_sqlite3.so" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<PublishTrimmed>true</PublishTrimmed>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.8" />
<PackageReference Include="MurmurHash.Net" Version="0.0.2" />
<PackageReference Include="Serilog" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="StackExchange.Redis" Version="2.6.116" />
<PackageReference Include="Standart.Hash.xxHash" Version="4.0.5" />
</ItemGroup>
</Project>

View File

@ -1,4 +1,6 @@
public class Program {
using Chksum.Utils;
public class Program {
static void Main(string[] args) {
Console.ForegroundColor = ConsoleColor.Red;
@ -6,45 +8,51 @@
Console.WriteLine("Please specify an option.");
PrintAvailableOptions();
return;
} else if (args.Length > 1) {
} else if (args.Length > 3) {
Console.WriteLine("Too many options.");
return;
}
ChksumUtils utils = new ChksumUtils();
utils.getBaseDir();
utils.ExtractEmbeddedLibrary();
Console.ForegroundColor = ConsoleColor.Green;
switch (args[0]) {
case "checksum":
Console.WriteLine("Starting the checksum process.");
Console.ResetColor();
Chksum.doTheThing();
try {
if (args[1] == "MD5") {
utils.doTheThing(args[1]);
}
int bufferSize = int.Parse(args[2]);
utils.doTheThing(args[1], bufferSize);
}
catch (FormatException) {
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Buffer was not a valid integer value. Please specify a valid integer value for the buffer size");
Console.ResetColor();
}
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Checksum process finished");
break;
case "countmd5":
Console.WriteLine("Counting md5 checksum files.");
case "saveToSqlite":
Console.ResetColor();
Chksum.countAllMd5Checksums();
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Finished counting all md5 checksum files.");
utils.saveToSqlite();
break;
case "deletemd5":
Console.WriteLine("Deleting all md5 checksum files.");
case "compareDatabases":
Console.ResetColor();
Chksum.deleteAllMd5Checksums();
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Deleted all md5 checksum files.");
utils.compareDatabases(args[1]);
break;
case "compareChecksums":
Console.WriteLine("Comparing all md5 checksum files. If there is none, creating one.");
case "checkIfFileWasDeleted":
Console.ResetColor();
Chksum.compareChecksums();
utils.checkIfFileWasDeleted();
break;
case "help":
PrintAvailableOptions();
@ -55,20 +63,22 @@
PrintAvailableOptions();
break;
}
utils.cleanup();
}
static void PrintAvailableOptions() {
String[] options = {
"checksum",
"countmd5",
"deletemd5",
"checksum - MD5, Murmur and XxHash - Default buffer size is 4096",
"compareDatabases",
"compareChecksums",
"saveToSqlite",
"checkIfFileWasDeleted",
"help"
};
Console.ResetColor();
Console.WriteLine("usage: chksum [option]");
Console.WriteLine("Here is a list of all available options:");
Console.WriteLine("usage: chksum [option] \nHere is a list of all available options:");
foreach (String option in options) {
Console.WriteLine("\t" + option);
}

468
src/Chksum/chksum.cs Normal file
View File

@ -0,0 +1,468 @@
using System.Collections.Concurrent;
using System.Reflection;
using System.Security.Cryptography;
using Microsoft.Data.Sqlite;
using Serilog;
using Serilog.Events;
using MurmurHash.Net;
using Standart.Hash.xxHash;
using StackExchange.Redis;
namespace Chksum.Utils;
public class ChksumUtils {
private ILogger logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Error)
.WriteTo.File("chksum.log")
.CreateLogger();
private int getTotalFileCount() {
int totalFileCount = Directory.GetFiles(Directory.GetCurrentDirectory(), "*", SearchOption.AllDirectories).Length;
logger.Debug("Total file count is {totalFileCount}", totalFileCount);
return totalFileCount - 4; // Remove the program, datbase, log and library from the totalFileCount
}
private string[] indexFiles() {
string[] indexedFiles = Directory.GetFiles(Directory.GetCurrentDirectory(), "*", SearchOption.AllDirectories);
string[] filesToExclude = { "Chksum", "chksum.db", "libe_sqlite3.so" };
indexedFiles = indexedFiles.Where(file => !filesToExclude.Contains(Path.GetFileName(file))).ToArray();
logger.Information("All files were indexed");
return indexedFiles;
}
public string DatabaseRoot { get; set; } = string.Empty;
public void getBaseDir() {
DatabaseRoot = AppDomain.CurrentDomain.BaseDirectory;
logger.Debug("DatabaseRoot is {DatabaseRoot}", DatabaseRoot);
}
public string libraryPath { get; set; } = string.Empty;
public void ExtractEmbeddedLibrary() {
libraryPath = Path.Combine(DatabaseRoot, "libe_sqlite3.so");
using (Stream? resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Chksum.Libraries.libe_sqlite3.so")) {
if (resourceStream != null) {
byte[] buffer = new byte[resourceStream.Length];
resourceStream.Read(buffer, 0, buffer.Length);
File.WriteAllBytes(libraryPath, buffer);
logger.Debug("libe_sqlite3.so was successfully created");
} else {
logger.Error("libe_sqlite3.so could not be loaded");
throw new Exception(libraryPath + " could not be loaded");
}
}
}
private void initializeDB() {
if (File.Exists("chksum.db")) {
logger.Information("A database already exits");
return;
}
using (var connection = new SqliteConnection("Data Source=chksum.db")) {
connection.Open();
var command = connection.CreateCommand();
command.CommandText =
@"
CREATE TABLE file (
filehash TEXT NOT NULL PRIMARY KEY,
filename TEXT NOT NULL,
pathtofile TEXT NOT NULL,
artist TEXT,
playbacklength INTEGER
);
";
command.ExecuteNonQuery();
var walCommand = connection.CreateCommand();
walCommand.CommandText =
@"
PRAGMA journal_mode = 'wal'
";
walCommand.ExecuteNonQuery();
logger.Information("Database was successfully created");
}
}
public void cleanDB() {
using (var connection = new SqliteConnection("Data Source=" + DatabaseRoot + "chksum.db")) {
var command = connection.CreateCommand();
command.CommandText =
@"
vacuum;
";
command.ExecuteNonQuery();
logger.Debug("Database was successfully vacuumed");
}
}
private void UpdateProgressBar(int current, int total) {
int progress = (int)((double)current / total * 100);
string progressText = $"Progress: {progress}% [{current}/{total}]";
Console.Write("\r" + progressText.PadRight(Console.WindowWidth));
}
private Dictionary<string, string> CalculateChecksums(string[] filenames) {
ConcurrentDictionary<string, string> checksums = new ConcurrentDictionary<string, string>();
int totalFiles = filenames.Length;
int processedFiles = 0;
Parallel.ForEach(filenames, (filename, state) => {
using (var md5 = MD5.Create()) {
using (var stream = File.OpenRead(filename)) {
var hash = md5.ComputeHash(stream);
var checksum = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
lock (checksums) {
checksums.TryAdd(filename, checksum);
}
}
Interlocked.Increment(ref processedFiles);
UpdateProgressBar(processedFiles, totalFiles);
}
});
return new Dictionary<string, string>(checksums);
}
private Dictionary<string, uint> CalculateChecksumsWithMurmur(string[] filenames, int userDefinedBufferSize) {
ConcurrentDictionary<string, uint> checksums = new ConcurrentDictionary<string, uint>();
int totalFiles = filenames.Length;
int processedFiles = 0;
Parallel.ForEach(filenames, (filename, state) => {
using (var stream = File.OpenRead(filename)) {
var hash = CalculateMurmurHash32(stream, userDefinedBufferSize);
lock (checksums) {
checksums.TryAdd(filename, hash);
}
Interlocked.Increment(ref processedFiles);
UpdateProgressBar(processedFiles, totalFiles);
}
});
return new Dictionary<string, uint>(checksums);
}
private uint CalculateMurmurHash32(Stream stream, int userDefinedBufferSize) {
int bufferSize = userDefinedBufferSize;
const uint seed = 123456U;
var buffer = new byte[bufferSize];
uint hash = seed;
int bytesRead;
ReadOnlySpan<byte> span = buffer;
while ((bytesRead = stream.Read(buffer, 0, bufferSize)) > 0) {
hash = MurmurHash3.Hash32(bytes: span, seed: 123456U);
}
return hash;
}
private Dictionary<string, ulong> CalculateChecksumsWithXxHash3(string[] filenames, int userDefinedBufferSize) {
ConcurrentDictionary<string, ulong> checksums = new ConcurrentDictionary<string, ulong>();
int totalFiles = filenames.Length;
int processedFiles = 0;
Parallel.ForEach(filenames, (filename, state) => {
using (var stream = File.OpenRead(filename)) {
var hash = CalculateXxHash3(stream, userDefinedBufferSize);
checksums.TryAdd(filename, hash);
}
Interlocked.Increment(ref processedFiles);
UpdateProgressBar(processedFiles, totalFiles);
});
return new Dictionary<string, ulong>(checksums);
}
private ulong CalculateXxHash3(Stream stream, int userDefinedBufferSize) {
int bufferSize = userDefinedBufferSize;
const ulong seed = 123456U;
var buffer = new byte[bufferSize];
ulong hash = seed;
int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0) {
hash = xxHash3.ComputeHash(buffer, buffer.Length);
}
return hash;
}
public void doTheThing(string hashalgo, int bufferSize = 4096) {
ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost");
IDatabase db = redis.GetDatabase();
if (getTotalFileCount() < 1) {
logger.Information("There were no files to checksum");
return;
}
Dictionary<string, object> fileHashes;
Dictionary<string, ulong> fileHashesXxHash3;
Dictionary<string, uint> fileHashesMurmur;
Dictionary<string, string> fileHashesMD5;
switch (hashalgo) {
case "MD5":
fileHashesMD5 = CalculateChecksums(indexFiles());
fileHashes = fileHashesMD5.ToDictionary(kv => kv.Key, kv => (object)kv.Value);
break;
case "Murmur":
fileHashesMurmur = CalculateChecksumsWithMurmur(indexFiles(), bufferSize);
fileHashes = fileHashesMurmur.ToDictionary(kv => kv.Key, kv => (object)kv.Value);
break;
case "XxHash":
fileHashesXxHash3 = CalculateChecksumsWithXxHash3(indexFiles(), bufferSize);
fileHashes = fileHashesXxHash3.ToDictionary(kv => kv.Key, kv => (object)kv.Value);
break;
default:
logger.Error("No valid hash algorithm was selected");
throw new Exception($"{hashalgo} is not a valid option. Valid options are MD5, Murmur and XxHash");
}
logger.Information("All files were checksummed");
HashEntry[] hashEntries = fileHashes.Select(kv => new HashEntry(kv.Key, kv.Value.ToString())).ToArray();
string hashKey = "fileHashes";
db.HashSet(hashKey, hashEntries);
logger.Information("Dictionary inserted into Redis.");
}
public void saveToSqlite() {
initializeDB();
ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost");
IDatabase db = redis.GetDatabase();
HashEntry[] fileHashes = db.HashGetAll("fileHashes");
logger.Information("Retrived all values from redis");
using (var connection = new SqliteConnection("Data Source=" + DatabaseRoot + "chksum.db;Mode=ReadWrite")) {
foreach (var file in fileHashes) {
var absolutePathToFile = file.Name.ToString();
string fileName = Path.GetFileName(absolutePathToFile.ToString());
string pathToFile = Path.GetRelativePath(DatabaseRoot, absolutePathToFile.ToString());
var fileHash = file.Value.ToString();
if (!checkIfFileMovedAndUpdatePathToFile(fileHash, fileName, pathToFile) && !checkIfFileAlreadyExistsInDatabase(fileHash, fileName)) {
connection.Open();
var InsertCommand = connection.CreateCommand();
InsertCommand.CommandText =
@"
INSERT INTO file (filehash, filename, pathtofile)
VALUES ($filehash, $filename, $pathtofile)
";
InsertCommand.Parameters.AddWithValue("$filehash", fileHash);
InsertCommand.Parameters.AddWithValue("$filename", fileName);
InsertCommand.Parameters.AddWithValue("$pathtofile", pathToFile);
InsertCommand.ExecuteNonQuery();
logger.Verbose("{fileName} which is located at {pathToFile} relative to the database with the hash {fileHash} was successfully inserted into the database", fileName, pathToFile, fileHash);
}
}
}
logger.Information("All filehashes were successfully inserted into the database");
var keys = db.Execute("KEYS", "*");
if (keys == null) {
logger.Error("No values found in redis");
return;
}
foreach (var key in (RedisValue[])keys) {
db.KeyDelete((RedisKey)key.ToString());
}
logger.Information("Redis was successfully cleared of any remaining data");
}
private bool checkIfFileAlreadyExistsInDatabase(string fileHash, string pathToFile) {
string filehash = string.Empty;
string pathtofile = string.Empty;
bool doesExist = false;
using (var connection = new SqliteConnection("Data Source=" + DatabaseRoot + "chksum.db;Mode=ReadOnly")) {
connection.Open();
var command = connection.CreateCommand();
command.CommandText =
@"
SELECT filehash, pathtofile FROM file WHERE filehash = $filehash
";
command.Parameters.AddWithValue("$filehash", fileHash);
using (var reader = command.ExecuteReader()) {
while (reader.Read()) {
filehash = reader.GetString(0);
pathtofile = reader.GetString(1);
}
}
logger.Verbose("{pathToFile} with the hash {fileHash} was successfully loaded", pathToFile, fileHash);
}
if (fileHash == filehash) {
logger.Verbose("File with filehash {filehash} already exists in the database", filehash);
doesExist = true;
}
return doesExist;
}
private bool checkIfFileMovedAndUpdatePathToFile(string fileHash, string fileName, string pathToFile) {
string pathtofile = string.Empty;
bool wasMoved = false;
using (var connection = new SqliteConnection("Data Source=" + DatabaseRoot + "chksum.db;Mode=ReadWrite")) {
connection.Open();
var command = connection.CreateCommand();
command.CommandText =
@"
SELECT pathtofile FROM file WHERE filehash = $filehash
";
command.Parameters.AddWithValue("$filehash", fileHash);
using (var reader = command.ExecuteReader()) {
while (reader.Read()) {
pathtofile = reader.GetString(0);
}
}
if (pathToFile != pathtofile && pathtofile != "") {
var command2 = connection.CreateCommand();
command2.CommandText =
@"
UPDATE file
SET pathtofile = $newpathtofile
WHERE filehash = $filehash
";
command2.Parameters.AddWithValue("$newpathtofile", pathToFile);
command2.Parameters.AddWithValue("$filehash", fileHash);
command2.ExecuteNonQuery();
logger.Verbose("File moved or is a duplicate:\n\tfrom\t{pathToFile}\n\tto \t{pathtofile}\n", pathToFile, pathtofile);
wasMoved = true;
}
logger.Verbose("{fileName} which is located at {pathToFile} relative to the database with the hash {fileHash} was successfully checked", fileName, pathToFile, fileHash);
}
return wasMoved;
}
public void checkIfFileWasDeleted() {
saveToSqlite();
string pathToFile = string.Empty;
using (var connection = new SqliteConnection("Data Source=" + DatabaseRoot + "chksum.db;Mode=ReadWrite")) {
connection.Open();
var selectCommand = connection.CreateCommand();
selectCommand.CommandText =
@"
Select pathtofile FROM file
";
using (var reader = selectCommand.ExecuteReader()) {
while (reader.Read()) {
pathToFile = reader.GetString(0);
if (File.Exists(pathToFile)) {
logger.Verbose("{pathToFile} exists", pathToFile);
continue;
}
var deleteCommand = connection.CreateCommand();
deleteCommand.CommandText =
@"
DELETE FROM file
WHERE pathtofile = $pathtofile
";
deleteCommand.Parameters.AddWithValue("$pathtofile", pathToFile);
deleteCommand.ExecuteNonQuery();
logger.Information("File deleted:\n\t{pathToFile}", pathToFile);
}
}
logger.Information("All deleted files were successfully removed from the database");
}
}
private List<string> getFilehashesFromDatabase(string connectionString) {
List<string> filehashesFromDatabase = new List<string>();
using (var connection = new SqliteConnection(connectionString)) {
string filehash = string.Empty;
connection.Open();
var selectCommand = connection.CreateCommand();
selectCommand.CommandText =
@"
Select filehash FROM file
";
using (var reader = selectCommand.ExecuteReader()) {
while (reader.Read()) {
filehash = reader.GetString(0);
filehashesFromDatabase.Add(filehash);
}
}
}
logger.Debug("All filehashes were successfully retrived from the database");
return filehashesFromDatabase;
}
public void compareDatabases(string filePathToOtherDatabase) {
saveToSqlite();
if (!File.Exists(filePathToOtherDatabase)) {
logger.Error("No database could be found at {filePathToOtherDatabase}", filePathToOtherDatabase);
throw new Exception("No database could be found at " + filePathToOtherDatabase);
}
List<string> filesThatDoNotExistsInTheRemote = getFilehashesFromDatabase("Data Source=" + DatabaseRoot + "chksum.db;Mode=ReadOnly").Except(getFilehashesFromDatabase("Data Source=" + filePathToOtherDatabase + ";Mode=ReadOnly")).ToList();
foreach (string file in filesThatDoNotExistsInTheRemote) {
using (var connection = new SqliteConnection("Data Source=" + DatabaseRoot + "chksum.db;Mode=ReadOnly")) {
string filename = string.Empty;
connection.Open();
var selectCommand = connection.CreateCommand();
selectCommand.CommandText =
@"
Select filename FROM file WHERE filehash = $filehash
";
selectCommand.Parameters.AddWithValue("$filehash", file);
using (var reader = selectCommand.ExecuteReader()) {
while (reader.Read()) {
filename = reader.GetString(0);
Console.WriteLine("File not found in remote:");
Console.WriteLine($"\t{filename}\n");
logger.Information("{filename} could not be found in the remote database", filename);
}
}
}
}
logger.Information("Compared both databases successfully");
}
public void cleanup() {
File.Delete(libraryPath);
logger.Information("Successfully deleted libe_sqlite3.so");
}
}

28
src/chksum.sln Normal file
View File

@ -0,0 +1,28 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chksum", "Chksum\Chksum.csproj", "{BBC56294-03CF-42E0-A838-75AF41EEE32B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chksum.Tests", "Chksum.Tests\Chksum.Tests.csproj", "{239727BC-7124-4985-A6F3-2700295AA06F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BBC56294-03CF-42E0-A838-75AF41EEE32B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BBC56294-03CF-42E0-A838-75AF41EEE32B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BBC56294-03CF-42E0-A838-75AF41EEE32B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BBC56294-03CF-42E0-A838-75AF41EEE32B}.Release|Any CPU.Build.0 = Release|Any CPU
{239727BC-7124-4985-A6F3-2700295AA06F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{239727BC-7124-4985-A6F3-2700295AA06F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{239727BC-7124-4985-A6F3-2700295AA06F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{239727BC-7124-4985-A6F3-2700295AA06F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal