ProgressiveFileCopier.cs 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. using System;
  2. using System.IO;
  3. using System.Threading;
  4. using System.Threading.Tasks;
  5. using MediaBrowser.Controller.Library;
  6. using MediaBrowser.Model.IO;
  7. namespace Jellyfin.Api.Helpers
  8. {
  9. /// <summary>
  10. /// Progressive file copier.
  11. /// </summary>
  12. public class ProgressiveFileCopier
  13. {
  14. private readonly string? _path;
  15. private readonly IDirectStreamProvider? _directStreamProvider;
  16. private readonly IStreamHelper _streamHelper;
  17. /// <summary>
  18. /// Initializes a new instance of the <see cref="ProgressiveFileCopier"/> class.
  19. /// </summary>
  20. /// <param name="streamHelper">Instance of the <see cref="IStreamHelper"/> interface.</param>
  21. /// <param name="path">Filepath to stream from.</param>
  22. public ProgressiveFileCopier(IStreamHelper streamHelper, string path)
  23. {
  24. _path = path;
  25. _streamHelper = streamHelper;
  26. _directStreamProvider = null;
  27. }
  28. /// <summary>
  29. /// Initializes a new instance of the <see cref="ProgressiveFileCopier"/> class.
  30. /// </summary>
  31. /// <param name="streamHelper">Instance of the <see cref="IStreamHelper"/> interface.</param>
  32. /// <param name="directStreamProvider">Instance of the <see cref="IDirectStreamProvider"/> interface.</param>
  33. public ProgressiveFileCopier(IStreamHelper streamHelper, IDirectStreamProvider directStreamProvider)
  34. {
  35. _directStreamProvider = directStreamProvider;
  36. _streamHelper = streamHelper;
  37. _path = null;
  38. }
  39. /// <summary>
  40. /// Write source stream to output.
  41. /// </summary>
  42. /// <param name="outputStream">Output stream.</param>
  43. /// <param name="cancellationToken">Cancellation token.</param>
  44. /// <returns>A <see cref="Task"/>.</returns>
  45. public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken)
  46. {
  47. if (_directStreamProvider != null)
  48. {
  49. await _directStreamProvider.CopyToAsync(outputStream, cancellationToken).ConfigureAwait(false);
  50. return;
  51. }
  52. var fileOptions = FileOptions.SequentialScan;
  53. // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
  54. if (Environment.OSVersion.Platform != PlatformID.Win32NT)
  55. {
  56. fileOptions |= FileOptions.Asynchronous;
  57. }
  58. await using var inputStream = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, fileOptions);
  59. const int emptyReadLimit = 100;
  60. var eofCount = 0;
  61. while (eofCount < emptyReadLimit)
  62. {
  63. var bytesRead = await _streamHelper.CopyToAsync(inputStream, outputStream, cancellationToken).ConfigureAwait(false);
  64. if (bytesRead == 0)
  65. {
  66. eofCount++;
  67. await Task.Delay(100, cancellationToken).ConfigureAwait(false);
  68. }
  69. else
  70. {
  71. eofCount = 0;
  72. }
  73. }
  74. }
  75. }
  76. }