/// <inheritdoc/>
        public async Task <ProcessResultModel> ProcessAsync(ProcessPathCommand command, CancellationToken ct)
        {
            Ensure.ArgumentNotNull(command, nameof(command));
            Ensure.ArgumentNotNull(ct, nameof(ct));

            if (string.IsNullOrWhiteSpace(command.SourcePath))
            {
                throw new UnexpectedNullException("Empty source path.");
            }

            if (string.IsNullOrWhiteSpace(command.DestinationPath))
            {
                throw new UnexpectedNullException("Empty destination path.");
            }

            var sourceFs    = fileSystemStrategy.Create(command.SourcePath);
            var destFs      = fileSystemStrategy.Create(command.DestinationPath);
            var watermarkFs = fileSystemStrategy.Create(command.WatermarkSourcePath);

            var commandClone = command.DeepClone();

            commandClone.SourcePath          = sourceFs.BuildAbsolutePath(command.SourcePath);
            commandClone.DestinationPath     = destFs.BuildAbsolutePath(command.DestinationPath);
            commandClone.WatermarkSourcePath = watermarkFs.BuildAbsolutePath(command.WatermarkSourcePath);

            // Creates all directories and subdirectories in the specified path unless they already exist.
            destFs.Directory.CreateDirectory(commandClone.DestinationPath);

            // SourcePath can be a directory, archive or file
            if (archiveReader.IsArchive(commandClone.SourcePath))
            {
                var extractedPath = destFs.Path.Combine(commandClone.DestinationPath, "Extracted");
                destFs.Directory.CreateDirectory(extractedPath);
                await archiveExtractor.ExtractAsync(commandClone.SourcePath, extractedPath, ct);

                // Use the extracted directory as source path
                commandClone.SourcePath = extractedPath;
            }

            var result = await imageExtractor.ProcessAsync(commandClone, ct);

            if (result == null)
            {
                throw new UnexpectedNullException("The images could not be processed.");
            }

            // Set GIFs
            result.Gifs = await gifImageWriter.WriteAsync(
                commandClone.DestinationPath, result.Images, commandClone.Delay, commandClone.BezierEasingTypePerAxis, ct);

            result.CombinedGif = await gifImageWriter.WriteAsync(
                commandClone.DestinationPath, result.Images, "combined", commandClone.Delay, commandClone.BezierEasingTypeCombined, ct);

            // Set application version
            var appInfo = appInfoFactory.Create();

            result.Version = appInfo.AppVersion;

            // Write JSON and set filename
            await jsonWriter.WriteAsync(commandClone.DestinationPath, "output", result, (filename) => { result.JsonFilename = filename; });

            return(result);
        }
        /// <inheritdoc/>
        protected override async Task <ProcessResultModel> ProtectedHandleAsync(ProcessObjectCommand request, CancellationToken cancellationToken)
        {
            var objectEntity = await Context.ObjectRepository.GetFirstOrDefaultAsync(e => e.Id == Guid.Parse(request.Id), cancellationToken);

            if (objectEntity == null)
            {
                throw new UnexpectedNullException($"{nameof(ObjectEntity)} not found.");
            }

            var directoryName = fileSystem.Path.GetDirectoryName(objectEntity.SourcePath);

            if (!directoryName.EndsWith(objectEntity.Id.ToString()))
            {
                throw new AmiException(string.Format(
                                           "The directory name of object {0} ends with an unexpected name: {1}",
                                           objectEntity.Id,
                                           directoryName));
            }

            // Create temporary directory and use it as destination path
            var tempDestPath = fileSystem.Path.Combine(configuration.Options.WorkingDirectory, "Temp", Guid.NewGuid().ToString());

            fileSystem.Directory.CreateDirectory(tempDestPath);

            var fullSourcePath = fileSystem.Path.Combine(configuration.Options.WorkingDirectory, objectEntity.SourcePath);

            var pathRequest = new ProcessPathCommand()
            {
                SourcePath               = fullSourcePath,
                DestinationPath          = tempDestPath,
                OutputSize               = request.OutputSize,
                AmountPerAxis            = request.AmountPerAxis,
                Delay                    = request.Delay,
                AxisTypes                = request.AxisTypes,
                ImageFormat              = request.ImageFormat,
                BezierEasingTypePerAxis  = request.BezierEasingTypePerAxis,
                BezierEasingTypeCombined = request.BezierEasingTypeCombined,
                Grayscale                = request.Grayscale
            };

            // Extract archive if needed
            if (archiveReader.IsArchive(fullSourcePath))
            {
                var extractedPath     = fileSystem.Path.Combine(directoryName, "Extracted");
                var fullExtractedPath = fileSystem.Path.Combine(configuration.Options.WorkingDirectory, extractedPath);

                // Use the extracted directory as source path
                pathRequest.SourcePath = fullExtractedPath;

                if (string.IsNullOrWhiteSpace(objectEntity.ExtractedPath))
                {
                    fileSystem.Directory.CreateDirectory(fullExtractedPath);

                    await archiveExtractor.ExtractAsync(fullSourcePath, fullExtractedPath, cancellationToken);

                    objectEntity.ModifiedDate  = DateTime.UtcNow;
                    objectEntity.ExtractedPath = extractedPath;

                    Context.ObjectRepository.Update(objectEntity);
                }
            }

            // Start the processing
            var result = await mediator.Send(pathRequest, cancellationToken);

            if (result == null)
            {
                throw new UnexpectedNullException($"The processing result of object {request.Id} was null.");
            }

            // Ensure "Results" directory exists
            var resultsDirectoryName = "Results";
            var resultsPath          = fileSystem.Path.Combine(configuration.Options.WorkingDirectory, resultsDirectoryName);

            fileSystem.Directory.CreateDirectory(resultsPath);

            // Move content of temporary directory and delete it
            var baseDestPath = fileSystem.Path.Combine(resultsDirectoryName, result.Id);
            var destPath     = fileSystem.Path.Combine(configuration.Options.WorkingDirectory, baseDestPath);

            fileSystem.Directory.Move(tempDestPath, destPath);

            var resultEntity = await Context.ResultRepository.GetFirstOrDefaultAsync(e => e.Id == Guid.Parse(result.Id), cancellationToken);

            if (resultEntity == null)
            {
                throw new UnexpectedNullException($"{nameof(ResultEntity)} not found.");
            }

            // Update BaseFsPath of ResultEntity
            resultEntity.BasePath     = baseDestPath;
            resultEntity.ModifiedDate = DateTime.UtcNow;
            Context.ResultRepository.Update(resultEntity);

            await Context.SaveChangesAsync(cancellationToken);

            return(result);
        }