< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.MediaEncoding.TranscodingSegmentCleaner
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/MediaEncoding/TranscodingSegmentCleaner.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 40
Coverable lines: 40
Total lines: 178
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 12
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
Start()100%210%
Stop()100%210%
Dispose()100%210%
Dispose(...)0%620%
GetOptions()100%210%
DeleteHlsSegmentFiles(...)0%7280%
DisposeTimer()0%620%

File(s)

/srv/git/jellyfin/MediaBrowser.Controller/MediaEncoding/TranscodingSegmentCleaner.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.IO;
 4using System.Linq;
 5using System.Threading;
 6using System.Threading.Tasks;
 7using MediaBrowser.Common.Configuration;
 8using MediaBrowser.Model.Configuration;
 9using MediaBrowser.Model.IO;
 10using Microsoft.Extensions.Logging;
 11
 12namespace MediaBrowser.Controller.MediaEncoding;
 13
 14/// <summary>
 15/// Transcoding segment cleaner.
 16/// </summary>
 17public class TranscodingSegmentCleaner : IDisposable
 18{
 19    private readonly TranscodingJob _job;
 20    private readonly ILogger<TranscodingSegmentCleaner> _logger;
 21    private readonly IConfigurationManager _config;
 22    private readonly IFileSystem _fileSystem;
 23    private readonly IMediaEncoder _mediaEncoder;
 24    private Timer? _timer;
 25    private int _segmentLength;
 26
 27    /// <summary>
 28    /// Initializes a new instance of the <see cref="TranscodingSegmentCleaner"/> class.
 29    /// </summary>
 30    /// <param name="job">Transcoding job dto.</param>
 31    /// <param name="logger">Instance of the <see cref="ILogger{TranscodingSegmentCleaner}"/> interface.</param>
 32    /// <param name="config">Instance of the <see cref="IConfigurationManager"/> interface.</param>
 33    /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
 34    /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
 35    /// <param name="segmentLength">The segment length of this transcoding job.</param>
 36    public TranscodingSegmentCleaner(TranscodingJob job, ILogger<TranscodingSegmentCleaner> logger, IConfigurationManage
 37    {
 038        _job = job;
 039        _logger = logger;
 040        _config = config;
 041        _fileSystem = fileSystem;
 042        _mediaEncoder = mediaEncoder;
 043        _segmentLength = segmentLength;
 044    }
 45
 46    /// <summary>
 47    /// Start timer.
 48    /// </summary>
 49    public void Start()
 50    {
 051        _timer = new Timer(TimerCallback, null, 20000, 20000);
 052    }
 53
 54    /// <summary>
 55    /// Stop cleaner.
 56    /// </summary>
 57    public void Stop()
 58    {
 059        DisposeTimer();
 060    }
 61
 62    /// <summary>
 63    /// Dispose cleaner.
 64    /// </summary>
 65    public void Dispose()
 66    {
 067        Dispose(true);
 068        GC.SuppressFinalize(this);
 069    }
 70
 71    /// <summary>
 72    /// Dispose cleaner.
 73    /// </summary>
 74    /// <param name="disposing">Disposing.</param>
 75    protected virtual void Dispose(bool disposing)
 76    {
 077        if (disposing)
 78        {
 079            DisposeTimer();
 80        }
 081    }
 82
 83    private EncodingOptions GetOptions()
 84    {
 085        return _config.GetEncodingOptions();
 86    }
 87
 88    private async void TimerCallback(object? state)
 89    {
 90        if (_job.HasExited)
 91        {
 92            DisposeTimer();
 93            return;
 94        }
 95
 96        var options = GetOptions();
 97        var enableSegmentDeletion = options.EnableSegmentDeletion;
 98        var segmentKeepSeconds = Math.Max(options.SegmentKeepSeconds, 20);
 99
 100        if (enableSegmentDeletion)
 101        {
 102            var downloadPositionTicks = _job.DownloadPositionTicks ?? 0;
 103            var downloadPositionSeconds = Convert.ToInt64(TimeSpan.FromTicks(downloadPositionTicks).TotalSeconds);
 104
 105            if (downloadPositionSeconds > 0 && segmentKeepSeconds > 0 && downloadPositionSeconds > segmentKeepSeconds)
 106            {
 107                var idxMaxToDelete = (downloadPositionSeconds - segmentKeepSeconds) / _segmentLength;
 108
 109                if (idxMaxToDelete > 0)
 110                {
 111                    await DeleteSegmentFiles(_job, 0, idxMaxToDelete, 1500).ConfigureAwait(false);
 112                }
 113            }
 114        }
 115    }
 116
 117    private async Task DeleteSegmentFiles(TranscodingJob job, long idxMin, long idxMax, int delayMs)
 118    {
 119        var path = job.Path ?? throw new ArgumentException("Path can't be null.");
 120
 121        _logger.LogDebug("Deleting segment file(s) index {Min} to {Max} from {Path}", idxMin, idxMax, path);
 122
 123        await Task.Delay(delayMs).ConfigureAwait(false);
 124
 125        try
 126        {
 127            if (job.Type == TranscodingJobType.Hls)
 128            {
 129                DeleteHlsSegmentFiles(path, idxMin, idxMax);
 130            }
 131        }
 132        catch (Exception ex)
 133        {
 134            _logger.LogDebug(ex, "Error deleting segment file(s) {Path}", path);
 135        }
 136    }
 137
 138    private void DeleteHlsSegmentFiles(string outputFilePath, long idxMin, long idxMax)
 139    {
 0140        var directory = Path.GetDirectoryName(outputFilePath)
 0141                        ?? throw new ArgumentException("Path can't be a root directory.", nameof(outputFilePath));
 142
 0143        var name = Path.GetFileNameWithoutExtension(outputFilePath);
 144
 0145        var filesToDelete = _fileSystem.GetFilePaths(directory)
 0146            .Where(f => long.TryParse(Path.GetFileNameWithoutExtension(f).Replace(name, string.Empty, StringComparison.O
 0147                        && (idx >= idxMin && idx <= idxMax));
 148
 0149        List<Exception>? exs = null;
 0150        foreach (var file in filesToDelete)
 151        {
 152            try
 153            {
 0154                _logger.LogDebug("Deleting HLS segment file {0}", file);
 0155                _fileSystem.DeleteFile(file);
 0156            }
 0157            catch (IOException ex)
 158            {
 0159                (exs ??= new List<Exception>()).Add(ex);
 0160                _logger.LogDebug(ex, "Error deleting HLS segment file {Path}", file);
 0161            }
 162        }
 163
 0164        if (exs is not null)
 165        {
 0166            throw new AggregateException("Error deleting HLS segment files", exs);
 167        }
 0168    }
 169
 170    private void DisposeTimer()
 171    {
 0172        if (_timer is not null)
 173        {
 0174            _timer.Dispose();
 0175            _timer = null;
 176        }
 0177    }
 178}