Sicherung gnaz neu
This commit is contained in:
@@ -1,14 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace RoboSharp
|
||||
{
|
||||
internal class ApplicationConstants
|
||||
{
|
||||
internal static Dictionary<string, string> ErrorCodes = new Dictionary<string, string>()
|
||||
{
|
||||
{ "ERROR 33 (0x00000021)", "The process cannot access the file because another process has locked a portion of the file." },
|
||||
{ "ERROR 32 (0x00000020)", "The process cannot access the file because it is being used by another process." },
|
||||
{ "ERROR 5 (0x00000005)", "Access is denied." }
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,684 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace RoboSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Source, Destination, and options for how to move or copy files.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/CopyOptions"/>
|
||||
/// </remarks>
|
||||
public class CopyOptions : ICloneable
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Create new CopyOptions with Default Settings
|
||||
/// </summary>
|
||||
public CopyOptions() { }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new CopyOptions object with the provided settings
|
||||
/// </summary>
|
||||
/// <param name="source"><inheritdoc cref="Source" path="*"/></param>
|
||||
/// <param name="destination"><inheritdoc cref="Destination" path="*"/></param>
|
||||
/// <param name="flags"><inheritdoc cref="CopyActionFlags" path="*"/></param>
|
||||
public CopyOptions(string source, string destination, CopyActionFlags flags)
|
||||
{
|
||||
this.Source = source ?? string.Empty;
|
||||
this.Destination = destination ?? string.Empty;
|
||||
this.ApplyActionFlags(flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clone a CopyOptions Object
|
||||
/// </summary>
|
||||
/// <param name="copyOptions">CopyOptions object to clone</param>
|
||||
/// <param name="NewSource">Specify a new source if desired. If left as null, will use Source from <paramref name="copyOptions"/></param>
|
||||
/// <param name="NewDestination">Specify a new source if desired. If left as null, will use Destination from <paramref name="copyOptions"/></param>
|
||||
public CopyOptions(CopyOptions copyOptions, string NewSource = null, string NewDestination = null)
|
||||
{
|
||||
Source = NewSource ?? copyOptions.Source;
|
||||
Destination = NewDestination ?? copyOptions.Destination;
|
||||
|
||||
AddAttributes = copyOptions.AddAttributes;
|
||||
CheckPerFile = copyOptions.CheckPerFile;
|
||||
CopyAll = copyOptions.CopyAll;
|
||||
CopyFilesWithSecurity = copyOptions.CopyFilesWithSecurity;
|
||||
CopyFlags = copyOptions.CopyFlags;
|
||||
CopySubdirectories = copyOptions.CopySubdirectories;
|
||||
CopySubdirectoriesIncludingEmpty = copyOptions.CopySubdirectoriesIncludingEmpty;
|
||||
CopySymbolicLink = copyOptions.CopySymbolicLink;
|
||||
CreateDirectoryAndFileTree = copyOptions.CreateDirectoryAndFileTree;
|
||||
Depth = copyOptions.Depth;
|
||||
DirectoryCopyFlags = copyOptions.DirectoryCopyFlags;
|
||||
DoNotCopyDirectoryInfo = copyOptions.DoNotCopyDirectoryInfo;
|
||||
DoNotUseWindowsCopyOffload = copyOptions.DoNotUseWindowsCopyOffload;
|
||||
EnableBackupMode = copyOptions.EnableBackupMode;
|
||||
EnableEfsRawMode = copyOptions.EnableEfsRawMode;
|
||||
EnableRestartMode = copyOptions.EnableRestartMode;
|
||||
EnableRestartModeWithBackupFallback = copyOptions.EnableRestartModeWithBackupFallback;
|
||||
FatFiles = copyOptions.FatFiles;
|
||||
FileFilter = copyOptions.FileFilter;
|
||||
FixFileSecurityOnAllFiles = copyOptions.FixFileSecurityOnAllFiles;
|
||||
FixFileTimesOnAllFiles = copyOptions.FixFileTimesOnAllFiles;
|
||||
InterPacketGap = copyOptions.InterPacketGap;
|
||||
Mirror = copyOptions.Mirror;
|
||||
MonitorSourceChangesLimit = copyOptions.MonitorSourceChangesLimit;
|
||||
MonitorSourceTimeLimit = copyOptions.MonitorSourceTimeLimit;
|
||||
MoveFiles = copyOptions.MoveFiles;
|
||||
MoveFilesAndDirectories = copyOptions.MoveFilesAndDirectories;
|
||||
MultiThreadedCopiesCount = copyOptions.MultiThreadedCopiesCount;
|
||||
Purge = copyOptions.Purge;
|
||||
RemoveAttributes = copyOptions.RemoveAttributes;
|
||||
RemoveFileInformation = copyOptions.RemoveFileInformation;
|
||||
RunHours = copyOptions.RunHours;
|
||||
TurnLongPathSupportOff = copyOptions.TurnLongPathSupportOff;
|
||||
UseUnbufferedIo = copyOptions.UseUnbufferedIo;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="CopyOptions.CopyOptions(CopyOptions, string, string)"/>
|
||||
public CopyOptions Clone(string NewSource = null, string NewDestination = null) => new CopyOptions(this, NewSource, NewDestination);
|
||||
object ICloneable.Clone() => Clone();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Option Constants
|
||||
|
||||
internal const string COPY_SUBDIRECTORIES = "/S ";
|
||||
internal const string COPY_SUBDIRECTORIES_INCLUDING_EMPTY = "/E ";
|
||||
internal const string DEPTH = "/LEV:{0} ";
|
||||
internal const string ENABLE_RESTART_MODE = "/Z ";
|
||||
internal const string ENABLE_BACKUP_MODE = "/B ";
|
||||
internal const string ENABLE_RESTART_MODE_WITH_BACKUP_FALLBACK = "/ZB ";
|
||||
internal const string USE_UNBUFFERED_IO = "/J ";
|
||||
internal const string ENABLE_EFSRAW_MODE = "/EFSRAW ";
|
||||
internal const string COPY_FLAGS = "/COPY:{0} ";
|
||||
internal const string COPY_FILES_WITH_SECURITY = "/SEC ";
|
||||
internal const string COPY_ALL = "/COPYALL ";
|
||||
internal const string REMOVE_FILE_INFORMATION = "/NOCOPY ";
|
||||
internal const string FIX_FILE_SECURITY_ON_ALL_FILES = "/SECFIX ";
|
||||
internal const string FIX_FILE_TIMES_ON_ALL_FILES = "/TIMFIX ";
|
||||
internal const string PURGE = "/PURGE ";
|
||||
internal const string MIRROR = "/MIR ";
|
||||
internal const string MOVE_FILES = "/MOV ";
|
||||
internal const string MOVE_FILES_AND_DIRECTORIES = "/MOVE ";
|
||||
internal const string ADD_ATTRIBUTES = "/A+:{0} ";
|
||||
internal const string REMOVE_ATTRIBUTES = "/A-:{0} ";
|
||||
internal const string CREATE_DIRECTORY_AND_FILE_TREE = "/CREATE ";
|
||||
internal const string FAT_FILES = "/FAT ";
|
||||
internal const string TURN_LONG_PATH_SUPPORT_OFF = "/256 ";
|
||||
internal const string MONITOR_SOURCE_CHANGES_LIMIT = "/MON:{0} ";
|
||||
internal const string MONITOR_SOURCE_TIME_LIMIT = "/MOT:{0} ";
|
||||
internal const string RUN_HOURS = "/RH:{0} ";
|
||||
internal const string CHECK_PER_FILE = "/PF ";
|
||||
internal const string INTER_PACKET_GAP = "/IPG:{0} ";
|
||||
internal const string COPY_SYMBOLIC_LINK = "/SL ";
|
||||
internal const string MULTITHREADED_COPIES_COUNT = "/MT:{0} ";
|
||||
internal const string DIRECTORY_COPY_FLAGS = "/DCOPY:{0} ";
|
||||
internal const string DO_NOT_COPY_DIRECTORY_INFO = "/NODCOPY ";
|
||||
internal const string DO_NOT_USE_WINDOWS_COPY_OFFLOAD = "/NOOFFLOAD ";
|
||||
|
||||
#endregion Option Constants
|
||||
|
||||
#region Option Defaults
|
||||
|
||||
/// <summary>
|
||||
/// The Default File Filter used that will allow copying of all files
|
||||
/// </summary>
|
||||
public const string DefaultFileFilter = "*.*" ;
|
||||
|
||||
private IEnumerable<string> fileFilter = new[] { DefaultFileFilter };
|
||||
private string copyFlags = "DAT";
|
||||
private string directoryCopyFlags = VersionManager.Version >= 6.2 ? "DA" : "T";
|
||||
|
||||
#endregion Option Defaults
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// The source file path where the RoboCommand is copying files from.
|
||||
/// </summary>
|
||||
public virtual string Source { get { return _source; } set { _source = value.CleanDirectoryPath(); } }
|
||||
private string _source;
|
||||
|
||||
/// <summary>
|
||||
/// The destination file path where the RoboCommand is copying files to.
|
||||
/// </summary>
|
||||
public virtual string Destination { get { return _destination; } set { _destination = value.CleanDirectoryPath(); } }
|
||||
private string _destination;
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to supply a set of files to copy or use wildcard characters (* or ?). <br/>
|
||||
/// JobOptions file saves these into the /IF (Include Files) section
|
||||
/// </summary>
|
||||
public IEnumerable<string> FileFilter
|
||||
{
|
||||
get
|
||||
{
|
||||
return fileFilter;
|
||||
}
|
||||
set
|
||||
{
|
||||
fileFilter = value;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Copies subdirectories. Note that this option excludes empty directories.
|
||||
/// [/S]
|
||||
/// </summary>
|
||||
public virtual bool CopySubdirectories { get; set; }
|
||||
/// <summary>
|
||||
/// Copies subdirectories. Note that this option includes empty directories.
|
||||
/// [/E]
|
||||
/// </summary>
|
||||
public virtual bool CopySubdirectoriesIncludingEmpty { get; set; }
|
||||
/// <summary>
|
||||
/// Copies only the top N levels of the source directory tree. The default is
|
||||
/// zero which does not limit the depth.
|
||||
/// [/LEV:N]
|
||||
/// </summary>
|
||||
public virtual int Depth { get; set; }
|
||||
/// <summary>
|
||||
/// Copies files in Restart mode.
|
||||
/// [/Z]
|
||||
/// </summary>
|
||||
public virtual bool EnableRestartMode { get; set; }
|
||||
/// <summary>
|
||||
/// Copies files in Backup mode.
|
||||
/// [/B]
|
||||
/// </summary>
|
||||
public virtual bool EnableBackupMode { get; set; }
|
||||
/// <summary>
|
||||
/// Uses Restart mode. If access is denied, this option uses Backup mode.
|
||||
/// [/ZB]
|
||||
/// </summary>
|
||||
public virtual bool EnableRestartModeWithBackupFallback { get; set; }
|
||||
/// <summary>
|
||||
/// Copy using unbuffered I/O (recommended for large files).
|
||||
/// [/J]
|
||||
/// </summary>
|
||||
public virtual bool UseUnbufferedIo { get; set; }
|
||||
/// <summary>
|
||||
/// Copies all encrypted files in EFS RAW mode.
|
||||
/// [/EFSRAW]
|
||||
/// </summary>
|
||||
public virtual bool EnableEfsRawMode { get; set; }
|
||||
/// <summary>
|
||||
/// This property should be set to a string consisting of all the flags to include (eg. DAT; DATSOU)
|
||||
/// Specifies the file properties to be copied. The following are the valid values for this option:
|
||||
///D Data
|
||||
///A Attributes
|
||||
///T Time stamps
|
||||
///S NTFS access control list (ACL)
|
||||
///O Owner information
|
||||
///U Auditing information
|
||||
///The default value for copyflags is DAT (data, attributes, and time stamps).
|
||||
///[/COPY:copyflags]
|
||||
/// </summary>
|
||||
public string CopyFlags
|
||||
{
|
||||
get
|
||||
{
|
||||
return copyFlags;
|
||||
}
|
||||
set => copyFlags = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// Copies files with security (equivalent to /copy:DAT).
|
||||
/// [/SEC]
|
||||
/// </summary>
|
||||
public virtual bool CopyFilesWithSecurity { get; set; }
|
||||
/// <summary>
|
||||
/// Copies all file information (equivalent to /copy:DATSOU).
|
||||
/// [/COPYALL]
|
||||
/// </summary>
|
||||
public virtual bool CopyAll { get; set; }
|
||||
/// <summary>
|
||||
/// Copies no file information (useful with Purge option).
|
||||
/// [/NOCOPY]
|
||||
/// </summary>
|
||||
public virtual bool RemoveFileInformation { get; set; }
|
||||
/// <summary>
|
||||
/// Fixes file security on all files, even skipped ones.
|
||||
/// [/SECFIX]
|
||||
/// </summary>
|
||||
public virtual bool FixFileSecurityOnAllFiles { get; set; }
|
||||
/// <summary>
|
||||
/// Fixes file times on all files, even skipped ones.
|
||||
/// [/TIMFIX]
|
||||
/// </summary>
|
||||
public virtual bool FixFileTimesOnAllFiles { get; set; }
|
||||
/// <summary>
|
||||
/// Deletes destination files and directories that no longer exist in the source.
|
||||
/// [/PURGE]
|
||||
/// </summary>
|
||||
public virtual bool Purge { get; set; }
|
||||
/// <summary>
|
||||
/// Mirrors a directory tree (equivalent to CopySubdirectoriesIncludingEmpty plus Purge).
|
||||
/// [/MIR]
|
||||
/// </summary>
|
||||
public virtual bool Mirror { get; set; }
|
||||
/// <summary>
|
||||
/// Moves files, and deletes them from the source after they are copied.
|
||||
/// [/MOV]
|
||||
/// </summary>
|
||||
public virtual bool MoveFiles { get; set; }
|
||||
/// <summary>
|
||||
/// Moves files and directories, and deletes them from the source after they are copied.
|
||||
/// [/MOVE]
|
||||
/// </summary>
|
||||
public virtual bool MoveFilesAndDirectories { get; set; }
|
||||
/// <summary>
|
||||
/// This property should be set to a string consisting of all the attributes to add (eg. AH; RASHCNET).
|
||||
/// Adds the specified attributes to copied files.
|
||||
/// [/A+:attributes]
|
||||
/// </summary>
|
||||
public string AddAttributes { get; set; }
|
||||
/// <summary>
|
||||
/// This property should be set to a string consisting of all the attributes to remove (eg. AH; RASHCNET).
|
||||
/// Removes the specified attributes from copied files.
|
||||
/// [/A-:attributes]
|
||||
/// </summary>
|
||||
public string RemoveAttributes { get; set; }
|
||||
/// <summary>
|
||||
/// Creates a directory tree and zero-length files only.
|
||||
/// [/CREATE]
|
||||
/// </summary>
|
||||
public virtual bool CreateDirectoryAndFileTree { get; set; }
|
||||
/// <summary>
|
||||
/// Creates destination files by using 8.3 character-length FAT file names only.
|
||||
/// [/FAT]
|
||||
/// </summary>
|
||||
public virtual bool FatFiles { get; set; }
|
||||
/// <summary>
|
||||
/// Turns off support for very long paths (longer than 256 characters).
|
||||
/// [/256]
|
||||
/// </summary>
|
||||
public virtual bool TurnLongPathSupportOff { get; set; }
|
||||
/// <summary>
|
||||
/// The default value of zero indicates that you do not wish to monitor for changes.
|
||||
/// Monitors the source, and runs again when more than N changes are detected.
|
||||
/// [/MON:N]
|
||||
/// </summary>
|
||||
public virtual int MonitorSourceChangesLimit { get; set; }
|
||||
/// <summary>
|
||||
/// The default value of zero indicates that you do not wish to monitor for changes.
|
||||
/// Monitors source, and runs again in M minutes if changes are detected.
|
||||
/// [/MOT:M]
|
||||
/// </summary>
|
||||
public virtual int MonitorSourceTimeLimit { get; set; }
|
||||
/// <summary>
|
||||
/// Specifies run times when new copies may be started. ( Copy Operation is scheduled to only operate within specified timeframe )
|
||||
/// [/rh:hhmm-hhmm] <br/>
|
||||
/// If copy operation is unfinished, robocopy will remain active in idle state until the specified time, at which it will resume copying.<br/>
|
||||
/// Must be in correct format. Incorrectly formatted strings will be ignored. <para/>
|
||||
/// Examples:<br/>
|
||||
/// 1500-1800 -> Robocopy will only copy between 3 PM and 5 PM <br/>
|
||||
/// 0015-0530 -> Robocopy will only copy between 12:15 AM and 5:30 AM <br/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this is set up, then the robocopy process will remain active after the program exits if the calling asemmbly does not call <see cref="RoboCommand.Stop()"/> prior to exiting the application.
|
||||
/// </remarks>
|
||||
public string RunHours
|
||||
{
|
||||
get => runHours;
|
||||
set
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(value))
|
||||
runHours = value?.Trim() ?? string.Empty;
|
||||
else if (CheckRunHoursString(value))
|
||||
runHours = value.Trim();
|
||||
}
|
||||
}
|
||||
private string runHours;
|
||||
|
||||
/// <summary>
|
||||
/// Checks the scheduled /RH (run hours) per file instead of per pass.
|
||||
/// [/PF]
|
||||
/// </summary>
|
||||
public virtual bool CheckPerFile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The default value of zero indicates that this feature is turned off.
|
||||
/// Specifies the inter-packet gap to free bandwidth on slow lines.
|
||||
/// [/IPG:N]
|
||||
/// </summary>
|
||||
public virtual int InterPacketGap { get; set; }
|
||||
/// <summary>
|
||||
/// Copies the symbolic link instead of the target.
|
||||
/// [/SL]
|
||||
/// </summary>
|
||||
public virtual bool CopySymbolicLink { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The default value of zero indicates that this feature is turned off.
|
||||
/// Creates multi-threaded copies with N threads. Must be an integer between 1 and 128.
|
||||
/// The MultiThreadedCopiesCount parameter cannot be used with the /IPG and EnableEfsRawMode parameters.
|
||||
/// [/MT:N]
|
||||
/// </summary>
|
||||
public virtual int MultiThreadedCopiesCount { get; set; }
|
||||
/// <summary>
|
||||
/// What to copy for directories (default is DA).
|
||||
/// (copyflags: D=Data, A=Attributes, T=Timestamps).
|
||||
/// [/DCOPY:copyflags]
|
||||
/// </summary>
|
||||
public string DirectoryCopyFlags
|
||||
{
|
||||
get { return directoryCopyFlags; }
|
||||
set { directoryCopyFlags = value; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Do not copy any directory info.
|
||||
/// [/NODCOPY]
|
||||
/// </summary>
|
||||
public virtual bool DoNotCopyDirectoryInfo { get; set; }
|
||||
/// <summary>
|
||||
/// Copy files without using the Windows Copy Offload mechanism.
|
||||
/// [/NOOFFLOAD]
|
||||
/// </summary>
|
||||
public virtual bool DoNotUseWindowsCopyOffload { get; set; }
|
||||
|
||||
#endregion Public Properties
|
||||
|
||||
#region < Parse (INTERNAL) >
|
||||
|
||||
/// <summary>
|
||||
/// Used by the Parse method to sanitize path for the command options.<br/>
|
||||
/// Evaluate the path. If needed, wrap it in quotes. <br/>
|
||||
/// If the path ends in a DirectorySeperatorChar, santize it to work as expected. <br/>
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns>Each return string includes a space at the end of the string to seperate it from the next option variable.</returns>
|
||||
private string WrapPath(string path)
|
||||
{
|
||||
if (!path.Contains(" ")) return $"{path} "; //No spaces, just return the path
|
||||
//Below this line, the path contains a space, so it must be wrapped in quotes.
|
||||
if (path.EndsWithDirectorySeperator()) return $"\"{path}.\" "; // Ends with a directory seperator - Requires a '.' to denote using that directory. ex: "F:\."
|
||||
return $"\"{path}\" ";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse the class properties and generate the command arguments
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal string Parse()
|
||||
{
|
||||
Debugger.Instance.DebugMessage("Parsing CopyOptions...");
|
||||
var version = VersionManager.Version;
|
||||
var options = new StringBuilder();
|
||||
|
||||
// Set Source and Destination
|
||||
options.Append(WrapPath(Source));
|
||||
options.Append(WrapPath(Destination));
|
||||
|
||||
// Set FileFilter
|
||||
// Quote each FileFilter item. The quotes are trimmed first to ensure that they are applied only once.
|
||||
var fileFilterQuotedItems = FileFilter.Select(word => "\"" + word.Trim('"') + "\"");
|
||||
string fileFilter = String.Join(" ", fileFilterQuotedItems);
|
||||
options.Append($"{fileFilter} ");
|
||||
|
||||
Debugger.Instance.DebugMessage(string.Format("Parsing CopyOptions progress ({0}).", options.ToString()));
|
||||
|
||||
#region Set Options
|
||||
var cleanedCopyFlags = CopyFlags.CleanOptionInput();
|
||||
var cleanedDirectoryCopyFlags = DirectoryCopyFlags.CleanOptionInput();
|
||||
|
||||
if (!cleanedCopyFlags.IsNullOrWhiteSpace())
|
||||
{
|
||||
options.Append(string.Format(COPY_FLAGS, cleanedCopyFlags));
|
||||
Debugger.Instance.DebugMessage(string.Format("Parsing CopyOptions progress ({0}).", options.ToString()));
|
||||
}
|
||||
if (!cleanedDirectoryCopyFlags.IsNullOrWhiteSpace() && version >= 5.1260026)
|
||||
{
|
||||
options.Append(string.Format(DIRECTORY_COPY_FLAGS, cleanedDirectoryCopyFlags));
|
||||
Debugger.Instance.DebugMessage(string.Format("Parsing CopyOptions progress ({0}).", options.ToString()));
|
||||
}
|
||||
if (CopySubdirectories)
|
||||
{
|
||||
options.Append(COPY_SUBDIRECTORIES);
|
||||
Debugger.Instance.DebugMessage(string.Format("Parsing CopyOptions progress ({0}).", options.ToString()));
|
||||
}
|
||||
if (CopySubdirectoriesIncludingEmpty)
|
||||
options.Append(COPY_SUBDIRECTORIES_INCLUDING_EMPTY);
|
||||
if (Depth > 0)
|
||||
options.Append(string.Format(DEPTH, Depth));
|
||||
if (EnableRestartMode)
|
||||
options.Append(ENABLE_RESTART_MODE);
|
||||
if (EnableBackupMode)
|
||||
options.Append(ENABLE_BACKUP_MODE);
|
||||
if (EnableRestartModeWithBackupFallback)
|
||||
options.Append(ENABLE_RESTART_MODE_WITH_BACKUP_FALLBACK);
|
||||
if (UseUnbufferedIo && version >= 6.2)
|
||||
options.Append(USE_UNBUFFERED_IO);
|
||||
if (EnableEfsRawMode)
|
||||
options.Append(ENABLE_EFSRAW_MODE);
|
||||
if (CopyFilesWithSecurity)
|
||||
options.Append(COPY_FILES_WITH_SECURITY);
|
||||
if (CopyAll)
|
||||
options.Append(COPY_ALL);
|
||||
if (RemoveFileInformation)
|
||||
options.Append(REMOVE_FILE_INFORMATION);
|
||||
if (FixFileSecurityOnAllFiles)
|
||||
options.Append(FIX_FILE_SECURITY_ON_ALL_FILES);
|
||||
if (FixFileTimesOnAllFiles)
|
||||
options.Append(FIX_FILE_TIMES_ON_ALL_FILES);
|
||||
if (Purge)
|
||||
options.Append(PURGE);
|
||||
if (Mirror)
|
||||
options.Append(MIRROR);
|
||||
if (MoveFiles)
|
||||
options.Append(MOVE_FILES);
|
||||
if (MoveFilesAndDirectories)
|
||||
options.Append(MOVE_FILES_AND_DIRECTORIES);
|
||||
if (!AddAttributes.IsNullOrWhiteSpace())
|
||||
options.Append(string.Format(ADD_ATTRIBUTES, AddAttributes.CleanOptionInput()));
|
||||
if (!RemoveAttributes.IsNullOrWhiteSpace())
|
||||
options.Append(string.Format(REMOVE_ATTRIBUTES, RemoveAttributes.CleanOptionInput()));
|
||||
if (CreateDirectoryAndFileTree)
|
||||
options.Append(CREATE_DIRECTORY_AND_FILE_TREE);
|
||||
if (FatFiles)
|
||||
options.Append(FAT_FILES);
|
||||
if (TurnLongPathSupportOff)
|
||||
options.Append(TURN_LONG_PATH_SUPPORT_OFF);
|
||||
if (MonitorSourceChangesLimit > 0)
|
||||
options.Append(string.Format(MONITOR_SOURCE_CHANGES_LIMIT, MonitorSourceChangesLimit));
|
||||
if (MonitorSourceTimeLimit > 0)
|
||||
options.Append(string.Format(MONITOR_SOURCE_TIME_LIMIT, MonitorSourceTimeLimit));
|
||||
if (!RunHours.IsNullOrWhiteSpace())
|
||||
options.Append(string.Format(RUN_HOURS, RunHours.CleanOptionInput()));
|
||||
if (CheckPerFile)
|
||||
options.Append(CHECK_PER_FILE);
|
||||
if (InterPacketGap > 0)
|
||||
options.Append(string.Format(INTER_PACKET_GAP, InterPacketGap));
|
||||
if (CopySymbolicLink)
|
||||
options.Append(COPY_SYMBOLIC_LINK);
|
||||
if (MultiThreadedCopiesCount > 0)
|
||||
options.Append(string.Format(MULTITHREADED_COPIES_COUNT, MultiThreadedCopiesCount));
|
||||
if (DoNotCopyDirectoryInfo && version >= 6.2)
|
||||
options.Append(DO_NOT_COPY_DIRECTORY_INFO);
|
||||
if (DoNotUseWindowsCopyOffload && version >= 6.2)
|
||||
options.Append(DO_NOT_USE_WINDOWS_COPY_OFFLOAD);
|
||||
#endregion Set Options
|
||||
|
||||
var parsedOptions = options.ToString();
|
||||
Debugger.Instance.DebugMessage(string.Format("CopyOptions parsed ({0}).", parsedOptions));
|
||||
return parsedOptions;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < RunHours (Public) >
|
||||
|
||||
private static Regex RunHours_OverallRegex = new Regex("^(?<StartTime>[0-2][0-9][0-5][0-9])-(?<EndTime>[0-2][0-9][0-5][0-9])$", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
|
||||
private static Regex RunHours_Check1 = new Regex("^[0-1][0-9][0-5][0-9]$", RegexOptions.Compiled); // Checks 0000 - 1959
|
||||
private static Regex RunHours_Check2 = new Regex("^[2][0-3][0-5][0-9]$", RegexOptions.Compiled); // Checks 2000 - 2359
|
||||
private GroupCollection RunHoursGroups => RunHours_OverallRegex.Match(RunHours).Groups;
|
||||
|
||||
/// <summary>
|
||||
/// Get the StartTime portion of <see cref="RunHours"/>
|
||||
/// </summary>
|
||||
/// <returns>hhmm or String.Empty</returns>
|
||||
public string GetRunHours_StartTime()
|
||||
{
|
||||
if (RunHours.IsNullOrWhiteSpace()) return string.Empty;
|
||||
return RunHoursGroups["StartTime"]?.Value ?? String.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the EndTime portion of <see cref="RunHours"/>
|
||||
/// </summary>
|
||||
/// <returns>hhmm or String.Empty</returns>
|
||||
public string GetRunHours_EndTime()
|
||||
{
|
||||
if (RunHours.IsNullOrWhiteSpace()) return string.Empty;
|
||||
return RunHoursGroups["EndTime"]?.Value ?? String.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method to check if some string is valid for use as with the <see cref="RunHours"/> property.
|
||||
/// </summary>
|
||||
/// <param name="runHours"></param>
|
||||
/// <returns>True if correct format, otherwise false</returns>
|
||||
public bool CheckRunHoursString(string runHours)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(runHours)) return true;
|
||||
if (!RunHours_OverallRegex.IsMatch(runHours.Trim())) return false;
|
||||
var times = RunHours_OverallRegex.Match(runHours.Trim());
|
||||
bool StartMatch = RunHours_Check1.IsMatch(times.Groups["StartTime"].Value) || RunHours_Check2.IsMatch(times.Groups["StartTime"].Value);
|
||||
bool EndMatch = RunHours_Check1.IsMatch(times.Groups["EndTime"].Value) || RunHours_Check2.IsMatch(times.Groups["EndTime"].Value);
|
||||
return StartMatch && EndMatch;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Flags >
|
||||
|
||||
/// <summary>
|
||||
/// Enum to define the high-level copy action to be taken by RoboCopy process.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum CopyActionFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// Default Functionality is to only copy the files within the source directory - does not copy any files within the subfolders.
|
||||
/// </summary>
|
||||
Default = 0,
|
||||
/// <inheritdoc cref="CopyOptions.CopySubdirectories"/>
|
||||
CopySubdirectories = 1,
|
||||
/// <inheritdoc cref="CopyOptions.CopySubdirectoriesIncludingEmpty"/>
|
||||
CopySubdirectoriesIncludingEmpty = 2,
|
||||
/// <inheritdoc cref="CopyOptions.Purge"/>
|
||||
Purge = 4,
|
||||
/// <inheritdoc cref="CopyOptions.CreateDirectoryAndFileTree"/>
|
||||
CreateDirectoryAndFileTree = 8,
|
||||
/// <inheritdoc cref="CopyOptions.MoveFiles"/>
|
||||
MoveFiles = 16,
|
||||
/// <inheritdoc cref="CopyOptions.MoveFilesAndDirectories"/>
|
||||
MoveFilesAndDirectories = 32,
|
||||
/// <inheritdoc cref="CopyOptions.Mirror"/>
|
||||
Mirror = CopySubdirectoriesIncludingEmpty | Purge, //6
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply the <see cref="CopyActionFlags"/> to the command
|
||||
/// </summary>
|
||||
/// <param name="flags">Options to apply</param>
|
||||
public virtual void ApplyActionFlags(CopyActionFlags flags)
|
||||
{
|
||||
this.CopySubdirectories = flags.HasFlag(CopyActionFlags.CopySubdirectories);
|
||||
this.CopySubdirectoriesIncludingEmpty = flags.HasFlag(CopyActionFlags.CopySubdirectoriesIncludingEmpty);
|
||||
this.Purge = flags.HasFlag(CopyActionFlags.Purge);
|
||||
this.Mirror = flags.HasFlag(CopyActionFlags.Mirror);
|
||||
this.MoveFiles = flags.HasFlag(CopyActionFlags.MoveFiles);
|
||||
this.MoveFilesAndDirectories = flags.HasFlag(CopyActionFlags.MoveFilesAndDirectories);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="CopyActionFlags"/> representation of this object
|
||||
/// </summary>
|
||||
public virtual CopyActionFlags GetCopyActionFlags()
|
||||
{
|
||||
var flags = CopyActionFlags.Default;
|
||||
if (this.CopySubdirectories) flags |=CopyActionFlags.CopySubdirectories;
|
||||
if (this.CopySubdirectoriesIncludingEmpty) flags |=CopyActionFlags.CopySubdirectoriesIncludingEmpty;
|
||||
if (this.Purge) flags |=CopyActionFlags.Purge;
|
||||
if (this.Mirror) flags |=CopyActionFlags.Mirror;
|
||||
if (this.MoveFiles) flags |=CopyActionFlags.MoveFiles;
|
||||
if (this.MoveFilesAndDirectories) flags |=CopyActionFlags.MoveFilesAndDirectories;
|
||||
return flags;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Other Public Methods >
|
||||
|
||||
/// <summary>
|
||||
/// Combine this object with another CopyOptions object. <br/>
|
||||
/// Any properties marked as true take priority. IEnumerable items are combined.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Source and Destination are only taken from the merged item if this object's Source/Destination values are null/empty. <br/>
|
||||
/// RunHours follows the same rules.
|
||||
///
|
||||
/// </remarks>
|
||||
/// <param name="copyOptions"></param>
|
||||
public void Merge(CopyOptions copyOptions)
|
||||
{
|
||||
Source = Source.ReplaceIfEmpty(copyOptions.Source);
|
||||
Destination = Destination.ReplaceIfEmpty(copyOptions.Destination);
|
||||
RunHours = RunHours.ReplaceIfEmpty(copyOptions.RunHours);
|
||||
|
||||
//int -> Take Greater Value
|
||||
Depth = Depth.GetGreaterVal(copyOptions.Depth);
|
||||
InterPacketGap = InterPacketGap.GetGreaterVal(copyOptions.InterPacketGap);
|
||||
MonitorSourceChangesLimit = MonitorSourceChangesLimit.GetGreaterVal(copyOptions.MonitorSourceChangesLimit);
|
||||
MonitorSourceTimeLimit = MonitorSourceTimeLimit.GetGreaterVal(copyOptions.MonitorSourceTimeLimit);
|
||||
MultiThreadedCopiesCount = MultiThreadedCopiesCount.GetGreaterVal(copyOptions.MultiThreadedCopiesCount);
|
||||
|
||||
//Flags
|
||||
AddAttributes = AddAttributes.CombineCharArr(copyOptions.AddAttributes);
|
||||
CopyFlags = CopyFlags.CombineCharArr(copyOptions.CopyFlags);
|
||||
DirectoryCopyFlags = DirectoryCopyFlags.CombineCharArr(copyOptions.DirectoryCopyFlags);
|
||||
RemoveAttributes = RemoveAttributes.CombineCharArr(copyOptions.RemoveAttributes);
|
||||
|
||||
//IEnumerable
|
||||
var list = new List<String>(FileFilter);
|
||||
list.AddRange(copyOptions.FileFilter);
|
||||
FileFilter = list;
|
||||
|
||||
//Bool
|
||||
CheckPerFile |= copyOptions.CheckPerFile;
|
||||
CopyAll |= copyOptions.CopyAll;
|
||||
CopyFilesWithSecurity |= copyOptions.CopyFilesWithSecurity;
|
||||
CopySubdirectories |= copyOptions.CopySubdirectories;
|
||||
CopySubdirectoriesIncludingEmpty |= copyOptions.CopySubdirectoriesIncludingEmpty;
|
||||
CopySymbolicLink |= copyOptions.CopySymbolicLink;
|
||||
CreateDirectoryAndFileTree |= copyOptions.CreateDirectoryAndFileTree;
|
||||
DoNotCopyDirectoryInfo |= copyOptions.DoNotCopyDirectoryInfo;
|
||||
DoNotUseWindowsCopyOffload |= copyOptions.DoNotUseWindowsCopyOffload;
|
||||
EnableBackupMode |= copyOptions.EnableBackupMode;
|
||||
EnableEfsRawMode |= copyOptions.EnableEfsRawMode;
|
||||
EnableRestartMode |= copyOptions.EnableRestartMode;
|
||||
EnableRestartModeWithBackupFallback |= copyOptions.EnableRestartModeWithBackupFallback;
|
||||
FatFiles |= copyOptions.FatFiles;
|
||||
FixFileSecurityOnAllFiles |= copyOptions.FixFileSecurityOnAllFiles;
|
||||
FixFileTimesOnAllFiles |= copyOptions.FixFileTimesOnAllFiles;
|
||||
Mirror |= copyOptions.Mirror;
|
||||
MoveFiles |= copyOptions.MoveFiles;
|
||||
MoveFilesAndDirectories |= copyOptions.MoveFilesAndDirectories;
|
||||
Purge |= copyOptions.Purge;
|
||||
RemoveFileInformation |= copyOptions.RemoveFileInformation;
|
||||
TurnLongPathSupportOff |= copyOptions.TurnLongPathSupportOff;
|
||||
UseUnbufferedIo |= copyOptions.UseUnbufferedIo;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace RoboSharp
|
||||
{
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
public sealed class Debugger
|
||||
{
|
||||
private static readonly Lazy<Debugger> instance = new Lazy<Debugger>(() => new Debugger());
|
||||
|
||||
[DebuggerHidden()]
|
||||
private Debugger()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static Debugger Instance
|
||||
{
|
||||
get { return instance.Value; }
|
||||
}
|
||||
|
||||
public EventHandler<DebugMessageArgs> DebugMessageEvent;
|
||||
|
||||
public class DebugMessageArgs : EventArgs
|
||||
{
|
||||
public object Message { get; set; }
|
||||
}
|
||||
|
||||
[DebuggerHidden()]
|
||||
private void RaiseDebugMessageEvent(object message)
|
||||
{
|
||||
DebugMessageEvent?.Invoke(this, new DebugMessageArgs
|
||||
{
|
||||
Message = message
|
||||
});
|
||||
}
|
||||
|
||||
[DebuggerHidden()]
|
||||
internal void DebugMessage(object data)
|
||||
{
|
||||
RaiseDebugMessageEvent(data);
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace RoboSharp.DefaultConfigurations
|
||||
{
|
||||
internal class RoboSharpConfig_DE : RoboSharpConfiguration
|
||||
{
|
||||
public RoboSharpConfig_DE() : base()
|
||||
{
|
||||
errorToken = "FEHLER";
|
||||
errorTokenRegex = RoboSharpConfiguration.ErrorTokenRegexGenerator(errorToken);
|
||||
//errorTokenRegex = new Regex($" FEHLER " + @"(\d{1,3}) \(0x\d{8}\) ", RegexOptions.Compiled);
|
||||
|
||||
// < File Tokens >
|
||||
|
||||
//LogParsing_NewFile = "New File";
|
||||
//LogParsing_OlderFile = "Older";
|
||||
//LogParsing_NewerFile = "Newer";
|
||||
//LogParsing_SameFile = "same";
|
||||
//LogParsing_ExtraFile = "*EXTRA File";
|
||||
//LogParsing_MismatchFile = "*Mismatch";
|
||||
//LogParsing_FailedFile = "*Failed";
|
||||
//LogParsing_FileExclusion = "named";
|
||||
|
||||
// < Directory Tokens >
|
||||
|
||||
//LogParsing_NewDir = "New Dir";
|
||||
//LogParsing_ExtraDir = "*EXTRA Dir";
|
||||
//LogParsing_ExistingDir = "Existing Dir";
|
||||
//LogParsing_DirectoryExclusion = "named";
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace RoboSharp.DefaultConfigurations
|
||||
{
|
||||
/// <summary>
|
||||
/// This is the Default Configuration class to use
|
||||
/// </summary>
|
||||
internal class RoboSharpConfig_EN : RoboSharpConfiguration
|
||||
{
|
||||
public RoboSharpConfig_EN() : base()
|
||||
{
|
||||
errorToken = "ERROR";
|
||||
errorTokenRegex = RoboSharpConfiguration.ErrorTokenRegexGenerator(errorToken);
|
||||
//errorTokenRegex = new Regex($" ERROR " + @"(\d{1,3}) \(0x\d{8}\) ", RegexOptions.Compiled);
|
||||
|
||||
// < File Tokens >
|
||||
|
||||
LogParsing_NewFile = "New File";
|
||||
LogParsing_OlderFile = "Older";
|
||||
LogParsing_NewerFile = "Newer";
|
||||
LogParsing_SameFile = "same";
|
||||
LogParsing_ExtraFile = "*EXTRA File";
|
||||
LogParsing_MismatchFile = "*Mismatch";
|
||||
LogParsing_FailedFile = "*Failed";
|
||||
LogParsing_FileExclusion = "named";
|
||||
|
||||
// < Directory Tokens >
|
||||
|
||||
LogParsing_NewDir = "New Dir";
|
||||
LogParsing_ExtraDir = "*EXTRA Dir";
|
||||
LogParsing_ExistingDir = "Existing Dir";
|
||||
LogParsing_DirectoryExclusion = "named";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
// Do Not change NameSpace here! -> Must be RoboSharp due to prior releases
|
||||
namespace RoboSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes an error that occured when generating the command
|
||||
/// </summary>
|
||||
public class CommandErrorEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Error Description
|
||||
/// </summary>
|
||||
public string Error { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If this CommandErrorEventArgs object was created in response to an exception, that exception is captured here. <br/>
|
||||
/// If no exception was thrown, this property will be null.
|
||||
/// </summary>
|
||||
public Exception Exception { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="CommandErrorEventArgs"/>
|
||||
/// </summary>
|
||||
/// <param name="error"><inheritdoc cref="Error"/></param>
|
||||
/// <param name="ex"><inheritdoc cref="Exception"/></param>
|
||||
public CommandErrorEventArgs(string error, Exception ex)
|
||||
{
|
||||
Error = error;
|
||||
this.Exception = ex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="CommandErrorEventArgs"/>
|
||||
/// </summary>
|
||||
/// <param name="ex">Exception to data to pass to the event handler</param>
|
||||
public CommandErrorEventArgs(Exception ex)
|
||||
{
|
||||
Error = ex.Message;
|
||||
this.Exception = ex;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using System;
|
||||
|
||||
// Do Not change NameSpace here! -> Must be RoboSharp due to prior releases
|
||||
namespace RoboSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Current File Progress reported as <see cref="double"/>
|
||||
/// </summary>
|
||||
public class CopyProgressEventArgs : EventArgs
|
||||
{
|
||||
/// <summary><inheritdoc cref="CopyProgressEventArgs"/></summary>
|
||||
/// <param name="progress"><inheritdoc cref="CurrentFileProgress"/></param>
|
||||
public CopyProgressEventArgs(double progress)
|
||||
{
|
||||
CurrentFileProgress = progress;
|
||||
}
|
||||
|
||||
/// <summary><inheritdoc cref="CopyProgressEventArgs"/></summary>
|
||||
/// <param name="progress"><inheritdoc cref="CurrentFileProgress"/></param>
|
||||
/// <param name="currentFile"><inheritdoc cref="CurrentFile"/></param>
|
||||
/// <param name="SourceDir"><inheritdoc cref="CurrentDirectory"/></param>
|
||||
public CopyProgressEventArgs(double progress, ProcessedFileInfo currentFile, ProcessedFileInfo SourceDir)
|
||||
{
|
||||
CurrentFileProgress = progress;
|
||||
CurrentFile = currentFile;
|
||||
CurrentDirectory = SourceDir;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current File Progress Percentage
|
||||
/// </summary>
|
||||
public double CurrentFileProgress { get; internal set; }
|
||||
|
||||
/// <inheritdoc cref="ProcessedFileInfo"/>
|
||||
public ProcessedFileInfo CurrentFile { get; internal set; }
|
||||
|
||||
/// <summary>Contains information about the Last Directory RoboCopy reported into the log. </summary>
|
||||
public ProcessedFileInfo CurrentDirectory{ get; internal set; }
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
// Do Not change NameSpace here! -> Must be RoboSharp due to prior releases
|
||||
namespace RoboSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about an Error reported by the RoboCopy process
|
||||
/// </summary>
|
||||
public class ErrorEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Error Code
|
||||
/// </summary>
|
||||
public string Error { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Error Description
|
||||
/// </summary>
|
||||
public string ErrorDescription { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Error Code
|
||||
/// </summary>
|
||||
public int ErrorCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Signed Error Code
|
||||
/// </summary>
|
||||
public string SignedErrorCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The File or Directory Path the Error refers to
|
||||
/// </summary>
|
||||
public string ErrorPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// DateTime the error occurred
|
||||
/// </summary>
|
||||
public DateTime DateTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Concatenate the <see cref="Error"/> and <see cref="ErrorDescription"/> into a string seperated by an <see cref="Environment.NewLine"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
if (ErrorDescription.IsNullOrWhiteSpace())
|
||||
return Error;
|
||||
else
|
||||
return String.Format("{0}{1}{2}", Error, Environment.NewLine, ErrorDescription);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="ErrorEventArgs"/>
|
||||
/// </summary>
|
||||
/// <param name="errorData"><inheritdoc cref="Error"/></param>
|
||||
/// <param name="descripData"><inheritdoc cref="ErrorCode"/></param>
|
||||
/// <param name="errTokenRegex">
|
||||
/// Regex used to split the Error Code into its various parts. <br/>
|
||||
/// Must have the following groups: Date, ErrCode, SignedErrCode, Descrip, Path
|
||||
/// </param>
|
||||
internal ErrorEventArgs(string errorData, string descripData, Regex errTokenRegex)
|
||||
{
|
||||
var match = errTokenRegex.Match(errorData);
|
||||
var groups = match.Groups;
|
||||
|
||||
//Date
|
||||
string dateStr = groups["Date"].Value;
|
||||
if (DateTime.TryParse(dateStr, out var DT))
|
||||
this.DateTime = DT;
|
||||
else
|
||||
this.DateTime = DateTime.Now;
|
||||
|
||||
//Path
|
||||
ErrorPath = groups["Path"].Value;
|
||||
|
||||
//Error Code
|
||||
ErrorCode = Convert.ToInt32(groups["ErrCode"].Value);
|
||||
SignedErrorCode = groups["SignedErrCode"].Value;
|
||||
|
||||
//Error String
|
||||
Error = errorData;
|
||||
ErrorDescription = descripData;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using System;
|
||||
|
||||
// Do Not change NameSpace here! -> Must be RoboSharp due to prior releases
|
||||
namespace RoboSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="ProcessedFileInfo"/>
|
||||
/// </summary>
|
||||
public class FileProcessedEventArgs : EventArgs
|
||||
{
|
||||
/// <inheritdoc cref="ProcessedFileInfo"/>
|
||||
public ProcessedFileInfo ProcessedFile { get; set; }
|
||||
|
||||
/// <inheritdoc cref="EventArgs.EventArgs"/>
|
||||
public FileProcessedEventArgs(ProcessedFileInfo file)
|
||||
{
|
||||
ProcessedFile = file;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using RoboSharp.Interfaces;
|
||||
using RoboSharp.Results;
|
||||
|
||||
// Do Not change NameSpace here! -> Must be RoboSharp due to prior releases
|
||||
namespace RoboSharp.EventArgObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Event Args provided by IProgressEstimator objects to notify the UI it should refresh the stat values
|
||||
/// </summary>
|
||||
public class IProgressEstimatorUpdateEventArgs : EventArgs
|
||||
{
|
||||
/// <summary> Dummy Args with Values of 0 to perform final updates through ProgressEstimator without creating new args every time</summary>
|
||||
internal static IProgressEstimatorUpdateEventArgs DummyArgs { get; } = new IProgressEstimatorUpdateEventArgs(null, null, null, null);
|
||||
|
||||
private IProgressEstimatorUpdateEventArgs() : base() { }
|
||||
|
||||
internal IProgressEstimatorUpdateEventArgs(IProgressEstimator estimator, IStatistic ByteChange, IStatistic FileChange, IStatistic DirChange) : base()
|
||||
{
|
||||
Estimator = estimator;
|
||||
ValueChange_Bytes = ByteChange ?? Statistic.Default_Bytes;
|
||||
ValueChange_Files = FileChange ?? Statistic.Default_Files;
|
||||
ValueChange_Directories = DirChange ?? Statistic.Default_Dirs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="Results.ProgressEstimator"/>
|
||||
/// </summary>
|
||||
private IProgressEstimator Estimator { get; }
|
||||
|
||||
/// <inheritdoc cref="IProgressEstimator.BytesStatistic"/>
|
||||
public IStatistic BytesStatistic => Estimator?.BytesStatistic;
|
||||
|
||||
/// <inheritdoc cref="IProgressEstimator.FilesStatistic"/>
|
||||
public IStatistic FilesStatistic => Estimator?.FilesStatistic;
|
||||
|
||||
/// <inheritdoc cref="IProgressEstimator.DirectoriesStatistic"/>
|
||||
public IStatistic DirectoriesStatistic => Estimator?.DirectoriesStatistic;
|
||||
|
||||
/// <summary>IStatistic Object that shows how much was added to the { <see cref="BytesStatistic"/> } object during this UI Update</summary>
|
||||
public IStatistic ValueChange_Bytes { get; }
|
||||
|
||||
/// <summary>IStatistic Object that shows how much was added to the { <see cref="FilesStatistic"/> } object during this UI Update</summary>
|
||||
public IStatistic ValueChange_Files { get; }
|
||||
|
||||
/// <summary>IStatistic Object that shows how much was added to the { <see cref="DirectoriesStatistic"/> } object during this UI Update</summary>
|
||||
public IStatistic ValueChange_Directories { get; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using RoboSharp.Interfaces;
|
||||
|
||||
// Do Not change NameSpace here! -> Must be RoboSharp due to prior releases
|
||||
namespace RoboSharp.EventArgObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Reports that a ProgressEstimator object is now available for binding
|
||||
/// </summary>
|
||||
public class ProgressEstimatorCreatedEventArgs : EventArgs
|
||||
{
|
||||
private ProgressEstimatorCreatedEventArgs() : base() { }
|
||||
|
||||
internal ProgressEstimatorCreatedEventArgs(IProgressEstimator estimator) : base()
|
||||
{
|
||||
ResultsEstimate = estimator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="Results.ProgressEstimator"/>
|
||||
/// </summary>
|
||||
public IProgressEstimator ResultsEstimate { get; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
using System;
|
||||
using RoboSharp.Results;
|
||||
using RoboSharp.Interfaces;
|
||||
|
||||
namespace RoboSharp.EventArgObjects
|
||||
{
|
||||
/// <summary> EventArgs for the <see cref="RoboCopyResultsList.ResultsListUpdated"/> delegate </summary>
|
||||
public class ResultListUpdatedEventArgs : EventArgs
|
||||
{
|
||||
private ResultListUpdatedEventArgs() { }
|
||||
|
||||
/// <summary> Create the EventArgs for the <see cref="RoboCopyResultsList.ResultsListUpdated"/> delegate </summary>
|
||||
/// <param name="list">Results list to present as an interface</param>
|
||||
public ResultListUpdatedEventArgs(IRoboCopyResultsList list)
|
||||
{
|
||||
ResultsList = list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read-Only interface to the List that has been updated.
|
||||
/// </summary>
|
||||
public IRoboCopyResultsList ResultsList { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using System;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using RoboSharp.EventArgObjects;
|
||||
|
||||
// Do Not change NameSpace here! -> Must be RoboSharp due to prior releases
|
||||
namespace RoboSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="Results.RoboCopyResults"/>
|
||||
/// </summary>
|
||||
public class RoboCommandCompletedEventArgs : TimeSpanEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Return the Results object
|
||||
/// </summary>
|
||||
/// <param name="results"></param>
|
||||
internal RoboCommandCompletedEventArgs(Results.RoboCopyResults results) : base(results.StartTime, results.EndTime, results.TimeSpan)
|
||||
{
|
||||
this.Results = results;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Results.RoboCopyResults"/>
|
||||
public Results.RoboCopyResults Results { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using RoboSharp.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace RoboSharp.EventArgObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// EventArgs to declare when a RoboCommand process starts
|
||||
/// </summary>
|
||||
public class RoboQueueCommandStartedEventArgs : EventArgs
|
||||
{
|
||||
private RoboQueueCommandStartedEventArgs() : base() { }
|
||||
internal RoboQueueCommandStartedEventArgs(IRoboCommand cmd) : base() { Command = cmd; StartTime = DateTime.Now; }
|
||||
|
||||
/// <summary>
|
||||
/// Command that started.
|
||||
/// </summary>
|
||||
public IRoboCommand Command { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns TRUE if the command's <see cref="RoboSharp.Results.ProgressEstimator"/> is available for binding
|
||||
/// </summary>
|
||||
public bool ProgressEstimatorAvailable => Command.IsRunning;
|
||||
|
||||
/// <summary>
|
||||
/// Local time the command started.
|
||||
/// </summary>
|
||||
public DateTime StartTime { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using RoboSharp.Interfaces;
|
||||
using RoboSharp.Results;
|
||||
|
||||
namespace RoboSharp.EventArgObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// EventArgs to declare when a RoboCommand process starts
|
||||
/// </summary>
|
||||
public class RoboQueueCompletedEventArgs : TimeSpanEventArgs
|
||||
{
|
||||
internal RoboQueueCompletedEventArgs(RoboQueueResults runResults, bool listOnlyRun) : base(runResults.StartTime, runResults.EndTime, runResults.TimeSpan)
|
||||
{
|
||||
RunResults = runResults;
|
||||
CopyOperation = !listOnlyRun;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RoboQueue Results Object
|
||||
/// </summary>
|
||||
public RoboQueueResults RunResults { get; }
|
||||
|
||||
/// <summary>
|
||||
/// TRUE if this run was a COPY OPERATION, FALSE is the results were created after a <see cref="RoboQueue.StartAll_ListOnly(string, string, string)"/> call.
|
||||
/// </summary>
|
||||
public bool CopyOperation { get; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.ComponentModel;
|
||||
using RoboSharp.Results;
|
||||
using RoboSharp.Interfaces;
|
||||
|
||||
namespace RoboSharp.EventArgObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface helper for dealing with Statistic Event Args
|
||||
/// </summary>
|
||||
public interface IStatisticPropertyChangedEventArg
|
||||
{
|
||||
/// <inheritdoc cref="Statistic.Type"/>
|
||||
Statistic.StatType StatType { get; }
|
||||
|
||||
/// <summary>TRUE if of type <see cref="StatChangedEventArg"/>. Otherwise false.</summary>
|
||||
bool Is_StatChangedEventArg { get; }
|
||||
|
||||
/// <summary>TRUE if of type <see cref="StatisticPropertyChangedEventArgs"/>. Otherwise false.</summary>
|
||||
bool Is_StatisticPropertyChangedEventArgs { get; }
|
||||
|
||||
/// <inheritdoc cref="PropertyChangedEventArgs.PropertyName"/>
|
||||
string PropertyName { get; }
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs provided by <see cref="Statistic.StatChangedHandler"/> when any individual property gets modified.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Under most circumstances, the 'PropertyName' property will detail which parameter has been updated. <br/>
|
||||
/// When the Statistic object has multiple values change via a method call ( Reset / Add / Subtract methods ), then PropertyName will be String.Empty, indicating multiple values have changed. <br/>
|
||||
/// If this is the case, then the <see cref="StatChangedEventArg.NewValue"/>, <see cref="StatChangedEventArg.OldValue"/>, and <see cref="StatChangedEventArg.Difference"/> will report the value from the sender's <see cref="Statistic.Total"/> property.
|
||||
/// </remarks>
|
||||
public class StatChangedEventArg : PropertyChangedEventArgs, IStatisticPropertyChangedEventArg
|
||||
{
|
||||
private StatChangedEventArg() : base("") { }
|
||||
internal StatChangedEventArg(Statistic stat, long oldValue, long newValue, string PropertyName) : base(PropertyName)
|
||||
{
|
||||
Sender = stat;
|
||||
StatType = stat.Type;
|
||||
OldValue = oldValue;
|
||||
NewValue = newValue;
|
||||
}
|
||||
|
||||
/// <summary> This is a reference to the Statistic that generated the EventArg object </summary>
|
||||
public IStatistic Sender { get; }
|
||||
|
||||
/// <inheritdoc cref="Statistic.Type"/>
|
||||
public Statistic.StatType StatType { get; }
|
||||
|
||||
/// <summary> Old Value of the object </summary>
|
||||
public long OldValue { get; }
|
||||
|
||||
/// <summary> Current Value of the object </summary>
|
||||
public long NewValue { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Result of NewValue - OldValue
|
||||
/// </summary>
|
||||
public long Difference => NewValue - OldValue;
|
||||
|
||||
bool IStatisticPropertyChangedEventArg.Is_StatChangedEventArg => true;
|
||||
|
||||
bool IStatisticPropertyChangedEventArg.Is_StatisticPropertyChangedEventArgs => false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs provided by <see cref="Statistic.PropertyChanged"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Under most circumstances, the 'PropertyName' property will detail which parameter has been updated. <br/>
|
||||
/// When the Statistic object has multiple values change via a method call ( Reset / Add / Subtract methods ), then PropertyName will be String.Empty, indicating multiple values have changed. <br/>
|
||||
/// If this is the case, then the <see cref="StatChangedEventArg.NewValue"/>, <see cref="StatChangedEventArg.OldValue"/>, and <see cref="StatChangedEventArg.Difference"/> will report the value from the sender's <see cref="Statistic.Total"/> property.
|
||||
/// </remarks>
|
||||
public class StatisticPropertyChangedEventArgs : PropertyChangedEventArgs, IStatisticPropertyChangedEventArg
|
||||
{
|
||||
private StatisticPropertyChangedEventArgs() : base("") { }
|
||||
internal StatisticPropertyChangedEventArgs(Statistic stat, Statistic oldValue, string PropertyName) : base(PropertyName)
|
||||
{
|
||||
//Sender = stat;
|
||||
StatType = stat.Type;
|
||||
OldValue = oldValue;
|
||||
NewValue = stat.Clone();
|
||||
Lazydifference = new Lazy<Statistic>(() => Statistic.Subtract(NewValue, OldValue));
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Statistic.Type"/>
|
||||
public Statistic.StatType StatType { get; }
|
||||
|
||||
/// <summary> Old Value of the object </summary>
|
||||
public IStatistic OldValue { get; }
|
||||
|
||||
/// <summary> Current Value of the object </summary>
|
||||
public IStatistic NewValue { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Result of NewValue - OldValue
|
||||
/// </summary>
|
||||
public IStatistic Difference => Lazydifference.Value;
|
||||
private Lazy<Statistic> Lazydifference;
|
||||
|
||||
bool IStatisticPropertyChangedEventArg.Is_StatChangedEventArg => false;
|
||||
|
||||
bool IStatisticPropertyChangedEventArg.Is_StatisticPropertyChangedEventArgs => true;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace RoboSharp.EventArgObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Provide a base class that includes a StartTime, EndTime and will calculate the TimeSpan in between
|
||||
/// </summary>
|
||||
public abstract class TimeSpanEventArgs : EventArgs, RoboSharp.Interfaces.ITimeSpan
|
||||
{
|
||||
private TimeSpanEventArgs() : base() { }
|
||||
|
||||
/// <summary>
|
||||
/// Create New Args
|
||||
/// </summary>
|
||||
/// <param name="startTime"></param>
|
||||
/// <param name="endTime"></param>
|
||||
public TimeSpanEventArgs(DateTime startTime, DateTime endTime) : base()
|
||||
{
|
||||
StartTime = startTime;
|
||||
EndTime = endTime;
|
||||
TimeSpan = EndTime.Subtract(StartTime);
|
||||
}
|
||||
|
||||
internal TimeSpanEventArgs(DateTime startTime, DateTime endTime, TimeSpan tSpan) : base()
|
||||
{
|
||||
StartTime = startTime;
|
||||
EndTime = endTime;
|
||||
TimeSpan = tSpan;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Local time the command started.
|
||||
/// </summary>
|
||||
public virtual DateTime StartTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Local time the command stopped.
|
||||
/// </summary>
|
||||
public virtual DateTime EndTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Length of time the process took to run
|
||||
/// </summary>
|
||||
public virtual TimeSpan TimeSpan { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,244 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace RoboSharp
|
||||
{
|
||||
internal static class ExtensionMethods
|
||||
{
|
||||
/// <summary> Encase the LogPath in quotes if needed </summary>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
[DebuggerHidden()]
|
||||
internal static string WrapPath(this string logPath) => (!logPath.StartsWith("\"") && logPath.Contains(" ")) ? $"\"{logPath}\"" : logPath;
|
||||
|
||||
/// <remarks> Extension method provided by RoboSharp package </remarks>
|
||||
/// <inheritdoc cref="System.String.IsNullOrWhiteSpace(string)"/>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
[DebuggerHidden()]
|
||||
internal static bool IsNullOrWhiteSpace(this string value) => string.IsNullOrWhiteSpace(value);
|
||||
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
[DebuggerHidden()]
|
||||
internal static long TryConvertLong(this string val)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Convert.ToInt64(val);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
[DebuggerHidden()]
|
||||
internal static int TryConvertInt(this string val)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Convert.ToInt32(val);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
[DebuggerHidden()]
|
||||
public static string CleanOptionInput(this string option)
|
||||
{
|
||||
// Get rid of forward slashes
|
||||
option = option.Replace("/", "");
|
||||
// Get rid of padding
|
||||
option = option.Trim();
|
||||
return option;
|
||||
}
|
||||
|
||||
public static string CleanDirectoryPath(this string path)
|
||||
{
|
||||
// Get rid of single and double quotes
|
||||
path = path?.Replace("\"", "");
|
||||
path = path?.Replace("\'", "");
|
||||
|
||||
//Validate against null / empty strings.
|
||||
if (string.IsNullOrWhiteSpace(path)) return string.Empty;
|
||||
|
||||
// Get rid of padding
|
||||
path = path.Trim();
|
||||
|
||||
// Get rid of trailing Directory Seperator Chars
|
||||
// Length greater than 3 because E:\ is the shortest valid path
|
||||
while (path.Length > 3 && path.EndsWithDirectorySeperator())
|
||||
{
|
||||
path = path.Substring(0, path.Length - 1);
|
||||
}
|
||||
|
||||
//Sanitize invalid paths -- Convert E: to E:\
|
||||
if (path.Length <= 2)
|
||||
{
|
||||
if (DriveRootRegex.IsMatch(path))
|
||||
return path.ToUpper() + '\\';
|
||||
else
|
||||
return path;
|
||||
}
|
||||
|
||||
// Fix UNC paths that are the root directory of a UNC drive
|
||||
if (Uri.TryCreate(path, UriKind.Absolute, out Uri URI) && URI.IsUnc)
|
||||
{
|
||||
if (path.EndsWith("$"))
|
||||
{
|
||||
path += '\\';
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private static readonly Regex DriveRootRegex = new Regex("[A-Za-z]:", RegexOptions.Compiled);
|
||||
|
||||
/// <summary>
|
||||
/// Check if the string ends with a directory seperator character
|
||||
/// </summary>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
[DebuggerHidden()]
|
||||
public static bool EndsWithDirectorySeperator(this string path) => path.EndsWith(Path.DirectorySeparatorChar.ToString()) || path.EndsWith(Path.AltDirectorySeparatorChar.ToString());
|
||||
|
||||
/// <summary>
|
||||
/// Convert <paramref name="StrTwo"/> into a char[]. Perform a ForEach( Char in strTwo) loop, and append any characters in Str2 to the end of this string if they don't already exist within this string.
|
||||
/// </summary>
|
||||
/// <param name="StrOne"></param>
|
||||
/// <param name="StrTwo"></param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
[DebuggerHidden()]
|
||||
internal static string CombineCharArr(this string StrOne, string StrTwo)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(StrTwo)) return StrOne;
|
||||
if (String.IsNullOrWhiteSpace(StrOne)) return StrTwo ?? StrOne;
|
||||
string ret = StrOne;
|
||||
char[] S2 = StrTwo.ToArray();
|
||||
foreach (char c in S2)
|
||||
{
|
||||
if (!ret.Contains(c))
|
||||
ret += c;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare the current value to that of the supplied value, and take the greater of the two.
|
||||
/// </summary>
|
||||
/// <param name="i"></param>
|
||||
/// <param name="i2"></param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
[DebuggerHidden()]
|
||||
internal static int GetGreaterVal(this int i, int i2) => i >= i2 ? i : i2;
|
||||
|
||||
/// <summary>
|
||||
/// Evaluate this string. If this string is null or empty, replace it with the supplied string.
|
||||
/// </summary>
|
||||
/// <param name="str1"></param>
|
||||
/// <param name="str2"></param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
[DebuggerHidden()]
|
||||
internal static string ReplaceIfEmpty(this string str1, string str2) => String.IsNullOrWhiteSpace(str1) ? str2 ?? String.Empty : str1;
|
||||
}
|
||||
}
|
||||
|
||||
namespace System.Threading
|
||||
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains methods for CancelleableSleep and WaitUntil
|
||||
/// </summary>
|
||||
internal static class ThreadEx
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Wait synchronously until this task has reached the specified <see cref="TaskStatus"/>
|
||||
/// </summary>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
public static void WaitUntil(this Task t, TaskStatus status)
|
||||
{
|
||||
while (t.Status < status)
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait asynchronously until this task has reached the specified <see cref="TaskStatus"/> <br/>
|
||||
/// Checks every 100ms
|
||||
/// </summary>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
public static async Task WaitUntilAsync(this Task t, TaskStatus status)
|
||||
{
|
||||
while (t.Status < status)
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait synchronously until this task has reached the specified <see cref="TaskStatus"/><br/>
|
||||
/// Checks every <paramref name="interval"/> milliseconds
|
||||
/// </summary>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
public static async Task WaitUntilAsync(this Task t, TaskStatus status, int interval)
|
||||
{
|
||||
while (t.Status < status)
|
||||
await Task.Delay(interval);
|
||||
}
|
||||
|
||||
/// <param name="timeSpan">TimeSpan to sleep the thread</param>
|
||||
/// <param name="token"><inheritdoc cref="CancellationToken"/></param>
|
||||
/// <inheritdoc cref="CancellableSleep(int, CancellationToken)"/>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
internal static Task<bool> CancellableSleep(TimeSpan timeSpan, CancellationToken token)
|
||||
{
|
||||
return CancellableSleep((int)timeSpan.TotalMilliseconds, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="CancellableSleep(int, CancellationToken[])"/>
|
||||
/// <inheritdoc cref="CancellableSleep(int, CancellationToken)"/>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
internal static Task<bool> CancellableSleep(TimeSpan timeSpan, CancellationToken[] tokens)
|
||||
{
|
||||
return CancellableSleep((int)timeSpan.TotalMilliseconds, tokens);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use await Task.Delay to sleep the thread. <br/>
|
||||
/// </summary>
|
||||
/// <returns>True if timer has expired (full duration slep), otherwise false.</returns>
|
||||
/// <param name="millisecondsTimeout">Number of milliseconds to wait"/></param>
|
||||
/// <param name="token"><inheritdoc cref="CancellationToken"/></param>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
internal static Task<bool> CancellableSleep(int millisecondsTimeout, CancellationToken token)
|
||||
{
|
||||
return Task.Delay(millisecondsTimeout, token).ContinueWith(t => t.Exception == default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use await Task.Delay to sleep the thread. <br/>
|
||||
/// Supplied tokens are used to create a LinkedToken that can cancel the sleep at any point.
|
||||
/// </summary>
|
||||
/// <returns>True if slept full duration, otherwise false.</returns>
|
||||
/// <param name="millisecondsTimeout">Number of milliseconds to wait"/></param>
|
||||
/// <param name="tokens">Use <see cref="CancellationTokenSource.CreateLinkedTokenSource(CancellationToken[])"/> to create the token used to cancel the delay</param>
|
||||
/// <inheritdoc cref="CancellableSleep(int, CancellationToken)"/>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
internal static Task<bool> CancellableSleep(int millisecondsTimeout, CancellationToken[] tokens)
|
||||
{
|
||||
var token = CancellationTokenSource.CreateLinkedTokenSource(tokens).Token;
|
||||
return Task.Delay(millisecondsTimeout, token).ContinueWith(t => t.Exception == default);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
#if NET40_OR_GREATER
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Principal;
|
||||
|
||||
namespace RoboSharp
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Create an authenticated user to test Source/Destination directory access
|
||||
/// <remarks>See Issue #43 and PR #45 </remarks>
|
||||
/// <remarks>This class is not available in NetCoreApp3.1, NetStandard2.0, and NetStandard2.1</remarks>
|
||||
/// </summary>
|
||||
internal class ImpersonatedUser : IDisposable
|
||||
{
|
||||
IntPtr userHandle;
|
||||
|
||||
WindowsImpersonationContext impersonationContext;
|
||||
|
||||
/// <inheritdoc cref="ImpersonatedUser"/>
|
||||
/// <inheritdoc cref="RoboCommand.Start(string, string, string)"></inheritdoc>
|
||||
internal ImpersonatedUser(string user, string domain, string password)
|
||||
{
|
||||
userHandle = IntPtr.Zero;
|
||||
|
||||
bool loggedOn = LogonUser(
|
||||
user,
|
||||
domain,
|
||||
password,
|
||||
LogonType.Interactive,
|
||||
LogonProvider.Default,
|
||||
out userHandle);
|
||||
|
||||
if (!loggedOn)
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
|
||||
// Begin impersonating the user
|
||||
impersonationContext = WindowsIdentity.Impersonate(userHandle);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IDisposable.Dispose"/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (userHandle != IntPtr.Zero)
|
||||
{
|
||||
CloseHandle(userHandle);
|
||||
|
||||
userHandle = IntPtr.Zero;
|
||||
|
||||
impersonationContext.Undo();
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
static extern bool LogonUser(
|
||||
|
||||
string lpszUsername,
|
||||
|
||||
string lpszDomain,
|
||||
|
||||
string lpszPassword,
|
||||
|
||||
LogonType dwLogonType,
|
||||
|
||||
LogonProvider dwLogonProvider,
|
||||
|
||||
out IntPtr phToken
|
||||
|
||||
);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
static extern bool CloseHandle(IntPtr hHandle);
|
||||
|
||||
enum LogonType : int
|
||||
{
|
||||
Interactive = 2,
|
||||
Network = 3,
|
||||
Batch = 4,
|
||||
Service = 5,
|
||||
NetworkCleartext = 8,
|
||||
NewCredentials = 9,
|
||||
}
|
||||
|
||||
enum LogonProvider : int
|
||||
{
|
||||
Default = 0,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,45 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using RoboSharp.Results;
|
||||
|
||||
namespace RoboSharp.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Object that provides <see cref="IStatistic"/> objects whose events can be bound to report estimated RoboCommand / RoboQueue progress periodically.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/IProgressEstimator"/>
|
||||
/// </remarks>
|
||||
public interface IProgressEstimator : IResults
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Estimate of current number of directories processed while the job is still running. <br/>
|
||||
/// Estimate is provided by parsing of the LogLines produces by RoboCopy.
|
||||
/// </summary>
|
||||
new IStatistic DirectoriesStatistic { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Estimate of current number of files processed while the job is still running. <br/>
|
||||
/// Estimate is provided by parsing of the LogLines produces by RoboCopy.
|
||||
/// </summary>
|
||||
new IStatistic FilesStatistic { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Estimate of current number of bytes processed while the job is still running. <br/>
|
||||
/// Estimate is provided by parsing of the LogLines produces by RoboCopy.
|
||||
/// </summary>
|
||||
new IStatistic BytesStatistic { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Parse this object's stats into a <see cref="RoboCopyExitCodes"/> enum.
|
||||
/// </summary>
|
||||
RoboCopyExitCodes GetExitCode();
|
||||
|
||||
/// <summary> Event that occurs when this IProgressEstimatorObject's IStatistic values have been updated. </summary>
|
||||
event ProgressEstimator.UIUpdateEventHandler ValuesUpdated;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using RoboSharp.Results;
|
||||
|
||||
namespace RoboSharp.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides <see cref="IStatistic"/> objects for File, Directory, and Bytes to allow comparison between ProgressEstimator and RoboCopyResults objects
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/IResults"/>
|
||||
/// </remarks>
|
||||
public interface IResults
|
||||
{
|
||||
/// <summary> Information about number of Directories Copied, Skipped, Failed, etc.</summary>
|
||||
IStatistic DirectoriesStatistic { get; }
|
||||
|
||||
/// <summary> Information about number of Files Copied, Skipped, Failed, etc.</summary>
|
||||
IStatistic FilesStatistic { get; }
|
||||
|
||||
/// <summary> Information about number of Bytes processed.</summary>
|
||||
IStatistic BytesStatistic { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboCopyExitStatus"/>
|
||||
RoboCopyExitStatus Status { get; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RoboSharp.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/IRoboCommand"/>
|
||||
/// </remarks>
|
||||
public interface IRoboCommand
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.Name"/>
|
||||
string Name { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.IsPaused"/>
|
||||
bool IsPaused { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.IsRunning"/>
|
||||
bool IsRunning { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.IsScheduled"/>
|
||||
bool IsScheduled{ get; }
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.IsCancelled"/>
|
||||
bool IsCancelled { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.StopIfDisposing"/>
|
||||
bool StopIfDisposing { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.ProgressEstimator"/>
|
||||
IProgressEstimator IProgressEstimator { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.IsPaused"/>
|
||||
string CommandOptions { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.CopyOptions"/>
|
||||
CopyOptions CopyOptions { get; set; }
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.SelectionOptions"/>
|
||||
SelectionOptions SelectionOptions { get; set; }
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.RetryOptions"/>
|
||||
RetryOptions RetryOptions { get; set; }
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.LoggingOptions"/>
|
||||
LoggingOptions LoggingOptions { get; set; }
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.JobOptions"/>
|
||||
JobOptions JobOptions{ get; }
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.Configuration"/>
|
||||
RoboSharpConfiguration Configuration { get; }
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Events
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.OnFileProcessed"/>
|
||||
event RoboCommand.FileProcessedHandler OnFileProcessed;
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.OnCommandError"/>
|
||||
event RoboCommand.CommandErrorHandler OnCommandError;
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.OnError"/>
|
||||
event RoboCommand.ErrorHandler OnError;
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.OnCommandCompleted"/>
|
||||
event RoboCommand.CommandCompletedHandler OnCommandCompleted;
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.OnCopyProgressChanged"/>
|
||||
event RoboCommand.CopyProgressHandler OnCopyProgressChanged;
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.OnProgressEstimatorCreated"/>
|
||||
event RoboCommand.ProgressUpdaterCreatedHandler OnProgressEstimatorCreated;
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.TaskFaulted"/>
|
||||
event System.UnhandledExceptionEventHandler TaskFaulted;
|
||||
|
||||
#endregion Events
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.Pause"/>
|
||||
void Pause();
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.Resume"/>
|
||||
void Resume();
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.Start(string, string, string)"/>
|
||||
Task Start(string domain = "", string username = "", string password = "");
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.Start_ListOnly(string, string, string)"/>
|
||||
Task Start_ListOnly(string domain = "", string username = "", string password = "");
|
||||
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.GetResults"/>
|
||||
Results.RoboCopyResults GetResults();
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.Stop()"/>
|
||||
void Stop();
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.Dispose()"/>
|
||||
void Dispose();
|
||||
|
||||
#if NET45_OR_GREATER || NETSTANDARD2_0_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.StartAsync_ListOnly(string, string, string)"/>
|
||||
Task<Results.RoboCopyResults> StartAsync_ListOnly(string domain = "", string username = "", string password = "");
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.StartAsync(string, string, string)"/>
|
||||
Task<Results.RoboCopyResults> StartAsync(string domain = "", string username = "", string password = "");
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#endregion Methods
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RoboSharp.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for a class factory object to produce <see cref="IRoboCommand"/> objects <br/>
|
||||
/// Usable by consumers to specify a factory object their library can rely on to create classes derived from the <see cref="RoboCommand"/> object. <br/>
|
||||
/// </summary>
|
||||
public interface IRoboCommandFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new <see cref="IRoboCommand"/> object using the parameterless constructor
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// new <see cref="IRoboCommand"/> object
|
||||
/// </returns>
|
||||
/// <inheritdoc cref="RoboCommand.RoboCommand()"/>
|
||||
IRoboCommand GetRoboCommand();
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="IRoboCommand"/> with the specified source and destination
|
||||
/// </summary>
|
||||
/// <param name="source"><inheritdoc cref="CopyOptions.Source" path="*"/></param>
|
||||
/// <param name="destination"><inheritdoc cref="CopyOptions.Destination" path="*"/></param>
|
||||
/// <inheritdoc cref="RoboCommand.RoboCommand(string, string, bool)"/>
|
||||
IRoboCommand GetRoboCommand(string source, string destination);
|
||||
|
||||
/// <inheritdoc cref="RoboCommandFactory.GetRoboCommand(string, string, CopyOptions.CopyActionFlags, SelectionOptions.SelectionFlags)"/>
|
||||
IRoboCommand GetRoboCommand(string source, string destination, CopyOptions.CopyActionFlags copyActionFlags);
|
||||
|
||||
/// <inheritdoc cref="RoboCommandFactory.GetRoboCommand(string, string, CopyOptions.CopyActionFlags, SelectionOptions.SelectionFlags)"/>
|
||||
IRoboCommand GetRoboCommand(string source, string destination, CopyOptions.CopyActionFlags copyActionFlags, SelectionOptions.SelectionFlags selectionFlags);
|
||||
|
||||
/*
|
||||
* The constructors within the region below have been intentionally left out of the interface.
|
||||
* This is because these constructors are for more advanced usage of the object, and the base interface should only enforce the
|
||||
* what will most likely be the two most commonly used constructors.
|
||||
*
|
||||
* Should consumers require the interface to be expanded, they can produce their own interface that is derived from this one.
|
||||
*/
|
||||
|
||||
#region
|
||||
|
||||
///// <summary>
|
||||
///// Create a new <see cref="IRoboCommand"/> with the specified name
|
||||
///// </summary>
|
||||
///// <inheritdoc cref="RoboCommand.RoboCommand(string, bool)"/>
|
||||
//IRoboCommand GetRoboCommand(string name, bool stopIfDisposing = true);
|
||||
|
||||
///// <summary>
|
||||
///// Create a new <see cref="IRoboCommand"/> with the specified source and destination
|
||||
///// </summary>
|
||||
///// <inheritdoc cref="RoboCommand.RoboCommand(string, string, bool)"/>
|
||||
//IRoboCommand GetRoboCommand(string source, string destination, bool stopIfDisposing = true);
|
||||
|
||||
///// <summary>
|
||||
///// Create a new <see cref="IRoboCommand"/> with the specified source, destination, and name
|
||||
///// </summary>
|
||||
///// <inheritdoc cref="RoboCommand.RoboCommand(string, string, string, bool)"/>
|
||||
//IRoboCommand GetRoboCommand(string source, string destination, string name, bool stopIfDisposing = true);
|
||||
|
||||
|
||||
///// <inheritdoc cref="RoboCommand.RoboCommand(string, string, string, bool, RoboSharpConfiguration, CopyOptions, SelectionOptions, RetryOptions, LoggingOptions, JobOptions)"/>
|
||||
//IRoboCommand GetRoboCommand(string name, string source = null, string destination = null, bool stopIfDisposing = true, RoboSharpConfiguration configuration = null, CopyOptions copyOptions = null, SelectionOptions selectionOptions = null, RetryOptions retryOptions = null, LoggingOptions loggingOptions = null, JobOptions jobOptions = null);
|
||||
|
||||
|
||||
///// <inheritdoc cref="RoboCommand.RoboCommand(RoboCommand, string, string, bool, bool, bool, bool, bool)"/>
|
||||
//IRoboCommand GetRoboCommand(RoboCommand command, string NewSource = null, string NewDestination = null, bool LinkConfiguration = true, bool LinkRetryOptions = true, bool LinkSelectionOptions = false, bool LinkLoggingOptions = false, bool LinkJobOptions = false);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.ComponentModel;
|
||||
using RoboSharp.Results;
|
||||
|
||||
namespace RoboSharp.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Read-Only interface for <see cref="RoboCopyCombinedExitStatus"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/IRoboCopyCombinedExitStatus"/>
|
||||
/// </remarks>
|
||||
public interface IRoboCopyCombinedExitStatus : INotifyPropertyChanged, ICloneable
|
||||
{
|
||||
/// <inheritdoc cref="RoboCopyCombinedExitStatus.WasCancelled"/>
|
||||
bool WasCancelled { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboCopyCombinedExitStatus.AnyNoCopyNoError"/>
|
||||
bool AnyNoCopyNoError { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboCopyCombinedExitStatus.AnyWasCancelled"/>
|
||||
bool AnyWasCancelled { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboCopyCombinedExitStatus.AllSuccessful"/>
|
||||
bool AllSuccessful { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboCopyCombinedExitStatus.AllSuccessful_WithWarnings"/>
|
||||
bool AllSuccessful_WithWarnings { get; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using RoboSharp.Results;
|
||||
|
||||
namespace RoboSharp.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface to provide Read-Only access to a <see cref="RoboCopyResultsList"/>
|
||||
/// <para/>Implements: <br/>
|
||||
/// <see cref="IEnumerable{T}"/> where T = <see cref="RoboCopyResults"/> <br/>
|
||||
/// <see cref="ICloneable"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/IRoboCopyResultsList"/>
|
||||
/// </remarks>
|
||||
public interface IRoboCopyResultsList : IEnumerable<RoboCopyResults>, INotifyCollectionChanged
|
||||
{
|
||||
#region < Properties >
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="RoboCopyResults"/> objects at the specified index.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="IndexOutOfRangeException"/>
|
||||
RoboCopyResults this[int i] { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboCopyResultsList.DirectoriesStatistic"/>
|
||||
IStatistic DirectoriesStatistic { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboCopyResultsList.BytesStatistic"/>
|
||||
IStatistic BytesStatistic { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboCopyResultsList.FilesStatistic"/>
|
||||
IStatistic FilesStatistic { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboCopyResultsList.SpeedStatistic"/>
|
||||
ISpeedStatistic SpeedStatistic { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboCopyResultsList.Status"/>
|
||||
IRoboCopyCombinedExitStatus Status { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboCopyResultsList.Collection"/>
|
||||
IReadOnlyList<RoboCopyResults> Collection { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboCopyResultsList.Count"/>
|
||||
int Count { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Methods >
|
||||
|
||||
/// <summary>
|
||||
/// Get a snapshot of the ByteStatistics objects from this list.
|
||||
/// </summary>
|
||||
/// <returns>New array of the ByteStatistic objects</returns>
|
||||
IStatistic[] GetByteStatistics();
|
||||
|
||||
/// <summary>
|
||||
/// Get a snapshot of the DirectoriesStatistic objects from this list.
|
||||
/// </summary>
|
||||
/// <returns>New array of the DirectoriesStatistic objects</returns>
|
||||
IStatistic[] GetDirectoriesStatistics();
|
||||
|
||||
/// <summary>
|
||||
/// Get a snapshot of the FilesStatistic objects from this list.
|
||||
/// </summary>
|
||||
/// <returns>New array of the FilesStatistic objects</returns>
|
||||
IStatistic[] GetFilesStatistics();
|
||||
|
||||
/// <summary>
|
||||
/// Get a snapshot of the FilesStatistic objects from this list.
|
||||
/// </summary>
|
||||
/// <returns>New array of the FilesStatistic objects</returns>
|
||||
RoboCopyExitStatus[] GetStatuses();
|
||||
|
||||
/// <summary>
|
||||
/// Get a snapshot of the FilesStatistic objects from this list.
|
||||
/// </summary>
|
||||
/// <returns>New array of the FilesStatistic objects</returns>
|
||||
ISpeedStatistic[] GetSpeedStatistics();
|
||||
|
||||
/// <summary>
|
||||
/// Combine the <see cref="RoboCopyResults.RoboCopyErrors"/> into a single array of errors
|
||||
/// </summary>
|
||||
/// <returns>New array of the ErrorEventArgs objects</returns>
|
||||
ErrorEventArgs[] GetErrors();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
using RoboSharp.Interfaces;
|
||||
using RoboSharp.Results;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RoboSharp.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for RoboQueue
|
||||
/// </summary>
|
||||
public interface IRoboQueue : IDisposable, INotifyPropertyChanged, IEnumerable<IRoboCommand>
|
||||
{
|
||||
#region < Properties >
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.AnyCancelled"/>
|
||||
bool AnyCancelled { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.AnyPaused"/>
|
||||
bool AnyPaused { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.AnyRunning"/>
|
||||
bool AnyRunning { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.Commands"/>
|
||||
ReadOnlyCollection<IRoboCommand> Commands { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.CopyOperationCompleted"/>
|
||||
bool CopyOperationCompleted { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.IsCopyOperationRunning"/>
|
||||
bool IsCopyOperationRunning { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.IsListOnlyRunning"/>
|
||||
bool IsListOnlyRunning { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.IsPaused"/>
|
||||
bool IsPaused { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.IsRunning"/>
|
||||
bool IsRunning { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.JobsComplete"/>
|
||||
int JobsComplete { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.JobsCompletedSuccessfully"/>
|
||||
int JobsCompletedSuccessfully { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.JobsCurrentlyRunning"/>
|
||||
int JobsCurrentlyRunning { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.JobsStarted"/>
|
||||
int JobsStarted { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.ListCount"/>
|
||||
int ListCount { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.ListOnlyCompleted"/>
|
||||
bool ListOnlyCompleted { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.ListResults"/>
|
||||
IRoboQueueResults ListResults { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.MaxConcurrentJobs"/>
|
||||
int MaxConcurrentJobs { get; set; }
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.Name"/>
|
||||
string Name { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.ProgressEstimator"/>
|
||||
IProgressEstimator ProgressEstimator { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.RunResults"/>
|
||||
IRoboQueueResults RunResults { get; }
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.WasCancelled"/>
|
||||
bool WasCancelled { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Events >
|
||||
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.OnCommandCompleted"/>
|
||||
event RoboCommand.CommandCompletedHandler OnCommandCompleted;
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.OnCommandError"/>
|
||||
event RoboCommand.CommandErrorHandler OnCommandError;
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.OnCommandStarted"/>
|
||||
event RoboQueue.CommandStartedHandler OnCommandStarted;
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.OnCopyProgressChanged"/>
|
||||
event RoboCommand.CopyProgressHandler OnCopyProgressChanged;
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.OnError"/>
|
||||
event RoboCommand.ErrorHandler OnError;
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.OnFileProcessed"/>
|
||||
event RoboCommand.FileProcessedHandler OnFileProcessed;
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.OnProgressEstimatorCreated"/>
|
||||
event RoboQueue.ProgressUpdaterCreatedHandler OnProgressEstimatorCreated;
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.RunCompleted"/>
|
||||
event RoboQueue.RunCompletedHandler RunCompleted;
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.RunResultsUpdated"/>
|
||||
event RoboCopyResultsList.ResultsListUpdated RunResultsUpdated;
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.TaskFaulted"/>
|
||||
event UnhandledExceptionEventHandler TaskFaulted;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Methods >
|
||||
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.PauseAll"/>
|
||||
void PauseAll();
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.ResumeAll"/>
|
||||
void ResumeAll();
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.StartAll"/>
|
||||
Task<IRoboQueueResults> StartAll(string domain = "", string username = "", string password = "");
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.StartAll_ListOnly"/>
|
||||
Task<IRoboQueueResults> StartAll_ListOnly(string domain = "", string username = "", string password = "");
|
||||
|
||||
/// <inheritdoc cref="RoboQueue.StopAll"/>
|
||||
void StopAll();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RoboSharp.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for the <see cref="RoboSharp.Results.RoboQueueResults"/> object. <br/>
|
||||
/// Implements <see cref="IRoboCopyResultsList"/>
|
||||
/// </summary>
|
||||
public interface IRoboQueueResults : IRoboCopyResultsList
|
||||
{
|
||||
/// <summary>
|
||||
/// Local time the command started.
|
||||
/// </summary>
|
||||
DateTime StartTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Local time the command stopped.
|
||||
/// </summary>
|
||||
DateTime EndTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Length of time the process took to run
|
||||
/// </summary>
|
||||
TimeSpan TimeSpan { get; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.ComponentModel;
|
||||
using RoboSharp.Results;
|
||||
|
||||
namespace RoboSharp.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Provide Read-Only access to a SpeedStatistic
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/ISpeedStatistic"/>
|
||||
/// </remarks>
|
||||
public interface ISpeedStatistic : INotifyPropertyChanged, ICloneable
|
||||
{
|
||||
/// <summary> Average Transfer Rate in Bytes/Second </summary>
|
||||
decimal BytesPerSec { get; }
|
||||
|
||||
/// <summary> Average Transfer Rate in MB/Minute</summary>
|
||||
decimal MegaBytesPerMin { get; }
|
||||
|
||||
/// <inheritdoc cref="SpeedStatistic.ToString"/>
|
||||
string ToString();
|
||||
|
||||
/// <returns>new <see cref="SpeedStatistic"/> object </returns>
|
||||
/// <inheritdoc cref="SpeedStatistic.SpeedStatistic(SpeedStatistic)"/>
|
||||
new SpeedStatistic Clone();
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using RoboSharp.Results;
|
||||
|
||||
|
||||
namespace RoboSharp.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Provide Read-Only access to a <see cref="Statistic"/> object
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/IStatistic"/>
|
||||
/// </remarks>
|
||||
public interface IStatistic : INotifyPropertyChanged, ICloneable
|
||||
{
|
||||
|
||||
#region < Properties >
|
||||
|
||||
/// <summary>
|
||||
/// Name of the Statistics Object
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="Statistic.StatType"/>
|
||||
/// </summary>
|
||||
Statistic.StatType Type { get; }
|
||||
|
||||
/// <summary> Total Scanned during the run</summary>
|
||||
long Total { get; }
|
||||
|
||||
/// <summary> Total Copied </summary>
|
||||
long Copied { get; }
|
||||
|
||||
/// <summary> Total Skipped </summary>
|
||||
long Skipped { get; }
|
||||
|
||||
/// <summary> </summary>
|
||||
long Mismatch { get; }
|
||||
|
||||
/// <summary> Total that failed to copy or move </summary>
|
||||
long Failed { get; }
|
||||
|
||||
/// <summary> Total Extra that exist in the Destination (but are missing from the Source)</summary>
|
||||
long Extras { get; }
|
||||
|
||||
/// <inheritdoc cref="Statistic.NonZeroValue"/>
|
||||
bool NonZeroValue { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Events >
|
||||
|
||||
/// <inheritdoc cref="Statistic.PropertyChanged"/>
|
||||
new event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary> Occurs when the <see cref="Total"/> Property is updated. </summary>
|
||||
event Statistic.StatChangedHandler OnTotalChanged;
|
||||
|
||||
/// <summary> Occurs when the <see cref="Copied"/> Property is updated. </summary>
|
||||
event Statistic.StatChangedHandler OnCopiedChanged;
|
||||
|
||||
/// <summary> Occurs when the <see cref="Skipped"/> Property is updated. </summary>
|
||||
event Statistic.StatChangedHandler OnSkippedChanged;
|
||||
|
||||
/// <summary> Occurs when the <see cref="Mismatch"/> Property is updated. </summary>
|
||||
event Statistic.StatChangedHandler OnMisMatchChanged;
|
||||
|
||||
/// <summary> Occurs when the <see cref="Failed"/> Property is updated. </summary>
|
||||
event Statistic.StatChangedHandler OnFailedChanged;
|
||||
|
||||
/// <summary> Occurs when the <see cref="Extras"/> Property is updated. </summary>
|
||||
event Statistic.StatChangedHandler OnExtrasChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < ToString Methods >
|
||||
|
||||
/// <inheritdoc cref="SpeedStatistic.ToString"/>
|
||||
string ToString();
|
||||
|
||||
/// <inheritdoc cref="Statistic.ToString(bool, bool, string, bool)"/>
|
||||
string ToString(bool IncludeType, bool IncludePrefix, string Delimiter, bool DelimiterAfterType = false);
|
||||
|
||||
/// <inheritdoc cref="Statistic.ToString_Type()"/>
|
||||
string ToString_Type();
|
||||
|
||||
/// <inheritdoc cref="Statistic.ToString_Total(bool, bool)"/>
|
||||
string ToString_Total(bool IncludeType = false, bool IncludePrefix = true);
|
||||
|
||||
/// <inheritdoc cref="Statistic.ToString_Copied"/>
|
||||
string ToString_Copied(bool IncludeType = false, bool IncludePrefix = true);
|
||||
|
||||
/// <inheritdoc cref="Statistic.ToString_Extras"/>
|
||||
string ToString_Extras(bool IncludeType = false, bool IncludePrefix = true);
|
||||
|
||||
/// <inheritdoc cref="Statistic.ToString_Failed"/>
|
||||
string ToString_Failed(bool IncludeType = false, bool IncludePrefix = true);
|
||||
|
||||
/// <inheritdoc cref="Statistic.ToString_Mismatch"/>
|
||||
string ToString_Mismatch(bool IncludeType = false, bool IncludePrefix = true);
|
||||
|
||||
/// <inheritdoc cref="Statistic.ToString_Skipped"/>
|
||||
string ToString_Skipped(bool IncludeType = false, bool IncludePrefix = true);
|
||||
|
||||
#endregion
|
||||
|
||||
/// <returns>new <see cref="Statistic"/> object </returns>
|
||||
/// <inheritdoc cref="Statistic.Statistic(Statistic)"/>
|
||||
new Statistic Clone();
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RoboSharp.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface to normalize all the Start/End/TimeSpan properties of various objects
|
||||
/// </summary>
|
||||
internal interface ITimeSpan
|
||||
{
|
||||
/// <summary>
|
||||
/// Local time the command started.
|
||||
/// </summary>
|
||||
DateTime StartTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Local time the command stopped.
|
||||
/// </summary>
|
||||
DateTime EndTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Length of time the process took to run
|
||||
/// </summary>
|
||||
TimeSpan TimeSpan { get; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,363 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using RoboSharp.Interfaces;
|
||||
using System.Threading.Tasks;
|
||||
using RoboSharp.Results;
|
||||
|
||||
namespace RoboSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single RoboCopy Job File
|
||||
/// <para/>Implements: <br/>
|
||||
/// <see cref="IRoboCommand"/> <br/>
|
||||
/// <see cref="ICloneable"/> <br/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/JobFile"/>
|
||||
/// </remarks>
|
||||
public class JobFile : ICloneable, IRoboCommand
|
||||
{
|
||||
|
||||
#region < Constructor >
|
||||
|
||||
/// <summary>
|
||||
/// Create a JobFile with Default Options
|
||||
/// </summary>
|
||||
public JobFile() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for ICloneable Interface
|
||||
/// </summary>
|
||||
/// <param name="jobFile"></param>
|
||||
public JobFile(JobFile jobFile)
|
||||
{
|
||||
this.roboCommand = jobFile.roboCommand.Clone();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clone the RoboCommand's options objects into a new JobFile
|
||||
/// </summary>
|
||||
/// <param name="cmd">RoboCommand whose options shall be cloned</param>
|
||||
/// <param name="filePath">Optional FilePath to specify for future call to <see cref="Save()"/></param>
|
||||
public JobFile(RoboCommand cmd, string filePath = "")
|
||||
{
|
||||
FilePath = filePath ?? "";
|
||||
roboCommand = cmd.Clone();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for Factory Methods
|
||||
/// </summary>
|
||||
private JobFile(string filePath, RoboCommand cmd)
|
||||
{
|
||||
FilePath = filePath;
|
||||
roboCommand = cmd;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Factory Methods >
|
||||
|
||||
/// <inheritdoc cref="JobFileBuilder.Parse(string)"/>
|
||||
public static JobFile ParseJobFile(string path)
|
||||
{
|
||||
RoboCommand cmd = JobFileBuilder.Parse(path);
|
||||
if (cmd != null) return new JobFile(path, cmd);
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="JobFileBuilder.Parse(StreamReader)"/>
|
||||
public static JobFile ParseJobFile(StreamReader streamReader)
|
||||
{
|
||||
RoboCommand cmd = JobFileBuilder.Parse(streamReader);
|
||||
if (cmd != null) return new JobFile("", cmd);
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="JobFileBuilder.Parse(FileInfo)"/>
|
||||
public static JobFile ParseJobFile(FileInfo file)
|
||||
{
|
||||
RoboCommand cmd = JobFileBuilder.Parse(file);
|
||||
if (cmd != null) return new JobFile(file.FullName, cmd);
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="JobFileBuilder.Parse(IEnumerable{String})"/>
|
||||
public static JobFile ParseJobFile(IEnumerable<string> FileText)
|
||||
{
|
||||
RoboCommand cmd = JobFileBuilder.Parse(FileText);
|
||||
if (cmd != null) return new JobFile("", cmd);
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < ICLONEABLE >
|
||||
|
||||
/// <summary>
|
||||
/// Create a clone of this JobFile
|
||||
/// </summary>
|
||||
public JobFile Clone() => new JobFile(this);
|
||||
|
||||
object ICloneable.Clone() => Clone();
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Constants >
|
||||
|
||||
/// <summary>
|
||||
/// Expected File Extension for Job Files exported from RoboCopy.
|
||||
/// </summary>
|
||||
public const string JOBFILE_Extension = ".RCJ";
|
||||
|
||||
/// <summary>
|
||||
/// FileFilter to use in an to search for this extension, such as with <see cref="DirectoryInfo.GetFiles(string)"/>
|
||||
/// </summary>
|
||||
public const string JOBFILE_SearchPattern = "*.RCJ";
|
||||
|
||||
/// <summary>
|
||||
/// FileFilter to use in a dialog window, such as the OpenFileDialog window.
|
||||
/// </summary>
|
||||
|
||||
public const string JOBFILE_DialogFilter = "RoboCopy Job|*.RCJ";
|
||||
#endregion
|
||||
|
||||
#region < Fields >
|
||||
|
||||
/// <summary>
|
||||
/// Options are stored in a RoboCommand object for simplicity.
|
||||
/// </summary>
|
||||
protected RoboCommand roboCommand;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Properties >
|
||||
|
||||
/// <summary>FilePath of the Job File </summary>
|
||||
public virtual string FilePath { get; set; }
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.Name"/>
|
||||
public string Job_Name
|
||||
{
|
||||
get => roboCommand.Name;
|
||||
set => roboCommand.Name = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.LoggingOptions"/>
|
||||
public CopyOptions CopyOptions => roboCommand.CopyOptions;
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.LoggingOptions"/>
|
||||
public LoggingOptions LoggingOptions => roboCommand.LoggingOptions;
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.LoggingOptions"/>
|
||||
public RetryOptions RetryOptions => roboCommand.RetryOptions;
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.LoggingOptions"/>
|
||||
public SelectionOptions SelectionOptions => roboCommand.SelectionOptions;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Methods >
|
||||
#pragma warning disable CS1573
|
||||
|
||||
/// <summary>
|
||||
/// Update the <see cref="FilePath"/> property and save the JobFile to the <paramref name="path"/>
|
||||
/// </summary>
|
||||
/// <param name="path">Update the <see cref="FilePath"/> property, then save the JobFile to this path.</param>
|
||||
/// <inheritdoc cref="Save()"/>
|
||||
/// <inheritdoc cref="RoboCommand.SaveAsJobFile(string, bool, bool, string, string, string)"/>
|
||||
public async Task Save(string path, bool IncludeSource = false, bool IncludeDestination = false)
|
||||
|
||||
{
|
||||
if (path.IsNullOrWhiteSpace()) throw new ArgumentException("path Property is Empty");
|
||||
FilePath = path;
|
||||
await roboCommand.SaveAsJobFile(FilePath, IncludeSource, IncludeDestination);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the JobFile to <see cref="FilePath"/>. <br/>
|
||||
/// Source and Destination will be included by default.
|
||||
/// </summary>
|
||||
/// <remarks>If path is null/empty, will throw <see cref="ArgumentException"/></remarks>
|
||||
/// <returns>Task that completes when the JobFile has been saved.</returns>
|
||||
/// <exception cref="ArgumentException"/>
|
||||
public async Task Save()
|
||||
{
|
||||
if (FilePath.IsNullOrWhiteSpace()) throw new ArgumentException("FilePath Property is Empty");
|
||||
await roboCommand.SaveAsJobFile(FilePath, true, true);
|
||||
}
|
||||
|
||||
#pragma warning restore CS1573
|
||||
#endregion
|
||||
|
||||
#region < IRoboCommand Interface >
|
||||
|
||||
#region < Events >
|
||||
|
||||
event RoboCommand.FileProcessedHandler IRoboCommand.OnFileProcessed
|
||||
{
|
||||
add
|
||||
{
|
||||
((IRoboCommand)roboCommand).OnFileProcessed += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
((IRoboCommand)roboCommand).OnFileProcessed -= value;
|
||||
}
|
||||
}
|
||||
|
||||
event RoboCommand.CommandErrorHandler IRoboCommand.OnCommandError
|
||||
{
|
||||
add
|
||||
{
|
||||
((IRoboCommand)roboCommand).OnCommandError += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
((IRoboCommand)roboCommand).OnCommandError -= value;
|
||||
}
|
||||
}
|
||||
|
||||
event RoboCommand.ErrorHandler IRoboCommand.OnError
|
||||
{
|
||||
add
|
||||
{
|
||||
((IRoboCommand)roboCommand).OnError += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
((IRoboCommand)roboCommand).OnError -= value;
|
||||
}
|
||||
}
|
||||
|
||||
event RoboCommand.CommandCompletedHandler IRoboCommand.OnCommandCompleted
|
||||
{
|
||||
add
|
||||
{
|
||||
((IRoboCommand)roboCommand).OnCommandCompleted += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
((IRoboCommand)roboCommand).OnCommandCompleted -= value;
|
||||
}
|
||||
}
|
||||
|
||||
event RoboCommand.CopyProgressHandler IRoboCommand.OnCopyProgressChanged
|
||||
{
|
||||
add
|
||||
{
|
||||
((IRoboCommand)roboCommand).OnCopyProgressChanged += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
((IRoboCommand)roboCommand).OnCopyProgressChanged -= value;
|
||||
}
|
||||
}
|
||||
|
||||
event RoboCommand.ProgressUpdaterCreatedHandler IRoboCommand.OnProgressEstimatorCreated
|
||||
{
|
||||
add
|
||||
{
|
||||
((IRoboCommand)roboCommand).OnProgressEstimatorCreated += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
((IRoboCommand)roboCommand).OnProgressEstimatorCreated -= value;
|
||||
}
|
||||
}
|
||||
|
||||
event UnhandledExceptionEventHandler IRoboCommand.TaskFaulted
|
||||
{
|
||||
add
|
||||
{
|
||||
((IRoboCommand)roboCommand).TaskFaulted += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
((IRoboCommand)roboCommand).TaskFaulted -= value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Properties >
|
||||
|
||||
string IRoboCommand.Name => roboCommand.Name;
|
||||
bool IRoboCommand.IsPaused => roboCommand.IsPaused;
|
||||
bool IRoboCommand.IsRunning => roboCommand.IsRunning;
|
||||
bool IRoboCommand.IsScheduled => roboCommand.IsScheduled;
|
||||
bool IRoboCommand.IsCancelled => roboCommand.IsCancelled;
|
||||
bool IRoboCommand.StopIfDisposing => roboCommand.StopIfDisposing;
|
||||
IProgressEstimator IRoboCommand.IProgressEstimator => roboCommand.IProgressEstimator;
|
||||
SelectionOptions IRoboCommand.SelectionOptions { get => ((IRoboCommand)roboCommand).SelectionOptions; set => ((IRoboCommand)roboCommand).SelectionOptions = value; }
|
||||
RetryOptions IRoboCommand.RetryOptions { get => ((IRoboCommand)roboCommand).RetryOptions; set => ((IRoboCommand)roboCommand).RetryOptions = value; }
|
||||
LoggingOptions IRoboCommand.LoggingOptions { get => roboCommand.LoggingOptions; set => roboCommand.LoggingOptions = value; }
|
||||
CopyOptions IRoboCommand.CopyOptions { get => ((IRoboCommand)roboCommand).CopyOptions; set => ((IRoboCommand)roboCommand).CopyOptions = value; }
|
||||
JobOptions IRoboCommand.JobOptions { get => ((IRoboCommand)roboCommand).JobOptions; }
|
||||
RoboSharpConfiguration IRoboCommand.Configuration => roboCommand.Configuration;
|
||||
string IRoboCommand.CommandOptions => roboCommand.CommandOptions;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Methods >
|
||||
|
||||
void IRoboCommand.Pause()
|
||||
{
|
||||
((IRoboCommand)roboCommand).Pause();
|
||||
}
|
||||
|
||||
void IRoboCommand.Resume()
|
||||
{
|
||||
((IRoboCommand)roboCommand).Resume();
|
||||
}
|
||||
|
||||
Task IRoboCommand.Start(string domain, string username, string password)
|
||||
{
|
||||
return ((IRoboCommand)roboCommand).Start(domain, username, password);
|
||||
}
|
||||
|
||||
void IRoboCommand.Stop()
|
||||
{
|
||||
((IRoboCommand)roboCommand).Stop();
|
||||
}
|
||||
|
||||
void IRoboCommand.Dispose()
|
||||
{
|
||||
roboCommand.Stop();
|
||||
}
|
||||
|
||||
Task IRoboCommand.Start_ListOnly(string domain, string username, string password)
|
||||
{
|
||||
return roboCommand.Start_ListOnly();
|
||||
}
|
||||
|
||||
Task<RoboCopyResults> IRoboCommand.StartAsync_ListOnly(string domain, string username, string password)
|
||||
{
|
||||
return roboCommand.StartAsync_ListOnly();
|
||||
}
|
||||
|
||||
Task<RoboCopyResults> IRoboCommand.StartAsync(string domain, string username, string password)
|
||||
{
|
||||
return roboCommand.StartAsync_ListOnly();
|
||||
}
|
||||
|
||||
RoboCopyResults IRoboCommand.GetResults()
|
||||
{
|
||||
return ((IRoboCommand)roboCommand).GetResults();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,586 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace RoboSharp
|
||||
{
|
||||
internal static class JobFileBuilder
|
||||
{
|
||||
#region < Constants & Regex >
|
||||
|
||||
/// <summary>
|
||||
/// Any comments within the job file lines will start with this string
|
||||
/// </summary>
|
||||
public const string JOBFILE_CommentPrefix = ":: ";
|
||||
|
||||
/// <inheritdoc cref="JobFile.JOBFILE_Extension"/>
|
||||
public const string JOBFILE_Extension = JobFile.JOBFILE_Extension;
|
||||
|
||||
/// <inheritdoc cref="JobFile.JOBFILE_Extension"/>
|
||||
internal const string JOBFILE_JobName = ":: JOB_NAME: ";
|
||||
|
||||
internal const string JOBFILE_StopIfDisposing = ":: StopIfDisposing: ";
|
||||
|
||||
/// <summary>Pattern to Identify the SWITCH, DELIMITER and VALUE section</summary>
|
||||
private const string RegString_SWITCH = "\\s*(?<SWITCH>\\/[A-Za-z]+[-]{0,1})(?<DELIMITER>\\s*:?\\s*)(?<VALUE>.+?)";
|
||||
/// <summary>Pattern to Identify the SWITCH, DELIMIETER and VALUE section</summary>
|
||||
private const string RegString_SWITCH_NumericValue = "\\s*(?<SWITCH>\\/[A-Za-z]+[-]{0,1})(?<DELIMITER>\\s*:?\\s*)(?<VALUE>[0-9]+?)";
|
||||
/// <summary>Pattern to Identify COMMENT sections - Throws out white space and comment delimiter '::' </summary>
|
||||
private const string RegString_COMMENT = "((?:\\s*[:]{2,}\\s*[:]{0,})(?<COMMENT>.*))";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Regex to check if an entire line is a comment
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Captured Group Names: <br/>
|
||||
/// COMMENT
|
||||
/// </remarks>
|
||||
private readonly static Regex LINE_IsComment = new Regex("^(?:\\s*[:]{2,}\\s*)(?<COMMENT>.*)$", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
|
||||
|
||||
/// <summary>
|
||||
/// Regex to check if the string is a flag for RoboCopy - These typically will have comments
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Captured Group Names: <br/>
|
||||
/// SWITCH <br/>
|
||||
/// DELIMITER <br/>
|
||||
/// VALUE <br/>
|
||||
/// COMMENT
|
||||
/// </remarks>
|
||||
private readonly static Regex LINE_IsSwitch = new Regex($"^{RegString_SWITCH}{RegString_COMMENT}$", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
|
||||
private readonly static Regex LINE_IsSwitch_NoComment = new Regex($"^{RegString_SWITCH}$", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
|
||||
|
||||
private readonly static Regex LINE_IsSwitch_NumericValue = new Regex($"^{RegString_SWITCH_NumericValue}{RegString_COMMENT}$", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
|
||||
private readonly static Regex LINE_IsSwitch_NumericValue_NoComment = new Regex($"^{RegString_SWITCH_NumericValue}$", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
|
||||
|
||||
/// <summary>
|
||||
/// JobName for ROboCommand is not valid parameter for RoboCopy, so we save it into a comment within the file
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Captured Group Names: <br/>
|
||||
/// FLAG <br/>
|
||||
/// NAME <br/>
|
||||
/// COMMENT
|
||||
/// </remarks>
|
||||
private readonly static Regex JobNameRegex = new Regex("^\\s*(?<FLAG>:: JOB_NAME:)\\s*(?<NAME>.*)", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
|
||||
|
||||
private readonly static Regex StopIfDisposingRegex = new Regex("^\\s*(?<FLAG>:: StopIfDisposing:)\\s*(?<VALUE>TRUE|FALSE)", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
|
||||
|
||||
/// <summary>
|
||||
/// Regex used for parsing File and Directory filters for /IF /XD and /XF flags
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Captured Group Names: <br/>
|
||||
/// PATH <br/>
|
||||
/// COMMENT
|
||||
/// </remarks>
|
||||
private readonly static Regex DirFileFilterRegex = new Regex($"^\\s*{RegString_FileFilter}{RegString_COMMENT}$", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
|
||||
private readonly static Regex DirFileFilterRegex_NoComment = new Regex($"^\\s*{RegString_FileFilter}$", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
|
||||
private const string RegString_FileFilter = "(?<PATH>.+?)";
|
||||
|
||||
|
||||
#region < Copy Options Regex >
|
||||
|
||||
/// <summary>
|
||||
/// Regex to find the SourceDirectory within the JobFile
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Captured Group Names: <br/>
|
||||
/// SWITCH <br/>
|
||||
/// PATH <br/>
|
||||
/// COMMENT
|
||||
/// </remarks>
|
||||
private readonly static Regex CopyOptionsRegex_SourceDir = new Regex("^\\s*(?<SWITCH>/SD:)(?<PATH>.*)(?<COMMENT>::.*)", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
|
||||
|
||||
/// <summary>
|
||||
/// Regex to find the DestinationDirectory within the JobFile
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Captured Group Names: <br/>
|
||||
/// SWITCH <br/>
|
||||
/// PATH <br/>
|
||||
/// COMMENT
|
||||
/// </remarks>
|
||||
private readonly static Regex CopyOptionsRegex_DestinationDir = new Regex("^\\s*(?<SWITCH>/DD:)(?<PATH>.*)(?<COMMENT>::.*)", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
|
||||
|
||||
/// <summary>
|
||||
/// Regex to determine if on the INCLUDE FILES section of the JobFile
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Each new path / filename should be on its own line
|
||||
/// </remarks>
|
||||
private readonly static Regex CopyOptionsRegex_IncludeFiles = new Regex("^\\s*(?<SWITCH>/IF)\\s*(.*)", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Selection Options Regex >
|
||||
|
||||
/// <summary>
|
||||
/// Regex to determine if on the EXCLUDE FILES section of the JobFile
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Each new path / filename should be on its own line
|
||||
/// </remarks>
|
||||
private readonly static Regex SelectionRegex_ExcludeFiles = new Regex("^\\s*(?<SWITCH>/XF).*", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
|
||||
|
||||
/// <summary>
|
||||
/// Regex to determine if on the EXCLUDE DIRECTORIES section of the JobFile
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Each new path / filename should be on its own line
|
||||
/// </remarks>
|
||||
private readonly static Regex SelectionRegex_ExcludeDirs = new Regex("^\\s*(?<SWITCH>/XD).*", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Methods that feed Main Parse Routine >
|
||||
|
||||
/// <summary>
|
||||
/// Read each line using <see cref="FileInfo.OpenText"/> and attempt to produce a Job File. <para/>
|
||||
/// If FileExtension != ".RCJ" -> returns null. Otherwise parses the file.
|
||||
/// </summary>
|
||||
/// <param name="file">FileInfo object for some Job File. File Path should end in .RCJ</param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
internal static RoboCommand Parse(FileInfo file)
|
||||
{
|
||||
if (file.Extension != JOBFILE_Extension) return null;
|
||||
return Parse(file.OpenText());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use <see cref="File.OpenText(string)"/> to read all lines from the supplied file path. <para/>
|
||||
/// If FileExtension != ".RCJ" -> returns null. Otherwise parses the file.
|
||||
/// </summary>
|
||||
/// <param name="path">File Path to some Job File. File Path should end in .RCJ</param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
internal static RoboCommand Parse(string path)
|
||||
{
|
||||
if (Path.GetExtension(path) != JOBFILE_Extension) return null;
|
||||
return Parse(File.OpenText(path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read each line from a StreamReader and attempt to produce a Job File.
|
||||
/// </summary>
|
||||
/// <param name="streamReader">StreamReader for a file stream that represents a Job File</param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
internal static RoboCommand Parse(StreamReader streamReader)
|
||||
{
|
||||
List<string> Lines = new List<string>();
|
||||
using (streamReader)
|
||||
{
|
||||
while (!streamReader.EndOfStream)
|
||||
Lines.Add(streamReader.ReadLine());
|
||||
}
|
||||
return Parse(Lines);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region <> Main Parse Routine >
|
||||
|
||||
/// <summary>
|
||||
/// Parse each line in <paramref name="Lines"/>, and attempt to create a new JobFile object.
|
||||
/// </summary>
|
||||
/// <param name="Lines">String[] read from a JobFile</param>
|
||||
/// <returns></returns>
|
||||
internal static RoboCommand Parse(IEnumerable<string> Lines)
|
||||
{
|
||||
//Extract information from the Lines to quicken processing in *OptionsRegex classes
|
||||
List<Group> Flags = new List<Group>();
|
||||
List<GroupCollection> ValueFlags = new List<GroupCollection>();
|
||||
RetryOptions retryOpt = new RetryOptions();
|
||||
|
||||
string JobName = null;
|
||||
bool stopIfDisposing = true;
|
||||
|
||||
foreach (string ln in Lines)
|
||||
{
|
||||
if (ln.IsNullOrWhiteSpace() | ln.Trim() == "::")
|
||||
{ }
|
||||
else if (LINE_IsSwitch.IsMatch(ln) || LINE_IsSwitch_NoComment.IsMatch(ln))
|
||||
{
|
||||
var groups = LINE_IsSwitch.Match(ln).Groups;
|
||||
|
||||
//Check RetryOptions inline since it only has 4 properties to check against
|
||||
if (groups["SWITCH"].Value == "/R" && LINE_IsSwitch_NumericValue.IsMatch(ln))
|
||||
{
|
||||
string val = LINE_IsSwitch_NumericValue.Match(ln).Groups["VALUE"].Value;
|
||||
retryOpt.RetryCount = val.IsNullOrWhiteSpace() ? retryOpt.RetryCount : Convert.ToInt32(val);
|
||||
}
|
||||
else if (groups["SWITCH"].Value == "/W")
|
||||
{
|
||||
string val = LINE_IsSwitch_NumericValue.Match(ln).Groups["VALUE"].Value;
|
||||
retryOpt.RetryWaitTime = val.IsNullOrWhiteSpace() ? retryOpt.RetryWaitTime : Convert.ToInt32(val);
|
||||
}
|
||||
else if (groups["SWITCH"].Value == "/REG")
|
||||
{
|
||||
retryOpt.SaveToRegistry = true;
|
||||
}
|
||||
else if (groups["SWITCH"].Value == "/TBD")
|
||||
{
|
||||
retryOpt.WaitForSharenames = true;
|
||||
}
|
||||
//All Other flags
|
||||
else
|
||||
{
|
||||
Flags.Add(groups["SWITCH"]);
|
||||
if (groups["DELIMITER"].Success)
|
||||
ValueFlags.Add(groups);
|
||||
}
|
||||
}
|
||||
else if (JobName == null && JobNameRegex.IsMatch(ln))
|
||||
{
|
||||
JobName = JobNameRegex.Match(ln).Groups["NAME"].Value.Trim();
|
||||
}
|
||||
else if (StopIfDisposingRegex.IsMatch(ln))
|
||||
{
|
||||
stopIfDisposing = Convert.ToBoolean(StopIfDisposingRegex.Match(ln).Groups["VALUE"].Value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
CopyOptions copyOpt = Build_CopyOptions(Flags, ValueFlags, Lines);
|
||||
SelectionOptions selectionOpt = Build_SelectionOptions(Flags, ValueFlags, Lines);
|
||||
LoggingOptions loggingOpt = Build_LoggingOptions(Flags, ValueFlags, Lines);
|
||||
JobOptions jobOpt = Build_JobOptions(Flags, ValueFlags, Lines);
|
||||
|
||||
return new RoboCommand(JobName ?? "", StopIfDisposing: stopIfDisposing, source: null, destination: null, configuration: null,
|
||||
copyOptions: copyOpt,
|
||||
selectionOptions: selectionOpt,
|
||||
retryOptions: retryOpt,
|
||||
loggingOptions: loggingOpt,
|
||||
jobOptions: jobOpt);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Copy Options >
|
||||
|
||||
/// <summary>
|
||||
/// Parser to create CopyOptions object for JobFiles
|
||||
/// </summary>
|
||||
private static CopyOptions Build_CopyOptions(IEnumerable<Group> Flags, IEnumerable<GroupCollection> ValueFlags, IEnumerable<string> Lines)
|
||||
{
|
||||
var options = new CopyOptions();
|
||||
|
||||
//Bool Checks
|
||||
options.CheckPerFile = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.CHECK_PER_FILE.Trim());
|
||||
options.CopyAll = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.COPY_ALL.Trim());
|
||||
options.CopyFilesWithSecurity = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.COPY_FILES_WITH_SECURITY.Trim());
|
||||
options.CopySubdirectories = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.COPY_SUBDIRECTORIES.Trim());
|
||||
options.CopySubdirectoriesIncludingEmpty = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.COPY_SUBDIRECTORIES_INCLUDING_EMPTY.Trim());
|
||||
options.CopySymbolicLink = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.COPY_SYMBOLIC_LINK.Trim());
|
||||
options.CreateDirectoryAndFileTree = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.CREATE_DIRECTORY_AND_FILE_TREE.Trim());
|
||||
options.DoNotCopyDirectoryInfo = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.DO_NOT_COPY_DIRECTORY_INFO.Trim());
|
||||
options.DoNotUseWindowsCopyOffload = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.DO_NOT_USE_WINDOWS_COPY_OFFLOAD.Trim());
|
||||
options.EnableBackupMode = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.ENABLE_BACKUP_MODE.Trim());
|
||||
options.EnableEfsRawMode = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.ENABLE_EFSRAW_MODE.Trim());
|
||||
options.EnableRestartMode = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.ENABLE_RESTART_MODE.Trim());
|
||||
options.EnableRestartModeWithBackupFallback = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.ENABLE_RESTART_MODE_WITH_BACKUP_FALLBACK.Trim());
|
||||
options.FatFiles = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.FAT_FILES.Trim());
|
||||
options.FixFileSecurityOnAllFiles = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.FIX_FILE_SECURITY_ON_ALL_FILES.Trim());
|
||||
options.FixFileTimesOnAllFiles = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.FIX_FILE_TIMES_ON_ALL_FILES.Trim());
|
||||
options.Mirror = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.MIRROR.Trim());
|
||||
options.MoveFiles = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.MOVE_FILES.Trim());
|
||||
options.MoveFilesAndDirectories = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.MOVE_FILES_AND_DIRECTORIES.Trim());
|
||||
options.Purge = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.PURGE.Trim());
|
||||
options.RemoveFileInformation = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.REMOVE_FILE_INFORMATION.Trim());
|
||||
options.TurnLongPathSupportOff = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.TURN_LONG_PATH_SUPPORT_OFF.Trim());
|
||||
options.UseUnbufferedIo = Flags.Any(flag => flag.Success && flag.Value == CopyOptions.USE_UNBUFFERED_IO.Trim());
|
||||
|
||||
//int / string values on same line as flag
|
||||
foreach (var match in ValueFlags)
|
||||
{
|
||||
string flag = match["SWITCH"].Value.Trim();
|
||||
string value = match["VALUE"].Value.Trim();
|
||||
|
||||
switch (flag)
|
||||
{
|
||||
case "/A+":
|
||||
options.AddAttributes = value;
|
||||
break;
|
||||
case "/COPY":
|
||||
options.CopyFlags = value;
|
||||
break;
|
||||
case "/LEV":
|
||||
options.Depth = value.TryConvertInt();
|
||||
break;
|
||||
case "/DD":
|
||||
options.Destination = value;
|
||||
break;
|
||||
case "/DCOPY":
|
||||
options.DirectoryCopyFlags = value;
|
||||
break;
|
||||
case "/IPG":
|
||||
options.InterPacketGap = value.TryConvertInt();
|
||||
break;
|
||||
case "/MON":
|
||||
options.MonitorSourceChangesLimit = value.TryConvertInt();
|
||||
break;
|
||||
case "/MOT":
|
||||
options.MonitorSourceTimeLimit = value.TryConvertInt();
|
||||
break;
|
||||
case "/MT":
|
||||
options.MultiThreadedCopiesCount = value.TryConvertInt();
|
||||
break;
|
||||
case "/A-":
|
||||
options.RemoveAttributes = value;
|
||||
break;
|
||||
case "/RH":
|
||||
options.RunHours = value;
|
||||
break;
|
||||
case "/SD":
|
||||
options.Source = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Multiple Lines
|
||||
if (Flags.Any(f => f.Value == "/IF"))
|
||||
{
|
||||
bool parsingIF = false;
|
||||
List<string> filters = new List<string>();
|
||||
string path = null;
|
||||
//Find the line that starts with the flag
|
||||
foreach (string ln in Lines)
|
||||
{
|
||||
if (ln.IsNullOrWhiteSpace() || LINE_IsComment.IsMatch(ln))
|
||||
{ }
|
||||
else if (LINE_IsSwitch.IsMatch(ln))
|
||||
{
|
||||
if (parsingIF) break; //Moving onto next section -> IF already parsed.
|
||||
parsingIF = ln.Trim().StartsWith("/IF");
|
||||
}
|
||||
else if (parsingIF)
|
||||
{
|
||||
//React to parsing the section - Comments are not expected on these lines
|
||||
path = null;
|
||||
if (DirFileFilterRegex.IsMatch(ln))
|
||||
{
|
||||
path = DirFileFilterRegex.Match(ln).Groups["PATH"].Value;
|
||||
}
|
||||
else if (DirFileFilterRegex_NoComment.IsMatch(ln))
|
||||
{
|
||||
path = DirFileFilterRegex_NoComment.Match(ln).Groups["PATH"].Value;
|
||||
}
|
||||
//Store the value
|
||||
if (!path.IsNullOrWhiteSpace())
|
||||
{
|
||||
filters.Add(path.WrapPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (filters.Count > 0) options.FileFilter = filters;
|
||||
} //End of FileFilter section
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Selection Options >
|
||||
|
||||
/// <summary>
|
||||
/// Parser to create SelectionOptions object for JobFiles
|
||||
/// </summary>
|
||||
private static SelectionOptions Build_SelectionOptions(IEnumerable<Group> Flags, IEnumerable<GroupCollection> ValueFlags, IEnumerable<string> Lines)
|
||||
{
|
||||
var options = new SelectionOptions();
|
||||
|
||||
//Bool Checks
|
||||
options.CompensateForDstDifference = Flags.Any(flag => flag.Success && flag.Value == SelectionOptions.COMPENSATE_FOR_DST_DIFFERENCE.Trim());
|
||||
options.ExcludeChanged = Flags.Any(flag => flag.Success && flag.Value == SelectionOptions.EXCLUDE_CHANGED.Trim());
|
||||
options.ExcludeExtra = Flags.Any(flag => flag.Success && flag.Value == SelectionOptions.EXCLUDE_EXTRA.Trim());
|
||||
options.ExcludeJunctionPoints = Flags.Any(flag => flag.Success && flag.Value == SelectionOptions.EXCLUDE_JUNCTION_POINTS.Trim());
|
||||
options.ExcludeJunctionPointsForDirectories = Flags.Any(flag => flag.Success && flag.Value == SelectionOptions.EXCLUDE_JUNCTION_POINTS_FOR_DIRECTORIES.Trim());
|
||||
options.ExcludeJunctionPointsForFiles = Flags.Any(flag => flag.Success && flag.Value == SelectionOptions.EXCLUDE_JUNCTION_POINTS_FOR_FILES.Trim());
|
||||
options.ExcludeLonely = Flags.Any(flag => flag.Success && flag.Value == SelectionOptions.EXCLUDE_LONELY.Trim());
|
||||
options.ExcludeNewer = Flags.Any(flag => flag.Success && flag.Value == SelectionOptions.EXCLUDE_NEWER.Trim());
|
||||
options.ExcludeOlder = Flags.Any(flag => flag.Success && flag.Value == SelectionOptions.EXCLUDE_OLDER.Trim());
|
||||
options.IncludeSame = Flags.Any(flag => flag.Success && flag.Value == SelectionOptions.INCLUDE_SAME.Trim());
|
||||
options.IncludeTweaked = Flags.Any(flag => flag.Success && flag.Value == SelectionOptions.INCLUDE_TWEAKED.Trim());
|
||||
options.OnlyCopyArchiveFiles = Flags.Any(flag => flag.Success && flag.Value == SelectionOptions.ONLY_COPY_ARCHIVE_FILES.Trim());
|
||||
options.OnlyCopyArchiveFilesAndResetArchiveFlag = Flags.Any(flag => flag.Success && flag.Value == SelectionOptions.ONLY_COPY_ARCHIVE_FILES_AND_RESET_ARCHIVE_FLAG.Trim());
|
||||
options.UseFatFileTimes = Flags.Any(flag => flag.Success && flag.Value == SelectionOptions.USE_FAT_FILE_TIMES.Trim());
|
||||
|
||||
//int / string values on same line as flag
|
||||
foreach (var match in ValueFlags)
|
||||
{
|
||||
string flag = match["SWITCH"].Value;
|
||||
string value = match["VALUE"].Value;
|
||||
|
||||
switch (flag)
|
||||
{
|
||||
case "/XA":
|
||||
options.ExcludeAttributes = value;
|
||||
break;
|
||||
case "/IA":
|
||||
options.IncludeAttributes = value;
|
||||
break;
|
||||
case "/MAXAGE":
|
||||
options.MaxFileAge = value;
|
||||
break;
|
||||
case "/MAX":
|
||||
options.MaxFileSize = value.TryConvertLong();
|
||||
break;
|
||||
case "/MAXLAD":
|
||||
options.MaxLastAccessDate = value;
|
||||
break;
|
||||
case "/MINAGE":
|
||||
options.MinFileAge = value;
|
||||
break;
|
||||
case "/MIN":
|
||||
options.MinFileSize = value.TryConvertLong();
|
||||
break;
|
||||
case "/MINLAD":
|
||||
options.MinLastAccessDate = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Multiple Lines
|
||||
bool parsingXD = false;
|
||||
bool parsingXF = false;
|
||||
bool xDParsed = false;
|
||||
bool xFParsed = false;
|
||||
string path = null;
|
||||
|
||||
foreach (string ln in Lines)
|
||||
{
|
||||
// Determine if parsing some section
|
||||
if (ln.IsNullOrWhiteSpace() || LINE_IsComment.IsMatch(ln) )
|
||||
{ }
|
||||
else if (!xFParsed && !parsingXF && SelectionRegex_ExcludeFiles.IsMatch(ln))
|
||||
{
|
||||
// Paths are not expected to be on this output line
|
||||
parsingXF = true;
|
||||
parsingXD = false;
|
||||
}
|
||||
else if (!xDParsed && !parsingXD && SelectionRegex_ExcludeDirs.IsMatch(ln))
|
||||
{
|
||||
// Paths are not expected to be on this output line
|
||||
parsingXF = false;
|
||||
parsingXD = true;
|
||||
}
|
||||
else if (LINE_IsSwitch.IsMatch(ln) || LINE_IsSwitch_NoComment.IsMatch(ln))
|
||||
{
|
||||
if (parsingXD)
|
||||
{
|
||||
parsingXD = false;
|
||||
xDParsed = true;
|
||||
}
|
||||
if (parsingXF)
|
||||
{
|
||||
parsingXF = false;
|
||||
xFParsed = true;
|
||||
}
|
||||
if (xDParsed && xFParsed) break;
|
||||
}
|
||||
else
|
||||
{
|
||||
//React to parsing the section - Comments are not expected on these lines
|
||||
path = null;
|
||||
if (DirFileFilterRegex.IsMatch(ln))
|
||||
{
|
||||
path = DirFileFilterRegex.Match(ln).Groups["PATH"].Value;
|
||||
}
|
||||
else if (DirFileFilterRegex_NoComment.IsMatch(ln))
|
||||
{
|
||||
path = DirFileFilterRegex_NoComment.Match(ln).Groups["PATH"].Value;
|
||||
}
|
||||
//Store the value
|
||||
if (!path.IsNullOrWhiteSpace())
|
||||
{
|
||||
if (parsingXF)
|
||||
options.ExcludedFiles.Add(path.WrapPath());
|
||||
else if (parsingXD)
|
||||
options.ExcludedDirectories.Add(path.WrapPath());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Logging Options >
|
||||
|
||||
/// <summary>
|
||||
/// Parser to create LoggingOptions object for JobFiles
|
||||
/// </summary>
|
||||
private static LoggingOptions Build_LoggingOptions(IEnumerable<Group> Flags, IEnumerable<GroupCollection> ValueFlags, IEnumerable<string> Lines)
|
||||
{
|
||||
var options = new LoggingOptions();
|
||||
|
||||
//Bool Checks
|
||||
options.IncludeFullPathNames = Flags.Any(flag => flag.Success && flag.Value == LoggingOptions.INCLUDE_FULL_PATH_NAMES.Trim());
|
||||
options.IncludeSourceTimeStamps = Flags.Any(flag => flag.Success && flag.Value == LoggingOptions.INCLUDE_SOURCE_TIMESTAMPS.Trim());
|
||||
options.ListOnly = Flags.Any(flag => flag.Success && flag.Value == LoggingOptions.LIST_ONLY.Trim());
|
||||
options.NoDirectoryList = Flags.Any(flag => flag.Success && flag.Value == LoggingOptions.NO_DIRECTORY_LIST.Trim());
|
||||
options.NoFileClasses = Flags.Any(flag => flag.Success && flag.Value == LoggingOptions.NO_FILE_CLASSES.Trim());
|
||||
options.NoFileList = Flags.Any(flag => flag.Success && flag.Value == LoggingOptions.NO_FILE_LIST.Trim());
|
||||
options.NoFileSizes = Flags.Any(flag => flag.Success && flag.Value == LoggingOptions.NO_FILE_SIZES.Trim());
|
||||
options.NoJobHeader = Flags.Any(flag => flag.Success && flag.Value == LoggingOptions.NO_JOB_HEADER.Trim());
|
||||
options.NoJobSummary = Flags.Any(flag => flag.Success && flag.Value == LoggingOptions.NO_JOB_SUMMARY.Trim());
|
||||
options.NoProgress = Flags.Any(flag => flag.Success && flag.Value == LoggingOptions.NO_PROGRESS.Trim());
|
||||
options.OutputAsUnicode = Flags.Any(flag => flag.Success && flag.Value == LoggingOptions.OUTPUT_AS_UNICODE.Trim());
|
||||
options.OutputToRoboSharpAndLog = Flags.Any(flag => flag.Success && flag.Value == LoggingOptions.OUTPUT_TO_ROBOSHARP_AND_LOG.Trim());
|
||||
options.PrintSizesAsBytes = Flags.Any(flag => flag.Success && flag.Value == LoggingOptions.PRINT_SIZES_AS_BYTES.Trim());
|
||||
options.ReportExtraFiles = Flags.Any(flag => flag.Success && flag.Value == LoggingOptions.REPORT_EXTRA_FILES.Trim());
|
||||
options.ShowEstimatedTimeOfArrival = Flags.Any(flag => flag.Success && flag.Value == LoggingOptions.SHOW_ESTIMATED_TIME_OF_ARRIVAL.Trim());
|
||||
options.VerboseOutput = Flags.Any(flag => flag.Success && flag.Value == LoggingOptions.VERBOSE_OUTPUT.Trim());
|
||||
|
||||
//int / string values on same line as flag
|
||||
foreach (var match in ValueFlags)
|
||||
{
|
||||
string flag = match["SWITCH"].Value;
|
||||
string value = match["VALUE"].Value;
|
||||
|
||||
switch (flag)
|
||||
{
|
||||
case "/LOG+":
|
||||
options.AppendLogPath = value;
|
||||
break;
|
||||
case "/UNILOG+":
|
||||
options.AppendUnicodeLogPath = value;
|
||||
break;
|
||||
case "/LOG":
|
||||
options.LogPath = value;
|
||||
break;
|
||||
case "/UNILOG":
|
||||
options.UnicodeLogPath = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Job Options >
|
||||
|
||||
/// <summary>
|
||||
/// Parser to create JobOptions object for JobFiles
|
||||
/// </summary>
|
||||
private static JobOptions Build_JobOptions(IEnumerable<Group> Flags, IEnumerable<GroupCollection> ValueFlags, IEnumerable<string> Lines)
|
||||
{
|
||||
return new JobOptions();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace RoboSharp
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/JobOptions"/>
|
||||
/// </remarks>
|
||||
public class JobOptions : ICloneable
|
||||
{
|
||||
// For more information, a good resource is here: https://adamtheautomator.com/robocopy/#Robocopy_Jobs
|
||||
|
||||
#region < Constructors >
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public JobOptions() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for ICloneable Interface
|
||||
/// </summary>
|
||||
/// <param name="options">JobOptions object to clone</param>
|
||||
public JobOptions(JobOptions options)
|
||||
{
|
||||
FilePath = options.FilePath;
|
||||
NoDestinationDirectory = options.NoDestinationDirectory;
|
||||
NoSourceDirectory = options.NoSourceDirectory;
|
||||
PreventCopyOperation = options.PreventCopyOperation;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < ICloneable >
|
||||
|
||||
/// <summary>
|
||||
/// Clone this JobOptions object
|
||||
/// </summary>
|
||||
/// <returns>New JobOptions object</returns>
|
||||
public JobOptions Clone() => new JobOptions(this);
|
||||
|
||||
object ICloneable.Clone() => Clone();
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Constants >
|
||||
|
||||
/// <summary>
|
||||
/// Take parameters from the named job file
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Usage: /JOB:"Path\To\File.RCJ"
|
||||
/// </remarks>
|
||||
internal const string JOB_LOADNAME = " /JOB:";
|
||||
|
||||
/// <summary>
|
||||
/// Save parameters to the named job file
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Usage: <br/>
|
||||
/// /SAVE:"Path\To\File" -> Creates Path\To\File.RCJ <br/>
|
||||
/// /SAVE:"Path\To\File.txt" -> Creates Path\To\File.txt.RCJ <br/>
|
||||
/// </remarks>
|
||||
internal const string JOB_SAVE = " /SAVE:";
|
||||
|
||||
/// <summary>
|
||||
/// Quit after processing command line
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Used when writing JobFile
|
||||
/// </remarks>
|
||||
internal const string JOB_QUIT = " /QUIT";
|
||||
|
||||
/// <summary>
|
||||
/// No source directory is specified
|
||||
/// </summary>
|
||||
internal const string JOB_NoSourceDirectory = " /NOSD";
|
||||
|
||||
/// <summary>
|
||||
/// No destination directory is specified
|
||||
/// </summary>
|
||||
internal const string JOB_NoDestinationDirectory = " /NODD";
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Properties >
|
||||
|
||||
/// <summary>
|
||||
/// FilePath to save the Job Options (.RCJ) file to. <br/>
|
||||
/// /SAVE:{FilePath}
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This causes RoboCopy to generate an RCJ file where the command options are stored to so it can be used later.<br/>
|
||||
/// <see cref="NoSourceDirectory"/> and <see cref="NoDestinationDirectory"/> options are only evaluated if this is set. <br/>
|
||||
/// </remarks>
|
||||
public virtual string FilePath { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// RoboCopy will validate the command, then exit before performing any Move/Copy/List operations. <br/>
|
||||
/// /QUIT
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This option is typically used when generating JobFiles. RoboCopy will exit after saving the Job FIle to the specified <see cref="FilePath"/>
|
||||
/// </remarks>
|
||||
public virtual bool PreventCopyOperation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="CopyOptions.Source"/> path will not be saved to the JobFile. <br/>
|
||||
/// /NOSD
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Default value is False, meaning if <see cref="CopyOptions.Source"/> is set, it will be saved to the JobFile RoboCopy generates.
|
||||
/// </remarks>
|
||||
public virtual bool NoSourceDirectory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="CopyOptions.Destination"/> path will not be saved to the JobFile. <br/>
|
||||
/// /NODD
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Default value is False, meaning if <see cref="CopyOptions.Destination"/> is set, it will be saved to the JobFile RoboCopy generates.
|
||||
/// </remarks>
|
||||
public virtual bool NoDestinationDirectory { get; set; }
|
||||
#endregion
|
||||
|
||||
#region < Methods >
|
||||
|
||||
/// <summary>
|
||||
/// Parse the properties and return the string
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal string Parse()
|
||||
{
|
||||
string options = "";
|
||||
if (!FilePath.IsNullOrWhiteSpace())
|
||||
{
|
||||
options += $"{JOB_SAVE}{FilePath.WrapPath()}";
|
||||
if (NoSourceDirectory) options += JOB_NoSourceDirectory;
|
||||
if (NoDestinationDirectory) options += JOB_NoDestinationDirectory;
|
||||
}
|
||||
if (PreventCopyOperation) options += JOB_QUIT;
|
||||
return options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the 'NAME' and other properties into the JobFile
|
||||
/// </summary>
|
||||
internal void RunPostProcessing(RoboCommand cmd)
|
||||
{
|
||||
if (FilePath.IsNullOrWhiteSpace()) return;
|
||||
if (!File.Exists(FilePath)) return;
|
||||
var txt = File.ReadAllLines(FilePath).ToList();
|
||||
//Write Options to JobFile
|
||||
txt.InsertRange(6, new string[]
|
||||
{
|
||||
"",
|
||||
"::",
|
||||
":: Options for RoboSharp.JobFile",
|
||||
"::",
|
||||
JobFileBuilder.JOBFILE_JobName + " " + cmd.Name,
|
||||
JobFileBuilder.JOBFILE_StopIfDisposing + " " + cmd.StopIfDisposing.ToString().ToUpper()
|
||||
});
|
||||
File.WriteAllLines(FilePath, txt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combine this object with another RetryOptions object. <br/>
|
||||
/// <see cref="FilePath"/> not not be modified.
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
public void Merge(JobOptions options)
|
||||
{
|
||||
NoSourceDirectory |= options.NoSourceDirectory;
|
||||
NoDestinationDirectory |= options.NoDestinationDirectory;
|
||||
PreventCopyOperation |= options.PreventCopyOperation;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace RoboSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Options related to the output logs generated by RoboCopy
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/LoggingOptions"/>
|
||||
/// </remarks>
|
||||
public class LoggingOptions : ICloneable
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Create new LoggingOptions with Default Settings
|
||||
/// </summary>
|
||||
public LoggingOptions() { }
|
||||
|
||||
/// <summary>
|
||||
/// Clone a LoggingOptions Object
|
||||
/// </summary>
|
||||
/// <param name="options">LoggingOptions object to clone</param>
|
||||
public LoggingOptions(LoggingOptions options)
|
||||
{
|
||||
ListOnly = options.ListOnly;
|
||||
ReportExtraFiles = options.ReportExtraFiles;
|
||||
VerboseOutput = options.VerboseOutput;
|
||||
IncludeSourceTimeStamps = options.IncludeSourceTimeStamps;
|
||||
IncludeFullPathNames = options.IncludeFullPathNames;
|
||||
PrintSizesAsBytes = options.PrintSizesAsBytes;
|
||||
NoFileSizes = options.NoFileSizes;
|
||||
NoFileClasses = options.NoFileClasses;
|
||||
NoFileList = options.NoFileList;
|
||||
NoDirectoryList = options.NoDirectoryList;
|
||||
NoProgress = options.NoProgress;
|
||||
ShowEstimatedTimeOfArrival = options.ShowEstimatedTimeOfArrival;
|
||||
LogPath = options.LogPath;
|
||||
AppendLogPath = options.AppendLogPath;
|
||||
UnicodeLogPath = options.UnicodeLogPath;
|
||||
AppendUnicodeLogPath = options.AppendUnicodeLogPath;
|
||||
OutputToRoboSharpAndLog = options.OutputToRoboSharpAndLog;
|
||||
NoJobHeader = options.NoJobHeader;
|
||||
NoJobSummary = options.NoJobSummary;
|
||||
OutputAsUnicode = options.OutputAsUnicode;
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="LoggingOptions.LoggingOptions(LoggingOptions)"/>
|
||||
public LoggingOptions Clone() => new LoggingOptions(this);
|
||||
|
||||
object ICloneable.Clone() => Clone();
|
||||
|
||||
#endregion
|
||||
|
||||
internal const string LIST_ONLY = "/L ";
|
||||
internal const string REPORT_EXTRA_FILES = "/X ";
|
||||
internal const string VERBOSE_OUTPUT = "/V ";
|
||||
internal const string INCLUDE_SOURCE_TIMESTAMPS = "/TS ";
|
||||
internal const string INCLUDE_FULL_PATH_NAMES = "/FP ";
|
||||
internal const string PRINT_SIZES_AS_BYTES = "/BYTES ";
|
||||
internal const string NO_FILE_SIZES = "/NS ";
|
||||
internal const string NO_FILE_CLASSES = "/NC ";
|
||||
internal const string NO_FILE_LIST = "/NFL ";
|
||||
internal const string NO_DIRECTORY_LIST = "/NDL ";
|
||||
internal const string NO_PROGRESS = "/NP ";
|
||||
internal const string SHOW_ESTIMATED_TIME_OF_ARRIVAL = "/ETA ";
|
||||
internal const string LOG_PATH = "/LOG:{0} ";
|
||||
internal const string APPEND_LOG_PATH = "/LOG+:{0} ";
|
||||
internal const string UNICODE_LOG_PATH = "/UNILOG:{0} ";
|
||||
internal const string APPEND_UNICODE_LOG_PATH = "/UNILOG+:{0} ";
|
||||
internal const string OUTPUT_TO_ROBOSHARP_AND_LOG = "/TEE ";
|
||||
internal const string NO_JOB_HEADER = "/NJH ";
|
||||
internal const string NO_JOB_SUMMARY = "/NJS ";
|
||||
internal const string OUTPUT_AS_UNICODE = "/UNICODE ";
|
||||
|
||||
/// <summary>
|
||||
/// Do not copy, timestamp or delete any files.
|
||||
/// [/L]
|
||||
/// </summary>
|
||||
public virtual bool ListOnly { get; set; }
|
||||
/// <summary>
|
||||
/// Report all extra files, not just those selected.
|
||||
/// [X]
|
||||
/// </summary>
|
||||
public virtual bool ReportExtraFiles { get; set; }
|
||||
/// <summary>
|
||||
/// Produce verbose output, showing skipped files.
|
||||
/// [V]
|
||||
/// </summary>
|
||||
public virtual bool VerboseOutput { get; set; } = true;
|
||||
/// <summary>
|
||||
/// Include source file time stamps in the output.
|
||||
/// [/TS]
|
||||
/// </summary>
|
||||
public virtual bool IncludeSourceTimeStamps { get; set; }
|
||||
/// <summary>
|
||||
/// Include full path names of files in the output.
|
||||
/// [/FP]
|
||||
/// </summary>
|
||||
public virtual bool IncludeFullPathNames { get; set; }
|
||||
/// <summary>
|
||||
/// Print sizes as bytes in the output.
|
||||
/// [/BYTES]
|
||||
/// </summary>
|
||||
public virtual bool PrintSizesAsBytes { get; set; }
|
||||
/// <summary>
|
||||
/// Do not log file sizes.
|
||||
/// [/NS]
|
||||
/// </summary>
|
||||
public virtual bool NoFileSizes { get; set; }
|
||||
/// <summary>
|
||||
/// Do not log file classes.
|
||||
/// [/NC]
|
||||
/// </summary>
|
||||
public virtual bool NoFileClasses { get; set; }
|
||||
/// <summary>
|
||||
/// Do not log file names.
|
||||
/// [/NFL]
|
||||
/// WARNING: If this is set to TRUE then GUI cannot handle showing progress correctly as it can't get information it requires from the log
|
||||
/// </summary>
|
||||
public virtual bool NoFileList { get; set; }
|
||||
/// <summary>
|
||||
/// Do not log directory names.
|
||||
/// [/NDL]
|
||||
/// </summary>
|
||||
public virtual bool NoDirectoryList { get; set; }
|
||||
/// <summary>
|
||||
/// Do not log percentage copied.
|
||||
/// [/NP]
|
||||
/// </summary>
|
||||
public virtual bool NoProgress { get; set; }
|
||||
/// <summary>
|
||||
/// Show estimated time of arrival of copied files.
|
||||
/// [/ETA]
|
||||
/// </summary>
|
||||
public virtual bool ShowEstimatedTimeOfArrival { get; set; }
|
||||
/// <summary>
|
||||
/// Output status to LOG file (overwrite existing log).
|
||||
/// [/LOG:file]
|
||||
/// </summary>
|
||||
public virtual string LogPath { get; set; }
|
||||
/// <summary>
|
||||
/// Output status to LOG file (append to existing log).
|
||||
/// [/LOG+:file]
|
||||
/// </summary>
|
||||
public virtual string AppendLogPath { get; set; }
|
||||
/// <summary>
|
||||
/// Output status to LOG file as UNICODE (overwrite existing log).
|
||||
/// [/UNILOG:file]
|
||||
/// </summary>
|
||||
public virtual string UnicodeLogPath { get; set; }
|
||||
/// <summary>
|
||||
/// Output status to LOG file as UNICODE (append to existing log).
|
||||
/// [/UNILOG+:file]
|
||||
/// </summary>
|
||||
public virtual string AppendUnicodeLogPath { get; set; }
|
||||
/// <summary>
|
||||
/// Output to RoboSharp and Log.
|
||||
/// [/TEE]
|
||||
/// </summary>
|
||||
public virtual bool OutputToRoboSharpAndLog { get; set; }
|
||||
/// <summary>
|
||||
/// Do not output a Job Header.
|
||||
/// [/NJH]
|
||||
/// </summary>
|
||||
public virtual bool NoJobHeader { get; set; }
|
||||
/// <summary>
|
||||
/// Do not output a Job Summary.
|
||||
/// [/NJS]
|
||||
/// WARNING: If this is set to TRUE then statistics will not work correctly as this information is gathered from the job summary part of the log
|
||||
/// </summary>
|
||||
public virtual bool NoJobSummary { get; set; }
|
||||
/// <summary>
|
||||
/// Output as UNICODE.
|
||||
/// [/UNICODE]
|
||||
/// </summary>
|
||||
public virtual bool OutputAsUnicode { get; set; }
|
||||
|
||||
/// <summary> Encase the LogPath in quotes if needed </summary>
|
||||
internal string WrapPath(string logPath) => (!logPath.StartsWith("\"") && logPath.Contains(" ")) ? $"\"{logPath}\"" : logPath;
|
||||
|
||||
internal string Parse()
|
||||
{
|
||||
var options = new StringBuilder();
|
||||
|
||||
if (ListOnly)
|
||||
options.Append(LIST_ONLY);
|
||||
if (ReportExtraFiles)
|
||||
options.Append(REPORT_EXTRA_FILES);
|
||||
if (VerboseOutput)
|
||||
options.Append(VERBOSE_OUTPUT);
|
||||
if (IncludeSourceTimeStamps)
|
||||
options.Append(INCLUDE_SOURCE_TIMESTAMPS);
|
||||
if (IncludeFullPathNames)
|
||||
options.Append(INCLUDE_FULL_PATH_NAMES);
|
||||
if (PrintSizesAsBytes)
|
||||
options.Append(PRINT_SIZES_AS_BYTES);
|
||||
if (NoFileSizes)
|
||||
options.Append(NO_FILE_SIZES);
|
||||
if (NoFileClasses)
|
||||
options.Append(NO_FILE_CLASSES);
|
||||
if (NoFileList)
|
||||
options.Append(NO_FILE_LIST);
|
||||
if (NoDirectoryList)
|
||||
options.Append(NO_DIRECTORY_LIST);
|
||||
if (NoProgress)
|
||||
options.Append(NO_PROGRESS);
|
||||
if (ShowEstimatedTimeOfArrival)
|
||||
options.Append(SHOW_ESTIMATED_TIME_OF_ARRIVAL);
|
||||
if (!LogPath.IsNullOrWhiteSpace())
|
||||
options.Append(string.Format(LOG_PATH, WrapPath(LogPath)));
|
||||
if (!AppendLogPath.IsNullOrWhiteSpace())
|
||||
options.Append(string.Format(APPEND_LOG_PATH, WrapPath(AppendLogPath)));
|
||||
if (!UnicodeLogPath.IsNullOrWhiteSpace())
|
||||
options.Append(string.Format(UNICODE_LOG_PATH, WrapPath(UnicodeLogPath)));
|
||||
if (!AppendUnicodeLogPath.IsNullOrWhiteSpace())
|
||||
options.Append(string.Format(APPEND_UNICODE_LOG_PATH, WrapPath(AppendUnicodeLogPath)));
|
||||
if (OutputToRoboSharpAndLog)
|
||||
options.Append(OUTPUT_TO_ROBOSHARP_AND_LOG);
|
||||
if (NoJobHeader)
|
||||
options.Append(NO_JOB_HEADER);
|
||||
if (NoJobSummary)
|
||||
options.Append(NO_JOB_SUMMARY);
|
||||
if (OutputAsUnicode)
|
||||
options.Append(OUTPUT_AS_UNICODE);
|
||||
|
||||
return options.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combine this object with another LoggingOptions object. <br/>
|
||||
/// Any properties marked as true take priority. IEnumerable items are combined. <br/>
|
||||
/// String Values will only be replaced if the primary object has a null/empty value for that property.
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
public void Merge(LoggingOptions options)
|
||||
{
|
||||
ListOnly |= options.ListOnly;
|
||||
ReportExtraFiles |= options.ReportExtraFiles;
|
||||
VerboseOutput |= options.VerboseOutput;
|
||||
IncludeSourceTimeStamps |= options.IncludeSourceTimeStamps;
|
||||
IncludeFullPathNames |= options.IncludeFullPathNames;
|
||||
PrintSizesAsBytes |= options.PrintSizesAsBytes;
|
||||
NoFileSizes |= options.NoFileSizes;
|
||||
NoFileClasses |= options.NoFileClasses;
|
||||
NoFileList |= options.NoFileList;
|
||||
NoDirectoryList |= options.NoDirectoryList;
|
||||
NoProgress |= options.NoProgress;
|
||||
ShowEstimatedTimeOfArrival |= options.ShowEstimatedTimeOfArrival;
|
||||
OutputToRoboSharpAndLog |= options.OutputToRoboSharpAndLog;
|
||||
NoJobHeader |= options.NoJobHeader;
|
||||
NoJobSummary |= options.NoJobSummary;
|
||||
OutputAsUnicode |= options.OutputAsUnicode;
|
||||
|
||||
LogPath = LogPath.ReplaceIfEmpty(options.LogPath);
|
||||
AppendLogPath = AppendLogPath.ReplaceIfEmpty(options.AppendLogPath);
|
||||
UnicodeLogPath = UnicodeLogPath.ReplaceIfEmpty(options.UnicodeLogPath);
|
||||
AppendUnicodeLogPath = AppendUnicodeLogPath.ReplaceIfEmpty(options.AppendUnicodeLogPath);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace RoboSharp
|
||||
{
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
/// <summary>
|
||||
/// Native Methods for Pause/Suspend/Resume processes
|
||||
/// </summary>
|
||||
public static class NativeMethods
|
||||
{
|
||||
[Flags]
|
||||
public enum ThreadAccess : int
|
||||
{
|
||||
TERMINATE = (0x0001),
|
||||
SUSPEND_RESUME = (0x0002),
|
||||
GET_CONTEXT = (0x0008),
|
||||
SET_CONTEXT = (0x0010),
|
||||
SET_INFORMATION = (0x0020),
|
||||
QUERY_INFORMATION = (0x0040),
|
||||
SET_THREAD_TOKEN = (0x0080),
|
||||
IMPERSONATE = (0x0100),
|
||||
DIRECT_IMPERSONATION = (0x0200)
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
|
||||
[DllImport("kernel32.dll")]
|
||||
static extern uint SuspendThread(IntPtr hThread);
|
||||
[DllImport("kernel32.dll")]
|
||||
static extern int ResumeThread(IntPtr hThread);
|
||||
|
||||
public static bool Suspend(this Process process)
|
||||
{
|
||||
if (process.HasExited) return false;
|
||||
foreach (ProcessThread thread in process.Threads)
|
||||
{
|
||||
var pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)thread.Id);
|
||||
if (pOpenThread == IntPtr.Zero)
|
||||
{
|
||||
break;
|
||||
}
|
||||
SuspendThread(pOpenThread);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public static bool Resume(this Process process)
|
||||
{
|
||||
if (process.HasExited) return false;
|
||||
foreach (ProcessThread thread in process.Threads)
|
||||
{
|
||||
var pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)thread.Id);
|
||||
if (pOpenThread == IntPtr.Zero)
|
||||
{
|
||||
break;
|
||||
}
|
||||
ResumeThread(pOpenThread);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
|
||||
}
|
||||
@@ -1,411 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Collections.Specialized;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading;
|
||||
|
||||
namespace System.Collections.Generic
|
||||
{
|
||||
/// <summary>
|
||||
/// Extends the Generic <see cref="List{T}"/> class with an event that will fire when the list is updated via standard list methods <para/>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of object the list will contain</typeparam>
|
||||
/// <remarks>
|
||||
/// This class is being provided by the RoboSharp DLL <br/>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/SelectionOptions"/>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/tree/dev/RoboSharp/ObservableList.cs"/> <br/>
|
||||
/// </remarks>
|
||||
public class ObservableList<T> : List<T>, INotifyCollectionChanged
|
||||
{
|
||||
#region < Constructors >
|
||||
|
||||
///<inheritdoc cref="List{T}()"/>
|
||||
public ObservableList() : base() { }
|
||||
|
||||
///<inheritdoc cref="List{T}(int)"/>
|
||||
public ObservableList(int capacity) : base(capacity) { }
|
||||
|
||||
///<inheritdoc cref="List{T}(IEnumerable{T})"/>
|
||||
public ObservableList(IEnumerable<T> collection) : base(collection) { }
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Events >
|
||||
|
||||
/// <summary> This event fires whenever the List's array is updated. </summary>
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Raise the <see cref="CollectionChanged"/> event. <br/>
|
||||
/// <para/>
|
||||
/// Override this method to provide post-processing of Added/Removed items within derived classes.
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
// This Syncronization code was taken from here: https://stackoverflow.com/a/54733415/12135042
|
||||
// This ensures that the CollectionChanged event is invoked from the proper thread, no matter where the change came from.
|
||||
|
||||
if (SynchronizationContext.Current == _synchronizationContext)
|
||||
{
|
||||
// Execute the CollectionChanged event on the current thread
|
||||
CollectionChanged?.Invoke(this, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Raises the CollectionChanged event on the creator thread
|
||||
_synchronizationContext.Send((callback) => CollectionChanged?.Invoke(this, e), null);
|
||||
}
|
||||
}
|
||||
private SynchronizationContext _synchronizationContext = SynchronizationContext.Current;
|
||||
|
||||
#region < Alternate methods for OnCollectionChanged + reasoning why it wasn't used >
|
||||
|
||||
/*
|
||||
* This standard method cannot be used because RoboQueue is adding results onto the ResultsLists as they complete, which means the events may not be on the original thread that RoboQueue was constructed in.
|
||||
* protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) => CollectionChanged?.Invoke(this, e);
|
||||
*/
|
||||
|
||||
// --------------------------------------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* This code was taken from here: https://www.codeproject.com/Articles/64936/Threadsafe-ObservableImmutable-Collection
|
||||
* It works, but its a bit more involved since it loops through all handlers.
|
||||
* This was not used due to being unavailable in some targets. (Same reasoning for creating new class instead of class provided by above link)
|
||||
*/
|
||||
|
||||
// /// <summary>
|
||||
// /// Raise the <see cref="CollectionChanged"/> event. <para/>
|
||||
// /// Override this method to provide post-processing of Added/Removed items within derived classes.
|
||||
// /// </summary>
|
||||
// /// <param name="e"></param>
|
||||
// protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
// {
|
||||
// var notifyCollectionChangedEventHandler = CollectionChanged;
|
||||
|
||||
// if (notifyCollectionChangedEventHandler == null)
|
||||
// return;
|
||||
|
||||
// foreach (NotifyCollectionChangedEventHandler handler in notifyCollectionChangedEventHandler.GetInvocationList())
|
||||
// {
|
||||
//#if NET40_OR_GREATER
|
||||
// var dispatcherObject = handler.Target as DispatcherObject;
|
||||
|
||||
// if (dispatcherObject != null && !dispatcherObject.CheckAccess())
|
||||
// {
|
||||
// dispatcherObject.Dispatcher.Invoke(handler, this, e);
|
||||
// }
|
||||
// else
|
||||
//#endif
|
||||
// handler(this, e); // note : this does not execute handler in target thread's context
|
||||
// }
|
||||
// }
|
||||
|
||||
// --------------------------------------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region < New Methods >
|
||||
|
||||
/// <summary>
|
||||
/// Replace an item in the list.
|
||||
/// </summary>
|
||||
/// <param name="itemToReplace">Search for this item in the list. If found, replace it. If not found, return false. <paramref name="newItem"/> will not be added to the list. </param>
|
||||
/// <param name="newItem">This item will replace the <paramref name="itemToReplace"/>. If <paramref name="itemToReplace"/> was not found, this item does not get added to the list.</param>
|
||||
/// <returns>True if the <paramref name="itemToReplace"/> was found in the list and successfully replaced. Otherwise false.</returns>
|
||||
public virtual bool Replace(T itemToReplace, T newItem)
|
||||
{
|
||||
if (!this.Contains(itemToReplace)) return false;
|
||||
return Replace(this.IndexOf(itemToReplace), newItem);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Replace an item in the list
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the item to replace</param>
|
||||
/// <param name="newItem">This item will replace the item at the specified <paramref name="index"/></param>
|
||||
/// <returns>True if the the item was successfully replaced. Otherwise throws. </returns>
|
||||
/// <exception cref="IndexOutOfRangeException"/>
|
||||
public virtual bool Replace(int index, T newItem)
|
||||
{
|
||||
this[index] = newItem;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the items in this list with the items in supplied collection. If the collection has more items than will be removed from the list, the remaining items will be added to the list. <br/>
|
||||
/// EX: List has 10 items, collection has 5 items, index of 8 is specified (which is item 9 on 0-based index) -> Item 9 + 10 are replaced, and remaining 3 items from collection are added to the list.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the item to replace</param>
|
||||
/// <param name="collection">Collection of items to insert into the list.</param>
|
||||
/// <returns>True if the the collection was successfully inserted into the list. Otherwise throws.</returns>
|
||||
/// <exception cref="IndexOutOfRangeException"/>
|
||||
/// <exception cref="ArgumentException"/>
|
||||
/// <exception cref="ArgumentNullException"/>
|
||||
public virtual bool Replace(int index, IEnumerable<T> collection)
|
||||
{
|
||||
int collectionCount = collection.Count();
|
||||
int ItemsAfterIndex = this.Count - index; // # of items in the list after the index
|
||||
int CountToReplace = collectionCount <= ItemsAfterIndex ? collectionCount : ItemsAfterIndex; //# if items that will be replaced
|
||||
int AdditionalItems = collectionCount - CountToReplace; //# of additional items that will be added to the list.
|
||||
|
||||
List<T> oldItemsList = this.GetRange(index, CountToReplace);
|
||||
|
||||
//Insert the collection
|
||||
base.RemoveRange(index, CountToReplace);
|
||||
if (AdditionalItems > 0)
|
||||
base.AddRange(collection);
|
||||
else
|
||||
base.InsertRange(index, collection);
|
||||
|
||||
List<T> insertedList = collection.ToList();
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItems: insertedList.GetRange(0, CountToReplace), oldItems: oldItemsList, index));
|
||||
if (AdditionalItems > 0)
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, insertedList.GetRange(CountToReplace, AdditionalItems)));
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Methods that Override List<T> Methods >
|
||||
|
||||
/// <summary>
|
||||
/// Get or Set the element at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index of the item to Get or Set.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException"/>
|
||||
new public T this[int index] {
|
||||
get => base[index];
|
||||
set {
|
||||
if (index >= 0 && index < Count)
|
||||
{
|
||||
//Perform Replace
|
||||
T old = base[index];
|
||||
base[index] = value;
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItem: value, oldItem: old, index));
|
||||
}
|
||||
else if (index == Count && index <= Capacity - 1)
|
||||
{
|
||||
//Append value to end only if the capacity doesn't need to be changed
|
||||
base.Add(value);
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value, index));
|
||||
}
|
||||
else
|
||||
{
|
||||
base[index] = value; // Generate ArgumentOutOfRangeException exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region < Add >
|
||||
|
||||
///<inheritdoc cref="List{T}.Add(T)"/>
|
||||
new public virtual void Add(T item)
|
||||
{
|
||||
base.Add(item);
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
|
||||
}
|
||||
|
||||
///<inheritdoc cref="System.Collections.Generic.List{T}.AddRange(IEnumerable{T})"/>
|
||||
new public virtual void AddRange(IEnumerable<T> collection)
|
||||
{
|
||||
if (collection == null || collection.Count() == 0) return;
|
||||
base.AddRange(collection);
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection.ToList()));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Insert >
|
||||
|
||||
///<inheritdoc cref="List{T}.Insert(int, T)"/>
|
||||
///<remarks> Generates <see cref="CollectionChanged"/> event for item that was added and item that was shifted ( Event is raised twice ) </remarks>
|
||||
new public virtual void Insert(int index, T item)
|
||||
{
|
||||
base.Insert(index, item);
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index: index));
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, this[index + 1], index + 1, index));
|
||||
}
|
||||
|
||||
///<inheritdoc cref="List{T}.InsertRange(int, IEnumerable{T})"/>
|
||||
///<remarks> Generates <see cref="CollectionChanged"/> event for items that were added and items that were shifted ( Event is raised twice )</remarks>
|
||||
new public virtual void InsertRange(int index, IEnumerable<T> collection)
|
||||
{
|
||||
if (collection == null || collection.Count() == 0) return;
|
||||
int i = index + collection.Count() < this.Count ? collection.Count() : this.Count - index;
|
||||
List<T> movedItems = base.GetRange(index, i);
|
||||
base.InsertRange(index, collection);
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, changedItems: collection.ToList(), index));
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, changedItems: movedItems, IndexOf(movedItems[0]), index));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Remove >
|
||||
|
||||
///<inheritdoc cref="List{T}.Clear"/>
|
||||
new public virtual void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
///<inheritdoc cref="List{T}.Remove(T)"/>
|
||||
new public virtual bool Remove(T item)
|
||||
{
|
||||
if (!base.Contains(item)) return false;
|
||||
|
||||
int i = base.IndexOf(item);
|
||||
if (base.Remove(item))
|
||||
{
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, i));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
///<inheritdoc cref="List{T}.RemoveAt(int)"/>
|
||||
new public virtual void RemoveAt(int index)
|
||||
{
|
||||
T item = base[index];
|
||||
base.RemoveAt(index);
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index: index));
|
||||
}
|
||||
|
||||
///<inheritdoc cref="List{T}.RemoveRange(int,int)"/>
|
||||
new public virtual void RemoveRange(int index, int count)
|
||||
{
|
||||
List<T> removedItems = base.GetRange(index, count);
|
||||
if (removedItems.Count > 0)
|
||||
{
|
||||
base.RemoveRange(index, count);
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems.ToList(), index));
|
||||
}
|
||||
}
|
||||
|
||||
///<inheritdoc cref="List{T}.RemoveAll(Predicate{T})"/>
|
||||
new public virtual int RemoveAll(Predicate<T> match)
|
||||
{
|
||||
List<T> removedItems = base.FindAll(match);
|
||||
int ret = removedItems.Count;
|
||||
if (ret > 0)
|
||||
{
|
||||
ret = base.RemoveAll(match);
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Move / Sort >
|
||||
|
||||
#region < Hide base members >
|
||||
|
||||
///<inheritdoc cref="Sort(int, int, IComparer{T},bool)"/>
|
||||
new virtual public void Sort(int index, int count, IComparer<T> comparer) => Sort(index, count, comparer, true);
|
||||
///<inheritdoc cref="Sort(IComparer{T}, bool)"/>
|
||||
new virtual public void Sort(IComparer<T> comparer) => Sort(comparer, true);
|
||||
///<inheritdoc cref="Sort(Comparison{T},bool)"/>
|
||||
new virtual public void Sort(Comparison<T> comparison) => Sort(comparison, true);
|
||||
///<inheritdoc cref="Sort(bool)"/>
|
||||
new virtual public void Sort() => Sort(true);
|
||||
///<inheritdoc cref="Reverse(int, int, bool)"/>
|
||||
new virtual public void Reverse(int index, int count) => Reverse(index, count, true);
|
||||
///<inheritdoc cref="Reverse(bool)"/>
|
||||
new virtual public void Reverse() => Reverse(true);
|
||||
|
||||
#endregion
|
||||
|
||||
///<inheritdoc cref="List{T}.Reverse()"/>
|
||||
///<inheritdoc cref="PerformMove"/>
|
||||
public virtual void Reverse(bool verbose)
|
||||
{
|
||||
PerformMove(new Action(() => base.Reverse()), this, verbose);
|
||||
}
|
||||
|
||||
///<inheritdoc cref="List{T}.Reverse(int, int)"/>
|
||||
///<inheritdoc cref="PerformMove"/>
|
||||
public virtual void Reverse(int index, int count, bool verbose)
|
||||
{
|
||||
List<T> OriginalOrder = base.GetRange(index, count);
|
||||
PerformMove(new Action(() => base.Reverse(index, count)), OriginalOrder, verbose);
|
||||
}
|
||||
|
||||
///<inheritdoc cref="List{T}.Sort()"/>
|
||||
///<inheritdoc cref="PerformMove"/>
|
||||
public virtual void Sort(bool verbose)
|
||||
{
|
||||
PerformMove(new Action(() => base.Sort()), this, verbose);
|
||||
}
|
||||
|
||||
///<inheritdoc cref="List{T}.Sort(Comparison{T})"/>
|
||||
///<inheritdoc cref="PerformMove"/>
|
||||
public virtual void Sort(Comparison<T> comparison, bool verbose)
|
||||
{
|
||||
PerformMove(new Action(() => base.Sort(comparison)), this, verbose);
|
||||
}
|
||||
|
||||
///<inheritdoc cref="List{T}.Sort(IComparer{T})"/>
|
||||
///<inheritdoc cref="PerformMove"/>
|
||||
public virtual void Sort(IComparer<T> comparer, bool verbose)
|
||||
{
|
||||
PerformMove(new Action(() => base.Sort(comparer)), this, verbose);
|
||||
}
|
||||
|
||||
///<inheritdoc cref="List{T}.Sort(int, int, IComparer{T})"/>
|
||||
///<inheritdoc cref="PerformMove"/>
|
||||
public virtual void Sort(int index, int count, IComparer<T> comparer, bool verbose)
|
||||
{
|
||||
List<T> OriginalOrder = base.GetRange(index, count);
|
||||
Action action = new Action(() => base.Sort(index, count, comparer));
|
||||
PerformMove(action, OriginalOrder, verbose);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Per <see cref="INotifyCollectionChanged"/> rules, generates <see cref="CollectionChanged"/> event for every item that has moved within the list. <br/>
|
||||
/// Set <paramref name="verbose"/> parameter in overload to generate a single <see cref="NotifyCollectionChangedAction.Reset"/> event instead.
|
||||
/// </remarks>
|
||||
/// <param name="MoveAction">Action to perform that will rearrange items in the list - should not add, remove or replace!</param>
|
||||
/// <param name="OriginalOrder">List of items that are intended to rearrage - can be whole or subset of list</param>
|
||||
/// <param name="verbose">
|
||||
/// If TRUE: Create a 'Move' OnCollectionChange event for all items that were moved within the list. <para/>
|
||||
/// If FALSE: Generate a single event with <see cref="NotifyCollectionChangedAction.Reset"/>
|
||||
/// </param>
|
||||
protected void PerformMove(Action MoveAction, List<T> OriginalOrder, bool verbose)
|
||||
{
|
||||
//Store Old List Order
|
||||
List<T> OldIndexList = this.ToList();
|
||||
|
||||
//Perform the move
|
||||
MoveAction.Invoke();
|
||||
|
||||
//Generate the event
|
||||
foreach (T obj in OriginalOrder)
|
||||
{
|
||||
int oldIndex = OldIndexList.IndexOf(obj);
|
||||
int newIndex = this.IndexOf(obj);
|
||||
if (oldIndex != newIndex)
|
||||
{
|
||||
if (verbose)
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, changedItem: obj, newIndex, oldIndex));
|
||||
else
|
||||
{
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//OldIndexList no longer needed
|
||||
OldIndexList.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace RoboSharp
|
||||
{
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
/// <summary>
|
||||
/// Object derived from TaskScheduler. Assisgns the task to some thread
|
||||
/// </summary>
|
||||
public class PriorityScheduler : TaskScheduler
|
||||
|
||||
{
|
||||
/// <summary> TaskScheduler for AboveNormal Priority Tasks </summary>
|
||||
public static PriorityScheduler AboveNormal = new PriorityScheduler(ThreadPriority.AboveNormal);
|
||||
|
||||
/// <summary> TaskScheduler for BelowNormal Priority Tasks </summary>
|
||||
public static PriorityScheduler BelowNormal = new PriorityScheduler(ThreadPriority.BelowNormal);
|
||||
|
||||
/// <summary> TaskScheduler for the lowest Priority Tasks </summary>
|
||||
public static PriorityScheduler Lowest = new PriorityScheduler(ThreadPriority.Lowest);
|
||||
|
||||
|
||||
private BlockingCollection<Task> _tasks = new BlockingCollection<Task>();
|
||||
private Thread[] _threads;
|
||||
private ThreadPriority _priority;
|
||||
private readonly int _maximumConcurrencyLevel = Math.Max(1, Environment.ProcessorCount);
|
||||
|
||||
public PriorityScheduler(ThreadPriority priority)
|
||||
{
|
||||
_priority = priority;
|
||||
}
|
||||
|
||||
public override int MaximumConcurrencyLevel
|
||||
{
|
||||
get { return _maximumConcurrencyLevel; }
|
||||
}
|
||||
|
||||
protected override IEnumerable<Task> GetScheduledTasks()
|
||||
{
|
||||
return _tasks;
|
||||
}
|
||||
|
||||
protected override void QueueTask(Task task)
|
||||
{
|
||||
_tasks.Add(task);
|
||||
bool _executing = false;
|
||||
if (_threads == null)
|
||||
{
|
||||
_threads = new Thread[_maximumConcurrencyLevel];
|
||||
for (int i = 0; i < _threads.Length; i++)
|
||||
{
|
||||
int local = i;
|
||||
_threads[i] = new Thread(() =>
|
||||
{
|
||||
foreach (Task t in _tasks.GetConsumingEnumerable())
|
||||
_executing = base.TryExecuteTask(t);
|
||||
});
|
||||
_threads[i].Name = string.Format("PriorityScheduler: ", i);
|
||||
_threads[i].Priority = _priority;
|
||||
_threads[i].IsBackground = true;
|
||||
_threads[i].Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
|
||||
{
|
||||
return false; // we might not want to execute task that should schedule as high or low priority inline
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
namespace RoboSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Message Type reported by RoboCopy
|
||||
/// </summary>
|
||||
public enum FileClassType
|
||||
{
|
||||
/// <summary>Details about a Directory</summary>
|
||||
NewDir,
|
||||
/// <summary>Details about a FILE</summary>
|
||||
File,
|
||||
/// <summary>Status Message reported by RoboCopy</summary>
|
||||
SystemMessage
|
||||
}
|
||||
|
||||
/// <summary>Contains information about the current item being processed by RoboCopy</summary>
|
||||
public class ProcessedFileInfo
|
||||
{
|
||||
/// <summary>Description of the item as reported by RoboCopy</summary>
|
||||
public string FileClass { get; set; }
|
||||
|
||||
/// <inheritdoc cref="RoboSharp.FileClassType"/>
|
||||
public FileClassType FileClassType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// File -> File Size <br/>
|
||||
/// Directory -> Number files in folder -> Can be negative if PURGE is used <br/>
|
||||
/// SystemMessage -> Should be 0
|
||||
/// </summary>
|
||||
public long Size { get; set; }
|
||||
|
||||
/// <summary>Folder or File Name / Message Text</summary>
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,521 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using RoboSharp.Interfaces;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.CompilerServices;
|
||||
using RoboSharp.EventArgObjects;
|
||||
|
||||
namespace RoboSharp.Results
|
||||
{
|
||||
/// <summary>
|
||||
/// Object that provides <see cref="IStatistic"/> objects whose events can be bound to report estimated RoboCommand progress periodically.
|
||||
/// <br/>
|
||||
/// Note: Only works properly with /V verbose set TRUE.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Subscribe to <see cref="RoboCommand.OnProgressEstimatorCreated"/> or <see cref="RoboQueue.OnProgressEstimatorCreated"/> to be notified when the ProgressEstimator becomes available for binding <br/>
|
||||
/// Create event handler to subscribe to the Events you want to handle: <para/>
|
||||
/// <code>
|
||||
/// private void OnProgressEstimatorCreated(object sender, Results.ProgressEstimatorCreatedEventArgs e) { <br/>
|
||||
/// e.ResultsEstimate.ByteStats.PropertyChanged += ByteStats_PropertyChanged;<br/>
|
||||
/// e.ResultsEstimate.DirStats.PropertyChanged += DirStats_PropertyChanged;<br/>
|
||||
/// e.ResultsEstimate.FileStats.PropertyChanged += FileStats_PropertyChanged;<br/>
|
||||
/// }<br/>
|
||||
/// </code>
|
||||
/// <para/>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/ProgressEstimator"/>
|
||||
/// </remarks>
|
||||
public class ProgressEstimator : IProgressEstimator, IResults
|
||||
{
|
||||
#region < Constructors >
|
||||
|
||||
private ProgressEstimator() { }
|
||||
|
||||
internal ProgressEstimator(RoboCommand cmd)
|
||||
{
|
||||
command = cmd;
|
||||
DirStatField = new Statistic(Statistic.StatType.Directories, "Directory Stats Estimate");
|
||||
FileStatsField = new Statistic(Statistic.StatType.Files, "File Stats Estimate");
|
||||
ByteStatsField = new Statistic(Statistic.StatType.Bytes, "Byte Stats Estimate");
|
||||
|
||||
tmpByte.EnablePropertyChangeEvent = false;
|
||||
tmpFile.EnablePropertyChangeEvent = false;
|
||||
tmpDir.EnablePropertyChangeEvent = false;
|
||||
this.StartUpdateTask(out UpdateTaskCancelSource);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Private Members >
|
||||
|
||||
private readonly RoboCommand command;
|
||||
private bool SkippingFile { get; set; }
|
||||
private bool CopyOpStarted { get; set; }
|
||||
internal bool FileFailed { get; set; }
|
||||
|
||||
private RoboSharpConfiguration Config => command?.Configuration;
|
||||
|
||||
// Stat Objects that will be publicly visible
|
||||
private readonly Statistic DirStatField;
|
||||
private readonly Statistic FileStatsField;
|
||||
private readonly Statistic ByteStatsField;
|
||||
|
||||
internal enum WhereToAdd { Copied, Skipped, Extra, MisMatch, Failed }
|
||||
|
||||
// Storage for last entered Directory and File objects
|
||||
/// <summary>Used for providing Source Directory in CopyProgressChanged args</summary>
|
||||
internal ProcessedFileInfo CurrentDir { get; private set; }
|
||||
/// <summary>Used for providing Source Directory in CopyProgressChanged args AND for byte Statistic</summary>
|
||||
internal ProcessedFileInfo CurrentFile { get; private set; }
|
||||
/// <summary> Marked as TRUE if this is LIST ONLY mode or the file is 0KB -- Value set during 'AddFile' method </summary>
|
||||
private bool CurrentFile_SpecialHandling { get; set; }
|
||||
|
||||
//Stat objects to house the data temporarily before writing to publicly visible stat objects
|
||||
readonly Statistic tmpDir =new Statistic(type: Statistic.StatType.Directories);
|
||||
readonly Statistic tmpFile = new Statistic(type: Statistic.StatType.Files);
|
||||
readonly Statistic tmpByte = new Statistic(type: Statistic.StatType.Bytes);
|
||||
|
||||
//UpdatePeriod
|
||||
private const int UpdatePeriod = 150; // Update Period in milliseconds to push Updates to a UI or RoboQueueProgressEstimator
|
||||
private readonly object DirLock = new object(); //Thread Lock for tmpDir
|
||||
private readonly object FileLock = new object(); //Thread Lock for tmpFile and tmpByte
|
||||
private readonly object UpdateLock = new object(); //Thread Lock for NextUpdatePush and UpdateTaskTrgger
|
||||
private DateTime NextUpdatePush = DateTime.Now.AddMilliseconds(UpdatePeriod);
|
||||
private TaskCompletionSource<object> UpdateTaskTrigger; // TCS that the UpdateTask awaits on
|
||||
private CancellationTokenSource UpdateTaskCancelSource; // While !Cancelled, UpdateTask continues looping
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Public Properties >
|
||||
|
||||
/// <summary>
|
||||
/// Estimate of current number of directories processed while the job is still running. <br/>
|
||||
/// Estimate is provided by parsing of the LogLines produces by RoboCopy.
|
||||
/// </summary>
|
||||
public IStatistic DirectoriesStatistic => DirStatField;
|
||||
|
||||
/// <summary>
|
||||
/// Estimate of current number of files processed while the job is still running. <br/>
|
||||
/// Estimate is provided by parsing of the LogLines produces by RoboCopy.
|
||||
/// </summary>
|
||||
public IStatistic FilesStatistic => FileStatsField;
|
||||
|
||||
/// <summary>
|
||||
/// Estimate of current number of bytes processed while the job is still running. <br/>
|
||||
/// Estimate is provided by parsing of the LogLines produces by RoboCopy.
|
||||
/// </summary>
|
||||
public IStatistic BytesStatistic => ByteStatsField;
|
||||
|
||||
RoboCopyExitStatus IResults.Status => new RoboCopyExitStatus((int)GetExitCode());
|
||||
|
||||
/// <summary> </summary>
|
||||
public delegate void UIUpdateEventHandler(IProgressEstimator sender, IProgressEstimatorUpdateEventArgs e);
|
||||
|
||||
/// <inheritdoc cref="IProgressEstimator.ValuesUpdated"/>
|
||||
public event UIUpdateEventHandler ValuesUpdated;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Public Methods >
|
||||
|
||||
/// <summary>
|
||||
/// Parse this object's stats into a <see cref="RoboCopyExitCodes"/> enum.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public RoboCopyExitCodes GetExitCode()
|
||||
{
|
||||
Results.RoboCopyExitCodes code = 0;
|
||||
|
||||
//Files Copied
|
||||
if (FileStatsField.Copied > 0)
|
||||
code |= Results.RoboCopyExitCodes.FilesCopiedSuccessful;
|
||||
|
||||
//Extra
|
||||
if (DirStatField.Extras > 0 | FileStatsField.Extras > 0)
|
||||
code |= Results.RoboCopyExitCodes.ExtraFilesOrDirectoriesDetected;
|
||||
|
||||
//MisMatch
|
||||
if (DirStatField.Mismatch > 0 | FileStatsField.Mismatch > 0)
|
||||
code |= Results.RoboCopyExitCodes.MismatchedDirectoriesDetected;
|
||||
|
||||
//Failed
|
||||
if (DirStatField.Failed > 0 | FileStatsField.Failed > 0)
|
||||
code |= Results.RoboCopyExitCodes.SomeFilesOrDirectoriesCouldNotBeCopied;
|
||||
|
||||
return code;
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Get RoboCopyResults Object ( Internal ) >
|
||||
|
||||
/// <summary>
|
||||
/// Repackage the statistics into a new <see cref="RoboCopyResults"/> object
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Used by ResultsBuilder as starting point for the results.
|
||||
/// Should not be used anywhere else, as it kills the worker thread that calculates the Statistics objects.
|
||||
/// </remarks>
|
||||
/// <returns></returns>
|
||||
internal RoboCopyResults GetResults()
|
||||
{
|
||||
//Stop the Update Task
|
||||
UpdateTaskCancelSource?.Cancel();
|
||||
UpdateTaskTrigger?.TrySetResult(null);
|
||||
|
||||
// - if copy operation wasn't completed, register it as failed instead.
|
||||
// - if file was to be marked as 'skipped', then register it as skipped.
|
||||
|
||||
ProcessPreviousFile();
|
||||
PushUpdate(); // Perform Final calculation before generating the Results Object
|
||||
|
||||
// Package up
|
||||
return new RoboCopyResults()
|
||||
{
|
||||
BytesStatistic = (Statistic)BytesStatistic,
|
||||
DirectoriesStatistic = (Statistic)DirectoriesStatistic,
|
||||
FilesStatistic = (Statistic)FilesStatistic,
|
||||
SpeedStatistic = new SpeedStatistic(),
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Calculate Dirs (Internal) >
|
||||
|
||||
/// <summary>Increment <see cref="DirStatField"/></summary>
|
||||
internal void AddDir(ProcessedFileInfo currentDir, bool CopyOperation)
|
||||
{
|
||||
|
||||
WhereToAdd? whereTo = null;
|
||||
bool SetCurrentDir = false;
|
||||
if (currentDir.FileClass.Equals(Config.LogParsing_ExistingDir, StringComparison.CurrentCultureIgnoreCase)) // Existing Dir
|
||||
{
|
||||
whereTo = WhereToAdd.Skipped;
|
||||
SetCurrentDir = true;
|
||||
}
|
||||
else if (currentDir.FileClass.Equals(Config.LogParsing_NewDir, StringComparison.CurrentCultureIgnoreCase)) //New Dir
|
||||
{
|
||||
whereTo = WhereToAdd.Copied;
|
||||
SetCurrentDir = true;
|
||||
}
|
||||
else if (currentDir.FileClass.Equals(Config.LogParsing_ExtraDir, StringComparison.CurrentCultureIgnoreCase)) //Extra Dir
|
||||
{
|
||||
whereTo = WhereToAdd.Extra;
|
||||
SetCurrentDir = false;
|
||||
}
|
||||
else if (currentDir.FileClass.Equals(Config.LogParsing_DirectoryExclusion, StringComparison.CurrentCultureIgnoreCase)) //Excluded Dir
|
||||
{
|
||||
whereTo = WhereToAdd.Skipped;
|
||||
SetCurrentDir = false;
|
||||
}
|
||||
//Store CurrentDir under various conditions
|
||||
if (SetCurrentDir) CurrentDir = currentDir;
|
||||
|
||||
lock (DirLock)
|
||||
{
|
||||
switch (whereTo)
|
||||
{
|
||||
case WhereToAdd.Copied: tmpDir.Total++; tmpDir.Copied++;break;
|
||||
case WhereToAdd.Extra: tmpDir.Extras++; break; //Extras do not count towards total
|
||||
case WhereToAdd.Failed: tmpDir.Total++; tmpDir.Failed++; break;
|
||||
case WhereToAdd.MisMatch: tmpDir.Total++; tmpDir.Mismatch++; break;
|
||||
case WhereToAdd.Skipped: tmpDir.Total++; tmpDir.Skipped++; break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Check if the UpdateTask should push an update to the public fields
|
||||
if (Monitor.TryEnter(UpdateLock))
|
||||
{
|
||||
if (NextUpdatePush <= DateTime.Now)
|
||||
UpdateTaskTrigger?.TrySetResult(null);
|
||||
Monitor.Exit(UpdateLock);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Calculate Files (Internal) >
|
||||
|
||||
/// <summary>
|
||||
/// Performs final processing of the previous file if needed
|
||||
/// </summary>
|
||||
private void ProcessPreviousFile()
|
||||
{
|
||||
if (CurrentFile != null)
|
||||
{
|
||||
if (FileFailed)
|
||||
{
|
||||
PerformByteCalc(CurrentFile, WhereToAdd.Failed);
|
||||
}
|
||||
else if (CopyOpStarted && CurrentFile_SpecialHandling)
|
||||
{
|
||||
PerformByteCalc(CurrentFile, WhereToAdd.Copied);
|
||||
}
|
||||
else if (SkippingFile)
|
||||
{
|
||||
PerformByteCalc(CurrentFile, WhereToAdd.Skipped);
|
||||
}
|
||||
else if (UpdateTaskCancelSource?.IsCancellationRequested ?? true)
|
||||
{
|
||||
//Default marks as failed - This should only occur during the 'GetResults()' method due to the if statement above.
|
||||
PerformByteCalc(CurrentFile, WhereToAdd.Failed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Increment <see cref="FileStatsField"/></summary>
|
||||
internal void AddFile(ProcessedFileInfo currentFile, bool CopyOperation)
|
||||
{
|
||||
ProcessPreviousFile();
|
||||
|
||||
CurrentFile = currentFile;
|
||||
SkippingFile = false;
|
||||
CopyOpStarted = false;
|
||||
FileFailed = false;
|
||||
|
||||
// Flag to perform checks during a ListOnly operation OR for 0kb files (They won't get Progress update, but will be created)
|
||||
bool SpecialHandling = !CopyOperation || currentFile.Size == 0;
|
||||
CurrentFile_SpecialHandling = SpecialHandling;
|
||||
|
||||
// EXTRA FILES
|
||||
if (currentFile.FileClass.Equals(Config.LogParsing_ExtraFile, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
PerformByteCalc(currentFile, WhereToAdd.Extra);
|
||||
}
|
||||
//MisMatch
|
||||
else if (currentFile.FileClass.Equals(Config.LogParsing_MismatchFile, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
PerformByteCalc(currentFile, WhereToAdd.MisMatch);
|
||||
}
|
||||
//Failed Files
|
||||
else if (currentFile.FileClass.Equals(Config.LogParsing_FailedFile, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
PerformByteCalc(currentFile, WhereToAdd.Failed);
|
||||
}
|
||||
|
||||
//Files to be Copied/Skipped
|
||||
else
|
||||
{
|
||||
SkippingFile = CopyOperation;//Assume Skipped, adjusted when CopyProgress is updated
|
||||
if (currentFile.FileClass.Equals(Config.LogParsing_NewFile, StringComparison.CurrentCultureIgnoreCase)) // New File
|
||||
{
|
||||
//Special handling for 0kb files & ListOnly -> They won't get Progress update, but will be created
|
||||
if (SpecialHandling)
|
||||
{
|
||||
SetCopyOpStarted();
|
||||
}
|
||||
}
|
||||
else if (currentFile.FileClass.Equals(Config.LogParsing_SameFile, StringComparison.CurrentCultureIgnoreCase)) //Identical Files
|
||||
{
|
||||
if (command.SelectionOptions.IncludeSame)
|
||||
{
|
||||
if (SpecialHandling) SetCopyOpStarted(); // Only add to Copied if ListOnly / 0-bytes
|
||||
}
|
||||
else
|
||||
PerformByteCalc(currentFile, WhereToAdd.Skipped);
|
||||
}
|
||||
else if (SpecialHandling) // These checks are always performed during a ListOnly operation
|
||||
{
|
||||
|
||||
switch (true)
|
||||
{
|
||||
//Skipped Or Copied Conditions
|
||||
case true when currentFile.FileClass.Equals(Config.LogParsing_NewerFile, StringComparison.CurrentCultureIgnoreCase): // ExcludeNewer
|
||||
SkippedOrCopied(currentFile, command.SelectionOptions.ExcludeNewer);
|
||||
break;
|
||||
case true when currentFile.FileClass.Equals(Config.LogParsing_OlderFile, StringComparison.CurrentCultureIgnoreCase): // ExcludeOlder
|
||||
SkippedOrCopied(currentFile, command.SelectionOptions.ExcludeOlder);
|
||||
break;
|
||||
case true when currentFile.FileClass.Equals(Config.LogParsing_ChangedExclusion, StringComparison.CurrentCultureIgnoreCase): //ExcludeChanged
|
||||
SkippedOrCopied(currentFile, command.SelectionOptions.ExcludeChanged);
|
||||
break;
|
||||
case true when currentFile.FileClass.Equals(Config.LogParsing_TweakedInclusion, StringComparison.CurrentCultureIgnoreCase): //IncludeTweaked
|
||||
SkippedOrCopied(currentFile, !command.SelectionOptions.IncludeTweaked);
|
||||
break;
|
||||
|
||||
//Mark As Skip Conditions
|
||||
case true when currentFile.FileClass.Equals(Config.LogParsing_FileExclusion, StringComparison.CurrentCultureIgnoreCase): //FileExclusion
|
||||
case true when currentFile.FileClass.Equals(Config.LogParsing_AttribExclusion, StringComparison.CurrentCultureIgnoreCase): //AttributeExclusion
|
||||
case true when currentFile.FileClass.Equals(Config.LogParsing_MaxFileSizeExclusion, StringComparison.CurrentCultureIgnoreCase): //MaxFileSizeExclusion
|
||||
case true when currentFile.FileClass.Equals(Config.LogParsing_MinFileSizeExclusion, StringComparison.CurrentCultureIgnoreCase): //MinFileSizeExclusion
|
||||
case true when currentFile.FileClass.Equals(Config.LogParsing_MaxAgeOrAccessExclusion, StringComparison.CurrentCultureIgnoreCase): //MaxAgeOrAccessExclusion
|
||||
case true when currentFile.FileClass.Equals(Config.LogParsing_MinAgeOrAccessExclusion, StringComparison.CurrentCultureIgnoreCase): //MinAgeOrAccessExclusion
|
||||
PerformByteCalc(currentFile, WhereToAdd.Skipped);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method meant only to be called from AddFile method while SpecialHandling is true - helps normalize code and avoid repetition
|
||||
/// </summary>
|
||||
private void SkippedOrCopied(ProcessedFileInfo currentFile, bool MarkSkipped)
|
||||
{
|
||||
if (MarkSkipped)
|
||||
PerformByteCalc(currentFile, WhereToAdd.Skipped);
|
||||
else
|
||||
{
|
||||
SetCopyOpStarted();
|
||||
//PerformByteCalc(currentFile, WhereToAdd.Copied);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Catch start copy progress of large files</summary>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
internal void SetCopyOpStarted()
|
||||
{
|
||||
SkippingFile = false;
|
||||
CopyOpStarted = true;
|
||||
}
|
||||
|
||||
/// <summary>Increment <see cref="FileStatsField"/>.Copied ( Triggered when copy progress = 100% ) </summary>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
internal void AddFileCopied(ProcessedFileInfo currentFile)
|
||||
{
|
||||
PerformByteCalc(currentFile, WhereToAdd.Copied);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform the calculation for the ByteStatistic
|
||||
/// </summary>
|
||||
private void PerformByteCalc(ProcessedFileInfo file, WhereToAdd where)
|
||||
{
|
||||
if (file == null) return;
|
||||
|
||||
//Reset Flags
|
||||
SkippingFile = false;
|
||||
CopyOpStarted = false;
|
||||
FileFailed = false;
|
||||
CurrentFile = null;
|
||||
CurrentFile_SpecialHandling = false;
|
||||
|
||||
//Perform Math
|
||||
lock (FileLock)
|
||||
{
|
||||
//Extra files do not contribute towards Copy Total.
|
||||
if (where == WhereToAdd.Extra)
|
||||
{
|
||||
tmpFile.Extras++;
|
||||
tmpByte.Extras += file.Size;
|
||||
}
|
||||
else
|
||||
{
|
||||
tmpFile.Total++;
|
||||
tmpByte.Total += file.Size;
|
||||
|
||||
switch (where)
|
||||
{
|
||||
case WhereToAdd.Copied:
|
||||
tmpFile.Copied++;
|
||||
tmpByte.Copied += file.Size;
|
||||
break;
|
||||
case WhereToAdd.Extra:
|
||||
break;
|
||||
case WhereToAdd.Failed:
|
||||
tmpFile.Failed++;
|
||||
tmpByte.Failed += file.Size;
|
||||
break;
|
||||
case WhereToAdd.MisMatch:
|
||||
tmpFile.Mismatch++;
|
||||
tmpByte.Mismatch += file.Size;
|
||||
break;
|
||||
case WhereToAdd.Skipped:
|
||||
tmpFile.Skipped++;
|
||||
tmpByte.Skipped += file.Size;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//Check if the UpdateTask should push an update to the public fields
|
||||
if (Monitor.TryEnter(UpdateLock))
|
||||
{
|
||||
if (NextUpdatePush <= DateTime.Now)
|
||||
UpdateTaskTrigger?.TrySetResult(null);
|
||||
Monitor.Exit(UpdateLock);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < PushUpdate to Public Stat Objects >
|
||||
|
||||
/// <summary>
|
||||
/// Creates a LongRunning task that is meant to periodically push out Updates to the UI on a thread isolated from the event thread.
|
||||
/// </summary>
|
||||
/// <param name="CancelSource"></param>
|
||||
/// <returns></returns>
|
||||
private Task StartUpdateTask(out CancellationTokenSource CancelSource)
|
||||
{
|
||||
CancelSource = new CancellationTokenSource();
|
||||
var CS = CancelSource;
|
||||
return Task.Run(async () =>
|
||||
{
|
||||
while (!CS.IsCancellationRequested)
|
||||
{
|
||||
lock(UpdateLock)
|
||||
{
|
||||
PushUpdate();
|
||||
UpdateTaskTrigger = new TaskCompletionSource<object>();
|
||||
NextUpdatePush = DateTime.Now.AddMilliseconds(UpdatePeriod);
|
||||
}
|
||||
await UpdateTaskTrigger.Task;
|
||||
}
|
||||
//Cleanup
|
||||
CS?.Dispose();
|
||||
UpdateTaskTrigger = null;
|
||||
UpdateTaskCancelSource = null;
|
||||
}, CS.Token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Push the update to the public Stat Objects
|
||||
/// </summary>
|
||||
private void PushUpdate()
|
||||
{
|
||||
//Lock the Stat objects, clone, reset them, then push the update to the UI.
|
||||
Statistic TD = null;
|
||||
Statistic TB = null;
|
||||
Statistic TF = null;
|
||||
lock (DirLock)
|
||||
{
|
||||
if (tmpDir.NonZeroValue)
|
||||
{
|
||||
TD = tmpDir.Clone();
|
||||
tmpDir.Reset();
|
||||
}
|
||||
}
|
||||
lock (FileLock)
|
||||
{
|
||||
if (tmpFile.NonZeroValue)
|
||||
{
|
||||
TF = tmpFile.Clone();
|
||||
tmpFile.Reset();
|
||||
}
|
||||
if (tmpByte.NonZeroValue)
|
||||
{
|
||||
TB = tmpByte.Clone();
|
||||
tmpByte.Reset();
|
||||
}
|
||||
}
|
||||
//Push UI update after locks are released, to avoid holding up the other thread for too long
|
||||
if (TD != null) DirStatField.AddStatistic(TD);
|
||||
if (TB != null) ByteStatsField.AddStatistic(TB);
|
||||
if (TF != null) FileStatsField.AddStatistic(TF);
|
||||
//Raise the event if any of the values have been updated
|
||||
if (TF != null || TD != null || TB != null)
|
||||
{
|
||||
ValuesUpdated?.Invoke(this, new IProgressEstimatorUpdateEventArgs(this, TB, TF, TD));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace RoboSharp.Results
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class to build a <see cref="RoboCopyResults"/> object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/ResultsBuilder"/>
|
||||
/// </remarks>
|
||||
internal class ResultsBuilder
|
||||
{
|
||||
private ResultsBuilder() { }
|
||||
|
||||
internal ResultsBuilder(RoboCommand roboCommand) {
|
||||
RoboCommand = roboCommand;
|
||||
Estimator = new ProgressEstimator(roboCommand);
|
||||
}
|
||||
|
||||
#region < Private Members >
|
||||
|
||||
///<summary>Reference back to the RoboCommand that spawned this object</summary>
|
||||
private readonly RoboCommand RoboCommand;
|
||||
|
||||
private readonly List<string> outputLines = new List<string>();
|
||||
|
||||
/// <summary>This is the last line that was logged.</summary>
|
||||
internal string LastLine => outputLines.Count > 0 ? outputLines.Last() : "";
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Command Options Properties >
|
||||
|
||||
/// <see cref="RoboCommand.CommandOptions"/>
|
||||
internal string CommandOptions { get; set; }
|
||||
|
||||
/// <inheritdoc cref="CopyOptions.Source"/>
|
||||
internal string Source { get; set; }
|
||||
|
||||
/// <inheritdoc cref="CopyOptions.Destination"/>
|
||||
internal string Destination { get; set; }
|
||||
|
||||
internal List<ErrorEventArgs> RoboCopyErrors { get; } = new List<ErrorEventArgs>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Counters in case cancellation >
|
||||
|
||||
/// <inheritdoc cref="ProgressEstimator"/>
|
||||
internal ProgressEstimator Estimator { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Add a LogLine reported by RoboCopy to the LogLines list.
|
||||
/// </summary>
|
||||
internal void AddOutput(string output)
|
||||
{
|
||||
if (output == null)
|
||||
return;
|
||||
|
||||
if (Regex.IsMatch(output, @"^\s*[\d\.,]+%\s*$", RegexOptions.Compiled)) //Ignore Progress Indicators
|
||||
return;
|
||||
|
||||
outputLines.Add(output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the results from parsing the logLines.
|
||||
/// </summary>
|
||||
/// <param name="exitCode"></param>
|
||||
/// <param name="IsProgressUpdateEvent">This is used by the ProgressUpdateEventArgs to ignore the loglines when generating the estimate </param>
|
||||
/// <returns></returns>
|
||||
internal RoboCopyResults BuildResults(int exitCode, bool IsProgressUpdateEvent = false)
|
||||
{
|
||||
var res = Estimator.GetResults(); //Start off with the estimated results, and replace if able
|
||||
|
||||
res.JobName = RoboCommand.Name;
|
||||
res.Status = new RoboCopyExitStatus(exitCode);
|
||||
|
||||
var statisticLines = IsProgressUpdateEvent ? new List<string>() : GetStatisticLines();
|
||||
|
||||
//Dir Stats
|
||||
if (exitCode >= 0 && statisticLines.Count >= 1)
|
||||
res.DirectoriesStatistic = Statistic.Parse(Statistic.StatType.Directories, statisticLines[0]);
|
||||
|
||||
//File Stats
|
||||
if (exitCode >= 0 && statisticLines.Count >= 2)
|
||||
res.FilesStatistic = Statistic.Parse(Statistic.StatType.Files, statisticLines[1]);
|
||||
|
||||
//Bytes
|
||||
if (exitCode >= 0 && statisticLines.Count >= 3)
|
||||
res.BytesStatistic = Statistic.Parse(Statistic.StatType.Bytes, statisticLines[2]);
|
||||
|
||||
//Speed Stats
|
||||
if (exitCode >= 0 && statisticLines.Count >= 6)
|
||||
res.SpeedStatistic = SpeedStatistic.Parse(statisticLines[4], statisticLines[5]);
|
||||
|
||||
res.LogLines = outputLines.ToArray();
|
||||
res.RoboCopyErrors = this.RoboCopyErrors.ToArray();
|
||||
res.Source = this.Source;
|
||||
res.Destination = this.Destination;
|
||||
res.CommandOptions = this.CommandOptions;
|
||||
return res;
|
||||
}
|
||||
|
||||
private List<string> GetStatisticLines()
|
||||
{
|
||||
var res = new List<string>();
|
||||
for (var i = outputLines.Count-1; i > 0; i--)
|
||||
{
|
||||
var line = outputLines[i];
|
||||
if (line.StartsWith("-----------------------"))
|
||||
break;
|
||||
|
||||
if (line.Contains(":") && !line.Contains("\\"))
|
||||
res.Add(line);
|
||||
}
|
||||
|
||||
res.Reverse();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace RoboSharp.Results
|
||||
{
|
||||
/// <summary>
|
||||
/// RoboCopy Exit Codes
|
||||
/// </summary>
|
||||
/// <remarks><see href="https://ss64.com/nt/robocopy-exit.html"/></remarks>
|
||||
[Flags]
|
||||
public enum RoboCopyExitCodes
|
||||
{
|
||||
/// <summary>No Files Copied, No Errors Occured</summary>
|
||||
NoErrorNoCopy = 0x0,
|
||||
/// <summary>One or more files were copied successfully</summary>
|
||||
FilesCopiedSuccessful = 0x1,
|
||||
/// <summary>
|
||||
/// Some Extra files or directories were detected.<br/>
|
||||
/// Examine the output log for details.
|
||||
/// </summary>
|
||||
ExtraFilesOrDirectoriesDetected = 0x2,
|
||||
/// <summary>
|
||||
/// Some Mismatched files or directories were detected.<br/>
|
||||
/// Examine the output log. Housekeeping might be required.
|
||||
/// </summary>
|
||||
MismatchedDirectoriesDetected = 0x4,
|
||||
/// <summary>
|
||||
/// Some files or directories could not be copied <br/>
|
||||
/// (copy errors occurred and the retry limit was exceeded).
|
||||
/// Check these errors further.
|
||||
/// </summary>
|
||||
SomeFilesOrDirectoriesCouldNotBeCopied = 0x8,
|
||||
/// <summary>
|
||||
/// Serious error. Robocopy did not copy any files.<br/>
|
||||
/// Either a usage error or an error due to insufficient access privileges on the source or destination directories.
|
||||
/// </summary>
|
||||
SeriousErrorOccurred = 0x10,
|
||||
/// <summary>
|
||||
/// The Robocopy process exited prior to completion
|
||||
/// </summary>
|
||||
Cancelled = -1,
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RoboSharp.Interfaces;
|
||||
|
||||
namespace RoboSharp.Results
|
||||
{
|
||||
/// <summary>
|
||||
/// Results provided by the RoboCopy command. Includes the Log, Exit Code, and statistics parsed from the log.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/RoboCopyResults"/>
|
||||
/// </remarks>
|
||||
public class RoboCopyResults : IResults, ITimeSpan
|
||||
{
|
||||
internal RoboCopyResults() { }
|
||||
|
||||
#region < Properties >
|
||||
|
||||
/// <inheritdoc cref="CopyOptions.Source"/>
|
||||
public string Source { get; internal set; }
|
||||
|
||||
/// <inheritdoc cref="CopyOptions.Destination"/>
|
||||
public string Destination { get; internal set; }
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.CommandOptions"/>
|
||||
public string CommandOptions { get; internal set; }
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.Name"/>
|
||||
public string JobName { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// All Errors that were generated by RoboCopy during the run.
|
||||
/// </summary>
|
||||
public ErrorEventArgs[] RoboCopyErrors{ get; internal set; }
|
||||
|
||||
/// <inheritdoc cref="RoboCopyExitStatus"/>
|
||||
public RoboCopyExitStatus Status { get; internal set; }
|
||||
|
||||
/// <summary> Information about number of Directories Copied, Skipped, Failed, etc.</summary>
|
||||
/// <remarks>
|
||||
/// If the job was cancelled, or run without a Job Summary, this will attempt to provide approximate results based on the Process.StandardOutput from Robocopy. <br/>
|
||||
/// Results should only be treated as accurate if <see cref="Status"/>.ExitCodeValue >= 0 and the job was run with <see cref="LoggingOptions.NoJobSummary"/> = FALSE
|
||||
/// </remarks>
|
||||
public Statistic DirectoriesStatistic { get; internal set; }
|
||||
|
||||
/// <summary> Information about number of Files Copied, Skipped, Failed, etc.</summary>
|
||||
/// <remarks>
|
||||
/// If the job was cancelled, or run without a Job Summary, this will attempt to provide approximate results based on the Process.StandardOutput from Robocopy. <br/>
|
||||
/// Results should only be treated as accurate if <see cref="Status"/>.ExitCodeValue >= 0 and the job was run with <see cref="LoggingOptions.NoJobSummary"/> = FALSE
|
||||
/// </remarks>
|
||||
public Statistic FilesStatistic { get; internal set; }
|
||||
|
||||
/// <summary> Information about number of Bytes processed.</summary>
|
||||
/// <remarks>
|
||||
/// If the job was cancelled, or run without a Job Summary, this will attempt to provide approximate results based on the Process.StandardOutput from Robocopy. <br/>
|
||||
/// Results should only be treated as accurate if <see cref="Status"/>.ExitCodeValue >= 0 and the job was run with <see cref="LoggingOptions.NoJobSummary"/> = FALSE
|
||||
/// </remarks>
|
||||
public Statistic BytesStatistic { get; internal set; }
|
||||
|
||||
/// <inheritdoc cref="RoboSharp.Results.SpeedStatistic"/>
|
||||
public SpeedStatistic SpeedStatistic { get; internal set; }
|
||||
|
||||
/// <summary> Output Text reported by RoboCopy </summary>
|
||||
public string[] LogLines { get; internal set; }
|
||||
|
||||
/// <summary> Time the RoboCopy process was started </summary>
|
||||
public DateTime StartTime { get; internal set; }
|
||||
|
||||
/// <summary> Time the RoboCopy process was completed / cancelled. </summary>
|
||||
public DateTime EndTime { get; internal set; }
|
||||
|
||||
/// <summary> Length of Time the RoboCopy Process ran </summary>
|
||||
public TimeSpan TimeSpan { get; internal set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region < IResults >
|
||||
|
||||
IStatistic IResults.BytesStatistic => BytesStatistic;
|
||||
IStatistic IResults.DirectoriesStatistic => DirectoriesStatistic;
|
||||
IStatistic IResults.FilesStatistic => FilesStatistic;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents the current object.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A string that represents the current object.
|
||||
/// </returns>
|
||||
public override string ToString()
|
||||
{
|
||||
string str = $"ExitCode: {Status.ExitCode}, Directories: {DirectoriesStatistic?.Total.ToString() ?? "Unknown"}, Files: {FilesStatistic?.Total.ToString() ?? "Unknown"}, Bytes: {BytesStatistic?.Total.ToString() ?? "Unknown"}";
|
||||
|
||||
if (SpeedStatistic != null)
|
||||
{
|
||||
str += $", Speed: {SpeedStatistic.BytesPerSec} Bytes/sec";
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,396 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
//using System.Linq;
|
||||
using System.Text;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using RoboSharp.EventArgObjects;
|
||||
using RoboSharp.Interfaces;
|
||||
using StatType = RoboSharp.Results.Statistic.StatType;
|
||||
using System.Collections;
|
||||
|
||||
namespace RoboSharp.Results
|
||||
{
|
||||
/// <summary>
|
||||
/// Object used to represent results from multiple <see cref="RoboCommand"/>s. <br/>
|
||||
/// As <see cref="RoboCopyResults"/> are added to this object, it will update the Totals and Averages accordingly.<para/>
|
||||
/// Implements:
|
||||
/// <br/><see cref="IRoboCopyResultsList"/>
|
||||
/// <br/><see cref="IList{RoboCopyResults}"/> where T = RoboCopyResults
|
||||
/// <br/><see cref="INotifyCollectionChanged"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/RoboCopyResultsList"/>
|
||||
/// </remarks>
|
||||
public sealed class RoboCopyResultsList : IRoboCopyResultsList, IList<RoboCopyResults>, INotifyCollectionChanged
|
||||
{
|
||||
#region < Constructors >
|
||||
|
||||
/// <inheritdoc cref="List{T}.List()"/>
|
||||
public RoboCopyResultsList() { InitCollection(null); Init(); }
|
||||
|
||||
/// <param name="result">Populate the new List object with this result as the first item.</param>
|
||||
/// <inheritdoc cref="List{T}.List(IEnumerable{T})"/>
|
||||
public RoboCopyResultsList(RoboCopyResults result) { ResultsList.Add(result); InitCollection(null); Init(); }
|
||||
|
||||
/// <inheritdoc cref="List{T}.List(IEnumerable{T})"/>
|
||||
public RoboCopyResultsList(IEnumerable<RoboCopyResults> collection) { InitCollection(collection); Init(); }
|
||||
|
||||
/// <summary>
|
||||
/// Clone a RoboCopyResultsList into a new object
|
||||
/// </summary>
|
||||
public RoboCopyResultsList(RoboCopyResultsList resultsList)
|
||||
{
|
||||
InitCollection(resultsList);
|
||||
Total_DirStatsField = GetLazyStat(resultsList.Total_DirStatsField, GetLazyFunc(GetDirectoriesStatistics, StatType.Directories));
|
||||
Total_FileStatsField = GetLazyStat(resultsList.Total_FileStatsField, GetLazyFunc(GetFilesStatistics, StatType.Files));
|
||||
Total_ByteStatsField = GetLazyStat(resultsList.Total_ByteStatsField, GetLazyFunc(GetByteStatistics, StatType.Bytes));
|
||||
Average_SpeedStatsField = GetLazyStat(resultsList.Average_SpeedStatsField, GetLazyAverageSpeedFunc());
|
||||
ExitStatusSummaryField = GetLazyStat(resultsList.ExitStatusSummaryField, GetLazCombinedStatusFunc());
|
||||
}
|
||||
|
||||
#region < Constructor Helper Methods >
|
||||
|
||||
private void InitCollection(IEnumerable<RoboCopyResults> collection)
|
||||
{
|
||||
ResultsList.AddRange(collection);
|
||||
ResultsList.CollectionChanged += OnCollectionChanged;
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
Total_DirStatsField = new Lazy<Statistic>(GetLazyFunc(GetDirectoriesStatistics, StatType.Directories));
|
||||
Total_FileStatsField = new Lazy<Statistic>(GetLazyFunc(GetFilesStatistics, StatType.Files));
|
||||
Total_ByteStatsField = new Lazy<Statistic>(GetLazyFunc(GetByteStatistics, StatType.Bytes));
|
||||
Average_SpeedStatsField = new Lazy<AverageSpeedStatistic>(GetLazyAverageSpeedFunc());
|
||||
ExitStatusSummaryField = new Lazy<RoboCopyCombinedExitStatus>(GetLazCombinedStatusFunc());
|
||||
}
|
||||
|
||||
private Func<Statistic> GetLazyFunc(Func<IStatistic[]> Action, StatType StatType) => new Func<Statistic>(() => Statistic.AddStatistics(Action.Invoke(), StatType));
|
||||
private Func<AverageSpeedStatistic> GetLazyAverageSpeedFunc() => new Func<AverageSpeedStatistic>(() => AverageSpeedStatistic.GetAverage(GetSpeedStatistics()));
|
||||
private Func<RoboCopyCombinedExitStatus> GetLazCombinedStatusFunc() => new Func<RoboCopyCombinedExitStatus>(() => RoboCopyCombinedExitStatus.CombineStatuses(GetStatuses()));
|
||||
private Lazy<T> GetLazyStat<T>(Lazy<T> lazyStat, Func<T> action) where T : ICloneable
|
||||
{
|
||||
if (lazyStat.IsValueCreated)
|
||||
{
|
||||
var clone = lazyStat.Value.Clone();
|
||||
return new Lazy<T>(() => (T)clone);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Lazy<T>(action);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Fields >
|
||||
|
||||
//These objects are the underlying Objects that may be bound to by consumers.
|
||||
//The values are updated upon request of the associated property.
|
||||
//This is so that properties are not returning new objects every request (which would break bindings)
|
||||
//If the statistic is never requested, then Lazy<> allows the list to skip performing the math against that statistic.
|
||||
|
||||
private Lazy<Statistic> Total_DirStatsField;
|
||||
private Lazy<Statistic> Total_ByteStatsField;
|
||||
private Lazy<Statistic> Total_FileStatsField;
|
||||
private Lazy<AverageSpeedStatistic> Average_SpeedStatsField;
|
||||
private Lazy<RoboCopyCombinedExitStatus> ExitStatusSummaryField;
|
||||
private readonly ObservableList<RoboCopyResults> ResultsList = new ObservableList<RoboCopyResults>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Events >
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for objects to send notification that the list behind an <see cref="IRoboCopyResultsList"/> interface has been updated
|
||||
/// </summary>
|
||||
public delegate void ResultsListUpdated(object sender, ResultListUpdatedEventArgs e);
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Public Properties >
|
||||
|
||||
/// <summary> Sum of all DirectoryStatistics objects </summary>
|
||||
/// <remarks>Underlying value is Lazy{Statistic} object - Initial value not calculated until first request. </remarks>
|
||||
public IStatistic DirectoriesStatistic => Total_DirStatsField?.Value;
|
||||
|
||||
/// <summary> Sum of all ByteStatistics objects </summary>
|
||||
/// <remarks>Underlying value is Lazy{Statistic} object - Initial value not calculated until first request. </remarks>
|
||||
public IStatistic BytesStatistic => Total_ByteStatsField?.Value;
|
||||
|
||||
/// <summary> Sum of all FileStatistics objects </summary>
|
||||
/// <remarks>Underlying value is Lazy{Statistic} object - Initial value not calculated until first request. </remarks>
|
||||
public IStatistic FilesStatistic => Total_FileStatsField?.Value;
|
||||
|
||||
/// <summary> Average of all SpeedStatistics objects </summary>
|
||||
/// <remarks>Underlying value is Lazy{SpeedStatistic} object - Initial value not calculated until first request. </remarks>
|
||||
public ISpeedStatistic SpeedStatistic => Average_SpeedStatsField?.Value;
|
||||
|
||||
/// <summary> Sum of all RoboCopyExitStatus objects </summary>
|
||||
/// <remarks>Underlying value is Lazy object - Initial value not calculated until first request. </remarks>
|
||||
public IRoboCopyCombinedExitStatus Status => ExitStatusSummaryField?.Value;
|
||||
|
||||
/// <summary> The Collection of RoboCopy Results. Add/Removal of <see cref="RoboCopyResults"/> objects must be performed through this object's methods, not on the list directly. </summary>
|
||||
public IReadOnlyList<RoboCopyResults> Collection => ResultsList;
|
||||
|
||||
/// <inheritdoc cref="List{T}.Count"/>
|
||||
public int Count => ResultsList.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Get or Set the element at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index of the item to Get or Set.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException"/>
|
||||
public RoboCopyResults this[int index] { get => ResultsList[index]; set => ResultsList[index] = value; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Get Array Methods ( Public ) >
|
||||
|
||||
/// <summary>
|
||||
/// Get a snapshot of the ByteStatistics objects from this list.
|
||||
/// </summary>
|
||||
/// <returns>New array of the ByteStatistic objects</returns>
|
||||
public IStatistic[] GetByteStatistics()
|
||||
{
|
||||
List<Statistic> tmp = new List<Statistic> { };
|
||||
foreach (RoboCopyResults r in this)
|
||||
tmp.Add(r?.BytesStatistic);
|
||||
return tmp.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a snapshot of the DirectoriesStatistic objects from this list.
|
||||
/// </summary>
|
||||
/// <returns>New array of the DirectoriesStatistic objects</returns>
|
||||
public IStatistic[] GetDirectoriesStatistics()
|
||||
{
|
||||
List<Statistic> tmp = new List<Statistic> { };
|
||||
foreach (RoboCopyResults r in this)
|
||||
tmp.Add(r?.DirectoriesStatistic);
|
||||
return tmp.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a snapshot of the FilesStatistic objects from this list.
|
||||
/// </summary>
|
||||
/// <returns>New array of the FilesStatistic objects</returns>
|
||||
public IStatistic[] GetFilesStatistics()
|
||||
{
|
||||
List<Statistic> tmp = new List<Statistic> { };
|
||||
foreach (RoboCopyResults r in this)
|
||||
tmp.Add(r?.FilesStatistic);
|
||||
return tmp.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a snapshot of the FilesStatistic objects from this list.
|
||||
/// </summary>
|
||||
/// <returns>New array of the FilesStatistic objects</returns>
|
||||
public RoboCopyExitStatus[] GetStatuses()
|
||||
{
|
||||
List<RoboCopyExitStatus> tmp = new List<RoboCopyExitStatus> { };
|
||||
foreach (RoboCopyResults r in this)
|
||||
tmp.Add(r?.Status);
|
||||
return tmp.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a snapshot of the FilesStatistic objects from this list.
|
||||
/// </summary>
|
||||
/// <returns>New array of the FilesStatistic objects</returns>
|
||||
public ISpeedStatistic[] GetSpeedStatistics()
|
||||
{
|
||||
List<SpeedStatistic> tmp = new List<SpeedStatistic> { };
|
||||
foreach (RoboCopyResults r in this)
|
||||
tmp.Add(r?.SpeedStatistic);
|
||||
return tmp.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combine the <see cref="RoboCopyResults.RoboCopyErrors"/> into a single array of errors
|
||||
/// </summary>
|
||||
/// <returns>New array of the ErrorEventArgs objects</returns>
|
||||
public ErrorEventArgs[] GetErrors()
|
||||
{
|
||||
List<ErrorEventArgs> tmp = new List<ErrorEventArgs> { };
|
||||
foreach (RoboCopyResults r in this)
|
||||
tmp.AddRange(r?.RoboCopyErrors);
|
||||
return tmp.ToArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < INotifyCollectionChanged >
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="ObservableList{T}.CollectionChanged"/>
|
||||
/// </summary>
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
/// <summary>Process the Added/Removed items, then fire the event</summary>
|
||||
/// <inheritdoc cref="ObservableList{T}.OnCollectionChanged(NotifyCollectionChangedEventArgs)"/>
|
||||
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (e.Action == NotifyCollectionChangedAction.Move) goto RaiseEvent; // Sorting causes no change in math -> Simply raise the event
|
||||
|
||||
//Reset means a drastic change -> Recalculate everything, then goto RaiseEvent
|
||||
if (e.Action == NotifyCollectionChangedAction.Reset)
|
||||
{
|
||||
//Bytes
|
||||
if (Total_ByteStatsField.IsValueCreated)
|
||||
{
|
||||
Total_ByteStatsField.Value.Reset(false);
|
||||
Total_ByteStatsField.Value.AddStatistic(GetByteStatistics());
|
||||
}
|
||||
//Directories
|
||||
if (Total_DirStatsField.IsValueCreated)
|
||||
{
|
||||
Total_DirStatsField.Value.Reset(false);
|
||||
Total_DirStatsField.Value.AddStatistic(GetDirectoriesStatistics());
|
||||
}
|
||||
//Files
|
||||
if (Total_FileStatsField.IsValueCreated)
|
||||
{
|
||||
Total_FileStatsField.Value.Reset(false);
|
||||
Total_FileStatsField.Value.AddStatistic(GetFilesStatistics());
|
||||
}
|
||||
//Exit Status
|
||||
if (ExitStatusSummaryField.IsValueCreated)
|
||||
{
|
||||
ExitStatusSummaryField.Value.Reset(false);
|
||||
ExitStatusSummaryField.Value.CombineStatus(GetStatuses());
|
||||
}
|
||||
//Speed
|
||||
if (Average_SpeedStatsField.IsValueCreated)
|
||||
{
|
||||
Average_SpeedStatsField.Value.Reset(false);
|
||||
Average_SpeedStatsField.Value.Average(GetSpeedStatistics());
|
||||
}
|
||||
|
||||
goto RaiseEvent;
|
||||
}
|
||||
|
||||
//Process New Items
|
||||
if (e.NewItems != null)
|
||||
{
|
||||
int i = 0;
|
||||
int i2 = e.NewItems.Count;
|
||||
foreach (RoboCopyResults r in e?.NewItems)
|
||||
{
|
||||
i++;
|
||||
bool RaiseValueChangeEvent = (e.OldItems == null || e.OldItems.Count == 0) && (i == i2); //Prevent raising the event if more calculation needs to be performed either from NewItems or from OldItems
|
||||
//Bytes
|
||||
if (Total_ByteStatsField.IsValueCreated)
|
||||
Total_ByteStatsField.Value.AddStatistic(r?.BytesStatistic, RaiseValueChangeEvent);
|
||||
//Directories
|
||||
if (Total_DirStatsField.IsValueCreated)
|
||||
Total_DirStatsField.Value.AddStatistic(r?.DirectoriesStatistic, RaiseValueChangeEvent);
|
||||
//Files
|
||||
if (Total_FileStatsField.IsValueCreated)
|
||||
Total_FileStatsField.Value.AddStatistic(r?.FilesStatistic, RaiseValueChangeEvent);
|
||||
//Exit Status
|
||||
if (ExitStatusSummaryField.IsValueCreated)
|
||||
ExitStatusSummaryField.Value.CombineStatus(r?.Status, RaiseValueChangeEvent);
|
||||
//Speed
|
||||
if (Average_SpeedStatsField.IsValueCreated)
|
||||
{
|
||||
Average_SpeedStatsField.Value.Add(r?.SpeedStatistic);
|
||||
if (RaiseValueChangeEvent) Average_SpeedStatsField.Value.CalculateAverage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Process Removed Items
|
||||
if (e.OldItems != null)
|
||||
{
|
||||
int i = 0;
|
||||
int i2 = e.OldItems.Count;
|
||||
foreach (RoboCopyResults r in e?.OldItems)
|
||||
{
|
||||
i++;
|
||||
bool RaiseValueChangeEvent = i == i2;
|
||||
//Bytes
|
||||
if (Total_ByteStatsField.IsValueCreated)
|
||||
Total_ByteStatsField.Value.Subtract(r?.BytesStatistic, RaiseValueChangeEvent);
|
||||
//Directories
|
||||
if (Total_DirStatsField.IsValueCreated)
|
||||
Total_DirStatsField.Value.Subtract(r?.DirectoriesStatistic, RaiseValueChangeEvent);
|
||||
//Files
|
||||
if (Total_FileStatsField.IsValueCreated)
|
||||
Total_FileStatsField.Value.Subtract(r?.FilesStatistic, RaiseValueChangeEvent);
|
||||
//Exit Status
|
||||
if (ExitStatusSummaryField.IsValueCreated && RaiseValueChangeEvent)
|
||||
{
|
||||
ExitStatusSummaryField.Value.Reset(false);
|
||||
ExitStatusSummaryField.Value.CombineStatus(GetStatuses());
|
||||
}
|
||||
//Speed
|
||||
if (Average_SpeedStatsField.IsValueCreated)
|
||||
{
|
||||
if (this.Count == 0)
|
||||
Average_SpeedStatsField.Value.Reset();
|
||||
else
|
||||
Average_SpeedStatsField.Value.Subtract(r.SpeedStatistic);
|
||||
if (RaiseValueChangeEvent) Average_SpeedStatsField.Value.CalculateAverage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RaiseEvent:
|
||||
//Raise the CollectionChanged event
|
||||
CollectionChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < ICloneable >
|
||||
|
||||
/// <summary> Clone this object to a new RoboCopyResultsList </summary>
|
||||
public RoboCopyResultsList Clone() => new RoboCopyResultsList(this);
|
||||
|
||||
#endregion
|
||||
|
||||
#region < IList{T} Implementation >
|
||||
|
||||
bool ICollection<RoboCopyResults>.IsReadOnly => false;
|
||||
|
||||
/// <inheritdoc cref="IList{T}.IndexOf(T)"/>
|
||||
public int IndexOf(RoboCopyResults item) => ResultsList.IndexOf(item);
|
||||
|
||||
/// <inheritdoc cref="ObservableList{T}.Insert(int, T)"/>
|
||||
public void Insert(int index, RoboCopyResults item) => ResultsList.Insert(index, item);
|
||||
|
||||
/// <inheritdoc cref="ObservableList{T}.RemoveAt(int)"/>
|
||||
public void RemoveAt(int index) => ResultsList.RemoveAt(index);
|
||||
|
||||
/// <inheritdoc cref="ObservableList{T}.Add(T)"/>
|
||||
public void Add(RoboCopyResults item) => ResultsList.Add(item);
|
||||
|
||||
/// <inheritdoc cref="ObservableList{T}.Clear"/>
|
||||
public void Clear() => ResultsList.Clear();
|
||||
|
||||
/// <inheritdoc cref="IList.Contains(object)"/>
|
||||
public bool Contains(RoboCopyResults item) => ResultsList.Contains(item);
|
||||
|
||||
/// <inheritdoc cref="ICollection.CopyTo(Array, int)"/>
|
||||
public void CopyTo(RoboCopyResults[] array, int arrayIndex) => ResultsList.CopyTo(array, arrayIndex);
|
||||
|
||||
/// <inheritdoc cref="ObservableList{T}.Remove(T)"/>
|
||||
public bool Remove(RoboCopyResults item) => ResultsList.Remove(item);
|
||||
|
||||
/// <inheritdoc cref="List{T}.GetEnumerator"/>
|
||||
public IEnumerator<RoboCopyResults> GetEnumerator() => ResultsList.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => ResultsList.GetEnumerator();
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using RoboSharp.Interfaces;
|
||||
|
||||
namespace RoboSharp.Results
|
||||
{
|
||||
/// <summary>
|
||||
/// Object that evaluates the ExitCode reported after RoboCopy finishes executing.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/RoboCopyExitStatus"/>
|
||||
/// </remarks>
|
||||
public class RoboCopyExitStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:System.Object"/> class.
|
||||
/// </summary>
|
||||
public RoboCopyExitStatus(int exitCodeValue)
|
||||
{
|
||||
ExitCodeValue = exitCodeValue;
|
||||
}
|
||||
|
||||
/// <summary>ExitCode as reported by RoboCopy</summary>
|
||||
public int ExitCodeValue { get; protected set; }
|
||||
|
||||
/// <summary>ExitCode reported by RoboCopy converted into the Enum</summary>
|
||||
public RoboCopyExitCodes ExitCode => (RoboCopyExitCodes)ExitCodeValue;
|
||||
|
||||
/// <inheritdoc cref="RoboCopyExitCodes.FilesCopiedSuccessful"/>
|
||||
public bool Successful => !WasCancelled && ExitCodeValue < 0x10;
|
||||
|
||||
/// <inheritdoc cref="RoboCopyExitCodes.MismatchedDirectoriesDetected"/>
|
||||
public bool HasWarnings => ExitCodeValue >= 0x4;
|
||||
|
||||
/// <inheritdoc cref="RoboCopyExitCodes.SeriousErrorOccurred"/>
|
||||
public bool HasErrors => ExitCodeValue >= 0x10;
|
||||
|
||||
/// <inheritdoc cref="RoboCopyExitCodes.Cancelled"/>
|
||||
public virtual bool WasCancelled => ExitCodeValue < 0x0;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents the current object.
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"ExitCode: {ExitCodeValue} ({ExitCode})";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the combination of multiple Exit Statuses
|
||||
/// </summary>
|
||||
public sealed class RoboCopyCombinedExitStatus : RoboCopyExitStatus, IRoboCopyCombinedExitStatus
|
||||
{
|
||||
#region < Constructor >
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RoboCopyCombinedExitStatus"/> class.
|
||||
/// </summary>
|
||||
public RoboCopyCombinedExitStatus() : base(0) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RoboCopyCombinedExitStatus"/> class.
|
||||
/// </summary>
|
||||
public RoboCopyCombinedExitStatus(int exitCodeValue) : base(exitCodeValue) { }
|
||||
|
||||
/// <summary>
|
||||
/// Clone this into a new instance
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
public RoboCopyCombinedExitStatus(RoboCopyCombinedExitStatus obj) :base((int)obj.ExitCode)
|
||||
{
|
||||
wascancelled = obj.wascancelled;
|
||||
noCopyNoError = obj.noCopyNoError;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Fields and Event >
|
||||
|
||||
//Private bools for the Combine methods
|
||||
private bool wascancelled;
|
||||
private bool noCopyNoError;
|
||||
private bool EnablePropertyChangeEvent = true;
|
||||
|
||||
/// <summary>This event when the ExitStatus summary has changed </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Public Properties >
|
||||
|
||||
/// <summary>Overides <see cref="RoboCopyExitStatus.WasCancelled"/></summary>
|
||||
/// <returns> <see cref="AnyWasCancelled"/></returns>
|
||||
public override bool WasCancelled => AnyWasCancelled;
|
||||
|
||||
/// <summary>
|
||||
/// Atleast one <see cref="RoboCopyExitStatus"/> objects combined into this result resulted in no errors and no files/directories copied.
|
||||
/// </summary>
|
||||
public bool AnyNoCopyNoError => noCopyNoError || ExitCodeValue == 0x0;
|
||||
|
||||
/// <summary>
|
||||
/// Atleast one <see cref="RoboCopyExitStatus"/> object combined into this result had been cancelled / exited prior to completion.
|
||||
/// </summary>
|
||||
public bool AnyWasCancelled => wascancelled || ExitCodeValue < 0x0;
|
||||
|
||||
/// <summary>
|
||||
/// All jobs completed without errors or warnings.
|
||||
/// </summary>
|
||||
public bool AllSuccessful => !WasCancelled && (ExitCodeValue == 0x0 || ExitCodeValue == 0x1);
|
||||
|
||||
/// <summary>
|
||||
/// All jobs completed without errors or warnings, but Extra Files/Folders were detected.
|
||||
/// </summary>
|
||||
public bool AllSuccessful_WithWarnings => !WasCancelled && Successful && ExitCodeValue > 0x1;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < RaiseEvent Methods >
|
||||
|
||||
#if !NET40
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
private object[] StoreCurrentValues()
|
||||
{
|
||||
return new object[10]
|
||||
{
|
||||
WasCancelled, AnyNoCopyNoError, AnyWasCancelled, AllSuccessful, AllSuccessful_WithWarnings, HasErrors, HasWarnings, Successful, ExitCode, ExitCodeValue
|
||||
};
|
||||
}
|
||||
|
||||
#if !NET40
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
private void CompareAndRaiseEvents(object[] OriginalValues)
|
||||
{
|
||||
object[] CurrentValues = StoreCurrentValues();
|
||||
if (CurrentValues[0] != OriginalValues[0]) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("WasCancelled"));
|
||||
if (CurrentValues[1] != OriginalValues[1]) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("AnyNoCopyNoError"));
|
||||
if (CurrentValues[2] != OriginalValues[2]) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("AnyWasCancelled"));
|
||||
if (CurrentValues[3] != OriginalValues[3]) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("AllSuccessful"));
|
||||
if (CurrentValues[4] != OriginalValues[4]) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("AllSuccessful_WithWarnings"));
|
||||
if (CurrentValues[5] != OriginalValues[5]) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("HasErrors"));
|
||||
if (CurrentValues[6] != OriginalValues[6]) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("HasWarnings"));
|
||||
if (CurrentValues[7] != OriginalValues[7]) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Successful"));
|
||||
if (CurrentValues[8] != OriginalValues[8]) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ExitCode"));
|
||||
if (CurrentValues[9] != OriginalValues[9]) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ExitCodeValue"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Combine the RoboCopyExitCodes of the supplied ExitStatus with this ExitStatus.
|
||||
/// </summary>
|
||||
/// <remarks>If any were Cancelled, set the WasCancelled property to TRUE. Otherwise combine the exit codes.</remarks>
|
||||
/// <param name="status">ExitStatus to combine with</param>
|
||||
#if !NET40
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
public void CombineStatus(RoboCopyExitStatus status)
|
||||
{
|
||||
if (status == null) return;
|
||||
object[] OriginalValues = EnablePropertyChangeEvent ? StoreCurrentValues() : null;
|
||||
//Combine the status
|
||||
if (status.WasCancelled)
|
||||
{
|
||||
wascancelled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (status.ExitCode == 0x0) this.noCopyNoError = true; //0x0 is lost if any other values have been added, so this logs the state
|
||||
RoboCopyExitCodes code = this.ExitCode | status.ExitCode;
|
||||
this.ExitCodeValue = (int)code;
|
||||
}
|
||||
//Raise Property Change Events
|
||||
if (EnablePropertyChangeEvent) CompareAndRaiseEvents(OriginalValues);
|
||||
}
|
||||
|
||||
internal void CombineStatus(RoboCopyExitStatus status, bool enablePropertyChangeEvent)
|
||||
{
|
||||
EnablePropertyChangeEvent = enablePropertyChangeEvent;
|
||||
CombineStatus(status);
|
||||
EnablePropertyChangeEvent = enablePropertyChangeEvent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combine all the RoboCopyExitStatuses together.
|
||||
/// </summary>
|
||||
/// <param name="status">Array or List of ExitStatuses to combine.</param>
|
||||
public void CombineStatus(IEnumerable<RoboCopyExitStatus> status)
|
||||
{
|
||||
foreach (RoboCopyExitStatus s in status)
|
||||
{
|
||||
EnablePropertyChangeEvent = s == status.Last();
|
||||
CombineStatus(s);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combine all the RoboCopyExitStatuses together.
|
||||
/// </summary>
|
||||
/// <param name="statuses">Array or List of ExitStatuses to combine.</param>
|
||||
/// <returns> new RoboCopyExitStatus object </returns>
|
||||
public static RoboCopyCombinedExitStatus CombineStatuses(IEnumerable<RoboCopyExitStatus> statuses)
|
||||
{
|
||||
RoboCopyCombinedExitStatus ret = new RoboCopyCombinedExitStatus(0);
|
||||
ret.CombineStatus(statuses);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the value of the object
|
||||
/// </summary>
|
||||
#if !NET40
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
public void Reset()
|
||||
{
|
||||
object[] OriginalValues = EnablePropertyChangeEvent ? StoreCurrentValues() : null;
|
||||
this.wascancelled = false;
|
||||
this.noCopyNoError = false;
|
||||
this.ExitCodeValue = 0;
|
||||
if (EnablePropertyChangeEvent) CompareAndRaiseEvents(OriginalValues);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the value of the object
|
||||
/// </summary>
|
||||
internal void Reset(bool enablePropertyChangeEvent)
|
||||
{
|
||||
EnablePropertyChangeEvent = enablePropertyChangeEvent;
|
||||
Reset();
|
||||
EnablePropertyChangeEvent = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public RoboCopyCombinedExitStatus Clone() => new RoboCopyCombinedExitStatus(this);
|
||||
|
||||
object ICloneable.Clone() => Clone();
|
||||
}
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using RoboSharp.Interfaces;
|
||||
using RoboSharp.EventArgObjects;
|
||||
using RoboSharp.Results;
|
||||
using WhereToAdd = RoboSharp.Results.ProgressEstimator.WhereToAdd;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace RoboSharp.Results
|
||||
{
|
||||
/// <summary>
|
||||
/// Updates the Statistics every 250ms
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/RoboQueueProgressEstimator"/>
|
||||
/// </remarks>
|
||||
internal class RoboQueueProgressEstimator : IProgressEstimator, IResults, IDisposable
|
||||
{
|
||||
#region < Constructors >
|
||||
|
||||
internal RoboQueueProgressEstimator()
|
||||
{
|
||||
tmpDirs = new Statistic(Statistic.StatType.Directories);
|
||||
tmpFiles = new Statistic(Statistic.StatType.Files);
|
||||
tmpBytes = new Statistic(Statistic.StatType.Bytes);
|
||||
|
||||
tmpDirs.EnablePropertyChangeEvent = false;
|
||||
tmpFiles.EnablePropertyChangeEvent = false;
|
||||
tmpBytes.EnablePropertyChangeEvent = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Private Members >
|
||||
|
||||
//ThreadSafe Bags/Queues
|
||||
private readonly ConcurrentDictionary<IProgressEstimator, object> SubscribedStats = new ConcurrentDictionary<IProgressEstimator, object>();
|
||||
|
||||
//Stats
|
||||
private readonly Statistic DirStatField = new Statistic(Statistic.StatType.Directories, "Directory Stats Estimate");
|
||||
private readonly Statistic FileStatsField = new Statistic(Statistic.StatType.Files, "File Stats Estimate");
|
||||
private readonly Statistic ByteStatsField = new Statistic(Statistic.StatType.Bytes, "Byte Stats Estimate");
|
||||
|
||||
//Add Tasks
|
||||
private int UpdatePeriodInMilliSecond = 250;
|
||||
private readonly Statistic tmpDirs;
|
||||
private readonly Statistic tmpFiles;
|
||||
private readonly Statistic tmpBytes;
|
||||
private DateTime NextUpdate = DateTime.Now;
|
||||
private readonly object StatLock = new object();
|
||||
private readonly object UpdateLock = new object();
|
||||
private bool disposedValue;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Public Properties >
|
||||
|
||||
/// <summary>
|
||||
/// Estimate of current number of directories processed while the job is still running. <br/>
|
||||
/// Estimate is provided by parsing of the LogLines produces by RoboCopy.
|
||||
/// </summary>
|
||||
public IStatistic DirectoriesStatistic => DirStatField;
|
||||
|
||||
/// <summary>
|
||||
/// Estimate of current number of files processed while the job is still running. <br/>
|
||||
/// Estimate is provided by parsing of the LogLines produces by RoboCopy.
|
||||
/// </summary>
|
||||
public IStatistic FilesStatistic => FileStatsField;
|
||||
|
||||
/// <summary>
|
||||
/// Estimate of current number of bytes processed while the job is still running. <br/>
|
||||
/// Estimate is provided by parsing of the LogLines produces by RoboCopy.
|
||||
/// </summary>
|
||||
public IStatistic BytesStatistic => ByteStatsField;
|
||||
|
||||
RoboCopyExitStatus IResults.Status => new RoboCopyExitStatus((int)GetExitCode());
|
||||
|
||||
/// <inheritdoc cref="IProgressEstimator.ValuesUpdated"/>
|
||||
public event ProgressEstimator.UIUpdateEventHandler ValuesUpdated;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Public Methods >
|
||||
|
||||
/// <summary>
|
||||
/// Parse this object's stats into a <see cref="RoboCopyExitCodes"/> enum.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public RoboCopyExitCodes GetExitCode()
|
||||
{
|
||||
Results.RoboCopyExitCodes code = 0;
|
||||
|
||||
//Files Copied
|
||||
if (FilesStatistic.Copied > 0)
|
||||
code |= Results.RoboCopyExitCodes.FilesCopiedSuccessful;
|
||||
|
||||
//Extra
|
||||
if (DirectoriesStatistic.Extras > 0 || FilesStatistic.Extras > 0)
|
||||
code |= Results.RoboCopyExitCodes.ExtraFilesOrDirectoriesDetected;
|
||||
|
||||
//MisMatch
|
||||
if (DirectoriesStatistic.Mismatch > 0 || FilesStatistic.Mismatch > 0)
|
||||
code |= Results.RoboCopyExitCodes.MismatchedDirectoriesDetected;
|
||||
|
||||
//Failed
|
||||
if (DirectoriesStatistic.Failed > 0 || FilesStatistic.Failed > 0)
|
||||
code |= Results.RoboCopyExitCodes.SomeFilesOrDirectoriesCouldNotBeCopied;
|
||||
|
||||
return code;
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Counting Methods ( private ) >
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to the update events of a <see cref="ProgressEstimator"/> object
|
||||
/// </summary>
|
||||
internal void BindToProgressEstimator(IProgressEstimator estimator)
|
||||
{
|
||||
if (!SubscribedStats.ContainsKey(estimator))
|
||||
{
|
||||
SubscribedStats.TryAdd(estimator, null);
|
||||
estimator.ValuesUpdated += Estimator_ValuesUpdated;
|
||||
}
|
||||
}
|
||||
|
||||
private void Estimator_ValuesUpdated(IProgressEstimator sender, IProgressEstimatorUpdateEventArgs e)
|
||||
{
|
||||
Statistic bytes = null;
|
||||
Statistic files = null;
|
||||
Statistic dirs = null;
|
||||
bool updateReqd = false;
|
||||
|
||||
//Wait indefinitely until the tmp stats are released -- Previously -> lock(StatLock) { }
|
||||
Monitor.Enter(StatLock);
|
||||
|
||||
//Update the Temp Stats
|
||||
tmpBytes.AddStatistic(e.ValueChange_Bytes);
|
||||
tmpFiles.AddStatistic(e.ValueChange_Files);
|
||||
tmpDirs.AddStatistic(e.ValueChange_Directories);
|
||||
|
||||
//Check if an update should be pushed to the public fields
|
||||
//If another thread already has this locked, then it is pushing out an update -> This thread exits the method after releasing StatLock
|
||||
if (Monitor.TryEnter(UpdateLock))
|
||||
{
|
||||
updateReqd = DateTime.Now >= NextUpdate && (tmpFiles.NonZeroValue || tmpDirs.NonZeroValue || tmpBytes.NonZeroValue);
|
||||
if (updateReqd)
|
||||
{
|
||||
if (tmpBytes.NonZeroValue)
|
||||
{
|
||||
bytes = tmpBytes.Clone();
|
||||
tmpBytes.Reset();
|
||||
}
|
||||
|
||||
if (tmpFiles.NonZeroValue)
|
||||
{
|
||||
files = tmpFiles.Clone();
|
||||
tmpFiles.Reset();
|
||||
}
|
||||
|
||||
if (tmpDirs.NonZeroValue)
|
||||
{
|
||||
dirs = tmpDirs.Clone();
|
||||
tmpDirs.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Monitor.Exit(StatLock); //Release the tmp Stats lock
|
||||
|
||||
if (updateReqd)
|
||||
{
|
||||
// Perform the Add Events
|
||||
if (bytes != null) ByteStatsField.AddStatistic(bytes);
|
||||
if (files != null) FileStatsField.AddStatistic(files);
|
||||
if (dirs != null) DirStatField.AddStatistic(dirs);
|
||||
//Raise the event, then update the NextUpdate time period
|
||||
ValuesUpdated?.Invoke(this, new IProgressEstimatorUpdateEventArgs(this, bytes, files, dirs));
|
||||
NextUpdate = DateTime.Now.AddMilliseconds(UpdatePeriodInMilliSecond);
|
||||
}
|
||||
|
||||
//Release the UpdateLock
|
||||
if (Monitor.IsEntered(UpdateLock))
|
||||
Monitor.Exit(UpdateLock);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribe from all bound Statistic objects
|
||||
/// </summary>
|
||||
internal void UnBind()
|
||||
{
|
||||
if (SubscribedStats != null)
|
||||
{
|
||||
foreach (var est in SubscribedStats.Keys)
|
||||
{
|
||||
est.ValuesUpdated -= Estimator_ValuesUpdated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < CancelTasks & DisposePattern >
|
||||
|
||||
/// <summary>
|
||||
/// Unbind all the ProgressEstimators
|
||||
/// </summary>
|
||||
internal void CancelTasks() => CancelTasks(true);
|
||||
|
||||
private void CancelTasks(bool RunUpdateTask)
|
||||
{
|
||||
//Preventn any additional events coming through
|
||||
UnBind();
|
||||
//Push the last update out after a short delay to allow any pending events through
|
||||
if (RunUpdateTask)
|
||||
{
|
||||
Task.Run( async () => {
|
||||
lock (UpdateLock)
|
||||
NextUpdate = DateTime.Now.AddMilliseconds(124);
|
||||
await Task.Delay(125);
|
||||
Estimator_ValuesUpdated(null, IProgressEstimatorUpdateEventArgs.DummyArgs);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// TODO: dispose managed state (managed objects)
|
||||
}
|
||||
|
||||
//Cancel the tasks
|
||||
CancelTasks(false);
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
||||
~RoboQueueProgressEstimator()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using RoboSharp.Interfaces;
|
||||
|
||||
namespace RoboSharp.Results
|
||||
{
|
||||
/// <summary>
|
||||
/// Object returned by RoboQueue when a run has completed.
|
||||
/// </summary>
|
||||
public sealed class RoboQueueResults : IRoboQueueResults, IRoboCopyResultsList, ITimeSpan
|
||||
{
|
||||
internal RoboQueueResults()
|
||||
{
|
||||
collection = new RoboCopyResultsList();
|
||||
StartTime = DateTime.Now;
|
||||
QueueProcessRunning = true;
|
||||
}
|
||||
|
||||
private RoboCopyResultsList collection { get; }
|
||||
private DateTime EndTimeField;
|
||||
private TimeSpan TimeSpanField;
|
||||
private bool QueueProcessRunning;
|
||||
|
||||
/// <summary>
|
||||
/// Add a result to the collection
|
||||
/// </summary>
|
||||
internal void Add(RoboCopyResults result) => collection.Add(result);
|
||||
|
||||
#region < IRoboQueueResults >
|
||||
|
||||
/// <summary> Time the RoboQueue task was started </summary>
|
||||
public DateTime StartTime { get; }
|
||||
|
||||
/// <summary> Time the RoboQueue task was completed / cancelled. </summary>
|
||||
/// <remarks> Should Only considered valid if <see cref="QueueComplete"/> = true.</remarks>
|
||||
public DateTime EndTime
|
||||
{
|
||||
get => EndTimeField;
|
||||
internal set
|
||||
{
|
||||
EndTimeField = value;
|
||||
TimeSpanField = value.Subtract(StartTime);
|
||||
QueueProcessRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Length of Time RoboQueue was running </summary>
|
||||
/// <remarks> Should Only considered valid if <see cref="QueueComplete"/> = true.</remarks>
|
||||
public TimeSpan TimeSpan => TimeSpanField;
|
||||
|
||||
/// <summary> TRUE if the RoboQueue object that created this results set has not finished running yet. </summary>
|
||||
public bool QueueRunning => QueueProcessRunning;
|
||||
|
||||
/// <summary> TRUE if the RoboQueue object that created this results has completed running, or has been cancelled. </summary>
|
||||
public bool QueueComplete => !QueueProcessRunning;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < IRoboCopyResultsList Implementation >
|
||||
|
||||
/// <inheritdoc cref="IRoboCopyResultsList.DirectoriesStatistic"/>
|
||||
public IStatistic DirectoriesStatistic => ((IRoboCopyResultsList)collection).DirectoriesStatistic;
|
||||
|
||||
/// <inheritdoc cref="IRoboCopyResultsList.BytesStatistic"/>
|
||||
public IStatistic BytesStatistic => ((IRoboCopyResultsList)collection).BytesStatistic;
|
||||
|
||||
/// <inheritdoc cref="IRoboCopyResultsList.FilesStatistic"/>
|
||||
public IStatistic FilesStatistic => ((IRoboCopyResultsList)collection).FilesStatistic;
|
||||
|
||||
/// <inheritdoc cref="IRoboCopyResultsList.SpeedStatistic"/>
|
||||
public ISpeedStatistic SpeedStatistic => ((IRoboCopyResultsList)collection).SpeedStatistic;
|
||||
|
||||
/// <inheritdoc cref="IRoboCopyResultsList.Status"/>
|
||||
public IRoboCopyCombinedExitStatus Status => ((IRoboCopyResultsList)collection).Status;
|
||||
|
||||
/// <inheritdoc cref="IRoboCopyResultsList.Collection"/>
|
||||
public IReadOnlyList<RoboCopyResults> Collection => ((IRoboCopyResultsList)collection).Collection;
|
||||
|
||||
/// <inheritdoc cref="IRoboCopyResultsList.Count"/>
|
||||
public int Count => ((IRoboCopyResultsList)collection).Count;
|
||||
|
||||
///<summary>Gets the <see cref="RoboCopyResults"/> object at the specified index. </summary>
|
||||
public RoboCopyResults this[int i] => ((IRoboCopyResultsList)collection)[i];
|
||||
|
||||
/// <inheritdoc cref="RoboCopyResultsList.CollectionChanged"/>
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged
|
||||
{
|
||||
add
|
||||
{
|
||||
((INotifyCollectionChanged)collection).CollectionChanged += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
((INotifyCollectionChanged)collection).CollectionChanged -= value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IRoboCopyResultsList.GetByteStatistics"/>
|
||||
public IStatistic[] GetByteStatistics()
|
||||
{
|
||||
return ((IRoboCopyResultsList)collection).GetByteStatistics();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IRoboCopyResultsList.GetDirectoriesStatistics"/>
|
||||
public IStatistic[] GetDirectoriesStatistics()
|
||||
{
|
||||
return ((IRoboCopyResultsList)collection).GetDirectoriesStatistics();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="RoboCopyResultsList.GetEnumerator"/>
|
||||
public IEnumerator<RoboCopyResults> GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<RoboCopyResults>)collection).GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IRoboCopyResultsList.GetFilesStatistics"/>
|
||||
public IStatistic[] GetFilesStatistics()
|
||||
{
|
||||
return ((IRoboCopyResultsList)collection).GetFilesStatistics();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IRoboCopyResultsList.GetSpeedStatistics"/>
|
||||
public ISpeedStatistic[] GetSpeedStatistics()
|
||||
{
|
||||
return ((IRoboCopyResultsList)collection).GetSpeedStatistics();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IRoboCopyResultsList.GetStatuses"/>
|
||||
public RoboCopyExitStatus[] GetStatuses()
|
||||
{
|
||||
return ((IRoboCopyResultsList)collection).GetStatuses();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable)collection).GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IRoboCopyResultsList.GetErrors"/>
|
||||
public ErrorEventArgs[] GetErrors()
|
||||
{
|
||||
return ((IRoboCopyResultsList)collection).GetErrors();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,387 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using RoboSharp.Interfaces;
|
||||
|
||||
namespace RoboSharp.Results
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information regarding average Transfer Speed. <br/>
|
||||
/// Note: Runs that do not perform any copy operations or that exited prematurely ( <see cref="RoboCopyExitCodes.Cancelled"/> ) will result in a null <see cref="SpeedStatistic"/> object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/SpeedStatistic"/>
|
||||
/// </remarks>
|
||||
public class SpeedStatistic : INotifyPropertyChanged, ISpeedStatistic
|
||||
{
|
||||
/// <summary>
|
||||
/// Create new SpeedStatistic
|
||||
/// </summary>
|
||||
public SpeedStatistic() { }
|
||||
|
||||
/// <summary>
|
||||
/// Clone a SpeedStatistic
|
||||
/// </summary>
|
||||
public SpeedStatistic(SpeedStatistic stat)
|
||||
{
|
||||
BytesPerSec = stat.BytesPerSec;
|
||||
MegaBytesPerMin = stat.MegaBytesPerMin;
|
||||
}
|
||||
|
||||
#region < Private & Protected Members >
|
||||
|
||||
private decimal BytesPerSecField = 0;
|
||||
private decimal MegaBytesPerMinField = 0;
|
||||
|
||||
/// <summary> This toggle Enables/Disables firing the <see cref="PropertyChanged"/> Event to avoid firing it when doing multiple consecutive changes to the values </summary>
|
||||
protected bool EnablePropertyChangeEvent { get; set; } = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Public Properties & Events >
|
||||
|
||||
/// <summary>This event will fire when the value of the SpeedStatistic is updated </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary>Raise Property Change Event</summary>
|
||||
protected void OnPropertyChange(string PropertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
|
||||
|
||||
/// <inheritdoc cref="ISpeedStatistic.BytesPerSec"/>
|
||||
public virtual decimal BytesPerSec
|
||||
{
|
||||
get => BytesPerSecField;
|
||||
protected set
|
||||
{
|
||||
if (BytesPerSecField != value)
|
||||
{
|
||||
BytesPerSecField = value;
|
||||
if (EnablePropertyChangeEvent) OnPropertyChange("MegaBytesPerMin");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ISpeedStatistic.BytesPerSec"/>
|
||||
public virtual decimal MegaBytesPerMin
|
||||
{
|
||||
get => MegaBytesPerMinField;
|
||||
protected set
|
||||
{
|
||||
if (MegaBytesPerMinField != value)
|
||||
{
|
||||
MegaBytesPerMinField = value;
|
||||
if (EnablePropertyChangeEvent) OnPropertyChange("MegaBytesPerMin");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Methods >
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents the current object.
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Speed: {BytesPerSec} Bytes/sec{Environment.NewLine}Speed: {MegaBytesPerMin} MegaBytes/min";
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ISpeedStatistic.Clone"/>
|
||||
public virtual SpeedStatistic Clone() => new SpeedStatistic(this);
|
||||
|
||||
object ICloneable.Clone() => Clone();
|
||||
|
||||
internal static SpeedStatistic Parse(string line1, string line2)
|
||||
{
|
||||
var res = new SpeedStatistic();
|
||||
|
||||
var pattern = new Regex(@"\d+([\.,]\d+)?");
|
||||
Match match;
|
||||
|
||||
match = pattern.Match(line1);
|
||||
if (match.Success)
|
||||
{
|
||||
res.BytesPerSec = Convert.ToDecimal(match.Value.Replace(',', '.'), CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
match = pattern.Match(line2);
|
||||
if (match.Success)
|
||||
{
|
||||
res.MegaBytesPerMin = Convert.ToDecimal(match.Value.Replace(',', '.'), CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This object represents the Average of several <see cref="SpeedStatistic"/> objects, and contains
|
||||
/// methods to facilitate that functionality.
|
||||
/// </summary>
|
||||
public sealed class AverageSpeedStatistic : SpeedStatistic
|
||||
{
|
||||
#region < Constructors >
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new <see cref="AverageSpeedStatistic"/> object with the default values.
|
||||
/// </summary>
|
||||
public AverageSpeedStatistic() : base() { }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new <see cref="AverageSpeedStatistic"/> object. <br/>
|
||||
/// Values will be set to the return values of <see cref="SpeedStatistic.BytesPerSec"/> and <see cref="SpeedStatistic.MegaBytesPerMin"/> <br/>
|
||||
/// </summary>
|
||||
/// <param name="speedStat">
|
||||
/// Either a <see cref="SpeedStatistic"/> or a <see cref="AverageSpeedStatistic"/> object. <br/>
|
||||
/// If a <see cref="AverageSpeedStatistic"/> is passed into this constructor, it wil be treated as the base <see cref="SpeedStatistic"/> instead.
|
||||
/// </param>
|
||||
public AverageSpeedStatistic(ISpeedStatistic speedStat) : base()
|
||||
{
|
||||
Divisor = 1;
|
||||
Combined_BytesPerSec = speedStat.BytesPerSec;
|
||||
Combined_MegaBytesPerMin = speedStat.MegaBytesPerMin;
|
||||
CalculateAverage();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new <see cref="AverageSpeedStatistic"/> object using <see cref="AverageSpeedStatistic.Average(IEnumerable{ISpeedStatistic})"/>. <br/>
|
||||
/// </summary>
|
||||
/// <param name="speedStats"><inheritdoc cref="Average(IEnumerable{ISpeedStatistic})"/></param>
|
||||
/// <inheritdoc cref="Average(IEnumerable{ISpeedStatistic})"/>
|
||||
public AverageSpeedStatistic(IEnumerable<ISpeedStatistic> speedStats) : base()
|
||||
{
|
||||
Average(speedStats);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clone an AverageSpeedStatistic
|
||||
/// </summary>
|
||||
public AverageSpeedStatistic(AverageSpeedStatistic stat) : base(stat)
|
||||
{
|
||||
Divisor = stat.Divisor;
|
||||
Combined_BytesPerSec = stat.BytesPerSec;
|
||||
Combined_MegaBytesPerMin = stat.MegaBytesPerMin;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Fields >
|
||||
|
||||
/// <summary> Sum of all <see cref="SpeedStatistic.BytesPerSec"/> </summary>
|
||||
private decimal Combined_BytesPerSec = 0;
|
||||
|
||||
/// <summary> Sum of all <see cref="SpeedStatistic.MegaBytesPerMin"/> </summary>
|
||||
private decimal Combined_MegaBytesPerMin = 0;
|
||||
|
||||
/// <summary> Total number of SpeedStats that were combined to produce the Combined_* values </summary>
|
||||
private long Divisor = 0;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Public Methods >
|
||||
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public override SpeedStatistic Clone() => new AverageSpeedStatistic(this);
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Reset Value Methods >
|
||||
|
||||
/// <summary>
|
||||
/// Set the values for this object to 0
|
||||
/// </summary>
|
||||
#if !NET40
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
public void Reset()
|
||||
{
|
||||
Combined_BytesPerSec = 0;
|
||||
Combined_MegaBytesPerMin = 0;
|
||||
Divisor = 0;
|
||||
BytesPerSec = 0;
|
||||
MegaBytesPerMin = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the values for this object to 0
|
||||
/// </summary>
|
||||
#if !NET40
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
internal void Reset(bool enablePropertyChangeEvent)
|
||||
{
|
||||
EnablePropertyChangeEvent = enablePropertyChangeEvent;
|
||||
Reset();
|
||||
EnablePropertyChangeEvent = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Add / Subtract methods are internal to allow usage within the RoboCopyResultsList object.
|
||||
// The 'Average' Methods will first Add the statistics to the current one, then recalculate the average.
|
||||
// Subtraction is only used when an item is removed from a RoboCopyResultsList
|
||||
// As such, public consumers should typically not require the use of subtract methods
|
||||
|
||||
#region < ADD ( internal ) >
|
||||
|
||||
/// <summary>
|
||||
/// Add the results of the supplied SpeedStatistic objects to this object. <br/>
|
||||
/// Does not automatically recalculate the average, and triggers no events.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If any supplied Speedstat object is actually an <see cref="AverageSpeedStatistic"/> object, default functionality will combine the private fields
|
||||
/// used to calculate the average speed instead of using the publicly reported speeds. <br/>
|
||||
/// This ensures that combining the average of multiple <see cref="AverageSpeedStatistic"/> objects returns the correct value. <br/>
|
||||
/// Ex: One object with 2 runs and one with 3 runs will return the average of all 5 runs instead of the average of two averages.
|
||||
/// </remarks>
|
||||
/// <param name="stat">SpeedStatistic Item to add</param>
|
||||
/// <param name="ForceTreatAsSpeedStat">
|
||||
/// Setting this to TRUE will instead combine the calculated average of the <see cref="AverageSpeedStatistic"/>, treating it as a single <see cref="SpeedStatistic"/> object. <br/>
|
||||
/// Ignore the private fields, and instead use the calculated speeds)
|
||||
/// </param>
|
||||
#if !NET40
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
internal void Add(ISpeedStatistic stat, bool ForceTreatAsSpeedStat = false)
|
||||
{
|
||||
if (stat == null) return;
|
||||
bool IsAverageStat = !ForceTreatAsSpeedStat && stat.GetType() == typeof(AverageSpeedStatistic);
|
||||
AverageSpeedStatistic AvgStat = IsAverageStat ? (AverageSpeedStatistic)stat : null;
|
||||
Divisor += IsAverageStat ? AvgStat.Divisor : 1;
|
||||
Combined_BytesPerSec += IsAverageStat ? AvgStat.Combined_BytesPerSec : stat.BytesPerSec;
|
||||
Combined_MegaBytesPerMin += IsAverageStat ? AvgStat.Combined_MegaBytesPerMin : stat.MegaBytesPerMin;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Add the supplied SpeedStatistic collection to this object.
|
||||
/// </summary>
|
||||
/// <param name="stats">SpeedStatistic collection to add</param>
|
||||
/// <param name="ForceTreatAsSpeedStat"><inheritdoc cref="Add(ISpeedStatistic, bool)"/></param>
|
||||
/// <inheritdoc cref="Add(ISpeedStatistic, bool)" path="/remarks"/>
|
||||
#if !NET40
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
internal void Add(IEnumerable<ISpeedStatistic> stats, bool ForceTreatAsSpeedStat = false)
|
||||
{
|
||||
foreach (SpeedStatistic stat in stats)
|
||||
Add(stat, ForceTreatAsSpeedStat);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Subtract ( internal ) >
|
||||
|
||||
/// <summary>
|
||||
/// Subtract the results of the supplied SpeedStatistic objects from this object.<br/>
|
||||
/// </summary>
|
||||
/// <param name="stat">Statistics Item to add</param>
|
||||
/// <param name="ForceTreatAsSpeedStat"><inheritdoc cref="Add(ISpeedStatistic, bool)"/></param>
|
||||
#if !NET40
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
internal void Subtract(SpeedStatistic stat, bool ForceTreatAsSpeedStat = false)
|
||||
{
|
||||
if (stat == null) return;
|
||||
bool IsAverageStat = !ForceTreatAsSpeedStat && stat.GetType() == typeof(AverageSpeedStatistic);
|
||||
AverageSpeedStatistic AvgStat = IsAverageStat ? (AverageSpeedStatistic)stat : null;
|
||||
Divisor -= IsAverageStat ? AvgStat.Divisor : 1;
|
||||
//Combine the values if Divisor is still valid
|
||||
if (Divisor >= 1)
|
||||
{
|
||||
Combined_BytesPerSec -= IsAverageStat ? AvgStat.Combined_BytesPerSec : stat.BytesPerSec;
|
||||
Combined_MegaBytesPerMin -= IsAverageStat ? AvgStat.Combined_MegaBytesPerMin : stat.MegaBytesPerMin;
|
||||
}
|
||||
//Cannot have negative speeds or divisors -> Reset all values
|
||||
if (Divisor < 1 || Combined_BytesPerSec < 0 || Combined_MegaBytesPerMin < 0)
|
||||
{
|
||||
Combined_BytesPerSec = 0;
|
||||
Combined_MegaBytesPerMin = 0;
|
||||
Divisor = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtract the supplied SpeedStatistic collection from this object.
|
||||
/// </summary>
|
||||
/// <param name="stats">SpeedStatistic collection to subtract</param>
|
||||
/// <param name="ForceTreatAsSpeedStat"><inheritdoc cref="Add(ISpeedStatistic, bool)"/></param>
|
||||
#if !NET40
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
internal void Subtract(IEnumerable<SpeedStatistic> stats, bool ForceTreatAsSpeedStat = false)
|
||||
{
|
||||
foreach (SpeedStatistic stat in stats)
|
||||
Subtract(stat, ForceTreatAsSpeedStat);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < AVERAGE ( public ) >
|
||||
|
||||
/// <summary>
|
||||
/// Immediately recalculate the BytesPerSec and MegaBytesPerMin values
|
||||
/// </summary>
|
||||
#if !NET40
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
internal void CalculateAverage()
|
||||
{
|
||||
EnablePropertyChangeEvent = false;
|
||||
//BytesPerSec
|
||||
var i = Divisor < 1 ? 0 : Math.Round(Combined_BytesPerSec / Divisor, 3);
|
||||
bool TriggerBPS = BytesPerSec != i;
|
||||
BytesPerSec = i;
|
||||
//MegaBytes
|
||||
i = Divisor < 1 ? 0 : Math.Round(Combined_MegaBytesPerMin / Divisor, 3);
|
||||
bool TriggerMBPM = MegaBytesPerMin != i;
|
||||
MegaBytesPerMin = i;
|
||||
//Trigger Events
|
||||
EnablePropertyChangeEvent = true;
|
||||
if (TriggerBPS) OnPropertyChange("BytesPerSec");
|
||||
if (TriggerMBPM) OnPropertyChange("MegaBytesPerMin");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combine the supplied <see cref="SpeedStatistic"/> objects, then get the average.
|
||||
/// </summary>
|
||||
/// <param name="stat">Stats object</param>
|
||||
/// <inheritdoc cref="Add(ISpeedStatistic, bool)" path="/remarks"/>
|
||||
public void Average(ISpeedStatistic stat)
|
||||
{
|
||||
Add(stat);
|
||||
CalculateAverage();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combine the supplied <see cref="SpeedStatistic"/> objects, then get the average.
|
||||
/// </summary>
|
||||
/// <param name="stats">Collection of <see cref="ISpeedStatistic"/> objects</param>
|
||||
/// <inheritdoc cref="Add(ISpeedStatistic, bool)" path="/remarks"/>
|
||||
public void Average(IEnumerable<ISpeedStatistic> stats)
|
||||
{
|
||||
Add(stats);
|
||||
CalculateAverage();
|
||||
}
|
||||
|
||||
/// <returns>New Statistics Object</returns>
|
||||
/// <inheritdoc cref=" Average(IEnumerable{ISpeedStatistic})"/>
|
||||
public static AverageSpeedStatistic GetAverage(IEnumerable<ISpeedStatistic> stats)
|
||||
{
|
||||
AverageSpeedStatistic stat = new AverageSpeedStatistic();
|
||||
stat.Average(stats);
|
||||
return stat;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,816 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using RoboSharp.Interfaces;
|
||||
using RoboSharp.EventArgObjects;
|
||||
|
||||
namespace RoboSharp.Results
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about number of items Copied, Skipped, Failed, etc.
|
||||
/// <para/>
|
||||
/// <see cref="RoboCopyResults"/> will not typically raise any events, but this object is used for other items, such as <see cref="ProgressEstimator"/> and <see cref="RoboCopyResultsList"/> to present results whose values may update periodically.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/Statistic"/>
|
||||
/// </remarks>
|
||||
public class Statistic : IStatistic
|
||||
{
|
||||
internal static IStatistic Default_Bytes = new Statistic(type: StatType.Bytes);
|
||||
internal static IStatistic Default_Files = new Statistic(type: StatType.Files);
|
||||
internal static IStatistic Default_Dirs = new Statistic(type: StatType.Directories);
|
||||
|
||||
#region < Constructors >
|
||||
|
||||
/// <summary> Create a new Statistic object of <see cref="StatType"/> </summary>
|
||||
[Obsolete("Statistic Types require Initialization with a StatType")]
|
||||
private Statistic() { }
|
||||
|
||||
/// <summary> Create a new Statistic object </summary>
|
||||
public Statistic(StatType type) { Type = type; }
|
||||
|
||||
/// <summary> Create a new Statistic object </summary>
|
||||
public Statistic(StatType type, string name) { Type = type; Name = name; }
|
||||
|
||||
/// <summary> Clone an existing Statistic object</summary>
|
||||
public Statistic(Statistic stat)
|
||||
{
|
||||
Type = stat.Type;
|
||||
NameField = stat.Name;
|
||||
TotalField = stat.Total;
|
||||
CopiedField = stat.Copied;
|
||||
SkippedField = stat.Skipped;
|
||||
MismatchField = stat.Mismatch;
|
||||
FailedField = stat.Failed;
|
||||
ExtrasField = stat.Extras;
|
||||
}
|
||||
|
||||
/// <summary> Clone an existing Statistic object</summary>
|
||||
internal Statistic(StatType type, string name, long total, long copied, long skipped, long mismatch, long failed, long extras)
|
||||
{
|
||||
Type = type;
|
||||
NameField = name;
|
||||
TotalField = total;
|
||||
CopiedField = copied;
|
||||
SkippedField = skipped;
|
||||
MismatchField = mismatch;
|
||||
FailedField = failed;
|
||||
ExtrasField = extras;
|
||||
}
|
||||
|
||||
/// <summary> Describe the Type of Statistics Object </summary>
|
||||
public enum StatType
|
||||
{
|
||||
/// <summary> Statistics object represents count of Directories </summary>
|
||||
Directories,
|
||||
/// <summary> Statistics object represents count of Files </summary>
|
||||
Files,
|
||||
/// <summary> Statistics object represents a Size ( number of bytes )</summary>
|
||||
Bytes
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Fields >
|
||||
|
||||
private string NameField = "";
|
||||
private long TotalField = 0;
|
||||
private long CopiedField = 0;
|
||||
private long SkippedField = 0;
|
||||
private long MismatchField = 0;
|
||||
private long FailedField = 0;
|
||||
private long ExtrasField = 0;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Events >
|
||||
|
||||
/// <summary> This toggle Enables/Disables firing the <see cref="PropertyChanged"/> Event to avoid firing it when doing multiple consecutive changes to the values </summary>
|
||||
internal bool EnablePropertyChangeEvent = true;
|
||||
private bool PropertyChangedListener => EnablePropertyChangeEvent && PropertyChanged != null;
|
||||
private bool Listener_TotalChanged => EnablePropertyChangeEvent && OnTotalChanged != null;
|
||||
private bool Listener_CopiedChanged => EnablePropertyChangeEvent && OnCopiedChanged != null;
|
||||
private bool Listener_SkippedChanged => EnablePropertyChangeEvent && OnSkippedChanged != null;
|
||||
private bool Listener_MismatchChanged => EnablePropertyChangeEvent && OnMisMatchChanged != null;
|
||||
private bool Listener_FailedChanged => EnablePropertyChangeEvent && OnFailedChanged != null;
|
||||
private bool Listener_ExtrasChanged => EnablePropertyChangeEvent && OnExtrasChanged != null;
|
||||
|
||||
/// <summary>
|
||||
/// This event will fire when the value of the statistic is updated via Adding / Subtracting methods. <br/>
|
||||
/// Provides <see cref="StatisticPropertyChangedEventArgs"/> object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Allows use with both binding to controls and <see cref="ProgressEstimator"/> binding. <br/>
|
||||
/// EventArgs can be passed into <see cref="AddStatistic(PropertyChangedEventArgs)"/> after casting.
|
||||
/// </remarks>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary>Handles any value changes </summary>
|
||||
public delegate void StatChangedHandler(Statistic sender, StatChangedEventArg e);
|
||||
|
||||
/// <summary> Occurs when the <see cref="Total"/> Property is updated. </summary>
|
||||
public event StatChangedHandler OnTotalChanged;
|
||||
|
||||
/// <summary> Occurs when the <see cref="Copied"/> Property is updated. </summary>
|
||||
public event StatChangedHandler OnCopiedChanged;
|
||||
|
||||
/// <summary> Occurs when the <see cref="Skipped"/> Property is updated. </summary>
|
||||
public event StatChangedHandler OnSkippedChanged;
|
||||
|
||||
/// <summary> Occurs when the <see cref="Mismatch"/> Property is updated. </summary>
|
||||
public event StatChangedHandler OnMisMatchChanged;
|
||||
|
||||
/// <summary> Occurs when the <see cref="Failed"/> Property is updated. </summary>
|
||||
public event StatChangedHandler OnFailedChanged;
|
||||
|
||||
/// <summary> Occurs when the <see cref="Extras"/> Property is updated. </summary>
|
||||
public event StatChangedHandler OnExtrasChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Properties >
|
||||
|
||||
/// <summary>
|
||||
/// Checks all values and determines if any of them are != 0.
|
||||
/// </summary>
|
||||
public bool NonZeroValue => TotalField != 0 || CopiedField != 0 || SkippedField != 0 || MismatchField != 0 || FailedField != 0 || ExtrasField != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Name of the Statistics Object
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get => NameField;
|
||||
set
|
||||
{
|
||||
if (value != NameField)
|
||||
{
|
||||
NameField = value ?? "";
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="StatType"/>
|
||||
/// </summary>
|
||||
public StatType Type { get; }
|
||||
|
||||
/// <inheritdoc cref="IStatistic.Total"/>
|
||||
public long Total {
|
||||
get => TotalField;
|
||||
internal set
|
||||
{
|
||||
if (TotalField != value)
|
||||
{
|
||||
var e = PrepEventArgs(TotalField, value, "Total");
|
||||
TotalField = value;
|
||||
if (e != null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, e.Value.Item2);
|
||||
OnTotalChanged?.Invoke(this, e.Value.Item1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IStatistic.Copied"/>
|
||||
public long Copied {
|
||||
get => CopiedField;
|
||||
internal set
|
||||
{
|
||||
if (CopiedField != value)
|
||||
{
|
||||
var e = PrepEventArgs(CopiedField, value, "Copied");
|
||||
CopiedField = value;
|
||||
if (e != null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, e.Value.Item2);
|
||||
OnCopiedChanged?.Invoke(this, e.Value.Item1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IStatistic.Skipped"/>
|
||||
public long Skipped {
|
||||
get => SkippedField;
|
||||
internal set
|
||||
{
|
||||
if (SkippedField != value)
|
||||
{
|
||||
var e = PrepEventArgs(SkippedField, value, "Skipped");
|
||||
SkippedField = value;
|
||||
if (e != null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, e.Value.Item2);
|
||||
OnSkippedChanged?.Invoke(this, e.Value.Item1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IStatistic.Mismatch"/>
|
||||
public long Mismatch {
|
||||
get => MismatchField;
|
||||
internal set
|
||||
{
|
||||
if (MismatchField != value)
|
||||
{
|
||||
var e = PrepEventArgs(MismatchField, value, "Mismatch");
|
||||
MismatchField = value;
|
||||
if (e != null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, e.Value.Item2);
|
||||
OnMisMatchChanged?.Invoke(this, e.Value.Item1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IStatistic.Failed"/>
|
||||
public long Failed {
|
||||
get => FailedField;
|
||||
internal set
|
||||
{
|
||||
if (FailedField != value)
|
||||
{
|
||||
var e = PrepEventArgs(FailedField, value, "Failed");
|
||||
FailedField = value;
|
||||
if (e != null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, e.Value.Item2);
|
||||
OnFailedChanged?.Invoke(this, e.Value.Item1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IStatistic.Extras"/>
|
||||
public long Extras {
|
||||
get => ExtrasField;
|
||||
internal set
|
||||
{
|
||||
if (ExtrasField != value)
|
||||
{
|
||||
var e = PrepEventArgs(ExtrasField, value, "Extras");
|
||||
ExtrasField = value;
|
||||
if (e != null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, e.Value.Item2);
|
||||
OnExtrasChanged?.Invoke(this, e.Value.Item1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < ToString Methods >
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents the current object.
|
||||
/// </summary>
|
||||
public override string ToString() => ToString(false, true, ", ");
|
||||
|
||||
/// <summary>
|
||||
/// Customize the returned string
|
||||
/// </summary>
|
||||
/// <param name="IncludeType">Include string representation of <see cref="Type"/></param>
|
||||
/// <param name="IncludePrefix">Include "Total:" / "Copied:" / etc in the string to identify the values</param>
|
||||
/// <param name="Delimiter">Value Delimieter</param>
|
||||
/// <param name="DelimiterAfterType">
|
||||
/// Include the delimiter after the 'Type' - Only used if <paramref name="IncludeType"/> us also true. <br/>
|
||||
/// When <paramref name="IncludeType"/> is true, a space always exist after the type string. This would add delimiter instead of the space.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// TRUE, TRUE, "," --> $"{Type} Total: {Total}, Copied: {Copied}, Skipped: {Skipped}, Mismatch: {Mismatch}, Failed: {Failed}, Extras: {Extras}" <para/>
|
||||
/// FALSE, TRUE, "," --> $"Total: {Total}, Copied: {Copied}, Skipped: {Skipped}, Mismatch: {Mismatch}, Failed: {Failed}, Extras: {Extras}" <para/>
|
||||
/// FALSE, FALSE, "," --> $"{Total}, {Copied}, {Skipped}, {Mismatch}, {Failed}, {Extras}"
|
||||
/// </returns>
|
||||
public string ToString(bool IncludeType, bool IncludePrefix, string Delimiter, bool DelimiterAfterType = false)
|
||||
{
|
||||
return $"{ToString_Type(IncludeType, DelimiterAfterType)}" + $"{(IncludeType && DelimiterAfterType ? Delimiter : "")}" +
|
||||
$"{ToString_Total(false, IncludePrefix)}{Delimiter}" +
|
||||
$"{ToString_Copied(false, IncludePrefix)}{Delimiter}" +
|
||||
$"{ToString_Skipped(false, IncludePrefix)}{Delimiter}" +
|
||||
$"{ToString_Mismatch(false, IncludePrefix)}{Delimiter}" +
|
||||
$"{ToString_Failed(false, IncludePrefix)}{Delimiter}" +
|
||||
$"{ToString_Extras(false, IncludePrefix)}";
|
||||
}
|
||||
|
||||
/// <summary> Get the <see cref="Type"/> as a string </summary>
|
||||
public string ToString_Type() => ToString_Type(true).Trim();
|
||||
private string ToString_Type(bool IncludeType, bool Trim = false) => IncludeType ? $"{Type}{(Trim? "" : " ")}" : "";
|
||||
|
||||
/// <summary>Get the string describing the <see cref="Total"/></summary>
|
||||
/// <returns></returns>
|
||||
/// <inheritdoc cref="ToString(bool, bool, string, bool)"/>
|
||||
public string ToString_Total(bool IncludeType = false, bool IncludePrefix = true) => $"{ToString_Type(IncludeType)}{(IncludePrefix? "Total: " : "")}{Total}";
|
||||
|
||||
/// <summary>Get the string describing the <see cref="Copied"/></summary>
|
||||
/// <inheritdoc cref="ToString_Total"/>
|
||||
public string ToString_Copied(bool IncludeType = false, bool IncludePrefix = true) => $"{ToString_Type(IncludeType)}{(IncludePrefix ? "Copied: " : "")}{Copied}";
|
||||
|
||||
/// <summary>Get the string describing the <see cref="Extras"/></summary>
|
||||
/// <inheritdoc cref="ToString_Total"/>
|
||||
public string ToString_Extras(bool IncludeType = false, bool IncludePrefix = true) => $"{ToString_Type(IncludeType)}{(IncludePrefix ? "Extras: " : "")}{Extras}";
|
||||
|
||||
/// <summary>Get the string describing the <see cref="Failed"/></summary>
|
||||
/// <inheritdoc cref="ToString_Total"/>
|
||||
public string ToString_Failed(bool IncludeType = false, bool IncludePrefix = true) => $"{ToString_Type(IncludeType)}{(IncludePrefix ? "Failed: " : "")}{Failed}";
|
||||
|
||||
/// <summary>Get the string describing the <see cref="Mismatch"/></summary>
|
||||
/// <inheritdoc cref="ToString_Total"/>
|
||||
public string ToString_Mismatch(bool IncludeType = false, bool IncludePrefix = true) => $"{ToString_Type(IncludeType)}{(IncludePrefix ? "Mismatch: " : "")}{Mismatch}";
|
||||
|
||||
/// <summary>Get the string describing the <see cref="Skipped"/></summary>
|
||||
/// <inheritdoc cref="ToString_Total"/>
|
||||
public string ToString_Skipped(bool IncludeType = false, bool IncludePrefix = true) => $"{ToString_Type(IncludeType)}{(IncludePrefix ? "Skipped: " : "")}{Skipped}";
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Parsing Methods >
|
||||
|
||||
/// <summary>
|
||||
/// Parse a string and for the tokens reported by RoboCopy
|
||||
/// </summary>
|
||||
/// <param name="type">Statistic Type to produce</param>
|
||||
/// <param name="line">LogLine produced by RoboCopy in Summary Section</param>
|
||||
/// <returns>New Statistic Object</returns>
|
||||
public static Statistic Parse(StatType type, string line)
|
||||
{
|
||||
var res = new Statistic(type);
|
||||
|
||||
var tokenNames = new[] { nameof(Total), nameof(Copied), nameof(Skipped), nameof(Mismatch), nameof(Failed), nameof(Extras) };
|
||||
var patternBuilder = new StringBuilder(@"^.*:");
|
||||
|
||||
foreach (var tokenName in tokenNames)
|
||||
{
|
||||
var tokenPattern = GetTokenPattern(tokenName);
|
||||
patternBuilder.Append(@"\s+").Append(tokenPattern);
|
||||
}
|
||||
|
||||
var pattern = patternBuilder.ToString();
|
||||
var match = Regex.Match(line, pattern);
|
||||
if (!match.Success)
|
||||
return res;
|
||||
|
||||
var props = res.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
|
||||
foreach (var tokenName in tokenNames)
|
||||
{
|
||||
var prop = props.FirstOrDefault(x => x.Name == tokenName);
|
||||
if (prop == null)
|
||||
continue;
|
||||
|
||||
var tokenString = match.Groups[tokenName].Value;
|
||||
var tokenValue = ParseTokenString(tokenString);
|
||||
prop.SetValue(res, tokenValue, null);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static string GetTokenPattern(string tokenName)
|
||||
{
|
||||
return $@"(?<{tokenName}>[\d\.]+(\s\w)?)";
|
||||
}
|
||||
|
||||
private static long ParseTokenString(string tokenString)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tokenString))
|
||||
return 0;
|
||||
|
||||
tokenString = tokenString.Trim();
|
||||
if (Regex.IsMatch(tokenString, @"^\d+$", RegexOptions.Compiled))
|
||||
return long.Parse(tokenString);
|
||||
|
||||
var match = Regex.Match(tokenString, @"(?<Mains>[\d\.,]+)(\.(?<Fraction>\d+))\s(?<Unit>\w)", RegexOptions.Compiled);
|
||||
if (match.Success)
|
||||
{
|
||||
var mains = match.Groups["Mains"].Value.Replace(".", "").Replace(",", "");
|
||||
var fraction = match.Groups["Fraction"].Value;
|
||||
var unit = match.Groups["Unit"].Value.ToLower();
|
||||
|
||||
var number = double.Parse($"{mains}.{fraction}", NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture);
|
||||
switch (unit)
|
||||
{
|
||||
case "k":
|
||||
// Byte = kBytes * 1024
|
||||
number *= Math.Pow(1024, 1);
|
||||
break;
|
||||
case "m":
|
||||
// Byte = MBytes * 1024 * 1024
|
||||
number *= Math.Pow(1024, 2);
|
||||
break;
|
||||
case "g":
|
||||
// Byte = GBytes * 1024 * 1024 * 1024
|
||||
number *= Math.Pow(1024, 3);
|
||||
break;
|
||||
case "t":
|
||||
// Byte = TBytes * 1024 * 1024 * 1024 * 1024
|
||||
number *= Math.Pow(1024, 4);
|
||||
break;
|
||||
}
|
||||
|
||||
return Convert.ToInt64(number);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Reset Method >
|
||||
|
||||
/// <summary>
|
||||
/// Set the values for this object to 0
|
||||
/// </summary>
|
||||
public void Reset(bool enablePropertyChangeEvent)
|
||||
{
|
||||
EnablePropertyChangeEvent = enablePropertyChangeEvent;
|
||||
Reset();
|
||||
EnablePropertyChangeEvent = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reset all values to Zero ( 0 ) -- Used by <see cref="RoboCopyResultsList"/> for the properties
|
||||
/// </summary>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
public void Reset()
|
||||
{
|
||||
Statistic OriginalValues = PropertyChangedListener && NonZeroValue ? this.Clone() : null;
|
||||
|
||||
//Total
|
||||
var eTotal = ConditionalPrepEventArgs(Listener_TotalChanged, TotalField, 0, "Total");
|
||||
TotalField = 0;
|
||||
//Copied
|
||||
var eCopied = ConditionalPrepEventArgs(Listener_CopiedChanged, CopiedField, 0, "Copied");
|
||||
CopiedField = 0;
|
||||
//Extras
|
||||
var eExtras = ConditionalPrepEventArgs(Listener_ExtrasChanged, ExtrasField, 0, "Extras");
|
||||
ExtrasField = 0;
|
||||
//Failed
|
||||
var eFailed = ConditionalPrepEventArgs(Listener_FailedChanged, FailedField, 0, "Failed");
|
||||
FailedField = 0;
|
||||
//Mismatch
|
||||
var eMismatch = ConditionalPrepEventArgs(Listener_MismatchChanged, MismatchField, 0, "Mismatch");
|
||||
MismatchField = 0;
|
||||
//Skipped
|
||||
var eSkipped = ConditionalPrepEventArgs(Listener_SkippedChanged, SkippedField, 0, "Skipped");
|
||||
SkippedField = 0;
|
||||
|
||||
//Trigger Events
|
||||
TriggerDeferredEvents(OriginalValues, eTotal, eCopied, eExtras, eFailed, eMismatch, eSkipped);
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Trigger Deferred Events >
|
||||
|
||||
/// <summary>
|
||||
/// Prep Event Args for SETTERS of the properties
|
||||
/// </summary>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
private Lazy<Tuple<StatChangedEventArg, StatisticPropertyChangedEventArgs>> PrepEventArgs(long OldValue, long NewValue, string PropertyName)
|
||||
{
|
||||
if (!EnablePropertyChangeEvent) return null;
|
||||
var old = this.Clone();
|
||||
return new Lazy<Tuple<StatChangedEventArg, StatisticPropertyChangedEventArgs>>(() =>
|
||||
{
|
||||
StatChangedEventArg e1 = new StatChangedEventArg(this, OldValue, NewValue, PropertyName);
|
||||
var e2 = new StatisticPropertyChangedEventArgs(this, old, PropertyName);
|
||||
return new Tuple<StatChangedEventArg, StatisticPropertyChangedEventArgs>(e1, e2);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prep event args for the ADD and RESET methods
|
||||
/// </summary>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
private StatChangedEventArg ConditionalPrepEventArgs(bool Listener, long OldValue, long NewValue, string PropertyName)=> !Listener || OldValue == NewValue ? null : new StatChangedEventArg(this, OldValue, NewValue, PropertyName);
|
||||
|
||||
/// <summary>
|
||||
/// Raises the events that were deferred while item was object was still being calculated by ADD / RESET
|
||||
/// </summary>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
private void TriggerDeferredEvents(Statistic OriginalValues, StatChangedEventArg eTotal, StatChangedEventArg eCopied, StatChangedEventArg eExtras, StatChangedEventArg eFailed, StatChangedEventArg eMismatch, StatChangedEventArg eSkipped)
|
||||
{
|
||||
//Perform Events
|
||||
int i = 0;
|
||||
if (eTotal != null)
|
||||
{
|
||||
i = 1;
|
||||
OnTotalChanged?.Invoke(this, eTotal);
|
||||
}
|
||||
if (eCopied != null)
|
||||
{
|
||||
i += 2;
|
||||
OnCopiedChanged?.Invoke(this, eCopied);
|
||||
}
|
||||
if (eExtras != null)
|
||||
{
|
||||
i += 4;
|
||||
OnExtrasChanged?.Invoke(this, eExtras);
|
||||
}
|
||||
if (eFailed != null)
|
||||
{
|
||||
i += 8;
|
||||
OnFailedChanged?.Invoke(this, eFailed);
|
||||
}
|
||||
if (eMismatch != null)
|
||||
{
|
||||
i += 16;
|
||||
OnMisMatchChanged?.Invoke(this, eMismatch);
|
||||
}
|
||||
if (eSkipped != null)
|
||||
{
|
||||
i += 32;
|
||||
OnSkippedChanged?.Invoke(this, eSkipped);
|
||||
}
|
||||
|
||||
//Trigger PropertyChangeEvent
|
||||
if (OriginalValues != null)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 1: PropertyChanged?.Invoke(this, new StatisticPropertyChangedEventArgs(this, OriginalValues, eTotal.PropertyName)); return;
|
||||
case 2: PropertyChanged?.Invoke(this, new StatisticPropertyChangedEventArgs(this, OriginalValues, eCopied.PropertyName)); return;
|
||||
case 4: PropertyChanged?.Invoke(this, new StatisticPropertyChangedEventArgs(this, OriginalValues, eExtras.PropertyName)); return;
|
||||
case 8: PropertyChanged?.Invoke(this, new StatisticPropertyChangedEventArgs(this, OriginalValues, eFailed.PropertyName)); return;
|
||||
case 16: PropertyChanged?.Invoke(this, new StatisticPropertyChangedEventArgs(this, OriginalValues, eMismatch.PropertyName)); return;
|
||||
case 32: PropertyChanged?.Invoke(this, new StatisticPropertyChangedEventArgs(this, OriginalValues, eSkipped.PropertyName)); return;
|
||||
default: PropertyChanged?.Invoke(this, new StatisticPropertyChangedEventArgs(this, OriginalValues, String.Empty)); return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < ADD Methods >
|
||||
|
||||
/// <summary>
|
||||
/// Add the supplied values to this Statistic object. <br/>
|
||||
/// Events are defered until all the fields have been added together.
|
||||
/// </summary>
|
||||
/// <param name="total"></param>
|
||||
/// <param name="copied"></param>
|
||||
/// <param name="extras"></param>
|
||||
/// <param name="failed"></param>
|
||||
/// <param name="mismatch"></param>
|
||||
/// <param name="skipped"></param>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(long total = 0, long copied = 0, long extras = 0, long failed = 0, long mismatch = 0, long skipped = 0)
|
||||
{
|
||||
//Store the original object values for the event args
|
||||
Statistic originalValues = PropertyChangedListener ? this.Clone() : null;
|
||||
|
||||
//Total
|
||||
long i = TotalField;
|
||||
TotalField += total;
|
||||
var eTotal = ConditionalPrepEventArgs(Listener_TotalChanged, i, TotalField, "Total");
|
||||
|
||||
//Copied
|
||||
i = CopiedField;
|
||||
CopiedField += copied;
|
||||
var eCopied = ConditionalPrepEventArgs(Listener_CopiedChanged, i, CopiedField, "Copied");
|
||||
|
||||
//Extras
|
||||
i = ExtrasField;
|
||||
ExtrasField += extras;
|
||||
var eExtras = ConditionalPrepEventArgs(Listener_ExtrasChanged, i, ExtrasField, "Extras");
|
||||
|
||||
//Failed
|
||||
i = FailedField;
|
||||
FailedField += failed;
|
||||
var eFailed = ConditionalPrepEventArgs(Listener_FailedChanged, i, FailedField, "Failed");
|
||||
|
||||
//Mismatch
|
||||
i = MismatchField;
|
||||
MismatchField += mismatch;
|
||||
var eMismatch = ConditionalPrepEventArgs(Listener_MismatchChanged, i, MismatchField, "Mismatch");
|
||||
|
||||
//Skipped
|
||||
i = SkippedField;
|
||||
SkippedField += skipped;
|
||||
var eSkipped = ConditionalPrepEventArgs(Listener_SkippedChanged, i, SkippedField, "Skipped");
|
||||
|
||||
//Trigger Events
|
||||
if (EnablePropertyChangeEvent)
|
||||
TriggerDeferredEvents(originalValues, eTotal, eCopied, eExtras, eFailed, eMismatch, eSkipped);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add the results of the supplied Statistics object to this Statistics object. <br/>
|
||||
/// Events are defered until all the fields have been added together.
|
||||
/// </summary>
|
||||
/// <param name="stat">Statistics Item to add</param>
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
public void AddStatistic(IStatistic stat)
|
||||
{
|
||||
if (stat != null && stat.Type == this.Type && stat.NonZeroValue)
|
||||
Add(stat.Total, stat.Copied, stat.Extras, stat.Failed, stat.Mismatch, stat.Skipped);
|
||||
}
|
||||
|
||||
|
||||
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
|
||||
/// <param name="enablePropertyChangedEvent"><inheritdoc cref="EnablePropertyChangeEvent" path="*"/></param>
|
||||
/// <inheritdoc cref="AddStatistic(IStatistic)"/>
|
||||
internal void AddStatistic(IStatistic stats, bool enablePropertyChangedEvent)
|
||||
{
|
||||
EnablePropertyChangeEvent = enablePropertyChangedEvent;
|
||||
AddStatistic(stats);
|
||||
EnablePropertyChangeEvent = true;
|
||||
|
||||
}
|
||||
#pragma warning restore CS1573
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Add the results of the supplied Statistics objects to this Statistics object.
|
||||
/// </summary>
|
||||
/// <param name="stats">Statistics Item to add</param>
|
||||
public void AddStatistic(IEnumerable<IStatistic> stats)
|
||||
{
|
||||
foreach (Statistic stat in stats)
|
||||
{
|
||||
EnablePropertyChangeEvent = stat == stats.Last();
|
||||
AddStatistic(stat);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds <see cref="StatChangedEventArg.Difference"/> to the appropriate property based on the 'PropertyChanged' value. <br/>
|
||||
/// Will only add the value if the <see cref="StatChangedEventArg.StatType"/> == <see cref="Type"/>.
|
||||
/// </summary>
|
||||
/// <param name="eventArgs">Arg provided by either <see cref="PropertyChanged"/> or a Statistic's object's On*Changed events</param>
|
||||
public void AddStatistic(PropertyChangedEventArgs eventArgs)
|
||||
{
|
||||
//Only process the args if of the proper type
|
||||
var e = eventArgs as IStatisticPropertyChangedEventArg;
|
||||
if (e == null) return;
|
||||
|
||||
if (e.StatType != this.Type || (e.Is_StatisticPropertyChangedEventArgs && e.Is_StatChangedEventArg))
|
||||
{
|
||||
// INVALID!
|
||||
}
|
||||
else if (e.Is_StatisticPropertyChangedEventArgs)
|
||||
{
|
||||
var e1 = (StatisticPropertyChangedEventArgs)eventArgs;
|
||||
AddStatistic(e1.Difference);
|
||||
}
|
||||
else if (e.Is_StatChangedEventArg)
|
||||
{
|
||||
var e2 = (StatChangedEventArg)eventArgs;
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
case "": //String.Empty means all fields have changed
|
||||
AddStatistic(e2.Sender);
|
||||
break;
|
||||
case "Copied":
|
||||
this.Copied += e2.Difference;
|
||||
break;
|
||||
case "Extras":
|
||||
this.Extras += e2.Difference;
|
||||
break;
|
||||
case "Failed":
|
||||
this.Failed += e2.Difference;
|
||||
break;
|
||||
case "Mismatch":
|
||||
this.Mismatch += e2.Difference;
|
||||
break;
|
||||
case "Skipped":
|
||||
this.Skipped += e2.Difference;
|
||||
break;
|
||||
case "Total":
|
||||
this.Total += e2.Difference;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combine the results of the supplied statistics objects of the specified type.
|
||||
/// </summary>
|
||||
/// <param name="stats">Collection of <see cref="Statistic"/> objects</param>
|
||||
/// <param name="statType">Create a new Statistic object of this type.</param>
|
||||
/// <returns>New Statistics Object</returns>
|
||||
public static Statistic AddStatistics(IEnumerable<IStatistic> stats, StatType statType)
|
||||
{
|
||||
Statistic ret = new Statistic(statType);
|
||||
ret.AddStatistic(stats.Where(s => s.Type == statType) );
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
#endregion ADD
|
||||
|
||||
#region < AVERAGE Methods >
|
||||
|
||||
/// <summary>
|
||||
/// Combine the supplied <see cref="Statistic"/> objects, then get the average.
|
||||
/// </summary>
|
||||
/// <param name="stats">Array of Stats objects</param>
|
||||
public void AverageStatistic(IEnumerable<IStatistic> stats)
|
||||
{
|
||||
this.AddStatistic(stats);
|
||||
int cnt = stats.Count() + 1;
|
||||
Total /= cnt;
|
||||
Copied /= cnt;
|
||||
Extras /= cnt;
|
||||
Failed /= cnt;
|
||||
Mismatch /= cnt;
|
||||
Skipped /= cnt;
|
||||
|
||||
}
|
||||
|
||||
/// <returns>New Statistics Object</returns>
|
||||
/// <inheritdoc cref=" AverageStatistic(IEnumerable{IStatistic})"/>
|
||||
public static Statistic AverageStatistics(IEnumerable<IStatistic> stats, StatType statType)
|
||||
{
|
||||
Statistic stat = AddStatistics(stats, statType);
|
||||
int cnt = stats.Count(s => s.Type == statType);
|
||||
if (cnt > 1)
|
||||
{
|
||||
stat.Total /= cnt;
|
||||
stat.Copied /= cnt;
|
||||
stat.Extras /= cnt;
|
||||
stat.Failed /= cnt;
|
||||
stat.Mismatch /= cnt;
|
||||
stat.Skipped /= cnt;
|
||||
}
|
||||
return stat;
|
||||
}
|
||||
|
||||
#endregion AVERAGE
|
||||
|
||||
#region < Subtract Methods >
|
||||
|
||||
/// <summary>
|
||||
/// Subtract Method used by <see cref="RoboCopyResultsList"/> <br/>
|
||||
/// Events are deferred until all value changes have completed.
|
||||
/// </summary>
|
||||
/// <param name="stat">Statistics Item to subtract</param>
|
||||
#if !NET40
|
||||
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
public void Subtract(IStatistic stat)
|
||||
{
|
||||
if (stat.Type == this.Type && stat.NonZeroValue)
|
||||
Add(-stat.Total, -stat.Copied, -stat.Extras, -stat.Failed, -stat.Mismatch, -stat.Skipped);
|
||||
}
|
||||
|
||||
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
|
||||
/// <param name="enablePropertyChangedEvent"><inheritdoc cref="EnablePropertyChangeEvent" path="*"/></param>
|
||||
/// <inheritdoc cref="Subtract(IStatistic)"/>
|
||||
internal void Subtract(IStatistic stats, bool enablePropertyChangedEvent)
|
||||
{
|
||||
EnablePropertyChangeEvent = enablePropertyChangedEvent;
|
||||
Subtract(stats);
|
||||
EnablePropertyChangeEvent = true;
|
||||
}
|
||||
#pragma warning restore CS1573
|
||||
|
||||
/// <summary>
|
||||
/// Subtract the results of the supplied Statistics objects to this Statistics object.
|
||||
/// </summary>
|
||||
/// <param name="stats">Statistics Item to subtract</param>
|
||||
public void Subtract(IEnumerable<IStatistic> stats)
|
||||
{
|
||||
foreach (Statistic stat in stats)
|
||||
{
|
||||
EnablePropertyChangeEvent = stat == stats.Last();
|
||||
Subtract(stat);
|
||||
}
|
||||
}
|
||||
|
||||
/// <param name="MainStat">Statistics object to clone</param>
|
||||
/// <param name="stats"><inheritdoc cref="Subtract(IStatistic)"/></param>
|
||||
/// <returns>Clone of the <paramref name="MainStat"/> object with the <paramref name="stats"/> subtracted from it.</returns>
|
||||
/// <inheritdoc cref="Subtract(IStatistic)"/>
|
||||
public static Statistic Subtract(IStatistic MainStat, IStatistic stats)
|
||||
{
|
||||
var ret = MainStat.Clone();
|
||||
ret.Subtract(stats);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endregion Subtract
|
||||
|
||||
|
||||
/// <inheritdoc cref="IStatistic.Clone"/>
|
||||
public Statistic Clone() => new Statistic(this);
|
||||
|
||||
object ICloneable.Clone() => new Statistic(this);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace RoboSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// RoboCopy switches for how to react if a copy/move operation errors
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/RetryOptions"/>
|
||||
/// </remarks>
|
||||
public class RetryOptions : ICloneable
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Create new RetryOptions with Default Settings
|
||||
/// </summary>
|
||||
public RetryOptions() { }
|
||||
|
||||
/// <summary>
|
||||
/// Clone a RetryOptions Object
|
||||
/// </summary>
|
||||
/// <param name="options">RetryOptions object to clone</param>
|
||||
public RetryOptions(RetryOptions options)
|
||||
{
|
||||
WaitForSharenames = options.WaitForSharenames;
|
||||
SaveToRegistry = options.SaveToRegistry;
|
||||
RetryWaitTime = options.RetryWaitTime;
|
||||
RetryCount = options.RetryCount;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="RetryOptions.RetryOptions(RetryOptions)"/>
|
||||
public RetryOptions Clone() => new RetryOptions(this);
|
||||
|
||||
object ICloneable.Clone() => Clone();
|
||||
|
||||
#endregion
|
||||
|
||||
internal const string RETRY_COUNT = "/R:{0} ";
|
||||
internal const string RETRY_WAIT_TIME = "/W:{0} ";
|
||||
internal const string SAVE_TO_REGISTRY = "/REG ";
|
||||
internal const string WAIT_FOR_SHARENAMES = "/TBD ";
|
||||
|
||||
private int retryCount = 0;
|
||||
private int retryWaitTime = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the number of retries N on failed copies (default is 0).
|
||||
/// [/R:N]
|
||||
/// </summary>
|
||||
public virtual int RetryCount
|
||||
{
|
||||
get { return retryCount; }
|
||||
set { retryCount = value; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Specifies the wait time N in seconds between retries (default is 30).
|
||||
/// [/W:N]
|
||||
/// </summary>
|
||||
public virtual int RetryWaitTime
|
||||
{
|
||||
get { return retryWaitTime; }
|
||||
set { retryWaitTime = value; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Saves RetryCount and RetryWaitTime in the Registry as default settings.
|
||||
/// [/REG]
|
||||
/// </summary>
|
||||
public virtual bool SaveToRegistry { get; set; }
|
||||
/// <summary>
|
||||
/// Wait for sharenames to be defined.
|
||||
/// [/TBD]
|
||||
/// </summary>
|
||||
public virtual bool WaitForSharenames { get; set; }
|
||||
|
||||
internal string Parse()
|
||||
{
|
||||
var options = new StringBuilder();
|
||||
|
||||
options.Append(string.Format(RETRY_COUNT, RetryCount));
|
||||
options.Append(string.Format(RETRY_WAIT_TIME, RetryWaitTime));
|
||||
|
||||
if (SaveToRegistry)
|
||||
options.Append(SAVE_TO_REGISTRY);
|
||||
if (WaitForSharenames)
|
||||
options.Append(WAIT_FOR_SHARENAMES);
|
||||
|
||||
return options.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combine this object with another RetryOptions object. <br/>
|
||||
/// Any properties marked as true take priority. IEnumerable items are combined. <br/>
|
||||
/// String Values will only be replaced if the primary object has a null/empty value for that property.
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
public void Merge(RetryOptions options)
|
||||
{
|
||||
RetryCount = RetryCount.GetGreaterVal(options.RetryCount);
|
||||
RetryWaitTime = RetryWaitTime.GetGreaterVal(options.RetryWaitTime);
|
||||
WaitForSharenames |= options.WaitForSharenames;
|
||||
SaveToRegistry |= options.SaveToRegistry;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,883 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using RoboSharp.Interfaces;
|
||||
using RoboSharp.EventArgObjects;
|
||||
|
||||
namespace RoboSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper for the RoboCopy process
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/RoboCommand"/>
|
||||
/// </remarks>
|
||||
public class RoboCommand : IDisposable, IRoboCommand, ICloneable
|
||||
{
|
||||
/// <summary>
|
||||
/// The base <see cref="RoboCommandFactory"/> object provided by the RoboSharp library.
|
||||
/// </summary>
|
||||
public static RoboCommandFactory Factory { get; } = new RoboCommandFactory();
|
||||
|
||||
#region < Constructors >
|
||||
|
||||
/// <summary>Create a new RoboCommand object</summary>
|
||||
public RoboCommand()
|
||||
{
|
||||
InitClassProperties();
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new RoboCommand object with the provided settings.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="Init"/>
|
||||
/// <inheritdoc cref="CopyOptions.CopyOptions(string, string, CopyOptions.CopyActionFlags)"/>
|
||||
/// <inheritdoc cref="SelectionOptions.SelectionOptions(SelectionOptions.SelectionFlags)"/>
|
||||
public RoboCommand(string source, string destination, CopyOptions.CopyActionFlags copyActionFlags, SelectionOptions.SelectionFlags selectionFlags = SelectionOptions.SelectionFlags.Default)
|
||||
{
|
||||
InitClassProperties();
|
||||
Init("", true, source, destination);
|
||||
this.copyOptions.ApplyActionFlags(copyActionFlags);
|
||||
this.selectionOptions.ApplySelectionFlags(selectionFlags);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Init"/>
|
||||
public RoboCommand(string name, bool stopIfDisposing = true)
|
||||
{
|
||||
InitClassProperties();
|
||||
Init(name, stopIfDisposing);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Init"/>
|
||||
public RoboCommand(string source, string destination, bool stopIfDisposing = true)
|
||||
{
|
||||
InitClassProperties();
|
||||
Init("", stopIfDisposing, source, destination);
|
||||
|
||||
}
|
||||
/// <inheritdoc cref="Init"/>
|
||||
public RoboCommand(string source, string destination, string name, bool stopIfDisposing = true)
|
||||
{
|
||||
InitClassProperties();
|
||||
Init(name, stopIfDisposing, source, destination);
|
||||
}
|
||||
|
||||
/// <remarks> Each of the Options objects can be specified within this constructor. If left = null, a new object will be generated using the default options for that object. </remarks>
|
||||
/// <inheritdoc cref="Init"/>
|
||||
public RoboCommand(string name, string source = null, string destination = null, bool StopIfDisposing = true, RoboSharpConfiguration configuration = null, CopyOptions copyOptions = null, SelectionOptions selectionOptions = null, RetryOptions retryOptions = null, LoggingOptions loggingOptions = null, JobOptions jobOptions = null)
|
||||
{
|
||||
this.configuration = configuration ?? new RoboSharpConfiguration();
|
||||
this.copyOptions = copyOptions ?? new CopyOptions();
|
||||
this.selectionOptions = selectionOptions ?? new SelectionOptions();
|
||||
this.retryOptions = retryOptions ?? new RetryOptions();
|
||||
this.loggingOptions = loggingOptions ?? new LoggingOptions();
|
||||
this.jobOptions = jobOptions ?? new JobOptions();
|
||||
Init(name, StopIfDisposing, source ?? CopyOptions.Source, destination ?? CopyOptions.Destination);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new RoboCommand with identical options at this RoboCommand
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If Desired, the new RoboCommand object will share some of the same Property objects as the input <paramref name="command"/>.
|
||||
/// For Example, that means that if a SelectionOption property changes, it will affect both RoboCommand objects since the <see cref="SelectionOptions"/> property is shared between them. <br/>
|
||||
/// If the Link* options are set to FALSE (default), then it will create new property objects whose settings match the current settings of <paramref name="command"/>.
|
||||
/// <para/> Properties that can be linked: <br/>
|
||||
/// <see cref="Configuration"/> ( Linked by default ) <br/>
|
||||
/// <see cref="RetryOptions"/> ( Linked by default )<br/>
|
||||
/// <see cref="SelectionOptions"/><br/>
|
||||
/// <see cref="LoggingOptions"/><br/>
|
||||
/// <see cref="JobOptions"/><br/>
|
||||
/// </remarks>
|
||||
/// <param name="command">RoboCommand to Clone</param>
|
||||
/// <param name="NewSource">Specify a new source if desired. If left as null, will use Source from <paramref name="command"/></param>
|
||||
/// <param name="NewDestination">Specify a new source if desired. If left as null, will use Destination from <paramref name="command"/></param>
|
||||
/// <param name="LinkConfiguration">Link the <see cref="Configuration"/> of the two commands ( True Default )</param>
|
||||
/// <param name="LinkLoggingOptions">Link the <see cref="LoggingOptions"/> of the two commands</param>
|
||||
/// <param name="LinkRetryOptions">Link the <see cref="RetryOptions"/> of the two commands ( True Default )</param>
|
||||
/// <param name="LinkSelectionOptions">Link the <see cref="SelectionOptions"/> of the two commands</param>
|
||||
/// <param name="LinkJobOptions">Link the <see cref="SelectionOptions"/> of the two commands</param>
|
||||
public RoboCommand(RoboCommand command, string NewSource = null, string NewDestination = null, bool LinkConfiguration = true, bool LinkRetryOptions = true, bool LinkSelectionOptions = false, bool LinkLoggingOptions = false, bool LinkJobOptions = false)
|
||||
{
|
||||
Name = command.Name;
|
||||
StopIfDisposing = command.StopIfDisposing;
|
||||
|
||||
configuration = LinkConfiguration ? command.configuration : command.configuration.Clone();
|
||||
copyOptions = new CopyOptions(command.CopyOptions, NewSource, NewDestination);
|
||||
JobOptions = LinkJobOptions ? command.jobOptions : command.jobOptions.Clone();
|
||||
loggingOptions = LinkLoggingOptions ? command.loggingOptions : command.loggingOptions.Clone();
|
||||
retryOptions = LinkRetryOptions ? command.retryOptions : command.retryOptions.Clone();
|
||||
selectionOptions = LinkSelectionOptions ? command.selectionOptions : command.SelectionOptions.Clone();
|
||||
}
|
||||
|
||||
/// <summary>Create a new RoboCommand object</summary>
|
||||
/// <param name="name"><inheritdoc cref="Name" path="*"/></param>
|
||||
/// <param name="stopIfDisposing"><inheritdoc cref="StopIfDisposing" path="*"/></param>
|
||||
/// <param name="source"><inheritdoc cref="RoboSharp.CopyOptions.Source" path="*"/></param>
|
||||
/// <param name="destination"><inheritdoc cref="RoboSharp.CopyOptions.Destination" path="*"/></param>
|
||||
private void Init(string name = "", bool stopIfDisposing = true, string source = "", string destination = "")
|
||||
{
|
||||
Name = name;
|
||||
StopIfDisposing = stopIfDisposing;
|
||||
CopyOptions.Source = source;
|
||||
CopyOptions.Destination = destination;
|
||||
}
|
||||
|
||||
private void InitClassProperties()
|
||||
{
|
||||
copyOptions = new CopyOptions();
|
||||
selectionOptions = new SelectionOptions();
|
||||
retryOptions = new RetryOptions();
|
||||
loggingOptions = new LoggingOptions();
|
||||
configuration = new RoboSharpConfiguration();
|
||||
jobOptions = new JobOptions();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.RoboCommand(RoboCommand, string, string, bool, bool, bool, bool, bool)"/>
|
||||
public RoboCommand Clone(string NewSource = null, string NewDestination = null, bool LinkConfiguration = true, bool LinkRetryOptions = true, bool LinkSelectionOptions = false, bool LinkLoggingOptions = false, bool LinkJobOptions = false)
|
||||
=> new RoboCommand(this, NewSource, NewDestination, LinkConfiguration, LinkRetryOptions, LinkSelectionOptions, LinkLoggingOptions, LinkJobOptions);
|
||||
|
||||
object ICloneable.Clone() => new RoboCommand(this, null, null, false, false, false, false, false);
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Private Vars >
|
||||
|
||||
// set up in Constructor
|
||||
private CopyOptions copyOptions;
|
||||
private SelectionOptions selectionOptions;
|
||||
private RetryOptions retryOptions;
|
||||
private LoggingOptions loggingOptions;
|
||||
private RoboSharpConfiguration configuration;
|
||||
private JobOptions jobOptions;
|
||||
|
||||
// Modified while running
|
||||
private Process process;
|
||||
private Task backupTask;
|
||||
private bool hasError;
|
||||
//private bool hasExited; //No longer evaluated
|
||||
private bool isPaused;
|
||||
private bool isRunning;
|
||||
private bool isCancelled;
|
||||
private Results.ResultsBuilder resultsBuilder;
|
||||
private Results.RoboCopyResults results;
|
||||
/// <summary> Stores the LastData processed by <see cref="process_OutputDataReceived(object, DataReceivedEventArgs)"/> </summary>
|
||||
private string LastDataReceived = "";
|
||||
|
||||
#endregion Private Vars
|
||||
|
||||
#region < Public Vars >
|
||||
|
||||
/// <summary> ID Tag for the job - Allows consumers to find/sort/remove/etc commands within a list via string comparison</summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary> Value indicating if process is currently paused </summary>
|
||||
public bool IsPaused { get { return isPaused; } }
|
||||
/// <summary> Value indicating if process is currently running </summary>
|
||||
public bool IsRunning { get { return isRunning; } }
|
||||
/// <summary> Value indicating if process was Cancelled </summary>
|
||||
public bool IsCancelled { get { return isCancelled; } }
|
||||
/// <summary> TRUE if <see cref="CopyOptions.RunHours"/> is set up (Copy Operation is scheduled to only operate within specified timeframe). Otherwise False. </summary>
|
||||
public bool IsScheduled { get => !String.IsNullOrWhiteSpace(CopyOptions.RunHours); }
|
||||
/// <summary> Get the parameters string passed to RoboCopy based on the current setup </summary>
|
||||
public string CommandOptions { get { return GenerateParameters(); } }
|
||||
/// <inheritdoc cref="RoboSharp.CopyOptions"/>
|
||||
public CopyOptions CopyOptions
|
||||
{
|
||||
get { return copyOptions; }
|
||||
set { copyOptions = value ?? copyOptions; }
|
||||
}
|
||||
/// <inheritdoc cref="RoboSharp.SelectionOptions"/>
|
||||
public SelectionOptions SelectionOptions
|
||||
{
|
||||
get { return selectionOptions; }
|
||||
set { selectionOptions = value ?? selectionOptions; }
|
||||
}
|
||||
/// <inheritdoc cref="RoboSharp.RetryOptions"/>
|
||||
public RetryOptions RetryOptions
|
||||
{
|
||||
get { return retryOptions; }
|
||||
set { retryOptions = value ?? retryOptions; }
|
||||
}
|
||||
/// <inheritdoc cref="RoboSharp.LoggingOptions"/>
|
||||
public LoggingOptions LoggingOptions
|
||||
{
|
||||
get { return loggingOptions; }
|
||||
set { loggingOptions = value ?? loggingOptions; }
|
||||
}
|
||||
/// <inheritdoc cref="RoboSharp.JobOptions"/>
|
||||
public JobOptions JobOptions
|
||||
{
|
||||
get { return jobOptions; }
|
||||
set { jobOptions = value ?? jobOptions; }
|
||||
}
|
||||
/// <inheritdoc cref="RoboSharp.RoboSharpConfiguration"/>
|
||||
public RoboSharpConfiguration Configuration
|
||||
{
|
||||
get { return configuration; }
|
||||
}
|
||||
/// <inheritdoc cref="Results.ProgressEstimator"/>
|
||||
/// <remarks>
|
||||
/// A new <see cref="Results.ProgressEstimator"/> object is created every time the <see cref="Start"/> method is called, but will not be created until called for the first time.
|
||||
/// </remarks>
|
||||
internal Results.ProgressEstimator ProgressEstimator { get; private set; }
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.ProgressEstimator"/>
|
||||
public IProgressEstimator IProgressEstimator => this.ProgressEstimator;
|
||||
|
||||
/// <summary>
|
||||
/// Value indicating if the process should be killed when the <see cref="Dispose()"/> method is called.<br/>
|
||||
/// For example, if the RoboCopy process should exit when the program exits, this should be set to TRUE (default).
|
||||
/// </summary>
|
||||
public bool StopIfDisposing { get; set; } = true;
|
||||
|
||||
#endregion Public Vars
|
||||
|
||||
#region < Events >
|
||||
|
||||
/// <summary>Handles <see cref="OnFileProcessed"/></summary>
|
||||
public delegate void FileProcessedHandler(IRoboCommand sender, FileProcessedEventArgs e);
|
||||
/// <summary>Occurs each time a new item has started processing</summary>
|
||||
public event FileProcessedHandler OnFileProcessed;
|
||||
|
||||
/// <summary>Handles <see cref="OnCommandError"/></summary>
|
||||
public delegate void CommandErrorHandler(IRoboCommand sender, CommandErrorEventArgs e);
|
||||
/// <summary>Occurs when an error occurs while generating the command that prevents the RoboCopy process from starting.</summary>
|
||||
public event CommandErrorHandler OnCommandError;
|
||||
|
||||
/// <summary>Handles <see cref="OnError"/></summary>
|
||||
public delegate void ErrorHandler(IRoboCommand sender, ErrorEventArgs e);
|
||||
/// <summary>Occurs an error is detected by RoboCopy </summary>
|
||||
public event ErrorHandler OnError;
|
||||
|
||||
/// <summary>Handles <see cref="OnCommandCompleted"/></summary>
|
||||
public delegate void CommandCompletedHandler(IRoboCommand sender, RoboCommandCompletedEventArgs e);
|
||||
/// <summary>Occurs when the RoboCopy process has finished executing and results are available.</summary>
|
||||
public event CommandCompletedHandler OnCommandCompleted;
|
||||
|
||||
/// <summary>Handles <see cref="OnCopyProgressChanged"/></summary>
|
||||
public delegate void CopyProgressHandler(IRoboCommand sender, CopyProgressEventArgs e);
|
||||
/// <summary>Occurs each time the current item's progress is updated</summary>
|
||||
public event CopyProgressHandler OnCopyProgressChanged;
|
||||
|
||||
/// <summary>Handles <see cref="OnProgressEstimatorCreated"/></summary>
|
||||
public delegate void ProgressUpdaterCreatedHandler(IRoboCommand sender, ProgressEstimatorCreatedEventArgs e);
|
||||
/// <summary>
|
||||
/// Occurs when a <see cref="Results.ProgressEstimator"/> is created during <see cref="Start"/>, allowing binding to occur within the event subscriber. <br/>
|
||||
/// This event will occur once per Start.
|
||||
/// </summary>
|
||||
public event ProgressUpdaterCreatedHandler OnProgressEstimatorCreated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs if the RoboCommand task is stopped due to an unhandled exception. Occurs instead of <see cref="OnCommandCompleted"/>
|
||||
/// </summary>
|
||||
public event UnhandledExceptionEventHandler TaskFaulted;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Pause / Stop / Resume >
|
||||
|
||||
/// <summary>Pause execution of the RoboCopy process when <see cref="IsPaused"/> == false</summary>
|
||||
public virtual void Pause()
|
||||
{
|
||||
if (process != null && !process.HasExited && isPaused == false)
|
||||
{
|
||||
Debugger.Instance.DebugMessage("RoboCommand execution paused.");
|
||||
isPaused = process.Suspend();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Resume execution of the RoboCopy process when <see cref="IsPaused"/> == true</summary>
|
||||
public virtual void Resume()
|
||||
{
|
||||
if (process != null && !process.HasExited && isPaused == true)
|
||||
{
|
||||
Debugger.Instance.DebugMessage("RoboCommand execution resumed.");
|
||||
process.Resume();
|
||||
isPaused = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Immediately Kill the RoboCopy process</summary>
|
||||
public virtual void Stop() => Stop(false);
|
||||
|
||||
private void Stop(bool DisposeProcess)
|
||||
{
|
||||
//Note: This previously checked for CopyOptions.RunHours.IsNullOrWhiteSpace() == TRUE prior to issuing the stop command
|
||||
//If the removal of that check broke your application, please create a new issue thread on the repo.
|
||||
if (process != null)
|
||||
{
|
||||
if (!isCancelled && (!process?.HasExited ?? true))
|
||||
{
|
||||
process?.Kill();
|
||||
isCancelled = true;
|
||||
}
|
||||
//hasExited = true;
|
||||
if (DisposeProcess)
|
||||
{
|
||||
process?.Dispose();
|
||||
process = null;
|
||||
}
|
||||
}
|
||||
isPaused = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Start Methods >
|
||||
|
||||
#if NET45_OR_GREATER || NETSTANDARD2_0_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
/// <summary>
|
||||
/// awaits <see cref="Start(string, string, string)"/> then returns the results.
|
||||
/// </summary>
|
||||
/// <returns>Returns the RoboCopy results once RoboCopy has finished executing.</returns>
|
||||
/// <inheritdoc cref="Start(string, string, string)"/>
|
||||
public virtual async Task<Results.RoboCopyResults> StartAsync(string domain = "", string username = "", string password = "")
|
||||
{
|
||||
await Start(domain, username, password);
|
||||
return GetResults();
|
||||
}
|
||||
|
||||
/// <summary>awaits <see cref="Start_ListOnly(string, string, string)"/> then returns the results.</summary>
|
||||
/// <returns>Returns the List-Only results once RoboCopy has finished executing.</returns>
|
||||
/// <inheritdoc cref="Start_ListOnly(string, string, string)"/>
|
||||
public virtual async Task<Results.RoboCopyResults> StartAsync_ListOnly(string domain = "", string username = "", string password = "")
|
||||
{
|
||||
await Start_ListOnly(domain, username, password);
|
||||
return GetResults();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Run the currently selected options in ListOnly mode by setting <see cref="LoggingOptions.ListOnly"/> = TRUE
|
||||
/// </summary>
|
||||
/// <returns>Task that awaits <see cref="Start(string, string, string)"/>, then resets the ListOnly option to original value.</returns>
|
||||
/// <inheritdoc cref="Start(string, string, string)"/>
|
||||
public virtual async Task Start_ListOnly(string domain = "", string username = "", string password = "")
|
||||
{
|
||||
bool _listOnly = LoggingOptions.ListOnly;
|
||||
LoggingOptions.ListOnly = true;
|
||||
await Start(domain, username, password);
|
||||
LoggingOptions.ListOnly = _listOnly;
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start the RoboCopy Process.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If overridden by a derived class, the override affects all Start* methods within RoboCommand. Base.Start() must be called to start the robocopy process.
|
||||
/// </remarks>
|
||||
/// <param name="domain"></param>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="password"></param>
|
||||
/// <returns>Returns a task that reports when the RoboCopy process has finished executing.</returns>
|
||||
/// <exception cref="InvalidOperationException"/>
|
||||
public virtual Task Start(string domain = "", string username = "", string password = "")
|
||||
{
|
||||
if (process != null | IsRunning) throw new InvalidOperationException("RoboCommand.Start() method cannot be called while process is already running / IsRunning = true.");
|
||||
Debugger.Instance.DebugMessage("RoboCommand started execution.");
|
||||
hasError = false;
|
||||
isCancelled = false;
|
||||
isPaused = false;
|
||||
isRunning = true;
|
||||
|
||||
resultsBuilder = new Results.ResultsBuilder(this);
|
||||
results = null;
|
||||
|
||||
#region Check Source and Destination
|
||||
|
||||
#if NET40_OR_GREATER
|
||||
// Authenticate on Target Server -- Create user if username is provided, else null
|
||||
ImpersonatedUser impersonation = username.IsNullOrWhiteSpace() ? null : impersonation = new ImpersonatedUser(username, domain, password);
|
||||
#endif
|
||||
// make sure source path is valid
|
||||
if (!Directory.Exists(CopyOptions.Source))
|
||||
{
|
||||
Debugger.Instance.DebugMessage("The Source directory does not exist.");
|
||||
hasError = true;
|
||||
OnCommandError?.Invoke(this, new CommandErrorEventArgs(new DirectoryNotFoundException("The Source directory does not exist.")));
|
||||
Debugger.Instance.DebugMessage("RoboCommand execution stopped due to error.");
|
||||
}
|
||||
|
||||
#region Create Destination Directory
|
||||
|
||||
//Check that the Destination Drive is accessible insteead [fixes #106]
|
||||
try
|
||||
{
|
||||
//Check if the destination drive is accessible -> should not cause exception [Fix for #106]
|
||||
DirectoryInfo dInfo = new DirectoryInfo(CopyOptions.Destination).Root;
|
||||
if (!dInfo.Exists)
|
||||
{
|
||||
Debugger.Instance.DebugMessage("The destination drive does not exist.");
|
||||
hasError = true;
|
||||
OnCommandError?.Invoke(this, new CommandErrorEventArgs(new DirectoryNotFoundException("The Destination Drive is invalid.")));
|
||||
Debugger.Instance.DebugMessage("RoboCommand execution stopped due to error.");
|
||||
}
|
||||
//If not list only, verify that drive has write access -> should cause exception if no write access [Fix #101]
|
||||
if (!LoggingOptions.ListOnly & !hasError)
|
||||
{
|
||||
dInfo = Directory.CreateDirectory(CopyOptions.Destination);
|
||||
if (!dInfo.Exists)
|
||||
{
|
||||
Debugger.Instance.DebugMessage("The destination directory does not exist.");
|
||||
hasError = true;
|
||||
OnCommandError?.Invoke(this, new CommandErrorEventArgs(new DirectoryNotFoundException("Unable to create Destination Folder. Check Write Access.")));
|
||||
Debugger.Instance.DebugMessage("RoboCommand execution stopped due to error.");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debugger.Instance.DebugMessage(ex.Message);
|
||||
hasError = true;
|
||||
OnCommandError?.Invoke(this, new CommandErrorEventArgs("The Destination directory is invalid.", ex));
|
||||
Debugger.Instance.DebugMessage("RoboCommand execution stopped due to error.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#if NET40_OR_GREATER
|
||||
//Dispose Authentification
|
||||
impersonation?.Dispose();
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
if (hasError)
|
||||
{
|
||||
isRunning = false;
|
||||
return Task.Delay(5);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Raise EstimatorCreatedEvent to alert consumers that the Estimator can now be bound to
|
||||
ProgressEstimator = resultsBuilder.Estimator;
|
||||
OnProgressEstimatorCreated?.Invoke(this, new ProgressEstimatorCreatedEventArgs(resultsBuilder.Estimator));
|
||||
return GetRoboCopyTask(resultsBuilder, domain, username, password);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start the RoboCopy process and the watcher task
|
||||
/// </summary>
|
||||
/// <returns>The continuation task that cleans up after the task that watches RoboCopy has finished executing.</returns>
|
||||
/// <exception cref="InvalidOperationException"/>
|
||||
private Task GetRoboCopyTask(Results.ResultsBuilder resultsBuilder, string domain = "", string username = "", string password = "")
|
||||
{
|
||||
if (process != null) throw new InvalidOperationException("Cannot start a new RoboCopy Process while this RoboCommand is already running.");
|
||||
|
||||
isRunning = true;
|
||||
DateTime StartTime = DateTime.Now;
|
||||
|
||||
backupTask = Task.Run( async () =>
|
||||
{
|
||||
|
||||
process = new Process();
|
||||
|
||||
//Declare Encoding
|
||||
process.StartInfo.StandardOutputEncoding = Configuration.StandardOutputEncoding;
|
||||
process.StartInfo.StandardErrorEncoding = Configuration.StandardErrorEncoding;
|
||||
|
||||
if (!string.IsNullOrEmpty(domain))
|
||||
{
|
||||
Debugger.Instance.DebugMessage(string.Format("RoboCommand running under domain - {0}", domain));
|
||||
process.StartInfo.Domain = domain;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(username))
|
||||
{
|
||||
Debugger.Instance.DebugMessage(string.Format("RoboCommand running under username - {0}", username));
|
||||
process.StartInfo.UserName = username;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(password))
|
||||
{
|
||||
Debugger.Instance.DebugMessage("RoboCommand password entered.");
|
||||
var ssPwd = new System.Security.SecureString();
|
||||
|
||||
for (int x = 0; x < password.Length; x++)
|
||||
{
|
||||
ssPwd.AppendChar(password[x]);
|
||||
}
|
||||
|
||||
process.StartInfo.Password = ssPwd;
|
||||
}
|
||||
|
||||
Debugger.Instance.DebugMessage("Setting RoboCopy process up...");
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
process.StartInfo.RedirectStandardError = true;
|
||||
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
process.StartInfo.CreateNoWindow = true;
|
||||
process.StartInfo.FileName = Configuration.RoboCopyExe;
|
||||
if (resultsBuilder != null)
|
||||
{
|
||||
resultsBuilder.Source = CopyOptions.Source;
|
||||
resultsBuilder.Destination = CopyOptions.Destination;
|
||||
resultsBuilder.CommandOptions = GenerateParameters();
|
||||
}
|
||||
process.StartInfo.Arguments = resultsBuilder?.CommandOptions ?? GenerateParameters();
|
||||
process.OutputDataReceived += process_OutputDataReceived;
|
||||
process.ErrorDataReceived += process_ErrorDataReceived;
|
||||
process.EnableRaisingEvents = true;
|
||||
|
||||
//Setup the WaitForExitAsync Task
|
||||
//hasExited = false;
|
||||
var ProcessExitedAsync = new TaskCompletionSource<object>();
|
||||
process.Exited += (sender, args) =>
|
||||
{
|
||||
process.WaitForExit(); //This looks counter-intuitive, but is required to ensure all output lines have been read before building results.
|
||||
//hasExited = true;
|
||||
ProcessExitedAsync.TrySetResult(null);
|
||||
};
|
||||
|
||||
//Start the Task
|
||||
Debugger.Instance.DebugMessage("RoboCopy process started.");
|
||||
process.Start();
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
_ = await ProcessExitedAsync.Task; //This allows task to release the thread to perform other work
|
||||
if (resultsBuilder != null) // Only replace results if a ResultsBuilder was supplied (Not supplied when saving as a JobFile)
|
||||
{
|
||||
results = resultsBuilder.BuildResults(process?.ExitCode ?? -1);
|
||||
}
|
||||
Debugger.Instance.DebugMessage("RoboCopy process exited.");
|
||||
}, CancellationToken.None);
|
||||
|
||||
Task continueWithTask = backupTask.ContinueWith((continuation) => // this task always runs
|
||||
{
|
||||
bool WasCancelled = process.ExitCode == -1;
|
||||
Stop(true); //Ensure process is disposed of - Sets IsRunning flags to false
|
||||
|
||||
//Run Post-Processing of the Generated JobFile if one was created.
|
||||
JobOptions.RunPostProcessing(this);
|
||||
|
||||
isRunning = false; //Now that all processing is complete, IsRunning should be reported as false.
|
||||
|
||||
if (continuation.IsFaulted && !WasCancelled) // If some fault occurred while processing, throw the exception to caller
|
||||
{
|
||||
TaskFaulted?.Invoke(this, new UnhandledExceptionEventArgs(continuation.Exception, true));
|
||||
throw continuation.Exception;
|
||||
}
|
||||
//Raise event announcing results are available
|
||||
if (!hasError && resultsBuilder != null)
|
||||
{
|
||||
results.StartTime = StartTime;
|
||||
results.EndTime = DateTime.Now;
|
||||
results.TimeSpan = results.EndTime.Subtract(results.StartTime);
|
||||
OnCommandCompleted?.Invoke(this, new RoboCommandCompletedEventArgs(results));
|
||||
}
|
||||
}, CancellationToken.None);
|
||||
|
||||
return continueWithTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save this RoboCommand's options to a new RoboCopyJob ( *.RCJ ) file. <br/>
|
||||
/// Note: This will not save the path submitted into <see cref="JobOptions.FilePath"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Job Files don't care if the Source/Destination are invalid, since they just save the command values to a file.
|
||||
/// </remarks>
|
||||
/// <param name="path"><inheritdoc cref="JobOptions.FilePath"/></param>
|
||||
/// <param name="IncludeSource">Save <see cref="CopyOptions.Source"/> into the RCJ file.</param>
|
||||
/// <param name="IncludeDestination">Save <see cref="CopyOptions.Destination"/> into the RCJ file.</param>
|
||||
/// <inheritdoc cref = "Start(string, string, string)" />
|
||||
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
|
||||
public async Task SaveAsJobFile(string path, bool IncludeSource = false, bool IncludeDestination = false, string domain = "", string username = "", string password = "")
|
||||
#pragma warning restore CS1573
|
||||
{
|
||||
//If currently running and this is called, clone the command, then run the save method against the clone.
|
||||
if (process != null)
|
||||
{
|
||||
var cmd = this.Clone();
|
||||
cmd.StopIfDisposing = true;
|
||||
try
|
||||
{
|
||||
await cmd.SaveAsJobFile(path, IncludeSource, IncludeDestination, domain, username, password);
|
||||
}
|
||||
catch(Exception Fault)
|
||||
{
|
||||
cmd.Dispose();
|
||||
throw Fault;
|
||||
}
|
||||
cmd.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
bool _QUIT = JobOptions.PreventCopyOperation;
|
||||
string _PATH = JobOptions.FilePath;
|
||||
bool _NODD = JobOptions.NoDestinationDirectory;
|
||||
bool _NOSD = JobOptions.NoSourceDirectory;
|
||||
|
||||
JobOptions.FilePath = path;
|
||||
JobOptions.NoSourceDirectory = !IncludeSource;
|
||||
JobOptions.NoDestinationDirectory = !IncludeDestination;
|
||||
JobOptions.PreventCopyOperation = true;
|
||||
Exception e = null;
|
||||
try
|
||||
{
|
||||
await GetRoboCopyTask(null, domain, username, password); //This should take approximately 1-2 seconds at most
|
||||
}
|
||||
catch (Exception Fault)
|
||||
{
|
||||
e = Fault;
|
||||
}
|
||||
finally
|
||||
{
|
||||
//Restore Original Settings
|
||||
JobOptions.FilePath = _PATH;
|
||||
JobOptions.NoSourceDirectory = _NOSD;
|
||||
JobOptions.NoDestinationDirectory = _NODD;
|
||||
JobOptions.PreventCopyOperation = _QUIT;
|
||||
//If an exception occured, rethrow it.
|
||||
if (e != null) throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Process Event Handlers >
|
||||
|
||||
/// <summary> Occurs when the Process reports an error prior to starting the robocopy process, not an 'error' from Robocopy </summary>
|
||||
void process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
if (OnCommandError != null && !e.Data.IsNullOrWhiteSpace())
|
||||
{
|
||||
hasError = true;
|
||||
OnCommandError(this, new CommandErrorEventArgs(e.Data, null));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> React to Process.StandardOutput </summary>
|
||||
void process_OutputDataReceived(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
var lastData = resultsBuilder.LastLine;
|
||||
resultsBuilder?.AddOutput(e.Data);
|
||||
|
||||
if (e.Data == null) return; // Nothing to do
|
||||
var data = e.Data.Trim().Replace("\0", ""); // ?
|
||||
if (data.IsNullOrWhiteSpace()) return; // Nothing to do
|
||||
if (LastDataReceived == data) return; // Sometimes RoboCopy reports same item multiple times - Typically for Progress indicators
|
||||
LastDataReceived = data;
|
||||
|
||||
if (Regex.IsMatch(data, "^[0-9]+[.]?[0-9]*%", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace))
|
||||
{
|
||||
var currentFile = resultsBuilder?.Estimator?.CurrentFile;
|
||||
var currentDir = resultsBuilder?.Estimator?.CurrentDir;
|
||||
|
||||
//Increment ProgressEstimator
|
||||
if (data == "100%")
|
||||
resultsBuilder?.Estimator?.AddFileCopied(currentFile);
|
||||
else
|
||||
resultsBuilder?.Estimator?.SetCopyOpStarted();
|
||||
|
||||
// copy progress data -> Use the CurrentFile and CurrentDir from the ResultsBuilder
|
||||
OnCopyProgressChanged?.Invoke(this,
|
||||
new CopyProgressEventArgs(
|
||||
Convert.ToDouble(data.Replace("%", ""), CultureInfo.InvariantCulture),
|
||||
currentFile, currentDir
|
||||
));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//Parse the string to determine which event to raise
|
||||
var splitData = data.Split(new char[] { '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (splitData.Length == 2) // Directory
|
||||
{
|
||||
// Regex to parse the string for FileCount, Path, and Type (Description)
|
||||
Regex DirRegex = new Regex("^(?<Type>\\*?[a-zA-Z]{0,10}\\s?[a-zA-Z]{0,3})\\s*(?<FileCount>[-]{0,1}[0-9]{1,100})\\t(?<Path>.+)", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
|
||||
|
||||
var file = new ProcessedFileInfo();
|
||||
file.FileClassType = FileClassType.NewDir;
|
||||
|
||||
if (DirRegex.IsMatch(data))
|
||||
{
|
||||
//New Method - Parsed using Regex
|
||||
GroupCollection MatchData = DirRegex.Match(data).Groups;
|
||||
file.FileClass = MatchData["Type"].Value.Trim();
|
||||
if (file.FileClass == "") file.FileClass = configuration.LogParsing_ExistingDir;
|
||||
long.TryParse(MatchData["FileCount"].Value, out long size);
|
||||
file.Size = size;
|
||||
file.Name = MatchData["Path"].Value.Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
//Old Method -> Left Intact for other language compatibilty / unforseen cases
|
||||
file.FileClass = "New Dir";
|
||||
long.TryParse(splitData[0].Replace("New Dir", "").Trim(), out long size);
|
||||
file.Size = size;
|
||||
file.Name = splitData[1];
|
||||
}
|
||||
|
||||
resultsBuilder?.Estimator?.AddDir(file, !this.LoggingOptions.ListOnly);
|
||||
OnFileProcessed?.Invoke(this, new FileProcessedEventArgs(file));
|
||||
}
|
||||
else if (splitData.Length == 3) // File
|
||||
{
|
||||
var file = new ProcessedFileInfo();
|
||||
file.FileClass = splitData[0].Trim();
|
||||
file.FileClassType = FileClassType.File;
|
||||
long size = 0;
|
||||
long.TryParse(splitData[1].Trim(), out size);
|
||||
file.Size = size;
|
||||
file.Name = splitData[2];
|
||||
resultsBuilder?.Estimator?.AddFile(file, !LoggingOptions.ListOnly);
|
||||
OnFileProcessed?.Invoke(this, new FileProcessedEventArgs(file));
|
||||
}
|
||||
else if (Configuration.ErrorTokenRegex.IsMatch(data)) // Error Message - Mark the current file as FAILED immediately - Don't raise OnError event until error description comes in though
|
||||
{
|
||||
/*
|
||||
* Mark the current file as Failed
|
||||
* TODO: This data may have to be parsed to determine if it involved the current file's filename, or some other error. At time of writing, it appears that it doesn't require this check.
|
||||
* */
|
||||
|
||||
ProgressEstimator.FileFailed = true;
|
||||
}
|
||||
else if (Configuration.ErrorTokenRegex.IsMatch(lastData)) // Error Message - Uses previous data instead since RoboCopy reports errors onto line 1, then description onto line 2.
|
||||
{
|
||||
ErrorEventArgs args = new ErrorEventArgs(lastData, data, Configuration.ErrorTokenRegex);
|
||||
resultsBuilder.RoboCopyErrors.Add(args);
|
||||
|
||||
//Check to Raise the event
|
||||
OnError?.Invoke(this, args);
|
||||
}
|
||||
else if (!data.StartsWith("----------")) // System Message
|
||||
{
|
||||
// Do not log errors that have already been logged
|
||||
var errorCode = ApplicationConstants.ErrorCodes.FirstOrDefault(x => data == x.Value);
|
||||
if (errorCode.Key == null)
|
||||
{
|
||||
var file = new ProcessedFileInfo();
|
||||
file.FileClass = "System Message";
|
||||
file.FileClassType = FileClassType.SystemMessage;
|
||||
file.Size = 0;
|
||||
file.Name = data;
|
||||
OnFileProcessed?.Invoke(this, new FileProcessedEventArgs(file));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Other Public Methods >
|
||||
|
||||
/// <inheritdoc cref="Results.RoboCopyResults"/>
|
||||
/// <returns>The RoboCopyResults object from the last run</returns>
|
||||
public Results.RoboCopyResults GetResults()
|
||||
{
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the results to null - This is to prevent adding results from a previous run being added to the results list by RoboQueue
|
||||
/// </summary>
|
||||
internal void ResetResults()
|
||||
{
|
||||
results = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate the Parameters and Switches to execute RoboCopy with based on the configured settings
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private string GenerateParameters()
|
||||
{
|
||||
Debugger.Instance.DebugMessage("Generating parameters...");
|
||||
Debugger.Instance.DebugMessage(CopyOptions);
|
||||
var parsedCopyOptions = CopyOptions.Parse();
|
||||
var parsedSelectionOptions = SelectionOptions.Parse();
|
||||
Debugger.Instance.DebugMessage("SelectionOptions parsed.");
|
||||
var parsedRetryOptions = RetryOptions.Parse();
|
||||
Debugger.Instance.DebugMessage("RetryOptions parsed.");
|
||||
var parsedLoggingOptions = LoggingOptions.Parse();
|
||||
Debugger.Instance.DebugMessage("LoggingOptions parsed.");
|
||||
var parsedJobOptions = JobOptions.Parse();
|
||||
Debugger.Instance.DebugMessage("LoggingOptions parsed.");
|
||||
//var systemOptions = " /V /R:0 /FP /BYTES /W:0 /NJH /NJS";
|
||||
|
||||
return string.Format("{0}{1}{2}{3} /BYTES {4}", parsedCopyOptions, parsedSelectionOptions,
|
||||
parsedRetryOptions, parsedLoggingOptions, parsedJobOptions);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GenerateParameters"/>
|
||||
public override string ToString()
|
||||
{
|
||||
return GenerateParameters();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combine this object's options with that of some JobFile
|
||||
/// </summary>
|
||||
/// <param name="jobFile"></param>
|
||||
public void MergeJobFile(JobFile jobFile)
|
||||
{
|
||||
Name = Name.ReplaceIfEmpty(jobFile.Job_Name);
|
||||
copyOptions.Merge(jobFile.CopyOptions);
|
||||
LoggingOptions.Merge(jobFile.LoggingOptions);
|
||||
RetryOptions.Merge(jobFile.RetryOptions);
|
||||
SelectionOptions.Merge(jobFile.SelectionOptions);
|
||||
JobOptions.Merge(((IRoboCommand)jobFile).JobOptions);
|
||||
//this.StopIfDisposing |= ((IRoboCommand)jobFile).StopIfDisposing;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < IDisposable Implementation >
|
||||
|
||||
bool disposed = false;
|
||||
|
||||
/// <summary>Dispose of this object. Kills RoboCopy process if <see cref="StopIfDisposing"/> == true && <see cref="IsScheduled"/> == false. </summary>
|
||||
/// <remarks><inheritdoc cref="IDisposable.Dispose" path="/summary"/></remarks>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizer -> Cleans up resources when garbage collected
|
||||
/// </summary>
|
||||
~RoboCommand()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
/// <summary>IDisposable Implementation</summary>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
if (StopIfDisposing && !IsScheduled)
|
||||
{
|
||||
Stop(true);
|
||||
}
|
||||
|
||||
//Release any hooks to the process, but allow it to continue running
|
||||
process?.Dispose();
|
||||
process = null;
|
||||
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
#endregion IDisposable Implementation
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
using RoboSharp.Interfaces;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RoboSharp
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Object that provides methods to generate new <see cref="IRoboCommand"/> objects.
|
||||
/// </summary>
|
||||
public class RoboCommandFactory : IRoboCommandFactory
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="IRoboCommand"/> object using default settings.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is used by the other methods within the <see cref="RoboCommandFactory"/> to generate the inital <see cref="IRoboCommand"/> object that will be returned.
|
||||
/// <br/>All settings are then applied to the object's options components (such as the source/destination parameters)
|
||||
/// <br/>As such, overriding this one method will to provide will provide the other factory methods with the customized default IRobocommand object.
|
||||
/// </remarks>
|
||||
/// <returns>new <see cref="IRoboCommand"/> object using the parameterless constructor</returns>
|
||||
public virtual IRoboCommand GetRoboCommand() => new RoboCommand();
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="IRoboCommand"/> object with the specified <paramref name="source"/> and <paramref name="destination"/>.
|
||||
/// </summary>
|
||||
/// <remarks/>
|
||||
/// <param name="source"><inheritdoc cref="CopyOptions.Source" path="*"/></param>
|
||||
/// <param name="destination"><inheritdoc cref="CopyOptions.Destination" path="*"/></param>
|
||||
/// <returns>new <see cref="IRoboCommand"/> object with the specified <paramref name="source"/> and <paramref name="destination"/>.</returns>
|
||||
/// <inheritdoc cref="GetRoboCommand()"/>
|
||||
public virtual IRoboCommand GetRoboCommand(string source, string destination)
|
||||
{
|
||||
var cmd = GetRoboCommand();
|
||||
cmd.CopyOptions.Source = source;
|
||||
cmd.CopyOptions.Destination = destination;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="IRoboCommand"/> object with the specified options
|
||||
/// </summary>
|
||||
/// <remarks/>
|
||||
/// <param name="copyActionFlags">The options to apply to the generated <see cref="IRoboCommand"/> object </param>
|
||||
/// <param name="selectionFlags">The options to apply to the generated <see cref="IRoboCommand"/> object </param>
|
||||
/// <inheritdoc cref="GetRoboCommand(string, string)"/>]
|
||||
/// <param name="destination"/><param name="source"/>
|
||||
public virtual IRoboCommand GetRoboCommand(string source, string destination, CopyOptions.CopyActionFlags copyActionFlags, SelectionOptions.SelectionFlags selectionFlags)
|
||||
{
|
||||
var cmd = GetRoboCommand(source, destination);
|
||||
cmd.CopyOptions.ApplyActionFlags(copyActionFlags);
|
||||
cmd.SelectionOptions.ApplySelectionFlags(selectionFlags);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GetRoboCommand(string, string, CopyOptions.CopyActionFlags, SelectionOptions.SelectionFlags)"/>
|
||||
public virtual IRoboCommand GetRoboCommand(string source, string destination, CopyOptions.CopyActionFlags copyActionFlags)
|
||||
{
|
||||
return GetRoboCommand(source, destination, copyActionFlags, SelectionOptions.SelectionFlags.Default);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,946 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Concurrent;
|
||||
using RoboSharp.EventArgObjects;
|
||||
using RoboSharp.Interfaces;
|
||||
using RoboSharp.Results;
|
||||
|
||||
namespace RoboSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains a private List{IRoboCommand} object with controlled methods for access to it. <br/>
|
||||
/// Attempting to modify the list while <see cref="IsRunning"/> = true results in <see cref="ListAccessDeniedException"/> being thrown.
|
||||
/// <para/>Implements the following: <br/>
|
||||
/// <see cref="IRoboQueue"/> <br/>
|
||||
/// <see cref="IEnumerable"/> -- Allow enumerating through the collection that is stored in a private list -- Also see <see cref="Commands"/> <br/>
|
||||
/// <see cref="INotifyCollectionChanged"/> -- Allow subscription to collection changes against the list <see cref="ObservableList{T}"/> <br/>
|
||||
/// <see cref="INotifyPropertyChanged"/> -- Most properties will trigger <see cref="PropertyChanged"/> events when updated.<br/>
|
||||
/// <see cref="IDisposable"/> -- Allow disposal of all <see cref="IRoboCommand"/> objects in the list.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/RoboQueue"/>
|
||||
/// </remarks>
|
||||
public sealed class RoboQueue : IRoboQueue, IDisposable, INotifyPropertyChanged, IEnumerable<IRoboCommand>, INotifyCollectionChanged
|
||||
{
|
||||
#region < Constructors >
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new (empty) <see cref="RoboQueue"/> object.
|
||||
/// </summary>
|
||||
public RoboQueue()
|
||||
{
|
||||
Init();
|
||||
Commands = new ReadOnlyCollection<IRoboCommand>(CommandList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new (empty) <see cref="RoboQueue"/> object with a specificed Name.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="RoboQueue(IEnumerable{IRoboCommand}, string, int)"/>
|
||||
public RoboQueue(string name, int maxConcurrentJobs = 1)
|
||||
{
|
||||
Init(name, maxConcurrentJobs);
|
||||
Commands = new ReadOnlyCollection<IRoboCommand>(CommandList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new <see cref="RoboQueue"/> object that contains the supplied <see cref="IRoboCommand"/>.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="RoboQueue(IEnumerable{IRoboCommand}, string, int)"/>
|
||||
public RoboQueue(IRoboCommand roboCommand, string name = "", int maxConcurrentJobs = 1)
|
||||
{
|
||||
CommandList.Add(roboCommand);
|
||||
Init(name, maxConcurrentJobs);
|
||||
Commands = new ReadOnlyCollection<IRoboCommand>(CommandList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new <see cref="RoboQueue"/> object that contains the supplied <see cref="IRoboCommand"/> collection.
|
||||
/// </summary>
|
||||
/// <param name="roboCommand">IRoboCommand(s) to populate the list with.</param>
|
||||
/// <param name="name"><inheritdoc cref="Name"/></param>
|
||||
/// <param name="maxConcurrentJobs"><inheritdoc cref="MaxConcurrentJobs"/></param>
|
||||
public RoboQueue(IEnumerable<IRoboCommand> roboCommand, string name = "", int maxConcurrentJobs = 1)
|
||||
{
|
||||
CommandList.AddRange(roboCommand);
|
||||
Init(name, maxConcurrentJobs);
|
||||
Commands = new ReadOnlyCollection<IRoboCommand>(CommandList);
|
||||
}
|
||||
|
||||
private void Init(string name = "", int maxConcurrentJobs = 1)
|
||||
{
|
||||
NameField = name;
|
||||
MaxConcurrentJobsField = maxConcurrentJobs;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Fields >
|
||||
|
||||
private readonly ObservableList<IRoboCommand> CommandList = new ObservableList<IRoboCommand>();
|
||||
private RoboQueueProgressEstimator Estimator;
|
||||
private bool disposedValue;
|
||||
private CancellationTokenSource TaskCancelSource;
|
||||
private string NameField;
|
||||
|
||||
private bool WasCancelledField = false;
|
||||
private bool IsPausedField = false;
|
||||
private bool IsCopyOperationRunningField = false;
|
||||
private bool IsListOperationRunningField = false;
|
||||
private bool ListOnlyCompletedField = false;
|
||||
private bool CopyOpCompletedField = false;
|
||||
|
||||
private int MaxConcurrentJobsField;
|
||||
private int JobsStartedField;
|
||||
private int JobsCompleteField;
|
||||
private int JobsCompletedSuccessfullyField;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Properties Dependent on CommandList >
|
||||
|
||||
/// <summary>
|
||||
/// Checks <see cref="IRoboCommand.IsRunning"/> property of all items in the list.
|
||||
/// <br/> INotifyPropertyChanged is not raised when this property changes.
|
||||
/// </summary>
|
||||
public bool AnyRunning => CommandList.Any(c => c.IsRunning);
|
||||
|
||||
/// <summary>
|
||||
/// Checks <see cref="IRoboCommand.IsPaused"/> property of all items in the list.
|
||||
/// <br/> INotifyPropertyChanged is not raised when this property changes.
|
||||
/// </summary>
|
||||
public bool AnyPaused => CommandList.Any(c => c.IsPaused);
|
||||
|
||||
/// <summary>
|
||||
/// Checks <see cref="IRoboCommand.IsCancelled"/> property of all items in the list.
|
||||
/// <br/> INotifyPropertyChanged is not raised when this property changes.
|
||||
/// </summary>
|
||||
public bool AnyCancelled => CommandList.Any(c => c.IsCancelled);
|
||||
|
||||
/// <summary>
|
||||
/// Check the list and get the count of RoboCommands that are either in the 'Run' or 'Paused' state. <br/>
|
||||
/// (Paused state is included since these can be resumed at any time)
|
||||
/// </summary>
|
||||
public int JobsCurrentlyRunning => CommandList.Where((C) => C.IsRunning | C.IsPaused).Count();
|
||||
|
||||
/// <summary> Number of RoboCommands in the list </summary>
|
||||
public int ListCount => CommandList.Count;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Properties >
|
||||
|
||||
/// <summary>
|
||||
/// Name of this collection of RoboCommands
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get => NameField;
|
||||
private set
|
||||
{
|
||||
if (value != NameField)
|
||||
{
|
||||
NameField = value;
|
||||
OnPropertyChanged("Name");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wraps the private <see cref="ObservableList{T}"/> into a ReadOnlyCollection for public consumption and data binding.
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<IRoboCommand> Commands { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="RoboCommand.ProgressEstimator"/> <para/>
|
||||
/// This object will produce the sum of all the ProgressEstimator objects generated by the commands within the list.
|
||||
/// <para/> After the first request, the values will be updated every 250ms while the Queue is still running.
|
||||
/// </summary>
|
||||
public IProgressEstimator ProgressEstimator => Estimator;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if a task is currently running or paused. <br/>
|
||||
/// When true, prevents starting new tasks and prevents modication of the list.
|
||||
/// </summary>
|
||||
public bool IsRunning => IsCopyOperationRunning || IsListOnlyRunning;
|
||||
|
||||
/// <summary>
|
||||
/// This is set true when <see cref="PauseAll"/> is called while any of the items in the list were running, and set false when <see cref="ResumeAll"/> or <see cref="StopAll"/> is called.
|
||||
/// </summary>
|
||||
public bool IsPaused
|
||||
{
|
||||
get => IsPausedField;
|
||||
private set
|
||||
{
|
||||
if (value != IsPausedField)
|
||||
{
|
||||
IsPausedField = value;
|
||||
OnPropertyChanged("IsPaused");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flag is set to TRUE if the 'Stop' command is issued. Reset to False when starting a new operation.
|
||||
/// </summary>
|
||||
public bool WasCancelled
|
||||
{
|
||||
get => WasCancelledField;
|
||||
private set
|
||||
{
|
||||
if (value != WasCancelledField)
|
||||
{
|
||||
WasCancelledField = value;
|
||||
OnPropertyChanged("WasCancelled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Indicates if the StartAll task is currently running. </summary>
|
||||
public bool IsCopyOperationRunning
|
||||
{
|
||||
get => IsCopyOperationRunningField;
|
||||
private set
|
||||
{
|
||||
if (value != IsCopyOperationRunningField)
|
||||
{
|
||||
bool running = IsRunning;
|
||||
IsCopyOperationRunningField = value;
|
||||
OnPropertyChanged("IsCopyOperationRunning");
|
||||
if (IsRunning != running) OnPropertyChanged("IsRunning");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Indicates if the StartAll_ListOnly task is currently running. </summary>
|
||||
public bool IsListOnlyRunning
|
||||
{
|
||||
get => IsListOperationRunningField;
|
||||
private set
|
||||
{
|
||||
if (value != IsListOperationRunningField)
|
||||
{
|
||||
bool running = IsRunning;
|
||||
IsListOperationRunningField = value;
|
||||
OnPropertyChanged("IsListOnlyRunning");
|
||||
if (IsRunning != running) OnPropertyChanged("IsRunning");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Indicates if the StartAll_ListOnly() operation has been completed. </summary>
|
||||
public bool ListOnlyCompleted
|
||||
{
|
||||
get => ListOnlyCompletedField;
|
||||
private set
|
||||
{
|
||||
if (value != ListOnlyCompletedField)
|
||||
{
|
||||
ListOnlyCompletedField = value;
|
||||
OnPropertyChanged("ListOnlyCompleted");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Indicates if the StartAll() operation has been completed. </summary>
|
||||
public bool CopyOperationCompleted
|
||||
{
|
||||
get => CopyOpCompletedField;
|
||||
private set
|
||||
{
|
||||
if (value != CopyOpCompletedField)
|
||||
{
|
||||
CopyOpCompletedField = value;
|
||||
OnPropertyChanged("CopyOperationCompleted");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specify the max number of RoboCommands to execute at the same time. <br/>
|
||||
/// Set Value to 0 to allow infinite number of jobs (Will issue all start commands at same time) <br/>
|
||||
/// Default Value = 1; <br/>
|
||||
/// </summary>
|
||||
public int MaxConcurrentJobs
|
||||
{
|
||||
get => MaxConcurrentJobsField;
|
||||
set
|
||||
{
|
||||
int newVal = value > 0 ? value : IsRunning & MaxConcurrentJobsField > 0 ? 1 : 0; //Allow > 0 at all times //If running, set value to 1
|
||||
if (newVal != MaxConcurrentJobsField)
|
||||
{
|
||||
MaxConcurrentJobsField = newVal;
|
||||
OnPropertyChanged("MaxConcurrentJobs");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Report how many <see cref="IRoboCommand.Start"/> tasks has completed during the run. <br/>
|
||||
/// This value is reset to 0 when a new run starts, and increments as each job exits.
|
||||
/// </summary>
|
||||
public int JobsComplete
|
||||
{
|
||||
get => JobsCompleteField;
|
||||
private set
|
||||
{
|
||||
if (value != JobsCompleteField)
|
||||
{
|
||||
JobsCompleteField = value;
|
||||
OnPropertyChanged("JobsComplete");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Report how many <see cref="IRoboCommand.Start"/> tasks has completed successfully during the run. <br/>
|
||||
/// This value is reset to 0 when a new run starts, and increments as each job exits.
|
||||
/// </summary>
|
||||
public int JobsCompletedSuccessfully
|
||||
{
|
||||
get => JobsCompletedSuccessfullyField;
|
||||
private set
|
||||
{
|
||||
if (value != JobsCompletedSuccessfullyField)
|
||||
{
|
||||
JobsCompletedSuccessfullyField = value;
|
||||
OnPropertyChanged("JobsCompletedSuccessfully");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Report how many <see cref="IRoboCommand.Start"/> tasks have been started during the run. <br/>
|
||||
/// This value is reset to 0 when a new run starts, and increments as each job starts.
|
||||
/// </summary>
|
||||
public int JobsStarted
|
||||
{
|
||||
get => JobsStartedField;
|
||||
private set
|
||||
{
|
||||
if (value != JobsStartedField)
|
||||
{
|
||||
JobsStartedField = value;
|
||||
OnPropertyChanged("JobsStarted");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains the results from the most recent run started via <see cref="StartAll_ListOnly(string, string, string)"/> <para/>
|
||||
/// Any time StartALL_ListOnly is called, a new RoboQueueResults object will be created. <br/>
|
||||
/// </summary>
|
||||
public IRoboQueueResults ListResults => ListResultsObj;
|
||||
private RoboQueueResults ListResultsObj { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Contains the results from the most recent run started via <see cref="StartAll"/> <para/>
|
||||
/// Any time StartALL is called, a new RoboQueueResults object will be created. <br/>
|
||||
/// </summary>
|
||||
public IRoboQueueResults RunResults => RunResultsObj;
|
||||
private RoboQueueResults RunResultsObj { get; set; }
|
||||
|
||||
/*
|
||||
* Possible To-Do: Code in ConcurrentQueue objects if issues arise with items being added to the ResultsObj lists.
|
||||
* private ConcurrentQueue<RoboCopyResults> ListResultsQueue = new ConcurrentQueue<RoboCopyResults>();
|
||||
* private ConcurrentQueue<RoboCopyResults> RunResultsQueue = new ConcurrentQueue<RoboCopyResults>();
|
||||
*/
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Events >
|
||||
|
||||
#region < IRoboCommand Events >
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.OnFileProcessed"/>
|
||||
/// <remarks>This bind to every IRoboCommand in the list.</remarks>
|
||||
public event RoboCommand.FileProcessedHandler OnFileProcessed;
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.OnCommandError"/>
|
||||
/// <remarks>This bind to every RoboCommand in the list.</remarks>
|
||||
public event RoboCommand.CommandErrorHandler OnCommandError;
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.OnError"/>
|
||||
/// <remarks>This bind to every RoboCommand in the list.</remarks>
|
||||
public event RoboCommand.ErrorHandler OnError;
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.OnCommandCompleted"/>
|
||||
/// <remarks>This will occur for every RoboCommand in the list.</remarks>
|
||||
public event RoboCommand.CommandCompletedHandler OnCommandCompleted;
|
||||
|
||||
/// <inheritdoc cref="RoboCommand.OnCopyProgressChanged"/>
|
||||
/// <remarks>This bind to every RoboCommand in the list.</remarks>
|
||||
public event RoboCommand.CopyProgressHandler OnCopyProgressChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < ListUpdated Events >
|
||||
|
||||
/// <summary> Occurs when the <see cref="ListResults"/> gets updated </summary>
|
||||
public event RoboCopyResultsList.ResultsListUpdated ListResultsUpdated;
|
||||
|
||||
/// <summary> Occurs when the <see cref="RunResults"/> gets updated </summary>
|
||||
public event RoboCopyResultsList.ResultsListUpdated RunResultsUpdated;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < ProgressUpdater Event >
|
||||
|
||||
/// <summary>Handles <see cref="OnProgressEstimatorCreated"/></summary>
|
||||
public delegate void ProgressUpdaterCreatedHandler(RoboQueue sender, ProgressEstimatorCreatedEventArgs e);
|
||||
/// <summary>
|
||||
/// Occurs when a <see cref="Results.ProgressEstimator"/> is created when starting a new task, allowing binding to occur within the event subscriber. <br/>
|
||||
/// This event will occur once per Start. See notes on <see cref="ProgressEstimator"/> for more details.
|
||||
/// </summary>
|
||||
public event ProgressUpdaterCreatedHandler OnProgressEstimatorCreated;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < CommandStarted Event >
|
||||
|
||||
/// <summary>Handles <see cref="OnCommandStarted"/></summary>
|
||||
public delegate void CommandStartedHandler(RoboQueue sender, RoboQueueCommandStartedEventArgs e);
|
||||
/// <summary>
|
||||
/// Occurs each time a Command has started succesfully
|
||||
/// </summary>
|
||||
public event CommandStartedHandler OnCommandStarted;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < RunComplete Event >
|
||||
|
||||
/// <summary>Handles <see cref="OnCommandCompleted"/></summary>
|
||||
public delegate void RunCompletedHandler(RoboQueue sender, RoboQueueCompletedEventArgs e);
|
||||
/// <summary>
|
||||
/// Occurs after when the task started by the StartAll and StartAll_ListOnly methods has finished executing.
|
||||
/// </summary>
|
||||
public event RunCompletedHandler RunCompleted;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < UnhandledException Fault >
|
||||
|
||||
/// <summary>
|
||||
/// Occurs if the RoboQueue task is stopped due to an unhandled exception. Occurs instead of <see cref="RoboQueue.RunCompleted"/>
|
||||
/// <br/> Also occurs if any of the RoboCommand objects raise <see cref="RoboCommand.TaskFaulted"/>
|
||||
/// </summary>
|
||||
public event UnhandledExceptionEventHandler TaskFaulted;
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Methods >
|
||||
|
||||
/// <summary>
|
||||
/// Get the current instance of the <see cref="ListResults"/> object
|
||||
/// </summary>
|
||||
/// <returns>New instance of the <see cref="ListResults"/> list.</returns>
|
||||
public RoboQueueResults GetListResults() => ListResultsObj;
|
||||
|
||||
/// <summary>
|
||||
/// Get the current of the <see cref="RunResults"/> object
|
||||
/// </summary>
|
||||
/// <returns>New instance of the <see cref="RunResults"/> list.</returns>
|
||||
public RoboQueueResults GetRunResults() => RunResultsObj;
|
||||
|
||||
/// <summary>
|
||||
/// Run <see cref="RoboCommand.Stop()"/> against all items in the list.
|
||||
/// </summary>
|
||||
public void StopAll()
|
||||
{
|
||||
//If a TaskCancelSource is present, request cancellation. The continuation tasks null the value out then call this method to ensure everything stopped once they complete.
|
||||
if (TaskCancelSource != null && !TaskCancelSource.IsCancellationRequested)
|
||||
{
|
||||
IsPaused = false;
|
||||
TaskCancelSource.Cancel(); // Cancel the IRoboCommand Task
|
||||
//IRoboCommand Continuation Task will call StopAllTask() method to ensure all processes are stopped & diposed.
|
||||
}
|
||||
else if (TaskCancelSource == null)
|
||||
{
|
||||
//This is supplied to allow stopping all commands if consumer manually looped through the list instead of using the Start* methods.
|
||||
CommandList.ForEach((c) => c.Stop());
|
||||
IsCopyOperationRunning = false;
|
||||
IsListOnlyRunning = false;
|
||||
IsPaused = false;
|
||||
}
|
||||
WasCancelled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loop through the items in the list and issue <see cref="IRoboCommand.Pause"/> on any commands where <see cref="IRoboCommand.IsRunning"/> is true.
|
||||
/// </summary>
|
||||
public void PauseAll()
|
||||
{
|
||||
CommandList.ForEach((c) => { if (c.IsRunning) c.Pause(); });
|
||||
IsPaused = IsRunning || AnyPaused;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loop through the items in the list and issue <see cref="IRoboCommand.Resume"/> on any commands where <see cref="IRoboCommand.IsPaused"/> is true.
|
||||
/// </summary>
|
||||
public void ResumeAll()
|
||||
{
|
||||
CommandList.ForEach((c) => { if (c.IsPaused) c.Resume(); });
|
||||
IsPaused = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Run List-Only Mode >
|
||||
|
||||
/// <summary>
|
||||
/// Set all IRoboCommand objects to ListOnly mode, run them, then set all RoboCommands back to their previous ListOnly mode setting.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="StartJobs"/>
|
||||
public Task<IRoboQueueResults> StartAll_ListOnly(string domain = "", string username = "", string password = "")
|
||||
{
|
||||
if (IsRunning) throw new InvalidOperationException("Cannot start a new RoboQueue Process while this RoboQueue is already running.");
|
||||
IsListOnlyRunning = true;
|
||||
ListOnlyCompleted = false;
|
||||
|
||||
ListResultsObj = new RoboQueueResults();
|
||||
ListResultsUpdated?.Invoke(this, new ResultListUpdatedEventArgs(ListResults));
|
||||
|
||||
//Run the commands
|
||||
Task Run = StartJobs(domain, username, password, true);
|
||||
Task<IRoboQueueResults> ResultsTask = Run.ContinueWith((continuation) =>
|
||||
{
|
||||
//Set Flags
|
||||
IsListOnlyRunning = false;
|
||||
IsPaused = false;
|
||||
ListOnlyCompleted = !WasCancelled && !continuation.IsFaulted;
|
||||
|
||||
// If some fault occurred while processing, throw the exception to caller
|
||||
if (continuation.IsFaulted)
|
||||
{
|
||||
TaskFaulted?.Invoke(this, new UnhandledExceptionEventArgs(continuation.Exception, true));
|
||||
throw continuation.Exception;
|
||||
}
|
||||
ListResultsObj.EndTime= DateTime.Now;
|
||||
RunCompleted?.Invoke(this, new RoboQueueCompletedEventArgs(ListResultsObj, true));
|
||||
return (IRoboQueueResults)ListResultsObj;
|
||||
}, CancellationToken.None
|
||||
);
|
||||
return ResultsTask;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Run User-Set Parameters >
|
||||
|
||||
/// <inheritdoc cref="StartJobs"/>
|
||||
public Task<IRoboQueueResults> StartAll(string domain = "", string username = "", string password = "")
|
||||
{
|
||||
if (IsRunning) throw new InvalidOperationException("Cannot start a new RoboQueue Process while this RoboQueue is already running.");
|
||||
|
||||
IsCopyOperationRunning = true;
|
||||
CopyOperationCompleted = false;
|
||||
|
||||
RunResultsObj = new RoboQueueResults();
|
||||
RunResultsUpdated?.Invoke(this, new ResultListUpdatedEventArgs(RunResults));
|
||||
|
||||
Task Run = StartJobs(domain, username, password, false);
|
||||
Task<IRoboQueueResults> ResultsTask = Run.ContinueWith((continuation) =>
|
||||
{
|
||||
IsCopyOperationRunning = false;
|
||||
IsPaused = false;
|
||||
CopyOperationCompleted = !WasCancelled && !continuation.IsFaulted;
|
||||
|
||||
// If some fault occurred while processing, throw the exception to caller
|
||||
if (continuation.IsFaulted)
|
||||
{
|
||||
TaskFaulted?.Invoke(this, new UnhandledExceptionEventArgs(continuation.Exception, true));
|
||||
throw continuation.Exception;
|
||||
}
|
||||
|
||||
RunResultsObj.EndTime = DateTime.Now;
|
||||
RunCompleted?.Invoke(this, new RoboQueueCompletedEventArgs(RunResultsObj, false));
|
||||
return (IRoboQueueResults)RunResultsObj;
|
||||
}, CancellationToken.None
|
||||
);
|
||||
return ResultsTask;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < StartJobs Method >
|
||||
|
||||
/// <summary>
|
||||
/// Create Task that Starts all RoboCommands.
|
||||
/// </summary>
|
||||
/// <remarks> <paramref name="domain"/>, <paramref name="password"/>, and <paramref name="username"/> are applied to all IRoboCommand objects during this run. </remarks>
|
||||
/// <returns> New Task that finishes after all RoboCommands have stopped executing </returns>
|
||||
private Task StartJobs(string domain = "", string username = "", string password = "", bool ListOnlyMode = false)
|
||||
{
|
||||
Debugger.Instance.DebugMessage("Starting Parallel execution of RoboQueue");
|
||||
|
||||
TaskCancelSource = new CancellationTokenSource();
|
||||
CancellationToken cancellationToken = TaskCancelSource.Token;
|
||||
var SleepCancelToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken).Token;
|
||||
|
||||
List<Task> TaskList = new List<Task>();
|
||||
JobsStarted = 0;
|
||||
JobsComplete = 0;
|
||||
JobsCompletedSuccessfully = 0;
|
||||
|
||||
WasCancelled = false;
|
||||
IsPaused = false;
|
||||
|
||||
//Create a Task to Start all the RoboCommands
|
||||
Task StartAll = Task.Factory.StartNew(async () =>
|
||||
{
|
||||
//Reset results of all commands in the list
|
||||
foreach (RoboCommand cmd in CommandList)
|
||||
cmd.ResetResults();
|
||||
|
||||
Estimator = new RoboQueueProgressEstimator();
|
||||
OnProgressEstimatorCreated?.Invoke(this, new ProgressEstimatorCreatedEventArgs(Estimator));
|
||||
|
||||
//Start all commands, running as many as allowed
|
||||
foreach (IRoboCommand cmd in CommandList)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested) break;
|
||||
|
||||
//Assign the events
|
||||
RoboCommand.CommandCompletedHandler handler = (o, e) => RaiseCommandCompleted(o, e, ListOnlyMode);
|
||||
cmd.OnCommandCompleted += handler;
|
||||
cmd.OnCommandError += this.OnCommandError;
|
||||
cmd.OnCopyProgressChanged += this.OnCopyProgressChanged;
|
||||
cmd.OnError += this.OnError;
|
||||
cmd.OnFileProcessed += this.OnFileProcessed;
|
||||
cmd.OnProgressEstimatorCreated += Cmd_OnProgressEstimatorCreated;
|
||||
cmd.TaskFaulted += TaskFaulted;
|
||||
|
||||
//Start the job
|
||||
//Once the job ends, unsubscribe events
|
||||
Task C = !ListOnlyMode ? cmd.Start(domain, username, password) : cmd.Start_ListOnly(domain, username, password);
|
||||
Task T = C.ContinueWith((t) =>
|
||||
{
|
||||
cmd.OnCommandCompleted -= handler;
|
||||
cmd.OnCommandError -= this.OnCommandError;
|
||||
cmd.OnCopyProgressChanged -= this.OnCopyProgressChanged;
|
||||
cmd.OnError -= this.OnError;
|
||||
cmd.OnFileProcessed -= this.OnFileProcessed;
|
||||
if (t.IsFaulted) throw t.Exception; // If some fault occurred while processing, throw the exception to caller
|
||||
}, CancellationToken.None);
|
||||
|
||||
TaskList.Add(T); //Add the continuation task to the list.
|
||||
|
||||
//Raise Events
|
||||
JobsStarted++; OnPropertyChanged("JobsStarted");
|
||||
if (cmd.IsRunning) OnCommandStarted?.Invoke(this, new RoboQueueCommandStartedEventArgs(cmd)); //Declare that a new command in the queue has started.
|
||||
OnPropertyChanged("JobsCurrentlyRunning"); //Notify the Property Changes
|
||||
|
||||
//Check if more jobs are allowed to run
|
||||
if (IsPaused) cmd.Pause(); //Ensure job that just started gets paused if Pausing was requested
|
||||
while (!cancellationToken.IsCancellationRequested && (IsPaused || (MaxConcurrentJobs > 0 && JobsCurrentlyRunning >= MaxConcurrentJobs && TaskList.Count < CommandList.Count)))
|
||||
await ThreadEx.CancellableSleep(500, SleepCancelToken);
|
||||
|
||||
} //End of ForEachLoop
|
||||
|
||||
//Asynchronous wait for either cancellation is requested OR all jobs to finish.
|
||||
//- Task.WaitAll is blocking -> not ideal, and also throws if cancellation is requested -> also not ideal.
|
||||
//- Task.WhenAll is awaitable, but does not provide allow cancellation
|
||||
//- If Cancelled, the 'WhenAll' task continues to run, but the ContinueWith task here will stop all tasks, thus completing the WhenAll task
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
_ = cancellationToken.Register(() => tcs.TrySetResult(null));
|
||||
_ = await Task.WhenAny(Task.WhenAll(TaskList.ToArray()), tcs.Task);
|
||||
}
|
||||
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).Unwrap();
|
||||
|
||||
//Continuation Task return results to caller
|
||||
Task ContinueWithTask = StartAll.ContinueWith(async (continuation) =>
|
||||
{
|
||||
Estimator?.CancelTasks();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
//If cancellation was requested -> Issue the STOP command to all commands in the list
|
||||
Debugger.Instance.DebugMessage("RoboQueue Task Was Cancelled");
|
||||
await StopAllTask(TaskList);
|
||||
}
|
||||
else if (continuation.IsFaulted)
|
||||
{
|
||||
Debugger.Instance.DebugMessage("RoboQueue Task Faulted");
|
||||
await StopAllTask(TaskList);
|
||||
throw continuation.Exception;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debugger.Instance.DebugMessage("RoboQueue Task Completed");
|
||||
}
|
||||
|
||||
TaskCancelSource?.Dispose();
|
||||
TaskCancelSource = null;
|
||||
|
||||
}, CancellationToken.None).Unwrap();
|
||||
|
||||
return ContinueWithTask;
|
||||
}
|
||||
|
||||
private async Task StopAllTask(IEnumerable<Task> StartedTasks)
|
||||
{
|
||||
CommandList.ForEach((c) => c.Stop());
|
||||
await Task.WhenAll(StartedTasks);
|
||||
|
||||
IsCopyOperationRunning = false;
|
||||
IsListOnlyRunning = false;
|
||||
IsPaused = false;
|
||||
|
||||
TaskCancelSource.Dispose();
|
||||
TaskCancelSource = null;
|
||||
}
|
||||
|
||||
private void Cmd_OnProgressEstimatorCreated(IRoboCommand sender, ProgressEstimatorCreatedEventArgs e)
|
||||
{
|
||||
Estimator?.BindToProgressEstimator(e.ResultsEstimate);
|
||||
sender.OnProgressEstimatorCreated -= Cmd_OnProgressEstimatorCreated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Intercept OnCommandCompleted from each IRoboCommand, react, then raise this object's OnCommandCompleted event
|
||||
/// </summary>
|
||||
private void RaiseCommandCompleted(IRoboCommand sender, RoboCommandCompletedEventArgs e, bool ListOnlyBinding)
|
||||
{
|
||||
if (ListOnlyBinding)
|
||||
{
|
||||
ListResultsObj.Add(sender.GetResults());
|
||||
ListResultsUpdated?.Invoke(this, new ResultListUpdatedEventArgs(ListResults));
|
||||
}
|
||||
else
|
||||
{
|
||||
RunResultsObj.Add(sender.GetResults());
|
||||
RunResultsUpdated?.Invoke(this, new ResultListUpdatedEventArgs(RunResults));
|
||||
}
|
||||
|
||||
//Notify the Property Changes
|
||||
if (!sender.IsCancelled)
|
||||
{
|
||||
JobsCompletedSuccessfully++;
|
||||
}
|
||||
JobsComplete++;
|
||||
OnPropertyChanged("JobsCurrentlyRunning");
|
||||
OnCommandCompleted?.Invoke(sender, e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < IDisposable Implementation >
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Estimator?.UnBind();
|
||||
|
||||
//IRoboCommand objects attach to a process, so must be in the 'unmanaged' section.
|
||||
foreach (IRoboCommand cmd in CommandList)
|
||||
cmd.Dispose();
|
||||
CommandList.Clear();
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizer -> Ensures that all IRoboCommand objects get disposed of properly when program exits
|
||||
/// </summary>
|
||||
~RoboQueue()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose all IRoboCommand objects contained in the list. - This will kill any Commands that have <see cref="RoboCommand.StopIfDisposing"/> = true (default) <br/>
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < INotifyPropertyChanged, INotifyCollectionChanged, IEnumerable >
|
||||
|
||||
/// <inheritdoc cref="INotifyPropertyChanged"/>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
private void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
|
||||
/// <inheritdoc cref="ObservableCollection{T}.CollectionChanged"/>
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged
|
||||
{
|
||||
add { CommandList.CollectionChanged += value; }
|
||||
remove { CommandList.CollectionChanged -= value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the enumerator for the enumeating through this object's <see cref="IRoboCommand"/> objects
|
||||
/// </summary>
|
||||
public IEnumerator<IRoboCommand> GetEnumerator()
|
||||
{
|
||||
return Commands.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable)Commands).GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < List Access Methods >
|
||||
|
||||
/// <summary>
|
||||
/// Exception thrown when attempting to run a method accesses the list backing a RoboQueue object while the tasks are in progress.
|
||||
/// </summary>
|
||||
public class ListAccessDeniedException : Exception
|
||||
{
|
||||
/// <remarks>This functionality is disabled if <see cref="IsRunning"/> == true.</remarks>
|
||||
/// <exception cref="ListAccessDeniedException"/>
|
||||
private const string StandardMsg = "Running methods that modify the list of RoboCommands methods while RoboQueue.IsRunning = TRUE is prohibited.";
|
||||
internal ListAccessDeniedException() : base(StandardMsg) { }
|
||||
internal ListAccessDeniedException(string message) : base($"{StandardMsg}\n{message}") { }
|
||||
internal ListAccessDeniedException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
|
||||
#region < Add >
|
||||
|
||||
/// <inheritdoc cref="List{T}.Add(T)"/>
|
||||
/// <inheritdoc cref="ListAccessDeniedException.StandardMsg"/>
|
||||
public void AddCommand(IRoboCommand item)
|
||||
{
|
||||
if (IsRunning) throw new ListAccessDeniedException();
|
||||
CommandList.Add(item);
|
||||
OnPropertyChanged("ListCount");
|
||||
OnPropertyChanged("Commands");
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="List{T}.Insert(int, T)"/>
|
||||
/// <inheritdoc cref="ListAccessDeniedException.StandardMsg"/>
|
||||
public void AddCommand(int index, IRoboCommand item)
|
||||
{
|
||||
if (IsRunning) throw new ListAccessDeniedException();
|
||||
CommandList.Insert(index, item);
|
||||
OnPropertyChanged("ListCount");
|
||||
OnPropertyChanged("Commands");
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="List{T}.AddRange(IEnumerable{T})"/>
|
||||
/// <inheritdoc cref="ListAccessDeniedException.StandardMsg"/>
|
||||
public void AddCommand(IEnumerable<IRoboCommand> collection)
|
||||
{
|
||||
if (IsRunning) throw new ListAccessDeniedException();
|
||||
CommandList.AddRange(collection);
|
||||
OnPropertyChanged("ListCount");
|
||||
OnPropertyChanged("Commands");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Remove >
|
||||
|
||||
/// <inheritdoc cref="List{T}.Remove(T)"/>
|
||||
/// <inheritdoc cref="ListAccessDeniedException.StandardMsg"/>
|
||||
public void RemoveCommand(IRoboCommand item)
|
||||
{
|
||||
if (IsRunning) throw new ListAccessDeniedException();
|
||||
CommandList.Remove(item);
|
||||
OnPropertyChanged("ListCount");
|
||||
OnPropertyChanged("Commands");
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="List{T}.RemoveAt(int)"/>
|
||||
/// <inheritdoc cref="ListAccessDeniedException.StandardMsg"/>
|
||||
public void RemoveCommand(int index)
|
||||
{
|
||||
if (IsRunning) throw new ListAccessDeniedException();
|
||||
CommandList.RemoveAt(index);
|
||||
OnPropertyChanged("ListCount");
|
||||
OnPropertyChanged("Commands");
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="List{T}.RemoveRange(int, int)"/>
|
||||
/// <inheritdoc cref="ListAccessDeniedException.StandardMsg"/>
|
||||
public void RemoveCommand(int index, int count)
|
||||
{
|
||||
if (IsRunning) throw new ListAccessDeniedException();
|
||||
CommandList.RemoveRange(index, count);
|
||||
OnPropertyChanged("ListCount");
|
||||
OnPropertyChanged("Commands");
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="List{T}.RemoveAll(Predicate{T})"/>
|
||||
/// <inheritdoc cref="ListAccessDeniedException.StandardMsg"/>
|
||||
public void RemovCommand(Predicate<IRoboCommand> match)
|
||||
{
|
||||
if (IsRunning) throw new ListAccessDeniedException();
|
||||
CommandList.RemoveAll(match);
|
||||
OnPropertyChanged("ListCount");
|
||||
OnPropertyChanged("Commands");
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="List{T}.Clear"/>
|
||||
/// <inheritdoc cref="ListAccessDeniedException.StandardMsg"/>
|
||||
public void ClearCommandList()
|
||||
{
|
||||
if (IsRunning) throw new ListAccessDeniedException();
|
||||
CommandList.Clear();
|
||||
OnPropertyChanged("ListCount");
|
||||
OnPropertyChanged("Commands");
|
||||
}
|
||||
|
||||
/// <summary>Performs <see cref="RemoveCommand(int)"/> then <see cref="AddCommand(int, IRoboCommand)"/></summary>
|
||||
public void ReplaceCommand(IRoboCommand item, int index)
|
||||
{
|
||||
if (IsRunning) throw new ListAccessDeniedException();
|
||||
CommandList.Replace(index, item);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Find / Contains / Etc >
|
||||
|
||||
/// <inheritdoc cref="List{T}.Contains(T)"/>
|
||||
public bool Contains(IRoboCommand item) => CommandList.Contains(item);
|
||||
|
||||
/// <inheritdoc cref="List{T}.ForEach(Action{T})"/>
|
||||
/// <inheritdoc cref="ListAccessDeniedException.StandardMsg"/>
|
||||
public void ForEach(Action<IRoboCommand> action)
|
||||
{
|
||||
if (IsRunning) throw new ListAccessDeniedException();
|
||||
CommandList.ForEach(action);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="List{T}.FindAll(Predicate{T})"/>
|
||||
public List<IRoboCommand> FindAll(Predicate<IRoboCommand> predicate) => CommandList.FindAll(predicate);
|
||||
|
||||
/// <inheritdoc cref="List{T}.Find(Predicate{T})"/>
|
||||
public IRoboCommand Find(Predicate<IRoboCommand> predicate) => CommandList.Find(predicate);
|
||||
|
||||
/// <inheritdoc cref="List{T}.IndexOf(T)"/>
|
||||
public int IndexOf(IRoboCommand item) => CommandList.IndexOf(item);
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0-windows</TargetFrameworks>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Version>1.2.8</Version>
|
||||
<Copyright>Copyright 2022</Copyright>
|
||||
<Authors>Terry</Authors>
|
||||
<owners>Terry</owners>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<PackageLicenseUrl>https://github.com/tjscience/RoboSharp/blob/master/license</PackageLicenseUrl>
|
||||
<PackageProjectUrl>https://github.com/tjscience/RoboSharp</PackageProjectUrl>
|
||||
<PackageIconUrl>https://raw.githubusercontent.com/tjscience/RoboSharp/master/robosharp.png</PackageIconUrl>
|
||||
<Description>RoboSharp is a .NET wrapper for the awesome Robocopy windows application.</Description>
|
||||
<summary>
|
||||
pull request #107
|
||||
</summary>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net40' Or '$(TargetFramework)' == 'net45'">
|
||||
<Reference Include="System.Management" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.1' Or '$(TargetFramework)' == 'netcoreapp3.1' Or '$(TargetFramework)' == 'net5.0'">
|
||||
<PackageReference Include="Microsoft.Management.Infrastructure">
|
||||
<Version>2.0.0</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="XML_Files\**" />
|
||||
<EmbeddedResource Remove="XML_Files\**" />
|
||||
<None Remove="XML_Files\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Management.Infrastructure" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,371 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using RoboSharp.DefaultConfigurations;
|
||||
|
||||
namespace RoboSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Setup the ErrorToken and the path to RoboCopy.exe.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/RoboSharpConfiguration"/>
|
||||
/// </remarks>
|
||||
public class RoboSharpConfiguration : ICloneable
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Create new LoggingOptions with Default Settings
|
||||
/// </summary>
|
||||
public RoboSharpConfiguration() { }
|
||||
|
||||
/// <summary>
|
||||
/// Clone a RoboSharpConfiguration Object
|
||||
/// </summary>
|
||||
/// <param name="options">RoboSharpConfiguration object to clone</param>
|
||||
public RoboSharpConfiguration(RoboSharpConfiguration options)
|
||||
{
|
||||
errorToken = options.errorToken;
|
||||
errorTokenRegex = options.errorTokenRegex;
|
||||
roboCopyExe = options.roboCopyExe;
|
||||
|
||||
#region < File Tokens >
|
||||
newFileToken = options.newFileToken;
|
||||
olderToken = options.olderToken;
|
||||
newerToken = options.newerToken;
|
||||
sameToken = options.sameToken;
|
||||
extraToken = options.extraToken;
|
||||
mismatchToken = options.mismatchToken;
|
||||
failedToken = options.failedToken;
|
||||
#endregion
|
||||
|
||||
#region < Directory Tokens >
|
||||
newerDirToken = options.newerDirToken;
|
||||
extraDirToken = options.extraDirToken;
|
||||
existingDirToken = options.existingDirToken;
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="RoboSharpConfiguration.RoboSharpConfiguration(RoboSharpConfiguration)"/>
|
||||
public RoboSharpConfiguration Clone() => new RoboSharpConfiguration(this);
|
||||
|
||||
object ICloneable.Clone() => Clone();
|
||||
|
||||
#endregion
|
||||
|
||||
private static readonly IDictionary<string, RoboSharpConfiguration>
|
||||
defaultConfigurations = new Dictionary<string, RoboSharpConfiguration>()
|
||||
{
|
||||
{"en", new RoboSharpConfig_EN() }, //en uses Defaults for LogParsing properties
|
||||
{"de", new RoboSharpConfig_DE() },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Error Token Identifier -- EN = "ERROR", DE = "FEHLER", etc <br/>
|
||||
/// Leave as / Set to null to use system default.
|
||||
/// </summary>
|
||||
public string ErrorToken
|
||||
{
|
||||
get { return errorToken ?? GetDefaultConfiguration().ErrorToken; }
|
||||
set
|
||||
{
|
||||
if (value != errorToken) ErrRegexInitRequired = true;
|
||||
errorToken = value;
|
||||
}
|
||||
}
|
||||
/// <summary> field backing <see cref="ErrorToken"/> property - Protected to allow DefaultConfig derived classes to set within constructor </summary>
|
||||
protected string errorToken = null;
|
||||
|
||||
/// <summary>
|
||||
/// Regex to identify Error Tokens with during LogLine parsing
|
||||
/// </summary>
|
||||
public Regex ErrorTokenRegex
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ErrRegexInitRequired) goto RegenRegex; //Regex Generation Required
|
||||
else if (errorTokenRegex != null) return errorTokenRegex; //field already assigned -> return the field
|
||||
else
|
||||
{
|
||||
//Try get default, if default has regex defined, use that.
|
||||
errorTokenRegex = GetDefaultConfiguration().errorTokenRegex;
|
||||
if (errorTokenRegex != null) return errorTokenRegex;
|
||||
}
|
||||
// Generate a new Regex Statement
|
||||
RegenRegex:
|
||||
errorTokenRegex = ErrorTokenRegexGenerator(ErrorToken); //new Regex($" {this.ErrorToken} " + @"(\d{1,3}) \(0x\d{8}\) ");
|
||||
ErrRegexInitRequired = false;
|
||||
return errorTokenRegex;
|
||||
}
|
||||
}
|
||||
/// <summary> Field backing <see cref="ErrorTokenRegex"/> property - Protected to allow DefaultConfig derived classes to set within constructor </summary>
|
||||
protected Regex errorTokenRegex;
|
||||
private bool ErrRegexInitRequired = false;
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new ErrorTokenRegex object from by insterting the <see cref="ErrorToken"/> into a standardized pattern.
|
||||
/// </summary>
|
||||
/// <param name="errorToken">Language Specific <see cref="ErrorToken"/></param>
|
||||
/// <returns></returns>
|
||||
internal static Regex ErrorTokenRegexGenerator(string errorToken)
|
||||
{
|
||||
Regex BaseErrTokenRegex = new Regex("(?<Date>.*?)\\s+IDENTIFIER\\s+(?<ErrCode>[0-9]+)\\s+(?<SignedErrCode>\\([0-9Xx]+\\))\\s+(?<Descrip>[\\w\\s]+(?!:))(?<Path>.*)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture);
|
||||
string pattern = BaseErrTokenRegex.ToString().Replace("IDENTIFIER", errorToken);
|
||||
return new Regex(pattern, BaseErrTokenRegex.Options);
|
||||
}
|
||||
|
||||
|
||||
#region < Tokens for Log Parsing >
|
||||
|
||||
#region < File Tokens >
|
||||
|
||||
/// <summary>
|
||||
/// Log Lines starting with this string indicate : New File -> Source FILE Exists, Destination does not
|
||||
/// </summary>
|
||||
public string LogParsing_NewFile
|
||||
{
|
||||
get { return newFileToken ?? GetDefaultConfiguration().newFileToken ?? "New File"; }
|
||||
set { newFileToken = value; }
|
||||
}
|
||||
private string newFileToken;
|
||||
|
||||
/// <summary>
|
||||
/// Log Lines starting with this string indicate : Destination File newer than Source
|
||||
/// </summary>
|
||||
public string LogParsing_OlderFile
|
||||
{
|
||||
get { return olderToken ?? GetDefaultConfiguration().olderToken ?? "Older"; }
|
||||
set { olderToken = value; }
|
||||
}
|
||||
private string olderToken;
|
||||
|
||||
/// <summary>
|
||||
/// Log Lines starting with this string indicate : Source File newer than Destination
|
||||
/// </summary>
|
||||
public string LogParsing_NewerFile
|
||||
{
|
||||
get { return newerToken ?? GetDefaultConfiguration().newerToken ?? "Newer"; }
|
||||
set { newerToken = value; }
|
||||
}
|
||||
private string newerToken;
|
||||
|
||||
/// <summary>
|
||||
/// Log Lines starting with this string indicate : Source FILE is identical to Destination File
|
||||
/// </summary>
|
||||
public string LogParsing_SameFile
|
||||
{
|
||||
get { return sameToken ?? GetDefaultConfiguration().sameToken ?? "same"; }
|
||||
set { sameToken = value; }
|
||||
}
|
||||
private string sameToken;
|
||||
|
||||
/// <summary>
|
||||
/// Log Lines starting with this string indicate : EXTRA FILE -> Destination Exists, but Source does not
|
||||
/// </summary>
|
||||
public string LogParsing_ExtraFile
|
||||
{
|
||||
get { return extraToken ?? GetDefaultConfiguration().extraToken ?? "*EXTRA File"; }
|
||||
set { extraToken = value; }
|
||||
}
|
||||
private string extraToken;
|
||||
|
||||
/// <summary>
|
||||
/// Log Lines starting with this string indicate : MISMATCH FILE
|
||||
/// </summary>
|
||||
public string LogParsing_MismatchFile
|
||||
{
|
||||
get { return mismatchToken ?? GetDefaultConfiguration().mismatchToken ?? "*Mismatch"; } // TODO: Needs Verification
|
||||
set { mismatchToken = value; }
|
||||
}
|
||||
private string mismatchToken;
|
||||
|
||||
/// <summary>
|
||||
/// Log Lines starting with this string indicate : File Failed to Copy
|
||||
/// </summary>
|
||||
public string LogParsing_FailedFile
|
||||
{
|
||||
get { return failedToken ?? GetDefaultConfiguration().failedToken ?? "*Failed"; } // TODO: Needs Verification
|
||||
set { failedToken = value; }
|
||||
}
|
||||
private string failedToken;
|
||||
|
||||
/// <summary>
|
||||
/// Log Lines starting with this string indicate : File was excluded by <see cref="SelectionOptions.ExcludedFiles"/> filters
|
||||
/// </summary>
|
||||
public string LogParsing_FileExclusion
|
||||
{
|
||||
get { return fileExcludedToken ?? GetDefaultConfiguration().fileExcludedToken ?? "named"; } // TODO: Needs Verification
|
||||
set { fileExcludedToken = value; }
|
||||
}
|
||||
private string fileExcludedToken;
|
||||
|
||||
/// <summary>
|
||||
/// Log Lines starting with this string indicate : File was excluded by <see cref="SelectionOptions.ExcludeAttributes"/> or <see cref="SelectionOptions.IncludeAttributes"/> filters
|
||||
/// </summary>
|
||||
public string LogParsing_AttribExclusion
|
||||
{
|
||||
get { return attribExcludedToken ?? GetDefaultConfiguration().attribExcludedToken ?? "attrib"; }
|
||||
set { attribExcludedToken = value; }
|
||||
}
|
||||
private string attribExcludedToken;
|
||||
|
||||
/// <summary>
|
||||
/// Log Lines starting with this string indicate : File was excluded by <see cref="SelectionOptions.MaxFileSize"/> filters
|
||||
/// </summary>
|
||||
public string LogParsing_MaxFileSizeExclusion
|
||||
{
|
||||
get { return maxfilesizeExcludedToken ?? GetDefaultConfiguration().maxfilesizeExcludedToken ?? "large"; }
|
||||
set { maxfilesizeExcludedToken = value; }
|
||||
}
|
||||
private string maxfilesizeExcludedToken;
|
||||
|
||||
/// <summary>
|
||||
/// Log Lines starting with this string indicate : File was excluded by <see cref="SelectionOptions.MinFileSize"/> filters
|
||||
/// </summary>
|
||||
public string LogParsing_MinFileSizeExclusion
|
||||
{
|
||||
get { return minfilesizeExcludedToken ?? GetDefaultConfiguration().minfilesizeExcludedToken ?? "small"; }
|
||||
set { minfilesizeExcludedToken = value; }
|
||||
}
|
||||
private string minfilesizeExcludedToken;
|
||||
|
||||
/// <summary>
|
||||
/// Log Lines starting with this string indicate : File was excluded by <see cref="SelectionOptions.MaxFileAge"/> or <see cref="SelectionOptions.MaxLastAccessDate"/>filters
|
||||
/// </summary>
|
||||
public string LogParsing_MaxAgeOrAccessExclusion
|
||||
{
|
||||
get { return maxageoraccessExcludedToken ?? GetDefaultConfiguration().maxageoraccessExcludedToken ?? "too old"; }
|
||||
set { maxageoraccessExcludedToken = value; }
|
||||
}
|
||||
private string maxageoraccessExcludedToken;
|
||||
|
||||
/// <summary>
|
||||
/// Log Lines starting with this string indicate : File was excluded by <see cref="SelectionOptions.MinFileAge"/> or <see cref="SelectionOptions.MinLastAccessDate"/>filters
|
||||
/// </summary>
|
||||
public string LogParsing_MinAgeOrAccessExclusion
|
||||
{
|
||||
get { return minageoraccessExcludedToken ?? GetDefaultConfiguration().minageoraccessExcludedToken ?? "too new"; }
|
||||
set { minageoraccessExcludedToken = value; }
|
||||
}
|
||||
private string minageoraccessExcludedToken;
|
||||
|
||||
/// <summary>
|
||||
/// Log Lines starting with this string indicate : File was excluded by <see cref="SelectionOptions.ExcludeChanged"/> filters
|
||||
/// </summary>
|
||||
public string LogParsing_ChangedExclusion
|
||||
{
|
||||
get { return changedExcludedToken ?? GetDefaultConfiguration().changedExcludedToken ?? "changed"; }
|
||||
set { changedExcludedToken = value; }
|
||||
}
|
||||
private string changedExcludedToken;
|
||||
|
||||
/// <summary>
|
||||
/// Log Lines starting with this string indicate : File was included by <see cref="SelectionOptions.IncludeTweaked"/> filters
|
||||
/// </summary>
|
||||
public string LogParsing_TweakedInclusion
|
||||
{
|
||||
get { return tweakedIncludedToken ?? GetDefaultConfiguration().tweakedIncludedToken ?? "tweaked"; }
|
||||
set { tweakedIncludedToken = value; }
|
||||
}
|
||||
private string tweakedIncludedToken;
|
||||
|
||||
#endregion
|
||||
|
||||
#region < Directory Tokens >
|
||||
|
||||
/// <summary>
|
||||
/// Log Lines starting with this string indicate : New Dir -> Directory will be copied to Destination
|
||||
/// </summary>
|
||||
public string LogParsing_NewDir
|
||||
{
|
||||
get { return newerDirToken ?? GetDefaultConfiguration().newerDirToken ?? "New Dir"; }
|
||||
set { newerDirToken = value; }
|
||||
}
|
||||
private string newerDirToken;
|
||||
|
||||
/// <summary>
|
||||
/// Log Lines starting with this string indicate : Extra Dir -> Does not exist in source
|
||||
/// </summary>
|
||||
public string LogParsing_ExtraDir
|
||||
{
|
||||
get { return extraDirToken ?? GetDefaultConfiguration().extraDirToken ?? "*EXTRA Dir"; }
|
||||
set { extraDirToken = value; }
|
||||
}
|
||||
private string extraDirToken;
|
||||
|
||||
/// <summary>
|
||||
/// Existing Dirs do not have an identifier on the line. Instead, this string will be used when creating the <see cref="ProcessedFileInfo"/> object to indicate an Existing Directory.
|
||||
/// </summary>
|
||||
public string LogParsing_ExistingDir
|
||||
{
|
||||
get { return existingDirToken ?? GetDefaultConfiguration().existingDirToken ?? "Existing Dir"; }
|
||||
set { existingDirToken = value; }
|
||||
}
|
||||
private string existingDirToken;
|
||||
|
||||
/// <summary>
|
||||
/// Log Lines starting with this string indicate : Folder was excluded by <see cref="SelectionOptions.ExcludedDirectories"/> filters
|
||||
/// </summary>
|
||||
public string LogParsing_DirectoryExclusion
|
||||
{
|
||||
get { return dirExcludedToken ?? GetDefaultConfiguration().dirExcludedToken ?? "named"; } // TODO: Needs Verification
|
||||
set { dirExcludedToken = value; }
|
||||
}
|
||||
private string dirExcludedToken;
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion </ Tokens for Log Parsing >
|
||||
|
||||
/// <summary>
|
||||
/// Specify the path to RoboCopy.exe here. If not set, use the default copy.
|
||||
/// </summary>
|
||||
public string RoboCopyExe
|
||||
{
|
||||
get { return roboCopyExe ?? "Robocopy.exe"; }
|
||||
set { roboCopyExe = value; }
|
||||
}
|
||||
private string roboCopyExe = null;
|
||||
|
||||
/// <Remarks>Default is retrieved from the OEMCodePage</Remarks>
|
||||
/// <inheritdoc cref="System.Diagnostics.ProcessStartInfo.StandardOutputEncoding" path="/summary"/>
|
||||
public System.Text.Encoding StandardOutputEncoding { get; set; } = System.Text.Encoding.GetEncoding(System.Globalization.CultureInfo.CurrentCulture.TextInfo.OEMCodePage);
|
||||
|
||||
/// <Remarks>Default is retrieved from the OEMCodePage</Remarks>
|
||||
/// <inheritdoc cref="System.Diagnostics.ProcessStartInfo.StandardErrorEncoding" path="/summary"/>
|
||||
public System.Text.Encoding StandardErrorEncoding { get; set; } = System.Text.Encoding.GetEncoding(System.Globalization.CultureInfo.CurrentCulture.TextInfo.OEMCodePage);
|
||||
|
||||
|
||||
private RoboSharpConfiguration defaultConfig = null;
|
||||
private RoboSharpConfiguration GetDefaultConfiguration()
|
||||
{
|
||||
if (defaultConfig != null) return defaultConfig;
|
||||
|
||||
// check for default with language Tag xx-YY (e.g. en-US)
|
||||
var currentLanguageTag = System.Globalization.CultureInfo.CurrentUICulture.IetfLanguageTag;
|
||||
if (defaultConfigurations.ContainsKey(currentLanguageTag))
|
||||
{
|
||||
defaultConfig = defaultConfigurations[currentLanguageTag];
|
||||
}
|
||||
else
|
||||
{
|
||||
// check for default with language Tag xx (e.g. en)
|
||||
var match = Regex.Match(currentLanguageTag, @"^\w+", RegexOptions.Compiled);
|
||||
if (match.Success)
|
||||
{
|
||||
var currentMainLanguageTag = match.Value;
|
||||
if (defaultConfigurations.ContainsKey(currentMainLanguageTag))
|
||||
{
|
||||
defaultConfig = defaultConfigurations[currentMainLanguageTag];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no match, fallback to en
|
||||
return defaultConfig ?? defaultConfigurations["en"];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,564 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.IO;
|
||||
|
||||
namespace RoboSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// RoboCopy Switches that determine which folders and files are selected for copying/moving
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://github.com/tjscience/RoboSharp/wiki/SelectionOptions"/>
|
||||
/// </remarks>
|
||||
public class SelectionOptions : ICloneable
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Create new SelectionOptions with Default Settings
|
||||
/// </summary>
|
||||
public SelectionOptions() { }
|
||||
|
||||
/// <summary>
|
||||
/// Create new SelectionOptions using the provided <paramref name="selectionFlags"/>
|
||||
/// </summary>
|
||||
public SelectionOptions(SelectionFlags selectionFlags)
|
||||
{
|
||||
ApplySelectionFlags(selectionFlags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clone a SelectionOptions Object
|
||||
/// </summary>
|
||||
public SelectionOptions(SelectionOptions options)
|
||||
{
|
||||
OnlyCopyArchiveFiles = options.OnlyCopyArchiveFiles;
|
||||
OnlyCopyArchiveFilesAndResetArchiveFlag = options.OnlyCopyArchiveFilesAndResetArchiveFlag;
|
||||
IncludeAttributes = options.IncludeAttributes;
|
||||
ExcludeAttributes = options.ExcludeAttributes;
|
||||
ExcludedFiles.AddRange(options.ExcludedFiles);
|
||||
ExcludedDirectories.AddRange(options.ExcludedDirectories);
|
||||
ExcludeChanged = options.ExcludeChanged;
|
||||
ExcludeNewer = options.ExcludeNewer;
|
||||
ExcludeOlder = options.ExcludeOlder;
|
||||
ExcludeExtra = options.ExcludeExtra;
|
||||
ExcludeLonely = options.ExcludeLonely;
|
||||
IncludeSame = options.IncludeSame;
|
||||
IncludeTweaked = options.IncludeTweaked;
|
||||
MaxFileSize = options.MaxFileSize;
|
||||
MinFileSize = options.MinFileSize;
|
||||
MaxFileAge = options.MaxFileAge;
|
||||
MinFileAge = options.MinFileAge;
|
||||
MaxLastAccessDate = options.MaxLastAccessDate;
|
||||
MinLastAccessDate = options.MinLastAccessDate;
|
||||
ExcludeJunctionPoints = options.ExcludeJunctionPoints;
|
||||
UseFatFileTimes = options.UseFatFileTimes;
|
||||
CompensateForDstDifference = options.CompensateForDstDifference; ;
|
||||
ExcludeJunctionPointsForFiles = options.ExcludeJunctionPointsForFiles;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clone this SelectionOptions Object
|
||||
/// </summary>
|
||||
public SelectionOptions Clone() => new SelectionOptions(this);
|
||||
|
||||
object ICloneable.Clone() => Clone();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Option Constants
|
||||
|
||||
internal const string ONLY_COPY_ARCHIVE_FILES = "/A ";
|
||||
internal const string ONLY_COPY_ARCHIVE_FILES_AND_RESET_ARCHIVE_FLAG = "/M ";
|
||||
internal const string INCLUDE_ATTRIBUTES = "/IA:{0} ";
|
||||
internal const string EXCLUDE_ATTRIBUTES = "/XA:{0} ";
|
||||
internal const string EXCLUDE_FILES = "/XF {0} ";
|
||||
internal const string EXCLUDE_DIRECTORIES = "/XD {0} ";
|
||||
internal const string EXCLUDE_CHANGED = "/XC ";
|
||||
internal const string EXCLUDE_NEWER = "/XN ";
|
||||
internal const string EXCLUDE_OLDER = "/XO ";
|
||||
internal const string EXCLUDE_EXTRA = "/XX ";
|
||||
internal const string EXCLUDE_LONELY = "/XL ";
|
||||
internal const string INCLUDE_SAME = "/IS ";
|
||||
internal const string INCLUDE_TWEAKED = "/IT ";
|
||||
internal const string MAX_FILE_SIZE = "/MAX:{0} ";
|
||||
internal const string MIN_FILE_SIZE = "/MIN:{0} ";
|
||||
internal const string MAX_FILE_AGE = "/MAXAGE:{0} ";
|
||||
internal const string MIN_FILE_AGE = "/MINAGE:{0} ";
|
||||
internal const string MAX_LAST_ACCESS_DATE = "/MAXLAD:{0} ";
|
||||
internal const string MIN_LAST_ACCESS_DATE = "/MINLAD:{0} ";
|
||||
internal const string EXCLUDE_JUNCTION_POINTS = "/XJ ";
|
||||
internal const string USE_FAT_FILE_TIMES = "/FFT ";
|
||||
internal const string COMPENSATE_FOR_DST_DIFFERENCE = "/DST ";
|
||||
internal const string EXCLUDE_JUNCTION_POINTS_FOR_DIRECTORIES = "/XJD ";
|
||||
internal const string EXCLUDE_JUNCTION_POINTS_FOR_FILES = "/XJF ";
|
||||
|
||||
#endregion Option Constants
|
||||
|
||||
#region < ExcludedDirs and ExcludedFiles >
|
||||
|
||||
private readonly List<string> excludedDirs = new List<string>();
|
||||
private readonly List<string> excludedFiles = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// This regex is used when the { <see cref="ExcludeFiles"/> } and { <see cref="ExcludeDirectories"/> } properties are set in order to split the input string to a List{string}
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Regex Tester to use with <see cref="Regex.Matches(string)"/> to get all the matches from a string.
|
||||
/// </remarks>
|
||||
public static Regex FileFolderNameRegexSplitter = new Regex("(?<VALUE>\".+?\"|[^\\s\\,\"\\|]+)", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
|
||||
|
||||
/// <summary>
|
||||
/// Use { <see cref="FileFolderNameRegexSplitter"/> } to split the <paramref name="inputString"/>, then add the matches to the suppplied <paramref name="list"/>.
|
||||
/// </summary>
|
||||
/// <param name="inputString">String to perform <see cref="Regex.Matches(string)"/> against</param>
|
||||
/// <param name="list">List to add regex matches to</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ParseAndAddToList(string inputString, List<string> list)
|
||||
{
|
||||
MatchCollection collection = FileFolderNameRegexSplitter.Matches(inputString);
|
||||
if (collection.Count == 0) return;
|
||||
foreach (Match c in collection)
|
||||
{
|
||||
string s = c.Groups["VALUE"].Value;
|
||||
list.Add(s);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Copies only files for which the Archive attribute is set.
|
||||
/// [/A]
|
||||
/// </summary>
|
||||
public virtual bool OnlyCopyArchiveFiles { get; set; }
|
||||
/// <summary>
|
||||
/// Copies only files for which the Archive attribute is set, and resets the Archive attribute.
|
||||
/// [/M]
|
||||
/// </summary>
|
||||
public virtual bool OnlyCopyArchiveFilesAndResetArchiveFlag { get; set; }
|
||||
/// <summary>
|
||||
/// This property should be set to a string consisting of all the attributes to include (eg. AH; RASHCNETO).
|
||||
/// Includes only files for which any of the specified attributes are set.
|
||||
/// [/IA:attributes]
|
||||
/// </summary>
|
||||
public virtual string IncludeAttributes { get; set; }
|
||||
/// <summary>
|
||||
/// This property should be set to a string consisting of all the attributes to exclude (eg. AH; RASHCNETO).
|
||||
/// Excludes files for which any of the specified attributes are set.
|
||||
/// [/XA:attributes]
|
||||
/// </summary>
|
||||
public virtual string ExcludeAttributes { get; set; }
|
||||
/// <summary>
|
||||
/// Files should be separated by spaces.
|
||||
/// Excludes files that match the specified names or paths. Note that FileName can include wildcard characters (* and ?).
|
||||
/// [/XF File File ...]
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property is now backed by the ExcludedFiles List{String} property. <br/>
|
||||
/// Get -> Ensures all strings in { <see cref="ExcludedFiles"/> } are wrapped in quotes if needed, and concats the items into a single string. <br/>
|
||||
/// Set -- Clears ExcludedFiles and splits this list using a regex to populate the list.
|
||||
/// </remarks>
|
||||
[Obsolete("This property is now backed by the ExcludedFiles List<String> property. \n Both Get/Set accessors still work similar to previous:\n" +
|
||||
"- 'Get' sanitizies then Joins all strings in the list into a single output string that is passed into RoboCopy.\n" +
|
||||
"- 'Set' clears the ExcludedFiles list, then splits the input string using regex to repopulate the list."
|
||||
)]
|
||||
public string ExcludeFiles
|
||||
{
|
||||
get
|
||||
{
|
||||
string RetString = "";
|
||||
foreach (string s in excludedFiles)
|
||||
{
|
||||
RetString += s.WrapPath() + " ";
|
||||
}
|
||||
return RetString.Trim();
|
||||
}
|
||||
set
|
||||
{
|
||||
excludedFiles.Clear();
|
||||
if (value.IsNullOrWhiteSpace()) return;
|
||||
ParseAndAddToList(value, excludedFiles);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to supply a set of files to copy or use wildcard characters (* or ?). <br/>
|
||||
/// JobOptions file saves these into the /IF (Include Files) section
|
||||
/// </summary>
|
||||
public List<string> ExcludedFiles
|
||||
{
|
||||
get
|
||||
{
|
||||
return excludedFiles;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Directories should be separated by spaces.
|
||||
/// Excludes directories that match the specified names or paths.
|
||||
/// [/XD Directory Directory ...]
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property is now backed by the ExcludedDirectories List{String} property. <br/>
|
||||
/// Get -> Ensures all strings in { <see cref="ExcludedDirectories"/> } are wrapped in quotes if needed, and concats the items into a single string. <br/>
|
||||
/// Set -> Clears ExcludedDirs and splits this list using a regex to populate the list.
|
||||
/// </remarks>
|
||||
[Obsolete("This property is now backed by the ExcludedDirectories List<String> property. \n Both Get/Set accessors still work similar to previous:\n" +
|
||||
"- 'Get' sanitizies then Joins all strings in the list into a single output string that is passed into RoboCopy.\n" +
|
||||
"- 'Set' clears the ExcludedDirectories list, then splits the input string using regex to repopulate the list."
|
||||
)]
|
||||
public string ExcludeDirectories
|
||||
{
|
||||
get
|
||||
{
|
||||
string RetString = "";
|
||||
foreach (string s in excludedDirs)
|
||||
{
|
||||
RetString += s.WrapPath() + " ";
|
||||
}
|
||||
return RetString.Trim();
|
||||
}
|
||||
set
|
||||
{
|
||||
excludedDirs.Clear();
|
||||
if (value.IsNullOrWhiteSpace()) return;
|
||||
ParseAndAddToList(value, excludedDirs);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Allows you to supply a set of files to copy or use wildcard characters (* or ?). <br/>
|
||||
/// JobOptions file saves these into the /IF (Include Files) section
|
||||
/// </summary>
|
||||
public List<string> ExcludedDirectories
|
||||
{
|
||||
get
|
||||
{
|
||||
return excludedDirs;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Excludes changed files.
|
||||
/// [/XC]
|
||||
/// </summary>
|
||||
public virtual bool ExcludeChanged { get; set; }
|
||||
/// <summary>
|
||||
/// Excludes newer files.
|
||||
/// [/XN]
|
||||
/// </summary>
|
||||
public virtual bool ExcludeNewer { get; set; }
|
||||
/// <summary>
|
||||
/// Excludes older files.
|
||||
/// [/XO]
|
||||
/// </summary>
|
||||
public virtual bool ExcludeOlder { get; set; }
|
||||
/// <summary>
|
||||
/// Excludes extra files and directories.
|
||||
/// [/XX]
|
||||
/// </summary>
|
||||
public virtual bool ExcludeExtra { get; set; }
|
||||
/// <summary>
|
||||
/// Excludes lonely files and directories.
|
||||
/// [/XL]
|
||||
/// </summary>
|
||||
public virtual bool ExcludeLonely { get; set; }
|
||||
/// <summary>
|
||||
/// Includes the same files.
|
||||
/// [/IS]
|
||||
/// </summary>
|
||||
public virtual bool IncludeSame { get; set; }
|
||||
/// <summary>
|
||||
/// Includes tweaked files.
|
||||
/// [/IT]
|
||||
/// </summary>
|
||||
public virtual bool IncludeTweaked { get; set; }
|
||||
/// <summary>
|
||||
/// Zero indicates that this feature is turned off.
|
||||
/// Specifies the maximum file size (to exclude files bigger than N bytes).
|
||||
/// [/MAX:N]
|
||||
/// </summary>
|
||||
public virtual long MaxFileSize { get; set; }
|
||||
/// <summary>
|
||||
/// Zero indicates that this feature is turned off.
|
||||
/// Specifies the minimum file size (to exclude files smaller than N bytes).
|
||||
/// [/MIN:N]
|
||||
/// </summary>
|
||||
public virtual long MinFileSize { get; set; }
|
||||
/// <summary>
|
||||
/// Specifies the maximum file age (to exclude files older than N days or date).
|
||||
/// [/MAXAGE:N OR YYYYMMDD]
|
||||
/// </summary>
|
||||
public virtual string MaxFileAge { get; set; }
|
||||
/// <summary>
|
||||
/// Specifies the minimum file age (exclude files newer than N days or date).
|
||||
/// [/MINAGE:N OR YYYYMMDD]
|
||||
/// </summary>
|
||||
public virtual string MinFileAge { get; set; }
|
||||
/// <summary>
|
||||
/// Specifies the maximum last access date (excludes files unused since Date).
|
||||
/// [/MAXLAD:YYYYMMDD]
|
||||
/// </summary>
|
||||
public virtual string MaxLastAccessDate { get; set; }
|
||||
/// <summary>
|
||||
/// Specifies the minimum last access date (excludes files used since N) If N is less
|
||||
/// than 1900, N specifies the number of days. Otherwise, N specifies a date
|
||||
/// in the format YYYYMMDD.
|
||||
/// [/MINLAD:N or YYYYMMDD]
|
||||
/// </summary>
|
||||
public virtual string MinLastAccessDate { get; set; }
|
||||
/// <summary>
|
||||
/// Excludes junction points, which are normally included by default.
|
||||
/// [/XJ]
|
||||
/// </summary>
|
||||
public virtual bool ExcludeJunctionPoints { get; set; }
|
||||
/// <summary>
|
||||
/// Assumes FAT file times (two-second precision).
|
||||
/// [/FFT]
|
||||
/// </summary>
|
||||
public virtual bool UseFatFileTimes { get; set; }
|
||||
/// <summary>
|
||||
/// Compensates for one-hour DST time differences.
|
||||
/// [/DST]
|
||||
/// </summary>
|
||||
public virtual bool CompensateForDstDifference { get; set; }
|
||||
/// <summary>
|
||||
/// Excludes junction points for directories.
|
||||
/// [/XJD]
|
||||
/// </summary>
|
||||
public virtual bool ExcludeJunctionPointsForDirectories { get; set; }
|
||||
/// <summary>
|
||||
/// Excludes junction points for files.
|
||||
/// [/XJF]
|
||||
/// </summary>
|
||||
public virtual bool ExcludeJunctionPointsForFiles { get; set; }
|
||||
|
||||
#endregion Public Properties
|
||||
|
||||
/// <param name="AttributesToInclude"><inheritdoc cref="ConvertFileAttrToString(FileAttributes?)"/></param>
|
||||
/// <inheritdoc cref="ConvertFileAttrToString(FileAttributes?)"/>
|
||||
public void SetIncludedAttributes(FileAttributes? AttributesToInclude) => this.IncludeAttributes = ConvertFileAttrToString(AttributesToInclude);
|
||||
|
||||
/// <param name="AttributesToExclude"><inheritdoc cref="ConvertFileAttrToString(FileAttributes?)"/></param>
|
||||
/// <inheritdoc cref="ConvertFileAttrToString(FileAttributes?)"/>
|
||||
public void SetExcludedAttributes(FileAttributes? AttributesToExclude) => this.ExcludeAttributes = ConvertFileAttrToString(AttributesToExclude);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="FileAttributes"/> enum to its RASHCNETO string.
|
||||
/// </summary>
|
||||
/// <param name="attributes">
|
||||
/// Accepts: ReadOnly, Archive, System, Hidden, Compressed, NotContentIndexed, Encrypted, Temporary, Offline <br/>
|
||||
/// Ignores: All Other Attributes <br/>
|
||||
/// Pass in NULL value to return empty string.
|
||||
/// </param>
|
||||
/// <returns>RASHCNETO depending on submitted enum</returns>
|
||||
public static string ConvertFileAttrToString(FileAttributes? attributes)
|
||||
{
|
||||
if (attributes is null) return String.Empty;
|
||||
string s = "";
|
||||
var Attr = (FileAttributes)attributes;
|
||||
if (Attr.HasFlag(FileAttributes.ReadOnly)) s += "R";
|
||||
if (Attr.HasFlag(FileAttributes.Archive)) s += "A";
|
||||
if (Attr.HasFlag(FileAttributes.System)) s += "S";
|
||||
if (Attr.HasFlag(FileAttributes.Hidden)) s += "H";
|
||||
if (Attr.HasFlag(FileAttributes.Compressed)) s += "C";
|
||||
if (Attr.HasFlag(FileAttributes.NotContentIndexed)) s += "N";
|
||||
if (Attr.HasFlag(FileAttributes.Encrypted)) s += "E";
|
||||
if (Attr.HasFlag(FileAttributes.Temporary)) s += "T";
|
||||
if (Attr.HasFlag(FileAttributes.Offline)) s += "O";
|
||||
return s;
|
||||
}
|
||||
|
||||
internal string Parse()
|
||||
{
|
||||
var options = new StringBuilder();
|
||||
|
||||
#region Set Options
|
||||
|
||||
if (OnlyCopyArchiveFiles)
|
||||
options.Append(ONLY_COPY_ARCHIVE_FILES);
|
||||
if (OnlyCopyArchiveFilesAndResetArchiveFlag)
|
||||
options.Append(ONLY_COPY_ARCHIVE_FILES_AND_RESET_ARCHIVE_FLAG);
|
||||
if (!IncludeAttributes.IsNullOrWhiteSpace())
|
||||
options.Append(string.Format(INCLUDE_ATTRIBUTES, IncludeAttributes.CleanOptionInput()));
|
||||
if (!ExcludeAttributes.IsNullOrWhiteSpace())
|
||||
options.Append(string.Format(EXCLUDE_ATTRIBUTES, ExcludeAttributes.CleanOptionInput()));
|
||||
#pragma warning disable CS0618 // Marked as Obsolete for consumers, but it originally functionality is still intact, so this still works properly.
|
||||
if (!ExcludeFiles.IsNullOrWhiteSpace())
|
||||
options.Append(string.Format(EXCLUDE_FILES, ExcludeFiles));
|
||||
if (!ExcludeDirectories.IsNullOrWhiteSpace())
|
||||
options.Append(string.Format(EXCLUDE_DIRECTORIES, ExcludeDirectories));
|
||||
#pragma warning restore CS0618
|
||||
if (ExcludeChanged)
|
||||
options.Append(EXCLUDE_CHANGED);
|
||||
if (ExcludeNewer)
|
||||
options.Append(EXCLUDE_NEWER);
|
||||
if (ExcludeOlder)
|
||||
options.Append(EXCLUDE_OLDER);
|
||||
if (ExcludeExtra)
|
||||
options.Append(EXCLUDE_EXTRA);
|
||||
if (ExcludeLonely)
|
||||
options.Append(EXCLUDE_LONELY);
|
||||
if (IncludeSame)
|
||||
options.Append(INCLUDE_SAME);
|
||||
if (IncludeTweaked)
|
||||
options.Append(INCLUDE_TWEAKED);
|
||||
if (MaxFileSize > 0)
|
||||
options.Append(string.Format(MAX_FILE_SIZE, MaxFileSize));
|
||||
if (MinFileSize > 0)
|
||||
options.Append(string.Format(MIN_FILE_SIZE, MinFileSize));
|
||||
if (!MaxFileAge.IsNullOrWhiteSpace())
|
||||
options.Append(string.Format(MAX_FILE_AGE, MaxFileAge.CleanOptionInput()));
|
||||
if (!MinFileAge.IsNullOrWhiteSpace())
|
||||
options.Append(string.Format(MIN_FILE_AGE, MinFileAge.CleanOptionInput()));
|
||||
if (!MaxLastAccessDate.IsNullOrWhiteSpace())
|
||||
options.Append(string.Format(MAX_LAST_ACCESS_DATE, MaxLastAccessDate.CleanOptionInput()));
|
||||
if (!MinLastAccessDate.IsNullOrWhiteSpace())
|
||||
options.Append(string.Format(MIN_LAST_ACCESS_DATE, MinLastAccessDate.CleanOptionInput()));
|
||||
if (ExcludeJunctionPoints)
|
||||
options.Append(EXCLUDE_JUNCTION_POINTS);
|
||||
if (ExcludeJunctionPointsForDirectories)
|
||||
options.Append(EXCLUDE_JUNCTION_POINTS_FOR_DIRECTORIES);
|
||||
if (ExcludeJunctionPointsForFiles)
|
||||
options.Append(EXCLUDE_JUNCTION_POINTS_FOR_FILES);
|
||||
if (UseFatFileTimes)
|
||||
options.Append(USE_FAT_FILE_TIMES);
|
||||
if (CompensateForDstDifference)
|
||||
options.Append(COMPENSATE_FOR_DST_DIFFERENCE);
|
||||
|
||||
#endregion Set Options
|
||||
|
||||
return options.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combine this object with another RetryOptions object. <br/>
|
||||
/// Any properties marked as true take priority. IEnumerable items are combined. <br/>
|
||||
/// String\Long Values will only be replaced if the primary object has a null/empty value for that property.
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
public void Merge(SelectionOptions options)
|
||||
{
|
||||
//File Attributes
|
||||
IncludeAttributes = IncludeAttributes.CombineCharArr(options.IncludeAttributes);
|
||||
ExcludeAttributes = ExcludeAttributes.CombineCharArr(options.ExcludeAttributes);
|
||||
|
||||
//File Age
|
||||
MaxFileAge = MaxFileAge.ReplaceIfEmpty(options.MaxFileAge);
|
||||
MinFileAge = MaxFileAge.ReplaceIfEmpty(options.MinFileAge);
|
||||
MaxLastAccessDate = MaxFileAge.ReplaceIfEmpty(options.MaxLastAccessDate);
|
||||
MinLastAccessDate = MaxFileAge.ReplaceIfEmpty(options.MinLastAccessDate);
|
||||
|
||||
//Long
|
||||
MaxFileSize |= options.MaxFileSize;
|
||||
MinFileSize |= options.MinFileSize;
|
||||
|
||||
//Lists
|
||||
ExcludedFiles.AddRange(options.ExcludedFiles);
|
||||
ExcludedDirectories.AddRange(options.ExcludedDirectories);
|
||||
|
||||
//Bools
|
||||
OnlyCopyArchiveFiles |= options.OnlyCopyArchiveFiles;
|
||||
OnlyCopyArchiveFilesAndResetArchiveFlag |= options.OnlyCopyArchiveFilesAndResetArchiveFlag;
|
||||
ExcludeChanged |= options.ExcludeChanged;
|
||||
ExcludeNewer |= options.ExcludeNewer;
|
||||
ExcludeOlder |= options.ExcludeOlder;
|
||||
ExcludeExtra |= options.ExcludeExtra;
|
||||
ExcludeLonely |= options.ExcludeLonely;
|
||||
IncludeSame |= options.IncludeSame;
|
||||
IncludeTweaked |= options.IncludeTweaked;
|
||||
ExcludeJunctionPoints |= options.ExcludeJunctionPoints;
|
||||
ExcludeJunctionPointsForFiles |= options.ExcludeJunctionPointsForFiles;
|
||||
ExcludeJunctionPointsForDirectories |= options.ExcludeJunctionPointsForDirectories;
|
||||
|
||||
UseFatFileTimes |= options.UseFatFileTimes;
|
||||
CompensateForDstDifference |= options.CompensateForDstDifference; ;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum to define various selection options that can be toggled for the RoboCopy process.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum SelectionFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// Set RoboCopy options to their defaults
|
||||
/// </summary>
|
||||
Default = 0,
|
||||
/// <inheritdoc cref="SelectionOptions.ExcludeChanged"/>
|
||||
ExcludeChanged = 1,
|
||||
/// <inheritdoc cref="SelectionOptions.ExcludeExtra"/>
|
||||
ExcludeExtra = 2,
|
||||
/// <inheritdoc cref="SelectionOptions.ExcludeLonely"/>
|
||||
ExcludeLonely = 4,
|
||||
/// <inheritdoc cref="SelectionOptions.ExcludeNewer"/>
|
||||
ExcludeNewer = 8,
|
||||
/// <inheritdoc cref="SelectionOptions.ExcludeOlder"/>
|
||||
ExcludeOlder = 16,
|
||||
/// <inheritdoc cref="SelectionOptions.ExcludeJunctionPoints"/>
|
||||
ExcludeJunctionPoints = 32,
|
||||
/// <inheritdoc cref="SelectionOptions.ExcludeJunctionPointsForDirectories"/>
|
||||
ExcludeJunctionPointsForDirectories = 64,
|
||||
/// <inheritdoc cref="SelectionOptions.ExcludeJunctionPointsForFiles"/>
|
||||
ExcludeJunctionPointsForFiles = 128,
|
||||
/// <inheritdoc cref="SelectionOptions.IncludeSame"/>
|
||||
IncludeSame = 256,
|
||||
/// <inheritdoc cref="SelectionOptions.IncludeTweaked"/>
|
||||
IncludeTweaked = 512,
|
||||
/// <inheritdoc cref="SelectionOptions.OnlyCopyArchiveFiles"/>
|
||||
OnlyCopyArchiveFiles = 1024,
|
||||
/// <inheritdoc cref="SelectionOptions.OnlyCopyArchiveFilesAndResetArchiveFlag"/>
|
||||
OnlyCopyArchiveFilesAndResetArchiveFlag = 2048,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply the <see cref="SelectionFlags"/> to this command
|
||||
/// </summary>
|
||||
/// <param name="flags">Options to apply</param>
|
||||
public virtual void ApplySelectionFlags(SelectionFlags flags)
|
||||
{
|
||||
this.ExcludeChanged = flags.HasFlag(SelectionFlags.ExcludeChanged);
|
||||
this.ExcludeExtra = flags.HasFlag(SelectionFlags.ExcludeExtra);
|
||||
this.ExcludeJunctionPoints = flags.HasFlag(SelectionFlags.ExcludeJunctionPoints);
|
||||
this.ExcludeJunctionPointsForDirectories = flags.HasFlag(SelectionFlags.ExcludeJunctionPointsForDirectories);
|
||||
this.ExcludeJunctionPointsForFiles = flags.HasFlag(SelectionFlags.ExcludeJunctionPointsForFiles);
|
||||
this.ExcludeLonely = flags.HasFlag(SelectionFlags.ExcludeLonely);
|
||||
this.ExcludeNewer = flags.HasFlag(SelectionFlags.ExcludeNewer);
|
||||
this.ExcludeOlder = flags.HasFlag(SelectionFlags.ExcludeOlder);
|
||||
this.IncludeSame = flags.HasFlag(SelectionFlags.IncludeSame);
|
||||
this.IncludeTweaked = flags.HasFlag(SelectionFlags.IncludeTweaked);
|
||||
this.OnlyCopyArchiveFiles = flags.HasFlag(SelectionFlags.OnlyCopyArchiveFiles);
|
||||
this.OnlyCopyArchiveFilesAndResetArchiveFlag = flags.HasFlag(SelectionFlags.OnlyCopyArchiveFilesAndResetArchiveFlag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translate the selection bools of this object to its <see cref="SelectionFlags"/> representation
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="SelectionFlags"/> representation of this object.</returns>
|
||||
public SelectionFlags GetSelectionFlags()
|
||||
{
|
||||
var flags = SelectionFlags.Default;
|
||||
|
||||
if (this.ExcludeChanged) flags |= SelectionFlags.ExcludeChanged;
|
||||
if (this.ExcludeExtra) flags |= SelectionFlags.ExcludeExtra;
|
||||
if (this.ExcludeJunctionPoints) flags |= SelectionFlags.ExcludeJunctionPoints;
|
||||
if (this.ExcludeJunctionPointsForDirectories) flags |= SelectionFlags.ExcludeJunctionPointsForDirectories;
|
||||
if (this.ExcludeJunctionPointsForFiles) flags |= SelectionFlags.ExcludeJunctionPointsForFiles;
|
||||
if (this.ExcludeLonely) flags |= SelectionFlags.ExcludeLonely;
|
||||
if (this.ExcludeNewer) flags |= SelectionFlags.ExcludeNewer;
|
||||
if (this.ExcludeOlder) flags |= SelectionFlags.ExcludeOlder;
|
||||
if (this.IncludeSame) flags |= SelectionFlags.IncludeSame;
|
||||
if (this.IncludeTweaked) flags |= SelectionFlags.IncludeTweaked;
|
||||
if (this.OnlyCopyArchiveFiles) flags |= SelectionFlags.OnlyCopyArchiveFiles;
|
||||
if (this.OnlyCopyArchiveFilesAndResetArchiveFlag) flags |= SelectionFlags.OnlyCopyArchiveFilesAndResetArchiveFlag;
|
||||
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RoboSharp
|
||||
{
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
public static class VersionManager
|
||||
{
|
||||
public enum VersionCheckType
|
||||
{
|
||||
UseRtlGetVersion,
|
||||
UseWMI
|
||||
}
|
||||
|
||||
public static VersionCheckType VersionCheck { get; set; } = VersionManager.VersionCheckType.UseRtlGetVersion;
|
||||
|
||||
|
||||
private static double? version;
|
||||
public static double Version
|
||||
{
|
||||
get
|
||||
{
|
||||
if (version == null)
|
||||
{
|
||||
if (VersionCheck == VersionCheckType.UseWMI)
|
||||
{
|
||||
var v = GetOsVersion();
|
||||
version = GetOsVersionNumber(v);
|
||||
return version.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
var osVersionInfo = new OSVERSIONINFOEX { OSVersionInfoSize = Marshal.SizeOf(typeof(OSVERSIONINFOEX)) };
|
||||
RtlGetVersion(ref osVersionInfo);
|
||||
var versionString = $"{osVersionInfo.MajorVersion}.{osVersionInfo.MinorVersion}{osVersionInfo.BuildNumber}";
|
||||
version = GetOsVersionNumber(versionString);
|
||||
return version.Value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return version.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static VersionManager()
|
||||
{
|
||||
System.Globalization.CultureInfo customCulture = (System.Globalization.CultureInfo)System.Threading.Thread.CurrentThread.CurrentCulture.Clone();
|
||||
customCulture.NumberFormat.NumberDecimalSeparator = ".";
|
||||
System.Threading.Thread.CurrentThread.CurrentCulture = customCulture;
|
||||
}
|
||||
|
||||
|
||||
private static string GetOsVersion()
|
||||
{
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
using (var session = Microsoft.Management.Infrastructure.CimSession.Create("."))
|
||||
|
||||
{
|
||||
var win32OperatingSystemCimInstance = session.QueryInstances("root\\cimv2", "WQL", "SELECT Version FROM Win32_OperatingSystem").FirstOrDefault();
|
||||
|
||||
if (win32OperatingSystemCimInstance?.CimInstanceProperties["Version"] != null)
|
||||
{
|
||||
return win32OperatingSystemCimInstance.CimInstanceProperties["Version"].Value.ToString();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if NET40_OR_GREATER
|
||||
using (System.Management.ManagementObjectSearcher objMOS = new System.Management.ManagementObjectSearcher("SELECT * FROM Win32_OperatingSystem"))
|
||||
{
|
||||
foreach (System.Management.ManagementObject objManagement in objMOS.Get())
|
||||
{
|
||||
var version = objManagement.GetPropertyValue("Version");
|
||||
|
||||
if (version != null)
|
||||
{
|
||||
return version.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return Environment.OSVersion.Version.ToString();
|
||||
}
|
||||
|
||||
private static double GetOsVersionNumber(string version)
|
||||
{
|
||||
if (version.IsNullOrWhiteSpace())
|
||||
return 0;
|
||||
|
||||
var segments = version.Split(new char[] { '.' });
|
||||
var major = Convert.ToDouble(segments[0]);
|
||||
var otherSegments = segments.Skip(1).ToArray();
|
||||
var dec = Convert.ToDouble("." + string.Join("", otherSegments), CultureInfo.InvariantCulture);
|
||||
return major + dec;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// taken from https://stackoverflow.com/a/49641055
|
||||
/// </summary>
|
||||
/// <param name="versionInfo"></param>
|
||||
/// <returns></returns>
|
||||
[SecurityCritical]
|
||||
[DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern int RtlGetVersion(ref OSVERSIONINFOEX versionInfo);
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct OSVERSIONINFOEX
|
||||
{
|
||||
// The OSVersionInfoSize field must be set to Marshal.SizeOf(typeof(OSVERSIONINFOEX))
|
||||
internal int OSVersionInfoSize;
|
||||
internal int MajorVersion;
|
||||
internal int MinorVersion;
|
||||
internal int BuildNumber;
|
||||
internal int PlatformId;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
|
||||
internal string CSDVersion;
|
||||
internal ushort ServicePackMajor;
|
||||
internal ushort ServicePackMinor;
|
||||
internal short SuiteMask;
|
||||
internal byte ProductType;
|
||||
internal byte Reserved;
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
|
||||
}
|
||||
Reference in New Issue
Block a user