From 0bdfcb7399a553cc55715893031a9a4dfa271c87 Mon Sep 17 00:00:00 2001 From: Kiril Markov Date: Wed, 7 Apr 2021 07:19:32 +0300 Subject: [PATCH] Add project files. --- Files.sln | 25 +++++ Files/.vscode/launch.json | 27 +++++ Files/.vscode/tasks.json | 42 ++++++++ Files/Files.csproj | 16 +++ Files/Program.cs | 156 +++++++++++++++++++++++++++ Files/Properties/launchSettings.json | 7 ++ Files/db.db | Bin 0 -> 57344 bytes global.json | 7 ++ 8 files changed, 280 insertions(+) create mode 100644 Files.sln create mode 100644 Files/.vscode/launch.json create mode 100644 Files/.vscode/tasks.json create mode 100644 Files/Files.csproj create mode 100644 Files/Program.cs create mode 100644 Files/Properties/launchSettings.json create mode 100644 Files/db.db create mode 100644 global.json diff --git a/Files.sln b/Files.sln new file mode 100644 index 0000000..1091ce7 --- /dev/null +++ b/Files.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31025.218 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Files", "Files\Files.csproj", "{28005FE3-CBA7-4D00-9272-180C392D35A9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {28005FE3-CBA7-4D00-9272-180C392D35A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {28005FE3-CBA7-4D00-9272-180C392D35A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {28005FE3-CBA7-4D00-9272-180C392D35A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {28005FE3-CBA7-4D00-9272-180C392D35A9}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FB5DD751-57CA-4F80-923A-1CE1C71654D7} + EndGlobalSection +EndGlobal diff --git a/Files/.vscode/launch.json b/Files/.vscode/launch.json new file mode 100644 index 0000000..8198701 --- /dev/null +++ b/Files/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/bin/Debug/net6.0/Files.dll", + "args": [], + "cwd": "${workspaceFolder}", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} \ No newline at end of file diff --git a/Files/.vscode/tasks.json b/Files/.vscode/tasks.json new file mode 100644 index 0000000..de89ee3 --- /dev/null +++ b/Files/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Files.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/Files.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "${workspaceFolder}/Files.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/Files/Files.csproj b/Files/Files.csproj new file mode 100644 index 0000000..8a58449 --- /dev/null +++ b/Files/Files.csproj @@ -0,0 +1,16 @@ + + + + Exe + net5.0 + + + + + + + + + + + diff --git a/Files/Program.cs b/Files/Program.cs new file mode 100644 index 0000000..f8449df --- /dev/null +++ b/Files/Program.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Spectre.Console; +using Microsoft.Data.Sqlite; +using Mono.Unix; +using System.Threading; +using System.CommandLine; +using System.CommandLine.Invocation; +using System.CommandLine.Parsing; +using System.Threading.Tasks; +using System.Linq; +using Dapper; +using System.Security.Cryptography; + +namespace Files { + class Program { + private static async Task IndexFiles(bool isVerbose, DirectoryInfo startDirectory, CancellationToken ct) { + await AnsiConsole.Status() + .StartAsync("Thinking...", async ctx => { + using var connection = new SqliteConnection("Data Source=db.db"); + connection.Open(); + await using var transaction = await connection.BeginTransactionAsync(); + + var cnt = connection.ExecuteScalar("SELECT count(*) FROM sqlite_master WHERE type='table' AND name=@tableName;", new { tableName = "files" }); + if (cnt == 0) + { + connection.Execute("CREATE TABLE IF NOT EXISTS files (name TEXT, size INTEGER, inode INTEGER);"); + } + + Queue directoriesQueue = new Queue(); + directoriesQueue.Enqueue(startDirectory?.ToString() ?? "."); + + try { + while (directoriesQueue.TryDequeue(out string peekedDir)) { + ctx.Status(peekedDir.Replace("[", "[[").Replace("]", "]]")); + + UnixDirectoryInfo dirInfo = new(peekedDir); + if (!dirInfo.CanAccess(Mono.Unix.Native.AccessModes.R_OK) + || !dirInfo.CanAccess(Mono.Unix.Native.AccessModes.X_OK)) { + AnsiConsole.MarkupLine($"[red]:cross_mark: NO_ACCESS:[/] :file_folder: {dirInfo.ToString().Replace("[", "[[").Replace("]", "]]")}"); + return; + } + UnixFileSystemInfo[] entries = dirInfo.GetFileSystemEntries(); + foreach (UnixFileSystemInfo entry in entries) { + string relativePath = Path.Combine(peekedDir, entry.Name); + if (!entry.CanAccess(Mono.Unix.Native.AccessModes.R_OK)) { + if (entry.IsDirectory) + AnsiConsole.MarkupLine($"[red]:cross_mark: NO_ACCESS:[/] :file_folder: {relativePath.Replace("[", "[[").Replace("]", "]]")}"); + else if (entry.IsRegularFile) + AnsiConsole.MarkupLine($"[red]:cross_mark: NO_ACCESS:[/] :page_facing_up: {relativePath.Replace("[", "[[").Replace("]", "]]")}"); + continue; + } + + if (entry.IsDirectory) { + directoriesQueue.Enqueue(relativePath); + continue; + } + + connection.Execute("INSERT INTO files (name, size, inode) VALUES (@name, @Length, @Inode);", new { name = relativePath, entry.Length, entry.Inode }); + + if (isVerbose) + AnsiConsole.MarkupLine($"[green]:check_mark: OK:[/] {relativePath.Replace("[", "[[").Replace("]", "]]")}"); + + if (ct.IsCancellationRequested) + return; + } + } + transaction.Commit(); + } catch (Exception exception) { + await transaction.RollbackAsync(); + AnsiConsole.WriteException(exception); + + return; + } + + ctx.Status("Finding duplicates..."); + ctx.Spinner(Spinner.Known.Aesthetic); + + var potential = connection.Query<(int cnt, long size)>("SELECT COUNT(*) cnt, size FROM files WHERE size != 0 GROUP BY size HAVING cnt > 1 ORDER BY size * cnt DESC;"); + + foreach (var potentialFile in potential) { + var sameSize = connection.Query("SELECT name, size, inode FROM files WHERE size = @size", + new { potentialFile.size }).ToList(); + + var equalGrouped = sameSize + .Where(r => r.Hash.HasValue) + .GroupBy(r=>r.Hash) + .Where(g=>g.Count() > 1) + .ToList(); + + foreach (var grp in equalGrouped) { + var root = new Tree(":double_exclamation_mark: " + grp.Key); + foreach (var item in grp) { + root.AddNode(item.Name); + } + AnsiConsole.Render(root); + } + } + + }); + } + + private static async Task Main(string[] args) { + var verboseOption = new Option(new []{"--verbose", "-v"} ,"Verbose"); + var directoryArgument = new Argument( + result => new DirectoryInfo("./"), isDefault: true) + { + Name = "directory", + Description = "Directory to scan.", + Arity = ArgumentArity.ZeroOrOne, + }.ExistingOnly(); + + var rootCommand = new RootCommand("$ File -v false ./") + { + verboseOption, + directoryArgument, + }; + + ParseResult result = rootCommand.Parse(args); + ArgumentResult dirResult = result.FindResultFor(directoryArgument); + var dir = new DirectoryInfo( + dirResult.Tokens.FirstOrDefault()?.Value + ?? dirResult.Argument.GetDefaultValue()?.ToString()); + + rootCommand.Handler = CommandHandler.Create( + async (verbose, ct) => await IndexFiles(verbose, dir, ct)); + + await rootCommand.InvokeAsync(args); + } + } + + public class DbRecord { + private readonly Lazy _guid; + + public DbRecord() { + _guid = new Lazy(GetHash); + } + + public string Name { get; set; } + public long Size { get; set; } + public long Inode { get; set; } + public Guid? Hash => _guid.Value; + + public Guid? GetHash() { + try { + using FileStream stream = File.OpenRead(Name); + var md5 = MD5.Create(); + var bytes = md5.ComputeHash(stream); + return new Guid(bytes); + } catch { + return null; + } + } + } +} diff --git a/Files/Properties/launchSettings.json b/Files/Properties/launchSettings.json new file mode 100644 index 0000000..f710fc4 --- /dev/null +++ b/Files/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "Files": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/Files/db.db b/Files/db.db new file mode 100644 index 0000000000000000000000000000000000000000..825c3369b35a07ab8d9cea9c6c790990257851e4 GIT binary patch literal 57344 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 literal 0 HcmV?d00001 diff --git a/global.json b/global.json new file mode 100644 index 0000000..ac1ec45 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "5.0.0", + "allowPrerelease": true, + "rollForward": "latestMinor" + } +} \ No newline at end of file