/// <summary>
        /// Called when <see cref="PerPatient"/> is false.  Called once per extraction
        /// </summary>
        public virtual void MoveAll(DirectoryInfo destinationDirectory, IDataLoadEventListener listener, GracefulCancellationToken cancellationToken)
        {
            bool atLeastOne = false;

            foreach (var e in LocationOfFiles.EnumerateFileSystemInfos(Pattern))
            {
                if (Directories && e is DirectoryInfo dir)
                {
                    var dest = Path.Combine(destinationDirectory.FullName, dir.Name);

                    // Recursively copy all files from input path to destination path
                    listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, ($"Copying directory '{e.FullName}' to '{dest}'")));
                    CopyFolder(e.FullName, dest);
                    atLeastOne = true;
                }

                if (!Directories && e is FileInfo f)
                {
                    var dest = Path.Combine(destinationDirectory.FullName, f.Name);
                    listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, ($"Copying file '{f.FullName}' to '{dest}'")));
                    File.Copy(f.FullName, dest);
                    atLeastOne = true;
                }
            }

            if (!atLeastOne)
            {
                listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, $"No {(Directories ? "Directories": "Files")} were found matching Pattern {Pattern} in {LocationOfFiles.FullName}"));
            }
        }
        /// <summary>
        /// Called when <see cref="PerPatient"/> is true.  Called once per private identifier.  Note that it is possible for 2 private identifiers to map to the same release identifier - be careful
        /// </summary>
        public virtual void MovePatient(object privateIdentifier, object releaseIdentifier, DirectoryInfo destinationDirectory, IDataLoadEventListener listener, GracefulCancellationToken cancellationToken)
        {
            bool atLeastOne = false;

            if (privateIdentifier == DBNull.Value || string.IsNullOrWhiteSpace(privateIdentifier?.ToString()))
            {
                listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, "Skipped NULL private identifier found in cohort when trying to copy files"));
                return;
            }

            if (releaseIdentifier == DBNull.Value || string.IsNullOrWhiteSpace(releaseIdentifier?.ToString()))
            {
                listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Error, $"Found NULL release identifier in cohort when trying to copy files.  This is not allowed as it breaks file name substitutions.  Private identifier was {privateIdentifier}"));
                return;
            }

            // What we will be writting into the file/path names in place of the private identifier
            var releaseSub = UsefulStuff.RemoveIllegalFilenameCharacters(releaseIdentifier.ToString());

            var patternAfterTokenInsertion = Pattern.Replace("$p", privateIdentifier.ToString());

            foreach (var e in LocationOfFiles.EnumerateFileSystemInfos(patternAfterTokenInsertion))
            {
                if (Directories && e is DirectoryInfo dir)
                {
                    var dest = Path.Combine(
                        destinationDirectory.FullName,
                        dir.Name.Replace(privateIdentifier.ToString(), releaseSub));

                    // Recursively copy all files from input path to destination path
                    listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, ($"Copying directory '{e.FullName}' to '{dest}'")));
                    CopyFolder(e.FullName, dest);
                    atLeastOne = true;
                }

                if (!Directories && e is FileInfo f)
                {
                    var dest = Path.Combine(
                        destinationDirectory.FullName,
                        f.Name.Replace(privateIdentifier.ToString(), releaseSub));

                    listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, ($"Copying file '{f.FullName}' to '{dest}'")));
                    File.Copy(f.FullName, dest);
                    atLeastOne = true;
                }
            }
            if (!atLeastOne)
            {
                listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, $"No {(Directories ? "Directories": "Files")} were found matching Pattern {patternAfterTokenInsertion} in {LocationOfFiles.FullName}.  For private identifier '{privateIdentifier}'"));
            }
        }