ProgressiveFileCopier.cs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. using System;
  2. using System.Buffers;
  3. using System.IO;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using Jellyfin.Api.Models.PlaybackDtos;
  7. using MediaBrowser.Common.Extensions;
  8. using MediaBrowser.Controller.Library;
  9. using MediaBrowser.Model.IO;
  10. namespace Jellyfin.Api.Helpers
  11. {
  12. /// <summary>
  13. /// Progressive file copier.
  14. /// </summary>
  15. public class ProgressiveFileCopier
  16. {
  17. private readonly TranscodingJobDto? _job;
  18. private readonly string? _path;
  19. private readonly CancellationToken _cancellationToken;
  20. private readonly IDirectStreamProvider? _directStreamProvider;
  21. private readonly TranscodingJobHelper _transcodingJobHelper;
  22. private long _bytesWritten;
  23. /// <summary>
  24. /// Initializes a new instance of the <see cref="ProgressiveFileCopier"/> class.
  25. /// </summary>
  26. /// <param name="path">The path to copy from.</param>
  27. /// <param name="job">The transcoding job.</param>
  28. /// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/>.</param>
  29. /// <param name="cancellationToken">The cancellation token.</param>
  30. public ProgressiveFileCopier(string path, TranscodingJobDto? job, TranscodingJobHelper transcodingJobHelper, CancellationToken cancellationToken)
  31. {
  32. _path = path;
  33. _job = job;
  34. _cancellationToken = cancellationToken;
  35. _transcodingJobHelper = transcodingJobHelper;
  36. }
  37. /// <summary>
  38. /// Initializes a new instance of the <see cref="ProgressiveFileCopier"/> class.
  39. /// </summary>
  40. /// <param name="directStreamProvider">Instance of the <see cref="IDirectStreamProvider"/> interface.</param>
  41. /// <param name="job">The transcoding job.</param>
  42. /// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/>.</param>
  43. /// <param name="cancellationToken">The cancellation token.</param>
  44. public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, TranscodingJobDto? job, TranscodingJobHelper transcodingJobHelper, CancellationToken cancellationToken)
  45. {
  46. _directStreamProvider = directStreamProvider;
  47. _job = job;
  48. _cancellationToken = cancellationToken;
  49. _transcodingJobHelper = transcodingJobHelper;
  50. }
  51. /// <summary>
  52. /// Gets or sets a value indicating whether allow read end of file.
  53. /// </summary>
  54. public bool AllowEndOfFile { get; set; } = true;
  55. /// <summary>
  56. /// Gets or sets copy start position.
  57. /// </summary>
  58. public long StartPosition { get; set; }
  59. /// <summary>
  60. /// Write source stream to output.
  61. /// </summary>
  62. /// <param name="outputStream">Output stream.</param>
  63. /// <param name="cancellationToken">Cancellation token.</param>
  64. /// <returns>A <see cref="Task"/>.</returns>
  65. public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken)
  66. {
  67. using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationToken);
  68. cancellationToken = linkedCancellationTokenSource.Token;
  69. try
  70. {
  71. if (_directStreamProvider != null)
  72. {
  73. await _directStreamProvider.CopyToAsync(outputStream, cancellationToken).ConfigureAwait(false);
  74. return;
  75. }
  76. var fileOptions = FileOptions.SequentialScan;
  77. var allowAsyncFileRead = false;
  78. if (AsyncFile.UseAsyncIO)
  79. {
  80. fileOptions |= FileOptions.Asynchronous;
  81. allowAsyncFileRead = true;
  82. }
  83. if (_path == null)
  84. {
  85. throw new ResourceNotFoundException(nameof(_path));
  86. }
  87. await using var inputStream = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, fileOptions);
  88. var eofCount = 0;
  89. const int EmptyReadLimit = 20;
  90. if (StartPosition > 0)
  91. {
  92. inputStream.Position = StartPosition;
  93. }
  94. while (eofCount < EmptyReadLimit || !AllowEndOfFile)
  95. {
  96. var bytesRead = await CopyToInternalAsync(inputStream, outputStream, allowAsyncFileRead, cancellationToken).ConfigureAwait(false);
  97. if (bytesRead == 0)
  98. {
  99. if (_job == null || _job.HasExited)
  100. {
  101. eofCount++;
  102. }
  103. await Task.Delay(100, cancellationToken).ConfigureAwait(false);
  104. }
  105. else
  106. {
  107. eofCount = 0;
  108. }
  109. }
  110. }
  111. finally
  112. {
  113. if (_job != null)
  114. {
  115. _transcodingJobHelper.OnTranscodeEndRequest(_job);
  116. }
  117. }
  118. }
  119. private async Task<int> CopyToInternalAsync(Stream source, Stream destination, bool readAsync, CancellationToken cancellationToken)
  120. {
  121. var array = ArrayPool<byte>.Shared.Rent(IODefaults.CopyToBufferSize);
  122. try
  123. {
  124. int bytesRead;
  125. int totalBytesRead = 0;
  126. if (readAsync)
  127. {
  128. bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false);
  129. }
  130. else
  131. {
  132. bytesRead = source.Read(array, 0, array.Length);
  133. }
  134. while (bytesRead != 0)
  135. {
  136. var bytesToWrite = bytesRead;
  137. if (bytesToWrite > 0)
  138. {
  139. await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
  140. _bytesWritten += bytesRead;
  141. totalBytesRead += bytesRead;
  142. if (_job != null)
  143. {
  144. _job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten);
  145. }
  146. }
  147. if (readAsync)
  148. {
  149. bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false);
  150. }
  151. else
  152. {
  153. bytesRead = source.Read(array, 0, array.Length);
  154. }
  155. }
  156. return totalBytesRead;
  157. }
  158. finally
  159. {
  160. ArrayPool<byte>.Shared.Return(array);
  161. }
  162. }
  163. }
  164. }