Problem
What do you think about this file system manipulation helper? There is an utility class Folder which I can use to define directory structure of my app:
static class Folders
{
public static Folder Bin =>
new Folder(Assembly.GetExecutingAssembly());
public static Folder App => Bin.Up();
public static Folder Docs => App.Down("Docs");
public static Folder Temp => App.Down("Temp");
}
Then I can do some manipulations in an easy way:
static void Main(string[] args)
{
Folders.Temp.Create();
Folders.Bin.Run("scan.exe.", "-no-ui");
Folders.Temp.CopyTo(Folders.Docs, f => f.EndsWith(".pdf"));
Folders.Temp.Empty();
}
Here is the library class Folder used above:
public class Folder
{
readonly string _path;
public Folder(Assembly assembly)
: this(Path.GetDirectoryName(assembly.Location))
{
}
public Folder(string path)
{
_path = path;
}
public override string ToString() => _path;
public static implicit operator string(Folder folder) =>
folder.ToString();
public IEnumerable<Folder> Folders =>
Directory.GetDirectories(this, "*", SearchOption.TopDirectoryOnly)
.Select(p => new Folder(p));
public IEnumerable<Folder> AllFolders =>
Directory.GetDirectories(this, "*", SearchOption.AllDirectories)
.Select(p => new Folder(p));
public IEnumerable<string> Files =>
Directory.GetFiles(this, "*.*", SearchOption.TopDirectoryOnly);
public IEnumerable<string> AllFiles =>
Directory.GetFiles(this, "*.*", SearchOption.AllDirectories);
public Folder Up() =>
new Folder(
new DirectoryInfo(this)
.Parent.FullName);
public Folder Down(string folderName) =>
new Folder(
Path.Combine(
this,
folderName));
public void Create() =>
Directory.CreateDirectory(this);
public void Empty()
{
var directoryInfo = new DirectoryInfo(this);
if (!directoryInfo.Exists)
return;
foreach (var file in AllFiles)
File.Delete(file);
foreach (var folder in Folders)
Directory.Delete(folder, true);
}
public void CopyTo(Folder destination) =>
CopyTo(destination, file => true);
public void CopyTo(Folder destination, Func<string, bool> filter)
{
//Create directories
foreach (string directoryPath in AllFolders)
Directory.CreateDirectory(directoryPath.Replace(this, destination));
//Copy all the files & replaces any files with the same name
foreach (string filePath in AllFiles.Where(filter))
File.Copy(filePath, filePath.Replace(this, destination), true);
}
public void Run(string exe, string args = "")
{
string defaultCurrentDirectory = Environment.CurrentDirectory;
Environment.CurrentDirectory = this;
try
{
var process = new Process();
process.StartInfo.FileName = exe;
process.StartInfo.Arguments = args;
process.Start();
process.WaitForExit();
if (process.ExitCode != 0)
throw new InvalidOperationException(
$"{exe} process failed with exit code {process.ExitCode}.");
}
finally
{
Environment.CurrentDirectory = defaultCurrentDirectory;
}
}
}
Solution
I’m always a fan of using interfaces wherever possible so that unit tests can mock out my component easily. So let’s create a couple of interfaces:
public interface IFolder
{
IEnumerable<IFolder> Folders { get; }
IEnumerable<IFolder> AllFolders { get; }
IEnumerable<string> Files { get; }
IEnumerable<string> AllFiles { get; }
IFolder Up();
IFolder Down(string folderName);
void Create();
void Empty();
void CopyTo(IFolder destination);
void CopyTo(IFolder destination, Func<string, bool> filter);
void Run(string exe, string args = "");
}
and
internal interface IFolders
{
IFolder Bin { get; }
IFolder App { get; }
IFolder Docs { get; }
IFolder Temp { get; }
}
I also like to re-use constants. And I added another implicit operator to go with your new constructor:
public class Folder : IFolder
{
private const string DirectoryWildcard = "*";
private const string FileWildcard = "*.*";
private readonly string _Path;
public Folder(Assembly assembly)
: this(Path.GetDirectoryName(assembly.Location))
{
}
public Folder(string path)
{
this._Path = path;
}
public override string ToString() => this._Path;
public static implicit operator string(Folder folder) => folder.ToString();
public static implicit operator Folder(string path) => new Folder(path);
public static implicit operator Folder(Assembly assembly) => new Folder(assembly);
public IEnumerable<IFolder> Folders => Directory
.GetDirectories(this, DirectoryWildcard, SearchOption.TopDirectoryOnly)
.Select(path => new Folder(path));
public IEnumerable<IFolder> AllFolders => Directory
.GetDirectories(this, DirectoryWildcard, SearchOption.AllDirectories)
.Select(path => new Folder(path));
public IEnumerable<string> Files => Directory.GetFiles(this, FileWildcard, SearchOption.TopDirectoryOnly);
public IEnumerable<string> AllFiles => Directory.GetFiles(this, FileWildcard, SearchOption.AllDirectories);
public IFolder Up() => new Folder(new DirectoryInfo(this).Parent.FullName);
public IFolder Down(string folderName) => new Folder(Path.Combine(this, folderName));
public void Create() => Directory.CreateDirectory(this);
public void Empty()
{
var directoryInfo = new DirectoryInfo(this);
if (!directoryInfo.Exists)
{
return;
}
foreach (var file in AllFiles)
{
File.Delete(file);
}
foreach (var folder in Folders)
{
Directory.Delete(folder.ToString(), true);
}
}
public void CopyTo(IFolder destination) => this.CopyTo(destination, file => true);
public void CopyTo(IFolder destination, Func<string, bool> filter)
{
// Create directories
foreach (var folder in this.AllFolders)
{
Directory.CreateDirectory(folder.ToString().Replace(this, destination.ToString()));
}
// Copy all the files & replaces any files with the same name
foreach (var filePath in this.AllFiles.Where(filter))
{
File.Copy(filePath, filePath.Replace(this, destination.ToString()), true);
}
}
public void Run(string exe, string args = "")
{
var defaultCurrentDirectory = Environment.CurrentDirectory;
Environment.CurrentDirectory = this;
try
{
var process = new Process { StartInfo = { FileName = exe, Arguments = args } };
process.Start();
process.WaitForExit();
if (process.ExitCode != 0)
{
throw new InvalidOperationException(
$"{exe} process failed with exit code {process.ExitCode}.");
}
}
finally
{
Environment.CurrentDirectory = defaultCurrentDirectory;
}
}
}
And finally, the Folders.cs
implementation:
internal class Folders : IFolders
{
private readonly Assembly _Assembly;
public Folders(Assembly assembly = null)
{
this._Assembly = assembly ?? Assembly.GetExecutingAssembly();
}
public static IFolders Default => new Folders();
public IFolder Bin => new Folder(this._Assembly);
public IFolder App => this.Bin.Up();
public IFolder Docs => this.App.Down("Docs");
public IFolder Temp => this.App.Down("Temp");
}
Now that it’s not static
, you’ll either have to create a new
one with a particular assembly, or Folders.Default
will have the current assembly as per original design.