Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to .NET 8 #7

Merged
merged 3 commits into from
Jul 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions FileSorter.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<StartupObject>FileSorter.Program</StartupObject>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<Version>0.0.1</Version>
Expand All @@ -20,7 +20,7 @@

<ItemGroup>
<PackageReference Include="ExifLib.Standard" Version="1.7.0" />
<PackageReference Include="PowerArgs" Version="3.6.0" />
<PackageReference Include="PowerArgs" Version="4.0.3" />
</ItemGroup>

</Project>
245 changes: 123 additions & 122 deletions Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
namespace FileSorter;

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
Expand All @@ -8,180 +10,179 @@

using PowerArgs;

namespace FileSorter
class Program
{
class Program
[TabCompletion, MyArgHook]
class ProgramArgs
{
[TabCompletion, MyArgHook]
class ProgramArgs
{
[HelpHook, ArgShortcut("?"), ArgShortcut("h"), ArgShortcut("--?"), ArgShortcut("--h"), ArgDescription("Shows help")]
public bool Help { get; set; }
[HelpHook, ArgShortcut("?"), ArgShortcut("h"), ArgShortcut("--?"), ArgShortcut("--h"), ArgDescription("Shows help")]
public bool Help { get; set; }

[ArgShortcut("i"), ArgShortcut("--i"), ArgShortcut("in"), ArgShortcut("--in"), ArgDescription(@"The directory containing the files to process"), ArgDefaultValue(@"."), ArgExistingDirectory]
public string InputDirectory { get; set; }

[ArgShortcut("o"), ArgShortcut("--o"), ArgShortcut("out"), ArgShortcut("--out"), ArgDescription(@"The directory in which to create folders containing the sorted files by year & month"), ArgDefaultValue(@".\out")]
public string OutputDirectory { get; set; }

[ArgShortcut("i"), ArgShortcut("--i"), ArgShortcut("in"), ArgShortcut("--in"), ArgDescription(@"The directory containing the files to process"), ArgDefaultValue(@"."), ArgExistingDirectory]
public string InputDirectory { get; set; }
[ArgShortcut("p"), ArgShortcut("--p"), ArgDescription(@"To denote the folder being processed contains images whose EXIF date should be used, if possible"), ArgDefaultValue(false)]
public bool IsPictures { get; set; }

[ArgShortcut("o"), ArgShortcut("--o"), ArgShortcut("out"), ArgShortcut("--out"), ArgDescription(@"The directory in which to create folders containing the sorted files by year & month"), ArgDefaultValue(@".\out")]
public string OutputDirectory { get; set; }
[ArgShortcut("whatif"), ArgShortcut("--whatif"), ArgDescription(@"Don't actually move files, just show what would happen if we were to move them"), ArgDefaultValue(false)]
public bool NoOp { get; set; }

[ArgShortcut("p"), ArgShortcut("--p"), ArgDescription(@"To denote the folder being processed contains images whose EXIF date should be used, if possible"), ArgDefaultValue(false)]
public bool IsPictures { get; set; }
[ArgShortcut("f"), ArgShortcut("--f"), ArgDescription(@"Automatically overwrite files in destination, if they exist"), ArgDefaultValue(false)]
public bool Force { get; set; }

[ArgShortcut("whatif"), ArgShortcut("--whatif"), ArgDescription(@"Don't actually move files, just show what would happen if we were to move them"), ArgDefaultValue(false)]
public bool NoOp { get; set; }
[ArgShortcut("u"), ArgShortcut("--u"), ArgDescription(@"True to update the creation & write time to match EXIF time, false otherwise"), ArgDefaultValue(false)]
public bool UpdateTimestamp { get; set; }

[ArgShortcut("f"), ArgShortcut("--f"), ArgDescription(@"Automatically overwrite files in destination, if they exist"), ArgDefaultValue(false)]
public bool Force { get; set; }
[ArgShortcut("n"), ArgShortcut("--n"), ArgDescription(@"Don't move any files (useful with -u to update times only)"), ArgDefaultValue(false)]
public bool NoMove { get; set; }

[ArgShortcut("u"), ArgShortcut("--u"), ArgDescription(@"True to update the creation & write time to match EXIF time, false otherwise"), ArgDefaultValue(false)]
public bool UpdateTimestamp { get; set; }
[ArgShortcut("r"), ArgShortcut("--r"), ArgDescription(@"True to process all files in all subdirectories of Input Directory. Compatible only with -NoMove"), ArgDefaultValue(false)]
public bool Recurse { get; set; }

[ArgShortcut("n"), ArgShortcut("--n"), ArgDescription(@"Don't move any files (useful with -u to update times only)"), ArgDefaultValue(false)]
public bool NoMove { get; set; }
[ArgShortcut("y"), ArgShortcut("--y"), ArgShortcut("--confirm"), ArgDescription(@"Do not prompt to commence operation"), ArgDefaultValue(false)]
public bool Confirm { get; set; }
}

[ArgShortcut("r"), ArgShortcut("--r"), ArgDescription(@"True to process all files in all subdirectories of Input Directory. Compatible only with -NoMove"), ArgDefaultValue(false)]
public bool Recurse { get; set; }
class MyArgHook : ArgHook
{
public override void BeforeValidateDefinition(HookContext context)
{
context.Definition.IsNonInteractive = true;

[ArgShortcut("y"), ArgShortcut("--y"), ArgShortcut("--confirm"), ArgDescription(@"Do not prompt to commence operation"), ArgDefaultValue(false)]
public bool Confirm { get; set; }
base.BeforeValidateDefinition(context);
}

class MyArgHook : ArgHook
public override void AfterPopulateProperties(HookContext context)
{
public override void BeforeValidateDefinition(HookContext context)
{
context.Definition.IsNonInteractive = true;
var input = context.Args as ProgramArgs;

base.BeforeValidateDefinition(context);
var errMsgs = new List<string>();
if (input.Recurse && !input.NoMove)
{
errMsgs.Add("ERROR: Recurse is only available if NoMove is also true");
}

public override void AfterPopulateProperties(HookContext context)
if (input.NoOp && input.Force)
{
var input = context.Args as ProgramArgs;

var errMsgs = new List<string>();
if (input.Recurse && !input.NoMove)
{
errMsgs.Add("ERROR: Recurse is only available if NoMove is also true");
}

if (input.NoOp && input.Force)
{
errMsgs.Add("ERROR: NoOp and Force cannot both be true");
}
errMsgs.Add("ERROR: NoOp and Force cannot both be true");
}

if (errMsgs.Any())
{
Console.WriteLine(string.Concat(string.Join(Environment.NewLine, errMsgs), Environment.NewLine));
PrintUsage();
context.CancelAllProcessing();
}
if (errMsgs.Any())
{
Console.WriteLine(string.Concat(string.Join(Environment.NewLine, errMsgs), Environment.NewLine));
PrintUsage();
context.CancelAllProcessing();
}
}
}

static void Main(string[] args)
static void Main(string[] args)
{
ProgramArgs input;
try
{
ProgramArgs input;
try
{
input = PowerArgs.Args.Parse<ProgramArgs>(args);
input = PowerArgs.Args.Parse<ProgramArgs>(args);

if (input == null || input.Help)
{
// means client ran with '-h' to output help.
return;
}
}
catch (PowerArgs.ArgException ex)
if (input == null || input.Help)
{
Console.WriteLine(ex.Message);
// means client ran with '-h' to output help.
return;
}
}
catch (PowerArgs.ArgException ex)
{
Console.WriteLine(ex.Message);
return;
}

var inputDirectory = string.IsNullOrEmpty(input.InputDirectory) ? Environment.CurrentDirectory : input.InputDirectory;
var outputDirectory = string.IsNullOrEmpty(input.OutputDirectory) ? inputDirectory : input.OutputDirectory;
var inputDirectory = string.IsNullOrEmpty(input.InputDirectory) ? Environment.CurrentDirectory : input.InputDirectory;
var outputDirectory = string.IsNullOrEmpty(input.OutputDirectory) ? inputDirectory : input.OutputDirectory;

var inputDirectoryInfo = new DirectoryInfo(inputDirectory);
var filesToProcess = inputDirectoryInfo.EnumerateFiles(@"*", new EnumerationOptions
{
RecurseSubdirectories = input.Recurse,
MatchCasing = MatchCasing.CaseInsensitive,
MatchType = MatchType.Simple,
ReturnSpecialDirectories = false
});
var inputDirectoryInfo = new DirectoryInfo(inputDirectory);
var filesToProcess = inputDirectoryInfo.EnumerateFiles(@"*", new EnumerationOptions
{
RecurseSubdirectories = input.Recurse,
MatchCasing = MatchCasing.CaseInsensitive,
MatchType = MatchType.Simple,
ReturnSpecialDirectories = false
});

var outputDirectoryInfo = new DirectoryInfo(outputDirectory);
var outputDirectoryInfo = new DirectoryInfo(outputDirectory);

if (!input.Confirm)
if (!input.Confirm)
{
Console.WriteLine($@"About to process {filesToProcess.LongCount()} files(s) from {inputDirectoryInfo.FullName} into {outputDirectoryInfo.FullName} ...");
Console.WriteLine(@"This operation cannot be undone! Press any key to continue or Esc to cancel");
if (Console.ReadKey().Key == ConsoleKey.Escape)
{
Console.WriteLine($@"About to process {filesToProcess.LongCount()} files(s) from {inputDirectoryInfo.FullName} into {outputDirectoryInfo.FullName} ...");
Console.WriteLine(@"This operation cannot be undone! Press any key to continue or Esc to cancel");
if (Console.ReadKey().Key == ConsoleKey.Escape)
{
return;
}
return;
}
}

Console.WriteLine(@"Processing files...");
Console.WriteLine(@"Processing files...");

Parallel.ForEach(filesToProcess, fi =>
{
var timeToUse = new[] { fi.CreationTime, fi.LastWriteTime, fi.LastAccessTime }.OrderBy(i => i).First();
Parallel.ForEach(filesToProcess, fi =>
{
var timeToUse = new[] { fi.CreationTime, fi.LastWriteTime, fi.LastAccessTime }.OrderBy(i => i).First();

if (input.IsPictures)
if (input.IsPictures)
{
try
{
try
using var exif = new ExifReader(fi.FullName);
exif.GetTagValue(ExifTags.DateTime, out DateTime time);
if (time != DateTime.MinValue)
{
using var exif = new ExifReader(fi.FullName);
exif.GetTagValue(ExifTags.DateTime, out DateTime time);
if (time != DateTime.MinValue)
if (input.UpdateTimestamp && time != fi.CreationTime)
{
if (input.UpdateTimestamp && time != fi.CreationTime)
Console.Write($@"Updating time on {fi.Name} from {timeToUse} -> {time} ...");
if (!input.NoOp)
{
Console.Write($@"Updating time on {fi.Name} from {timeToUse} -> {time} ...");
if (!input.NoOp)
{
fi.CreationTime = fi.LastWriteTime = time;
}
Console.WriteLine();
fi.CreationTime = fi.LastWriteTime = time;
}

timeToUse = time;
Console.WriteLine();
}

timeToUse = time;
}
catch { }
}
catch { }
}

if (!input.NoMove && !input.Recurse)
if (!input.NoMove && !input.Recurse)
{
var dirName = Path.Combine(timeToUse.ToString(@"yyyy"), timeToUse.ToString(@"MM MMMM"));
var targetFolder = Path.Combine(outputDirectory, dirName);

if (!input.NoOp)
{
var dirName = Path.Combine(timeToUse.ToString(@"yyyy"), timeToUse.ToString(@"MM MMMM"));
var targetFolder = Path.Combine(outputDirectory, dirName);
Directory.CreateDirectory(targetFolder);
}

if (!input.NoOp)
Console.Write($@"{fi.Name} -> {dirName} ...");
if (!input.NoOp)
{
try
{
Directory.CreateDirectory(targetFolder);
File.Move(fi.FullName, Path.Combine(targetFolder, fi.Name), input.Force);
}

Console.Write($@"{fi.Name} -> {dirName} ...");
if (!input.NoOp)
catch (IOException ex)
{
try
{
File.Move(fi.FullName, Path.Combine(targetFolder, fi.Name), input.Force);
}
catch (IOException ex)
{
Console.Error.WriteLine($@"Error: {ex.Message}");
}
Console.Error.WriteLine($@"Error: {ex.Message}");
}
Console.WriteLine();
}
});
}

private static void PrintUsage()
{
Console.Write(PowerArgs.ArgUsage.GenerateUsageFromTemplate<ProgramArgs>());
}
Console.WriteLine();
}
});
}

private static void PrintUsage()
{
Console.Write(PowerArgs.ArgUsage.GenerateUsageFromTemplate<ProgramArgs>());
}
}