private async Task CopyToStreamWithProgress(Stream source, Stream destination, ProgressSocketSessionService sessionService, WebsocketSession targetSession, int totalBytes = -1, string stepId = "", CancellationToken cancellationToken = default(CancellationToken), int bufferSize = 0x1000)
        {
            byte[] buffer = new byte[bufferSize];
            int    currentBytesRead;
            int    totalBytesRead = 0;

            while ((currentBytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0)
            {
                bool shouldAbort = targetSession.GetAttributeValue <bool>("abort");
                if (shouldAbort)
                {
                    break;
                }
                await destination.WriteAsync(buffer, 0, currentBytesRead, cancellationToken);

                cancellationToken.ThrowIfCancellationRequested();

                totalBytesRead += currentBytesRead;
                sessionService.UpdateProgress(targetSession.SessionKey, totalBytesRead, totalBytes, stepId);

                //await Task.Delay(1); // Stops worker threads from going crazy with CPU
            }
        }
        public async Task <Models.FileMap> StreamFileToDiskWithProgress(HttpRequest request, ModelStateDictionary modelState, ILogger logger, ProgressSocketSessionService sessionService, string sessionKey)
        {
            if (!modelState.IsValid)
            {
                return(null);
            }

            if (!IsMultipartContentType(request.ContentType))
            {
                modelState.AddModelError("File", "Form content type must be 'multipart'");
                return(null);
            }

            var boundary = GetBoundary(MediaTypeHeaderValue.Parse(request.ContentType), sessionService.DefaultFormOptions.MultipartBoundaryLengthLimit);
            var reader   = new MultipartReader(boundary, request.Body);
            var section  = await reader.ReadNextSectionAsync();

            string filenameForDisplay = "";
            string filenameOnDisk     = "";
            string fileContentType    = "";

            WebsocketSession targetSession = sessionService.GetSessionByKey(sessionKey);

            if (targetSession == null)
            {
                modelState.AddModelError("File", "The requested session could not be found.");
                return(null);
            }
            targetSession.SetAttribute("abort", false);

            int totalBytes = targetSession.GetAttributeValue <int>("unitTotal");

            if (section == null)
            {
                sessionService.UpdateProgress(targetSession.SessionKey, -1, -1, "");
            }

            while (section != null)
            {
                var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition);

                if (hasContentDispositionHeader)
                {
                    // This check assumes that there's a file
                    // present without form data. If form data
                    // is present, this method immediately fails
                    // and returns the model error.
                    if (!HasFileContentDisposition(contentDisposition))
                    {
                        modelState.AddModelError("File", $"The request couldn't be processed (Error 2).");
                        return(null);
                    }
                    else
                    {
                        // Don't trust the file name sent by the client. To display
                        // the file name, HTML-encode the value.
                        filenameForDisplay = WebUtility.HtmlEncode(contentDisposition.FileName.Value);
                        filenameOnDisk     = Path.GetRandomFileName();
                        fileContentType    = GetStaticFileContentType(contentDisposition.FileName.Value);

                        if (!Directory.Exists(sessionService.QuarantinedFilePath))
                        {
                            Directory.CreateDirectory(sessionService.QuarantinedFilePath);
                        }
                        string quarantinedPath = Path.Combine(sessionService.QuarantinedFilePath, filenameOnDisk);

                        await SaveFileToDiskWithProgress(section, contentDisposition, modelState, quarantinedPath, sessionService.PermittedExtensions, sessionService.FileSizeLimit, sessionService, targetSession, totalBytes);

                        if (!modelState.IsValid)
                        {
                            return(null);
                        }

                        if (!Directory.Exists(sessionService.FileStoragePath))
                        {
                            Directory.CreateDirectory(sessionService.FileStoragePath);
                        }

                        System.IO.File.Move(quarantinedPath, Path.Combine(sessionService.FileStoragePath, filenameOnDisk));
                        logger.LogInformation("Uploaded file '{TrustedFileNameForDisplay}' saved to '{TargetFilePath}' as {TrustedFileNameForFileStorage}",
                                              filenameForDisplay,
                                              sessionService.FileStoragePath,
                                              filenameOnDisk);
                    }
                }

                // Drain any remaining section body that hasn't been consumed and
                // read the headers for the next section.
                section = await reader.ReadNextSectionAsync();
            }

            return(new Models.FileMap()
            {
                FilenameForDisplay = filenameForDisplay, FilenameOnDisk = filenameOnDisk, ContentType = fileContentType
            });
        }