public TranscodingJobState SaveProgress(int id, bool failed, bool done, TimeSpan progress, TimeSpan?verifyProgress, string machineName)
        {
            InsertClientHeartbeat(machineName);

            TranscodingJobState jobState = failed
                ? TranscodingJobState.Failed
                : done
                    ? TranscodingJobState.Done
                    : TranscodingJobState.InProgress;

            using (var connection = Helper.GetConnection())
            {
                connection.Open();

                // Only allow progress updates for tasks which have statetaskState = InProgress
                // This will prevent out-of-order updates causing tasks set to either Failed or Done
                // to be set back to InProgress
                int updatedRows = connection.Execute(
                    "UPDATE FfmpegTasks SET Progress = @Progress, VerifyProgress = @VerifyProgress, Heartbeat = @Heartbeat, TaskState = @State, HeartbeatMachineName = @MachineName WHERE Id = @Id" +
                    " AND TaskState = @InProgressState;",
                    new
                {
                    Id              = id,
                    Progress        = progress.TotalSeconds,
                    VerifyProgress  = verifyProgress?.TotalSeconds,
                    Heartbeat       = DateTimeOffset.UtcNow.UtcDateTime,
                    State           = jobState,
                    InProgressState = TranscodingJobState.InProgress,
                    machineName
                });

                if (updatedRows != 1)
                {
                    throw new Exception($"Failed to update progress for task id {id}");
                }
            }
            return(jobState);
        }
        private static void UpdateVideoJob(BaseJob job, IDbConnection connection)
        {
            JobRequest jobRequest = connection.Query <JobRequest>(
                "SELECT JobCorrelationId, VideoSourceFilename, AudioSourceFilename, DestinationFilename, Needed, EnableDash, EnablePsnr FROM FfmpegVideoRequest WHERE JobCorrelationId = @Id",
                new { Id = job.JobCorrelationId })
                                    .SingleOrDefault();

            if (jobRequest == null)
            {
                throw new ArgumentException($@"Job with correlation id {job.JobCorrelationId} not found");
            }

            jobRequest.Targets = connection.Query <DestinationFormat>(
                "SELECT JobCorrelationId, Width, Height, VideoBitrate, AudioBitrate FROM FfmpegVideoRequestTargets WHERE JobCorrelationId = @Id;",
                new { Id = job.JobCorrelationId })
                                 .ToArray();

            using (var scope = TransactionUtils.CreateTransactionScope())
            {
                Type jobType = job.GetType();
                TranscodingJobState jobState = job.Failed
                    ? TranscodingJobState.Failed
                    : job.Done
                        ? TranscodingJobState.Done
                        : TranscodingJobState.InProgress;

                if (jobType == typeof(VideoTranscodingJob))
                {
                    int updatedRows = connection.Execute(
                        "UPDATE FfmpegVideoJobs SET Progress = @Progress, Heartbeat = @Heartbeat, State = @State, HeartbeatMachineName = @MachineName WHERE Id = @Id;",
                        new
                    {
                        Id          = job.Id,
                        Progress    = job.Progress.TotalSeconds,
                        Heartbeat   = DateTimeOffset.UtcNow.UtcDateTime,
                        State       = jobState,
                        MachineName = job.MachineName,
                    });

                    if (updatedRows != 1)
                    {
                        throw new Exception($"Failed to update progress for job id {job.Id}");
                    }

                    if (jobRequest.EnablePsnr)
                    {
                        foreach (FfmpegPart chunk in ((VideoTranscodingJob)job).Chunks)
                        {
                            connection.Execute(
                                "UPDATE FfmpegVideoParts SET PSNR = @Psnr WHERE Id = @Id;",
                                new { Id = chunk.Id, Psnr = chunk.Psnr });
                        }
                    }
                }
                else if (jobType == typeof(MergeJob))
                {
                    int updatedRows = connection.Execute(
                        "UPDATE FfmpegMergeJobs SET Progress = @Progress, Heartbeat = @Heartbeat, State = @State, HeartbeatMachineName = @MachineName WHERE Id = @Id;",
                        new
                    {
                        Id          = job.Id,
                        Progress    = job.Progress.TotalSeconds,
                        Heartbeat   = DateTimeOffset.UtcNow.UtcDateTime,
                        State       = jobState,
                        MachineName = job.MachineName
                    });

                    if (updatedRows != 1)
                    {
                        throw new Exception($"Failed to update progress for job id {job.Id}");
                    }

                    var states = connection.Query <string>(
                        "SELECT State FROM FfmpegMergeJobs WHERE JobCorrelationId = @Id;",
                        new { Id = job.JobCorrelationId })
                                 .Select(value => Enum.Parse(typeof(TranscodingJobState), value))
                                 .Cast <TranscodingJobState>();

                    if (states.All(x => x == TranscodingJobState.Done))
                    {
                        string tempFolder = String.Concat(Path.GetDirectoryName(jobRequest.DestinationFilename),
                                                          Path.DirectorySeparatorChar, jobRequest.JobCorrelationId.ToString("N"));

                        Directory.Delete(tempFolder, true);
                    }
                }
                else if (jobType == typeof(Mp4boxJob))
                {
                    int updatedRows = connection.Execute(
                        "UPDATE Mp4boxJobs SET Heartbeat = @Heartbeat, State = @State, HeartbeatMachineName = @MachineName WHERE JobCorrelationId = @Id;",
                        new
                    {
                        Id          = job.JobCorrelationId,
                        Progress    = job.Progress.TotalSeconds,
                        Heartbeat   = DateTimeOffset.UtcNow.UtcDateTime,
                        State       = jobState,
                        MachineName = job.MachineName
                    });

                    if (updatedRows != 1)
                    {
                        throw new Exception($"Failed to update progress for job id {job.Id}");
                    }
                }

                scope.Complete();
            }

            using (var scope = TransactionUtils.CreateTransactionScope())
            {
                ICollection <TranscodingJobState> totalJobs = connection.Query <TranscodingJobState>(
                    "SELECT State FROM FfmpegVideoJobs WHERE JobCorrelationId = @Id;",
                    new { Id = jobRequest.JobCorrelationId })
                                                              .ToList();

                if (totalJobs.Any(x => x != TranscodingJobState.Done))
                {
                    // Not all transcoding jobs are finished
                    return;
                }

                totalJobs = connection.Query <TranscodingJobState>(
                    "SELECT State FROM FfmpegMergeJobs WHERE JobCorrelationId = @Id;",
                    new { Id = jobRequest.JobCorrelationId })
                            .ToList();
                if (totalJobs.Any(x => x != TranscodingJobState.Done))
                {
                    // Not all merge jobs are finished
                    return;
                }

                string destinationFilename      = jobRequest.DestinationFilename;
                string fileNameWithoutExtension =
                    Path.GetFileNameWithoutExtension(destinationFilename);
                string fileExtension = Path.GetExtension(destinationFilename);
                string outputFolder  = Path.GetDirectoryName(destinationFilename);

                if (totalJobs.Count == 0)
                {
                    QueueMergeJob(job, connection, outputFolder, fileNameWithoutExtension, fileExtension, jobRequest);
                }
                else if (jobRequest.EnableDash)
                {
                    QueueMpegDashMergeJob(job, destinationFilename, connection, jobRequest, fileNameWithoutExtension,
                                          outputFolder, fileExtension);
                }

                scope.Complete();
            }
        }