Skip to content

Commit

Permalink
Add command line arguments and make behavior configurable.
Browse files Browse the repository at this point in the history
You can now turn any step on or off (whether to copy new files, whether to update existing files, whether to delete extra remaining files). You can choose two new steps: delete changed files or delete identical files. Who knows where it can be useful.
  • Loading branch information
KirillOsenkov committed Jan 23, 2016
1 parent b8ea605 commit d61ff81
Show file tree
Hide file tree
Showing 8 changed files with 439 additions and 72 deletions.
4 changes: 3 additions & 1 deletion src/ContentSync.Tests/Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ private void T(Folder source, Folder destination)
source.CreateOnDisk(root);
destination.CreateOnDisk(root);

Sync.Directories(Path.Combine(root, source.Name), Path.Combine(root, destination.Name));
var sourcePath = Path.Combine(root, source.Name);
var destinationPath = Path.Combine(root, destination.Name);
Sync.Directories(sourcePath, destinationPath, new Arguments(sourcePath, destinationPath));

var actual = Folder.FromDisk(Path.Combine(root, destination.Name));

Expand Down
203 changes: 203 additions & 0 deletions src/ContentSync/Arguments.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace GuiLabs.FileUtilities
{
public class Arguments
{
private string[] args;

public string Source { get; set; }
public string Destination { get; set; }
public string Pattern { get; set; } = "*";

public bool CopyLeftOnlyFiles { get; private set; } = true;
public bool UpdateChangedFiles { get; private set; } = true;
public bool DeleteRightOnlyFiles { get; private set; } = true;
public bool CopyEmptyDirectories { get; private set; } = true;
public bool DeleteRightOnlyDirectories { get; private set; } = true;
public bool DeleteSameFiles { get; private set; }
public bool DeleteChangedFiles { get; private set; }
public bool WhatIf { get; set; }
public bool Quiet { get; set; }
public bool Help { get; set; }

public string Error { get; set; } = "";

public Arguments()
{
args = new string[0];
}

public Arguments(params string[] args)
{
this.args = args;
Parse();
}

private void Parse()
{
var switches = new List<string>();
var paths = new List<string>();

foreach (var arg in args)
{
if (arg.StartsWith("/") || arg.StartsWith("-"))
{
switches.Add(arg.Substring(1).ToLowerInvariant());
}
else
{
paths.Add(TrimQuotes(arg));
}
}

if (switches.Any())
{
if (switches.Where(s => s != "q" && s != "whatif").Any())
{
// if any of c, u, d, ds or dc are specified, they're in explicit mode, assume all defaults false
CopyLeftOnlyFiles = false;
UpdateChangedFiles = false;
DeleteRightOnlyFiles = false;
CopyEmptyDirectories = false;
DeleteRightOnlyDirectories = false;
}

foreach (var key in switches)
{
switch (key)
{
case "c":
CopyLeftOnlyFiles = true;
break;
case "u":
UpdateChangedFiles = true;
break;
case "cu":
CopyLeftOnlyFiles = true;
UpdateChangedFiles = true;
break;
case "d":
DeleteRightOnlyFiles = true;
break;
case "cd":
CopyLeftOnlyFiles = true;
DeleteRightOnlyFiles = true;
break;
case "cud":
CopyLeftOnlyFiles = true;
UpdateChangedFiles = true;
DeleteRightOnlyFiles = true;
break;
case "ds":
DeleteSameFiles = true;
break;
case "dc":
DeleteChangedFiles = true;
break;
case "q":
Quiet = true;
Log.Quiet = true;
break;
case "h":
case "help":
case "?":
Help = true;
Error = "Help argument cannot be combined with any other arguments";
return;
case "whatif":
WhatIf = true;
if (switches.Count == 1)
{
// if whatif is the only switch, assume the defaults
CopyLeftOnlyFiles = true;
UpdateChangedFiles = true;
DeleteRightOnlyFiles = true;
CopyEmptyDirectories = true;
DeleteRightOnlyDirectories = true;
}

break;
default:
Error += "Unrecognized argument: " + key + Environment.NewLine;
return;
}
}
}

if (Quiet && WhatIf)
{
Error = "-q and -whatif are incompatible. Choose one or the other.";
return;
}

if (DeleteChangedFiles && UpdateChangedFiles)
{
Error = "Incompatible options: -u and -dc (can't update and delete changed files at the same time";
return;
}

if (paths.Count < 2)
{
Error = "Two paths need to be specified (source and destination)";
return;
}

if (paths.Count > 3)
{
Error = "Unable to process extra argument: " + paths[3];
return;
}

if (paths.Count == 3)
{
var patterns = paths.Where(p => p.Contains("*") || p.Contains("?")).ToArray();
if (patterns.Length != 1)
{
Error = $"Expected exactly one file pattern, {patterns.Length} was specified";
return;
}

if (paths[2] != patterns[0])
{
Error = "File pattern should be specified after Source and Destination arguments";
return;
}

Pattern = paths[2];
}

if (Pattern != "*")
{
// when we're not comparing all files, don't synchronize directories
CopyEmptyDirectories = false;
DeleteRightOnlyDirectories = false;
}

if (!CopyLeftOnlyFiles)
{
CopyEmptyDirectories = false;
}

if (!DeleteRightOnlyFiles)
{
DeleteRightOnlyDirectories = false;
}

Source = paths[0];
Destination = paths[1];
}

private static string TrimQuotes(string path)
{
if (path.Length > 2 && path[0] == '"' && path[path.Length - 1] == '"')
{
path = path.Substring(1, path.Length - 2);
}

return path;
}
}
}
2 changes: 2 additions & 0 deletions src/ContentSync/ContentSync.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
<Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup>
<Compile Include="Arguments.cs" />
<Compile Include="FileSystem.cs" />
<Compile Include="Log.cs" />
<Compile Include="Program.cs" />
<Compile Include="DiffResults.cs" />
Expand Down
67 changes: 67 additions & 0 deletions src/ContentSync/FileSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.IO;

