22 Commits

Author SHA1 Message Date
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
f99ca8bb26 revert a6c994fa65
revert previous commit
2023-06-23 23:08:14 +00:00
a6c994fa65 Major changes
Now using an SQlite database to store the file hash plus a bunch of other information
2023-06-24 01:02:03 +02:00
5 changed files with 199 additions and 87 deletions

View File

@ -39,19 +39,23 @@ Go to the publish folder
cd src/Chksum/bin/Release/net7.0/linux-x64/publish cd src/Chksum/bin/Release/net7.0/linux-x64/publish
``` ```
Copy the libe_sqlite3.so to your /usr/local/lib or /usr/lib
```bash
cp libe_sqlite3.so /usr/local/lib
```
Run executable Run executable
```bash ```bash
LD_LIBRARY_PATH=/usr/local/lib ./Chksum ./Chksum
``` ```
Info ## Enabling verbose output for troubleshooting
LD_LIBRARY_PATH=/usr/local/lib is needed to tell the executable where the library is located 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.
Alternately you can put the libe_sqlite3.so into the same folder as the executable ```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.

View File

@ -18,6 +18,9 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.8" /> <PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.8" />
<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" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -8,7 +8,7 @@ public class Program {
Console.WriteLine("Please specify an option."); Console.WriteLine("Please specify an option.");
PrintAvailableOptions(); PrintAvailableOptions();
return; return;
} else if (args.Length > 1) { } else if (args.Length > 1 && args[0] != "compareDatabases") {
Console.WriteLine("Too many options."); Console.WriteLine("Too many options.");
return; return;
} }
@ -30,15 +30,18 @@ public class Program {
Console.ForegroundColor = ConsoleColor.Green; Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Checksum process finished"); Console.WriteLine("Checksum process finished");
break; break;
case "compareChecksums": case "compareDatabases":
Console.WriteLine("Comparing all md5 checksum files. If there is none, creating one.");
Console.ResetColor(); Console.ResetColor();
utils.compareChecksums(); utils.compareDatabases(args[1]);
break; break;
case "createDB": case "createDB":
utils.initializeDB(); utils.initializeDB();
break; break;
case "checkIfFileWasDeleted":
Console.ResetColor();
utils.checkIfFileWasDeleted();
break;
case "help": case "help":
PrintAvailableOptions(); PrintAvailableOptions();
break; break;
@ -57,6 +60,7 @@ public class Program {
"checksum", "checksum",
"compareChecksums", "compareChecksums",
"createDB", "createDB",
"checkIfFileWasDeleted",
"help" "help"
}; };

View File

@ -1,16 +1,36 @@
using System.Collections.Concurrent;
using System.Reflection; using System.Reflection;
using System.Security.Cryptography;
using Microsoft.Data.Sqlite; using Microsoft.Data.Sqlite;
using Serilog;
using Serilog.Events;
namespace Chksum.Utils; namespace Chksum.Utils;
public class ChksumUtils { public class ChksumUtils {
private ILogger logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Error)
.WriteTo.File("chksum.log")
.CreateLogger();
private int getFileCount() { private int getTotalFileCount() {
int fileCount = Directory.GetFiles(Directory.GetCurrentDirectory()).Length; // Get file count in current directory int totalFileCount = Directory.GetFiles(Directory.GetCurrentDirectory(), "*", SearchOption.AllDirectories).Length;
return fileCount; logger.Debug("Total file count is {totalFileCount}", totalFileCount);
return totalFileCount - 3; // Remove the program, datbase 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 string DatabaseRoot { get; set; } = string.Empty;
public void getBaseDir() { public void getBaseDir() {
DatabaseRoot = AppDomain.CurrentDomain.BaseDirectory; DatabaseRoot = AppDomain.CurrentDomain.BaseDirectory;
logger.Debug("DatabaseRoot is {DatabaseRoot}", DatabaseRoot);
} }
public string libraryPath { get; set; } = string.Empty; public string libraryPath { get; set; } = string.Empty;
@ -22,7 +42,9 @@ public class ChksumUtils {
byte[] buffer = new byte[resourceStream.Length]; byte[] buffer = new byte[resourceStream.Length];
resourceStream.Read(buffer, 0, buffer.Length); resourceStream.Read(buffer, 0, buffer.Length);
File.WriteAllBytes(libraryPath, buffer); File.WriteAllBytes(libraryPath, buffer);
logger.Debug("libe_sqlite3.so was successfully created");
} else { } else {
logger.Error("libe_sqlite3.so could not be loaded");
throw new Exception(libraryPath + " could not be loaded"); throw new Exception(libraryPath + " could not be loaded");
} }
} }
@ -30,6 +52,7 @@ public class ChksumUtils {
public void initializeDB() { public void initializeDB() {
if (File.Exists("chksum.db")) { if (File.Exists("chksum.db")) {
logger.Information("A database already exits");
return; return;
} }
@ -48,6 +71,7 @@ public class ChksumUtils {
); );
"; ";
command.ExecuteNonQuery(); command.ExecuteNonQuery();
logger.Information("Database was successfully created");
} }
} }
@ -59,57 +83,69 @@ public class ChksumUtils {
vacuum; vacuum;
"; ";
command.ExecuteNonQuery(); command.ExecuteNonQuery();
logger.Debug("Database was successfully vacuumed");
} }
} }
private string CalculateMD5(string filename) { private Dictionary<string, string> CalculateChecksums(string[] filenames) {
using (var md5 = System.Security.Cryptography.MD5.Create()) { ConcurrentDictionary<string, string> checksums = new ConcurrentDictionary<string, string>();
using (var stream = File.OpenRead(filename)) {
var hash = md5.ComputeHash(stream);
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}
}
}
public void doTheThing() { Parallel.ForEach(filenames, (filename, state) => {
foreach (var directory in Directory.GetDirectories(Directory.GetCurrentDirectory())) using (var md5 = MD5.Create()) {
using (var connection = new SqliteConnection("Data Source=" + DatabaseRoot + "chksum.db;Mode=ReadWrite")) { using (var stream = File.OpenRead(filename)) {
Directory.SetCurrentDirectory(directory); // Set new root var hash = md5.ComputeHash(stream);
if (getFileCount() >= 1) { var checksum = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
DirectoryInfo dir = new DirectoryInfo(Directory.GetCurrentDirectory());
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files) {
string fileName = file.Name;
string absolutePathToFile = Path.GetFullPath(fileName);
string pathToFile = Path.GetRelativePath(DatabaseRoot, absolutePathToFile);
string fileHash = CalculateMD5(fileName);
if (checkIfFileMovedAndUpdatePathToFile(fileHash, fileName, pathToFile) == false && checkIfFileAlreadyExists(fileHash, fileName) == false) { lock (checksums) {
connection.Open(); checksums.TryAdd(filename, checksum);
var command = connection.CreateCommand();
command.CommandText =
@"
INSERT INTO file (filehash, filename, pathtofile)
VALUES ($filehash, $filename, $pathtofile)
";
command.Parameters.AddWithValue("$filehash", fileHash);
command.Parameters.AddWithValue("$filename", fileName);
command.Parameters.AddWithValue("$pathtofile", pathToFile);
command.ExecuteNonQuery();
} }
} }
} }
doTheThing(); });
logger.Debug("All files were checksummed");
return new Dictionary<string, string>(checksums);
}
public void doTheThing() {
using (var connection = new SqliteConnection("Data Source=" + DatabaseRoot + "chksum.db;Mode=ReadWrite")) {
if (getTotalFileCount() < 1) {
logger.Information("There were no files to checksum");
return;
}
connection.Open();
Dictionary<string, string> fileHashes = CalculateChecksums(indexFiles());
foreach (var file in fileHashes) {
string absolutePathToFile = file.Key;
string fileName = Path.GetFileName(absolutePathToFile);
string pathToFile = Path.GetRelativePath(DatabaseRoot, absolutePathToFile);
string fileHash = file.Value;
if (checkIfFileMovedAndUpdatePathToFile(fileHash, fileName, pathToFile) == false && checkIfFileAlreadyExistsInDatabase(fileHash, fileName) == false) {
var command = connection.CreateCommand();
command.CommandText =
@"
INSERT INTO file (filehash, filename, pathtofile)
VALUES ($filehash, $filename, $pathtofile)
";
command.Parameters.AddWithValue("$filehash", fileHash);
command.Parameters.AddWithValue("$filename", fileName);
command.Parameters.AddWithValue("$pathtofile", pathToFile);
command.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 files were successfully written to the database");
} }
} }
private bool checkIfFileAlreadyExists(string fileHash, string pathToFile) { private bool checkIfFileAlreadyExistsInDatabase(string fileHash, string pathToFile) {
string filehash = string.Empty; string filehash = string.Empty;
string pathtofile = string.Empty; string pathtofile = string.Empty;
bool doesExist = false; bool doesExist = false;
using (var connection = new SqliteConnection("Data Source=" + DatabaseRoot + "chksum.db;Mode=ReadWrite")) { using (var connection = new SqliteConnection("Data Source=" + DatabaseRoot + "chksum.db;Mode=ReadOnly")) {
connection.Open(); connection.Open();
var command = connection.CreateCommand(); var command = connection.CreateCommand();
@ -125,12 +161,11 @@ public class ChksumUtils {
pathtofile = reader.GetString(1); pathtofile = reader.GetString(1);
} }
} }
logger.Verbose("{pathToFile} with the hash {fileHash} was successfully loaded", pathToFile, fileHash);
} }
if (fileHash == filehash) { if (fileHash == filehash) {
Console.WriteLine("Duplicate files found:"); logger.Verbose("File with filehash {filehash} already exists in the database", filehash);
Console.WriteLine($"\toriginal\t{pathToFile}");
Console.WriteLine($"\tduplicate\t{pathtofile}\n");
doesExist = true; doesExist = true;
} }
return doesExist; return doesExist;
@ -168,51 +203,117 @@ public class ChksumUtils {
command2.Parameters.AddWithValue("$filehash", fileHash); command2.Parameters.AddWithValue("$filehash", fileHash);
command2.ExecuteNonQuery(); command2.ExecuteNonQuery();
Console.WriteLine("File moved:"); Console.WriteLine("File moved or is a duplicate:");
Console.WriteLine($"\tfrom\t{pathToFile}"); Console.WriteLine($"\tfrom\t{pathToFile}");
Console.WriteLine($"\tto \t{pathtofile}\n"); Console.WriteLine($"\tto \t{pathtofile}\n");
wasMoved = true; wasMoved = true;
} }
return wasMoved; 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() {
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();
Console.WriteLine("File deleted:");
Console.WriteLine($"\t{pathToFile}\n");
logger.Verbose("File deleted: {pathToFile}", pathToFile);
}
}
logger.Information("All deleted files were successfully removed from the database");
} }
} }
public void compareChecksums() { private List<string> getFilehashesFromDatabase(string connectionString) {
foreach (var directory in Directory.GetDirectories(Directory.GetCurrentDirectory())) { List<string> filehashesFromDatabase = new List<string>();
Directory.SetCurrentDirectory(directory); // Set new root
if (getFileCount() >= 1) { using (var connection = new SqliteConnection(connectionString)) {
DirectoryInfo dir = new DirectoryInfo(Directory.GetCurrentDirectory()); string filehash = string.Empty;
FileInfo[] files = dir.GetFiles();
// files.ToList().ForEach(i => Console.WriteLine(i.ToString())); // Print all files in files array connection.Open();
foreach (FileInfo file in files) {
string fileName = file.Name; var selectCommand = connection.CreateCommand();
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName); selectCommand.CommandText =
string checksumFile = Directory.GetCurrentDirectory() + "/" + fileNameWithoutExtension + ".md5"; @"
string fileMd5Checksum = fileNameWithoutExtension + ".md5"; Select filehash FROM file
if (File.Exists(fileMd5Checksum)) { ";
string newFileChecksum = CalculateMD5(fileName) + " " + fileName;
string existingFileChecksum = File.ReadAllText(fileMd5Checksum); using (var reader = selectCommand.ExecuteReader()) {
string newFileName = newFileChecksum.Substring(34); while (reader.Read()) {
string existingFileName = existingFileChecksum.Substring(34); filehash = reader.GetString(0);
if (newFileChecksum.Equals(existingFileChecksum)) { filehashesFromDatabase.Add(filehash);
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); logger.Debug("All filehashes were successfully retrived from the database");
// TODO Tell the user to check which file is the correct one return filehashesFromDatabase;
} }
} else {
File.AppendAllText(checksumFile, CalculateMD5(fileName) + " " + fileName); public void compareDatabases(string filePathToOtherDatabase) {
Console.WriteLine("Calculated checksum for: " + checksumFile); 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.Verbose("{filename} could not be found in the remote database", filename);
} }
} }
} }
compareChecksums();
} }
logger.Information("Compared both databases successfully");
} }
public void cleanup() { public void cleanup() {
File.Delete(libraryPath); File.Delete(libraryPath);
logger.Debug("Successfully deleted libe_sqlite3.so");
} }
} }