Add project files.

This commit is contained in:
2021-04-07 07:19:32 +03:00
parent dc5850d175
commit 0bdfcb7399
8 changed files with 280 additions and 0 deletions

25
Files.sln Normal file
View File

@@ -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

27
Files/.vscode/launch.json vendored Normal file
View File

@@ -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}"
}
]
}

42
Files/.vscode/tasks.json vendored Normal file
View File

@@ -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"
}
]
}

16
Files/Files.csproj Normal file
View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="2.0.78" />
<PackageReference Include="Mono.Posix.NETStandard" Version="5.20.1-preview" />
<PackageReference Include="Spectre.Console" Version="0.38.1-preview.0.24" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.0-preview.2.21154.2" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20574.7" />
</ItemGroup>
</Project>

156
Files/Program.cs Normal file
View File

@@ -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<int>("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<string> directoriesQueue = new Queue<string>();
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<DbRecord>("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<bool>(new []{"--verbose", "-v"} ,"Verbose");
var directoryArgument = new Argument<DirectoryInfo>(
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<bool, CancellationToken>(
async (verbose, ct) => await IndexFiles(verbose, dir, ct));
await rootCommand.InvokeAsync(args);
}
}
public class DbRecord {
private readonly Lazy<Guid?> _guid;
public DbRecord() {
_guid = new Lazy<Guid?>(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;
}
}
}
}

View File

@@ -0,0 +1,7 @@
{
"profiles": {
"Files": {
"commandName": "Project"
}
}
}

BIN
Files/db.db Normal file

Binary file not shown.

7
global.json Normal file
View File

@@ -0,0 +1,7 @@
{
"sdk": {
"version": "5.0.0",
"allowPrerelease": true,
"rollForward": "latestMinor"
}
}