Ejemplo n.º 1
0
        public void ProcessIo_ExitNonZero()
        {
            if (!Utils.IsRuntimeWindowsPlatform)
            {
                return;
            }

            var process = new ProcessIo(@"C:\Windows\System32\net.exe");

            process.Execute(new ProcessArgs().Append("use").Append("7874987498"));
            Assert.AreNotEqual(0, process.ExitCode);
            Assert.IsTrue(process.ErrorOutputArray.Count > 0);
            Assert.IsTrue(process.StandardOutputArray.Count == 0);
        }
        /// <inheritdoc cref="IArchiver.ListFiles"/>
        public IEnumerable <IFileInArchive> ListFiles(string archivePath)
        {
            if (!File.Exists(archivePath))
            {
                return(Enumerable.Empty <IFileInArchive>());
            }

            var prolibExe = new ProcessIo(_prolibPath);

            if (!prolibExe.TryExecute(new ProcessArgs().Append(archivePath, "-list")))   // -date mdy
            {
                throw new Exception("Error while listing files from a .pl.", new Exception(prolibExe.BatchOutput.ToString()));
            }

            var outputList = new List <IFileInArchive>();
            var regex      = new Regex(@"^(.+)\s+(\d+)\s+(\w+)\s+(\d+)\s+(\d{2}\/\d{2}\/\d{2}\s\d{2}\:\d{2}\:\d{2})\s(\d{2}\/\d{2}\/\d{2}\s\d{2}\:\d{2}\:\d{2})");

            foreach (var output in prolibExe.StandardOutputArray)
            {
                var match = regex.Match(output);
                if (match.Success)
                {
                    // Third match is the file type. PROLIB recognizes two file types: R (r-code file type) and O (any other file type).
                    // Fourth is the offset, the distance, in bytes, of the start of the file from the beginning of the library.
                    var type    = match.Groups[3].Value;
                    var newFile = new FileInProlib {
                        PathInArchive = match.Groups[1].Value.TrimEnd(),
                        SizeInBytes   = ulong.Parse(match.Groups[2].Value),
                        IsRcode       = !string.IsNullOrEmpty(type) && type[0] == 'R'
                    };
                    if (DateTime.TryParseExact(match.Groups[5].Value, @"MM/dd/yy HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var date))
                    {
                        newFile.DateAdded = date;
                    }

                    if (DateTime.TryParseExact(match.Groups[6].Value, @"MM/dd/yy HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
                    {
                        newFile.LastWriteTime = date;
                    }

                    outputList.Add(newFile);
                }
            }

            return(outputList);
        }
Ejemplo n.º 3
0
 public ProlibPackager(string archivePath, string prolibPath)
 {
     _archivePath = archivePath;
     _prolibExe   = new ProcessIo(prolibPath);
 }
Ejemplo n.º 4
0
 public ProlibExtractor(string archivePath, string prolibPath, string plExtractionFolder)
 {
     _archivePath        = archivePath;
     _plExtractionFolder = plExtractionFolder;
     _prolibExe          = new ProcessIo(prolibPath);
 }
Ejemplo n.º 5
0
        /// <summary>
        /// Deploy a given list of files (can reduce the list if there are duplicated items so it returns it)
        /// </summary>
        public List <FileToDeploy> DeployFiles(List <FileToDeploy> deployToDo, Action <float> updateDeploymentPercentage = null)
        {
            int[] totalFile   = { 0 };
            int[] nbFilesDone = { 0 };

            // make sure to transfer a given file only once at the same place (happens with .cls file since a source
            // can have several .r files generated if it is used in another classes)
            deployToDo = deployToDo
                         .GroupBy(deploy => deploy.To)
                         .Select(group => group.FirstOrDefault(move => Path.GetFileNameWithoutExtension(move.From ?? "").Equals(Path.GetFileNameWithoutExtension(move.Origin))) ?? group.First())
                         .ToList();

            totalFile[0] = deployToDo.Count;

            // check that every target dir exist (for copy/move deployments)
            deployToDo
            .Where(deploy => deploy.DeployType == DeployType.Copy || deploy.DeployType == DeployType.Move)
            .GroupBy(deploy => Path.GetDirectoryName(deploy.To))
            .Select(group => group.First())
            .ToNonNullList()
            .ForEach(deploy => Utils.CreateDirectory(Path.GetDirectoryName(deploy.To)));

            #region for archives (zip/pl)

            // for archives, compute the path to the archive file (+ make sure the directory of the archive exists)
            deployToDo.Where(deploy => deploy.DeployType <= DeployType.Archive).ToNonNullList().ForEach(deploy => {
                var ext = deploy.DeployType == DeployType.Prolib ? ".pl" : ".zip";
                var pos = deploy.To.LastIndexOf(ext, StringComparison.CurrentCultureIgnoreCase);
                if (pos >= 0)
                {
                    var posEnd                   = pos + ext.Length;
                    deploy.ArchivePath           = deploy.To.Substring(0, posEnd);
                    deploy.RelativePathInArchive = deploy.To.Substring(posEnd + 1);

                    // ensure that the folder to the .archive file exists
                    Utils.CreateDirectory(Path.GetDirectoryName(deploy.ArchivePath));

                    // for .zip, open the zip stream for later usage
                    if (deploy.DeployType > DeployType.Prolib)
                    {
                        if (!_openedZip.ContainsKey(deploy.ArchivePath))
                        {
                            try {
                                if (!File.Exists(deploy.ArchivePath))
                                {
                                    _openedZip.Add(deploy.ArchivePath, ZipStorer.Create(deploy.ArchivePath, "Created with 3P @ " + DateTime.Now + "\r\n" + Config.UrlWebSite));
                                }
                                else
                                {
                                    _openedZip.Add(deploy.ArchivePath, ZipStorer.Open(deploy.ArchivePath, FileAccess.Write));
                                    _filesToRemoveFromZip.Add(deploy.ArchivePath, new HashSet <string>());
                                }
                            } catch (Exception e) {
                                ErrorHandler.ShowErrors(e, "Couldn't create/open the .zip file");
                            }
                        }

                        // we didn't create the zip? then we need to remove this file if it exists
                        if (_filesToRemoveFromZip.ContainsKey(deploy.ArchivePath))
                        {
                            _filesToRemoveFromZip[deploy.ArchivePath].Add(deploy.RelativePathInArchive.Replace('\\', '/'));
                        }
                    }
                }
            });

            #endregion

            #region for .pl deployments, we treat them before anything else

            // for PL, we need to MOVE each file into a temporary folder with the internal structure of the .pl file,
            // then move it back where it was for further deploys...

            var plDeployments = deployToDo
                                .Where(deploy => deploy.DeployType == DeployType.Prolib)
                                .ToNonNullList();

            if (plDeployments.Count > 0)
            {
                // then we create a unique temporary folder for each .pl
                var dicPlToTempFolder = new Dictionary <string, string>(StringComparer.CurrentCultureIgnoreCase);
                foreach (var pathPl in plDeployments.Where(deploy => !string.IsNullOrEmpty(deploy.ArchivePath)).Select(deploy => deploy.ArchivePath).Distinct())
                {
                    // create a unique temp folder for this .pl
                    if (!dicPlToTempFolder.ContainsKey(pathPl))
                    {
                        var plDirPath = Path.GetDirectoryName(pathPl);
                        if (plDirPath != null)
                        {
                            var uniqueTempFolder = Path.Combine(plDirPath, Path.GetFileName(pathPl) + "~" + Path.GetRandomFileName());
                            dicPlToTempFolder.Add(pathPl, uniqueTempFolder);
                            Utils.CreateDirectory(uniqueTempFolder, FileAttributes.Hidden);
                        }
                    }
                }

                var prolibMessage = new StringBuilder();

                // for each .pl that needs to be created...
                foreach (var pl in dicPlToTempFolder)
                {
                    var pl1 = pl;
                    var onePlDeployments = plDeployments
                                           .Where(deploy => !string.IsNullOrEmpty(deploy.ArchivePath) && deploy.ArchivePath.Equals(pl1.Key))
                                           .ToNonNullList();
                    if (onePlDeployments.Count == 0)
                    {
                        continue;
                    }

                    //  we set the temporary folder on which each file will be copied..
                    // Tuple : <(base) temp directory, relative path in pl, path to .pl>
                    var dicTempFolderToPl = new Dictionary <string, Tuple <string, string, string> >(StringComparer.CurrentCultureIgnoreCase);
                    foreach (var fileToDeploy in onePlDeployments)
                    {
                        if (string.IsNullOrEmpty(fileToDeploy.ArchivePath))
                        {
                            continue;
                        }

                        if (dicPlToTempFolder.ContainsKey(fileToDeploy.ArchivePath))
                        {
                            fileToDeploy.ToTemp = Path.Combine(
                                dicPlToTempFolder[fileToDeploy.ArchivePath],
                                fileToDeploy.To.Replace(fileToDeploy.ArchivePath, "").TrimStart('\\')
                                );

                            // If not already done, remember that the *.r code in this temp folder must be integrated to this .pl file
                            var tempSubFolder = Path.GetDirectoryName(fileToDeploy.ToTemp);
                            if (!string.IsNullOrEmpty(tempSubFolder) && !dicTempFolderToPl.ContainsKey(tempSubFolder))
                            {
                                dicTempFolderToPl.Add(
                                    tempSubFolder,
                                    new Tuple <string, string, string>(
                                        dicPlToTempFolder[fileToDeploy.ArchivePath],                                                  // path of the temp dir
                                        Path.GetDirectoryName(fileToDeploy.To.Replace(fileToDeploy.ArchivePath, "").TrimStart('\\')), // relative path in .pl
                                        fileToDeploy.ArchivePath)                                                                     // path to the .pl file
                                    );

                                // also, create the folder
                                Utils.CreateDirectory(tempSubFolder);
                            }
                        }
                    }

                    var prolibExe = new ProcessIo(ProEnv.ProlibPath);

                    // for each subfolder in the .pl
                    foreach (var plSubFolder in dicTempFolderToPl)
                    {
                        var onePlSubFolderDeployments = onePlDeployments
                                                        .Where(deploy => plSubFolder.Key.Equals(Path.GetDirectoryName(deploy.ToTemp)))
                                                        .ToNonNullList();
                        if (onePlSubFolderDeployments.Count == 0)
                        {
                            continue;
                        }

                        Parallel.ForEach(onePlSubFolderDeployments, deploy => {
                            if (File.Exists(deploy.From))
                            {
                                deploy.IsOk = !string.IsNullOrEmpty(deploy.ToTemp) && Utils.MoveFile(deploy.From, deploy.ToTemp);
                            }
                            if (deploy.IsOk)
                            {
                                nbFilesDone[0]++;
                            }
                            if (updateDeploymentPercentage != null)
                            {
                                updateDeploymentPercentage((float)nbFilesDone[0] / totalFile[0] * 100);
                            }
                        });

                        // now we just need to add the content of temp folders into the .pl
                        prolibExe.StartInfo.WorkingDirectory = plSubFolder.Value.Item1; // base temp dir
                        prolibExe.Arguments = plSubFolder.Value.Item3.ProQuoter() + " -create -nowarn -add " + Path.Combine(plSubFolder.Value.Item2, "*").ProQuoter();
                        if (!prolibExe.TryDoWait(true))
                        {
                            prolibMessage.Append(prolibExe.ErrorOutput);
                        }

                        Parallel.ForEach(onePlSubFolderDeployments, deploy => {
                            deploy.IsOk = deploy.IsOk && Utils.MoveFile(deploy.ToTemp, deploy.From);
                        });
                    }

                    // compress .pl
                    prolibExe.StartInfo.WorkingDirectory = Path.GetDirectoryName(pl.Key) ?? "";
                    prolibExe.Arguments = pl.Key.ProQuoter() + " -compress -nowarn";
                    if (!prolibExe.TryDoWait(true))
                    {
                        prolibMessage.Append(prolibExe.ErrorOutput);
                    }

                    // delete temp folders
                    Utils.DeleteDirectory(pl.Value, true);
                }

                if (prolibMessage.Length > 0)
                {
                    UserCommunication.Notify("Errors occured when trying to create/add files to the .pl file :<br>" + prolibMessage, MessageImg.MsgError, "Prolib output", "Errors");
                }
            }

            #endregion

            #region for zip

            // remove the files that are already in the zip file or they will appear twice when we add them
            foreach (var kpv in _filesToRemoveFromZip)
            {
                ZipStorer zip           = _openedZip[kpv.Key];
                var       filesToDelete = zip.ReadCentralDir().Where(zipFileEntry => kpv.Value.Contains(zipFileEntry.FilenameInZip)).ToList();
                _openedZip.Remove(kpv.Key);
                ZipStorer.RemoveEntries(ref zip, filesToDelete);
                _openedZip.Add(kpv.Key, zip);
            }

            #endregion


            // do a deployment action for each file (parallel for MOVE and COPY)
            Parallel.ForEach(deployToDo.Where(deploy => deploy.DeployType >= DeployType.Copy), file => {
                if (DeploySingleFile(file))
                {
                    nbFilesDone[0]++;
                }
                if (updateDeploymentPercentage != null)
                {
                    updateDeploymentPercentage((float)nbFilesDone[0] / totalFile[0] * 100);
                }
            });
            // don't use parallel for the other types
            foreach (var file in deployToDo.Where(deploy => deploy.DeployType < DeployType.Copy))
            {
                if (DeploySingleFile(file))
                {
                    nbFilesDone[0]++;
                }
                if (updateDeploymentPercentage != null)
                {
                    updateDeploymentPercentage((float)nbFilesDone[0] / totalFile[0] * 100);
                }
            }

            #region for zip, dispose of zipStorers

            // also, need to dispose of the object/stream here
            foreach (var zipStorer in _openedZip)
            {
                zipStorer.Value.Close();
            }
            _openedZip.Clear();

            #endregion

            return(deployToDo);
        }
        /// <inheritdoc cref="IArchiverBasic.ArchiveFileSet"/>
        public int ArchiveFileSet(IEnumerable <IFileToArchive> filesToArchive)
        {
            var filesToPack = filesToArchive.ToList();

            filesToPack.ForEach(f => f.Processed = false);
            int totalFiles     = filesToPack.Count;
            int totalFilesDone = 0;

            foreach (var plGroupedFiles in filesToPack.GroupBy(f => f.ArchivePath))
            {
                string uniqueTempFolder = null;
                try {
                    var archiveFolder = CreateArchiveFolder(plGroupedFiles.Key);

                    // create a unique temp folder for this .pl
                    uniqueTempFolder = Path.Combine(archiveFolder, $"{Path.GetFileName(plGroupedFiles.Key)}~{Path.GetRandomFileName()}");
                    var dirInfo = Directory.CreateDirectory(uniqueTempFolder);
                    dirInfo.Attributes |= FileAttributes.Hidden;

                    var subFolders = new Dictionary <string, List <FilesToMove> >();
                    foreach (var file in plGroupedFiles)
                    {
                        var subFolderPath = Path.GetDirectoryName(Path.Combine(uniqueTempFolder, file.PathInArchive));
                        if (!string.IsNullOrEmpty(subFolderPath))
                        {
                            if (!subFolders.ContainsKey(subFolderPath))
                            {
                                subFolders.Add(subFolderPath, new List <FilesToMove>());
                                if (!Directory.Exists(subFolderPath))
                                {
                                    Directory.CreateDirectory(subFolderPath);
                                }
                            }

                            if (File.Exists(file.SourcePath))
                            {
                                subFolders[subFolderPath].Add(new FilesToMove(file.SourcePath, Path.Combine(uniqueTempFolder, file.PathInArchive), file.PathInArchive));
                            }
                        }
                    }

                    var prolibExe = new ProcessIo(_prolibPath)
                    {
                        WorkingDirectory = uniqueTempFolder
                    };

                    foreach (var subFolder in subFolders)
                    {
                        _cancelToken?.ThrowIfCancellationRequested();

                        // move files to the temp subfolder
                        Parallel.ForEach(subFolder.Value, file => {
                            if (file.Move)
                            {
                                File.Move(file.Origin, file.Temp);
                            }
                            else
                            {
                                File.Copy(file.Origin, file.Temp);
                            }
                        });

                        // for files containing a space, we don't have a choice, call extract for each...
                        foreach (var file in subFolder.Value.Where(f => f.RelativePath.Contains(" ")))
                        {
                            if (!prolibExe.TryExecute(new ProcessArgs().Append(plGroupedFiles.Key, "-create", "-nowarn", "-add", file.RelativePath)))
                            {
                                throw new ArchiverException($"Failed to pack {file.Origin.PrettyQuote()} into {plGroupedFiles.Key.PrettyQuote()} and relative archive path {file.RelativePath}.", new ArchiverException(prolibExe.BatchOutput.ToString()));
                            }
                        }

                        var remainingFiles = subFolder.Value.Where(f => !f.RelativePath.Contains(" ")).ToList();
                        if (remainingFiles.Count > 0)
                        {
                            // for the other files, we can use the -pf parameter
                            var pfContent = new StringBuilder();
                            pfContent.AppendLine("-create -nowarn -add");
                            foreach (var file in remainingFiles)
                            {
                                pfContent.AppendLine(file.RelativePath);
                            }

                            var pfPath = Path.Combine(uniqueTempFolder, $"{Path.GetFileName(plGroupedFiles.Key)}~{Path.GetRandomFileName()}.pf");

                            File.WriteAllText(pfPath, pfContent.ToString(), _encoding);

                            if (!prolibExe.TryExecute(new ProcessArgs().Append(plGroupedFiles.Key, "-pf", pfPath)))
                            {
                                throw new ArchiverException($"Failed to pack to {plGroupedFiles.Key.PrettyQuote()}.", new Exception(prolibExe.BatchOutput.ToString()));
                            }

                            if (File.Exists(pfPath))
                            {
                                File.Delete(pfPath);
                            }
                        }

                        // move files from the temp subfolder
                        foreach (var file in subFolder.Value)
                        {
                            try {
                                if (file.Move)
                                {
                                    File.Move(file.Temp, file.Origin);
                                }
                                else if (!File.Exists(file.Temp))
                                {
                                    throw new ArchiverException($"Failed to move back the temporary file {file.Origin} from {file.Temp}.");
                                }
                            } catch (Exception e) {
                                throw new ArchiverException($"Failed to move back the temporary file {file.Origin} from {file.Temp}.", e);
                            }

                            totalFilesDone++;
                            OnProgress?.Invoke(this, ArchiverEventArgs.NewProgress(plGroupedFiles.Key, file.RelativePath, Math.Round(totalFilesDone / (double)totalFiles * 100, 2)));
                        }
                    }

                    // compress .pl
                    prolibExe.TryExecute(new ProcessArgs().Append(plGroupedFiles.Key, "-compress", "-nowarn"));

                    foreach (var file in plGroupedFiles)
                    {
                        file.Processed = true;
                    }
                } catch (OperationCanceledException) {
                    throw;
                } catch (Exception e) {
                    throw new ArchiverException($"Failed to pack to {plGroupedFiles.Key.PrettyQuote()}.", e);
                } finally {
                    // delete temp folder
                    if (Directory.Exists(uniqueTempFolder))
                    {
                        Directory.Delete(uniqueTempFolder, true);
                    }
                }
            }

            return(totalFilesDone);
        }
        /// <inheritdoc cref="IArchiver.ExtractFileSet"/>
        public int ExtractFileSet(IEnumerable <IFileInArchiveToExtract> filesToExtractIn)
        {
            var filesToExtract = filesToExtractIn.ToList();

            filesToExtract.ForEach(f => f.Processed = false);
            int totalFiles     = filesToExtract.Count;
            int totalFilesDone = 0;
            var prolibExe      = new ProcessIo(_prolibPath);

            foreach (var plGroupedFiles in filesToExtract.GroupBy(f => f.ArchivePath))
            {
                if (!File.Exists(plGroupedFiles.Key))
                {
                    continue;
                }

                // process only files that actually exist
                var archiveFileList        = ListFiles(plGroupedFiles.Key).Select(f => f.PathInArchive).ToHashSet();
                var plGroupedFilesFiltered = plGroupedFiles.Where(f => archiveFileList.Contains(f.PathInArchive)).ToList();

                try {
                    foreach (var extractDirGroupedFiles in plGroupedFilesFiltered.GroupBy(f => Path.GetDirectoryName(f.ExtractionPath)))
                    {
                        prolibExe.WorkingDirectory = extractDirGroupedFiles.Key;
                        Directory.CreateDirectory(extractDirGroupedFiles.Key);

                        // for files containing a space, we don't have a choice, call extract for each...
                        foreach (var file in extractDirGroupedFiles.Where(deploy => deploy.PathInArchive.Contains(" ")))
                        {
                            _cancelToken?.ThrowIfCancellationRequested();
                            if (File.Exists(file.ExtractionPath))
                            {
                                File.Delete(file.ExtractionPath);
                            }
                            if (!prolibExe.TryExecute(new ProcessArgs().Append(plGroupedFiles.Key, "-nowarn", "-yank", file.PathInArchive)))
                            {
                                throw new ArchiverException($"Failed to extract {file.PathInArchive.PrettyQuote()} from {plGroupedFiles.Key.PrettyQuote()}.", new Exception(prolibExe.BatchOutput.ToString()));
                            }
                            totalFilesDone++;
                            OnProgress?.Invoke(this, ArchiverEventArgs.NewProgress(plGroupedFiles.Key, file.PathInArchive, Math.Round(totalFilesDone / (double)totalFiles * 100, 2)));
                        }

                        _cancelToken?.ThrowIfCancellationRequested();
                        var remainingFiles = extractDirGroupedFiles.Where(deploy => !deploy.PathInArchive.Contains(" ")).ToList();
                        if (remainingFiles.Count > 0)
                        {
                            // for the other files, we can use the -pf parameter
                            var pfContent = new StringBuilder();
                            pfContent.AppendLine("-nowarn");
                            pfContent.AppendLine("-yank");
                            foreach (var file in remainingFiles)
                            {
                                pfContent.AppendLine(file.PathInArchive);
                                if (File.Exists(file.ExtractionPath))
                                {
                                    File.Delete(file.ExtractionPath);
                                }
                            }

                            var pfPath = Path.Combine(extractDirGroupedFiles.Key, $"{Path.GetFileName(plGroupedFiles.Key)}~{Path.GetRandomFileName()}.pf");

                            File.WriteAllText(pfPath, pfContent.ToString(), _encoding);

                            if (!prolibExe.TryExecute(new ProcessArgs().Append(plGroupedFiles.Key, "-pf", pfPath)))
                            {
                                throw new ArchiverException($"Failed to extract from {plGroupedFiles.Key.PrettyQuote()}.", new Exception(prolibExe.BatchOutput.ToString()));
                            }

                            foreach (var file in remainingFiles)
                            {
                                totalFilesDone++;
                                OnProgress?.Invoke(this, ArchiverEventArgs.NewProgress(plGroupedFiles.Key, file.PathInArchive, Math.Round(totalFilesDone / (double)totalFiles * 100, 2)));
                            }

                            if (File.Exists(pfPath))
                            {
                                File.Delete(pfPath);
                            }
                        }
                    }

                    foreach (var file in plGroupedFiles)
                    {
                        file.Processed = true;
                    }
                } catch (OperationCanceledException) {
                    throw;
                } catch (Exception e) {
                    throw new ArchiverException($"Failed to process {plGroupedFiles.Key.PrettyQuote()}.", e);
                }
            }

            return(totalFilesDone);
        }
        /// <inheritdoc cref="IArchiver.MoveFileSet"/>
        public int MoveFileSet(IEnumerable <IFileInArchiveToMove> filesToMoveIn)
        {
            var filesToMove = filesToMoveIn.ToList();

            filesToMove.ForEach(f => f.Processed = false);
            int totalFiles     = filesToMove.Count;
            int totalFilesDone = 0;

            foreach (var plGroupedFiles in filesToMove.GroupBy(f => f.ArchivePath))
            {
                string uniqueTempFolder = null;
                try {
                    // process only files that actually exist
                    var archiveFileList        = ListFiles(plGroupedFiles.Key).Select(f => f.PathInArchive).ToHashSet();
                    var plGroupedFilesFiltered = plGroupedFiles.Where(f => archiveFileList.Contains(f.PathInArchive)).ToList();

                    if (!plGroupedFilesFiltered.Any())
                    {
                        continue;
                    }

                    var archiveFolder = CreateArchiveFolder(plGroupedFiles.Key);

                    // create a unique temp folder for this .pl
                    uniqueTempFolder = Path.Combine(archiveFolder, $"{Path.GetFileName(plGroupedFiles.Key)}~{Path.GetRandomFileName()}");
                    var dirInfo = Directory.CreateDirectory(uniqueTempFolder);
                    dirInfo.Attributes |= FileAttributes.Hidden;

                    var subFolders = new Dictionary <string, List <FilesToMove> >();
                    foreach (var file in plGroupedFilesFiltered)
                    {
                        var subFolderPath = Path.GetDirectoryName(Path.Combine(uniqueTempFolder, file.NewRelativePathInArchive));
                        if (!string.IsNullOrEmpty(subFolderPath))
                        {
                            if (!subFolders.ContainsKey(subFolderPath))
                            {
                                subFolders.Add(subFolderPath, new List <FilesToMove>());
                                if (!Directory.Exists(subFolderPath))
                                {
                                    Directory.CreateDirectory(subFolderPath);
                                }
                            }
                            subFolders[subFolderPath].Add(new FilesToMove(file.PathInArchive, Path.Combine(uniqueTempFolder, file.NewRelativePathInArchive), file.NewRelativePathInArchive));
                        }
                    }

                    var prolibExe = new ProcessIo(_prolibPath);

                    foreach (var subFolder in subFolders)
                    {
                        _cancelToken?.ThrowIfCancellationRequested();

                        foreach (var file in subFolder.Value)
                        {
                            prolibExe.WorkingDirectory = Path.GetDirectoryName(file.Temp);
                            if (!prolibExe.TryExecute(new ProcessArgs().Append(plGroupedFiles.Key, "-nowarn", "yank", file.Origin)))
                            {
                                throw new ArchiverException($"Failed to extract {file.Origin.PrettyQuote()} from {plGroupedFiles.Key.PrettyQuote()}.", new Exception(prolibExe.BatchOutput.ToString()));
                            }

                            if (!prolibExe.TryExecute(new ProcessArgs().Append(plGroupedFiles.Key, "-nowarn", "-delete", file.Origin)))
                            {
                                throw new ArchiverException($"Failed to delete {file.Origin.PrettyQuote()} in {plGroupedFiles.Key.PrettyQuote()}.", new ArchiverException(prolibExe.BatchOutput.ToString()));
                            }

                            File.Move(Path.Combine(prolibExe.WorkingDirectory, Path.GetFileName(file.Origin)), file.Temp);

                            prolibExe.WorkingDirectory = uniqueTempFolder;
                            prolibExe.TryExecute(new ProcessArgs().Append(plGroupedFiles.Key, "-nowarn", "-delete", file.RelativePath));
                            if (!prolibExe.TryExecute(new ProcessArgs().Append(plGroupedFiles.Key, "-create", "-nowarn", "-add", file.RelativePath)))
                            {
                                throw new ArchiverException($"Failed to pack {file.Origin.PrettyQuote()} into {plGroupedFiles.Key.PrettyQuote()} and relative archive path {file.RelativePath}.", new ArchiverException(prolibExe.BatchOutput.ToString()));
                            }

                            totalFilesDone++;
                            OnProgress?.Invoke(this, ArchiverEventArgs.NewProgress(plGroupedFiles.Key, file.RelativePath, Math.Round(totalFilesDone / (double)totalFiles * 100, 2)));
                        }
                    }

                    // compress .pl
                    prolibExe.TryExecute(new ProcessArgs().Append(plGroupedFiles.Key, "-compress", "-nowarn"));

                    // delete temp folder
                    Directory.Delete(uniqueTempFolder, true);

                    foreach (var file in plGroupedFiles)
                    {
                        file.Processed = true;
                    }
                } catch (OperationCanceledException) {
                    throw;
                } catch (Exception e) {
                    throw new ArchiverException($"Failed to pack to {plGroupedFiles.Key.PrettyQuote()}.", e);
                } finally {
                    // delete temp folder
                    if (Directory.Exists(uniqueTempFolder))
                    {
                        Directory.Delete(uniqueTempFolder, true);
                    }
                }
            }

            return(totalFilesDone);
        }