namespace GuiLabs.FileUtilities
{
public class FileSystem
{
public static void CopyFile(string source, string destination, bool speculative)
{
if (speculative)
{
Log.WriteLine($"Would copy {source} to {destination}");
}
else
{
Log.WriteLine($"Copy {source} to {destination}");
var destinationFolder = Path.GetDirectoryName(destination);
Directory.CreateDirectory(destinationFolder);
File.Copy(source, destination, overwrite: true);
}
}

public static void DeleteFile(string deletedFilePath, bool speculative)
{
if (speculative)
{
Log.WriteLine("Would delete " + deletedFilePath);
}
else
{
Log.WriteLine("Delete " + deletedFilePath);
var attributes = File.GetAttributes(deletedFilePath);
if ((attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
{
File.SetAttributes(deletedFilePath, attributes & ~FileAttributes.ReadOnly);
}

File.Delete(deletedFilePath);
}
}

public static void CreateDirectory(string newFolder, bool speculative)
{
if (speculative)
{
Log.WriteLine("Would create " + newFolder);
}
else
{
Log.WriteLine("Create " + newFolder);
Directory.CreateDirectory(newFolder);
}
}

public static void DeleteDirectory(string deletedFolderPath, bool speculative)
{
if (speculative)
{
Log.WriteLine("Would delete " + deletedFolderPath);
}
else
{
Log.WriteLine("Delete " + deletedFolderPath);
Directory.Delete(deletedFolderPath, recursive: true);
}
}
}
}
14 changes: 7 additions & 7 deletions src/ContentSync/Folders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ public class Folders
/// <summary>
/// Assumes both leftRoot and rightRoot are existing folders.
/// </summary>
public static FolderDiffResults DiffFolders(string leftRoot, string rightRoot)
public static FolderDiffResults DiffFolders(string leftRoot, string rightRoot, string pattern)
{
var leftRelativePaths = GetRelativePathsOfAllFiles(leftRoot);
var leftRelativePaths = GetRelativePathsOfAllFiles(leftRoot, pattern);
var leftOnlyFolders = GetRelativePathsOfAllFolders(leftRoot);
var rightRelativePaths = GetRelativePathsOfAllFiles(rightRoot);
var rightRelativePaths = GetRelativePathsOfAllFiles(rightRoot, pattern);
var rightOnlyFolders = GetRelativePathsOfAllFolders(rightRoot);

var leftOnlyFiles = new List<string>();
Expand Down Expand Up @@ -93,18 +93,18 @@ public static FolderDiffResults DiffFolders(string leftRoot, string rightRoot)
}
}

public static HashSet<string> GetRelativePathsOfAllFiles(string rootFolder)
public static HashSet<string> GetRelativePathsOfAllFiles(string rootFolder, string pattern)
{
using (Log.MeasureTime("Scanning files in " + rootFolder))
using (Log.MeasureTime("Scanning files"))
{
var files = Directory.GetFiles(rootFolder, "*", SearchOption.AllDirectories);
var files = Directory.GetFiles(rootFolder, pattern, SearchOption.AllDirectories);
return GetRelativePaths(rootFolder, files);
}
}

public static HashSet<string> GetRelativePathsOfAllFolders(string rootFolder)
{
using (Log.MeasureTime("Scanning folders in " + rootFolder))
using (Log.MeasureTime("Scanning folders"))
{
var folders = Directory.GetDirectories(rootFolder, "*", SearchOption.AllDirectories);
return GetRelativePaths(rootFolder, folders);
Expand Down
Loading

0 comments on commit d61ff81

Please sign in to comment.