//TODO Look into more efficient methods https://designingefficientsoftware.wordpress.com/2011/03/03/efficient-file-io-from-csharp/ private void TransferFiles(TransferBase transfer, long bufferSize = 65535) { string file = null; Stream sourceStream = null; Stream destStream = null; Action <string, Stream, Stream> finishMethod = null; Action <long> updateMethod = null; long totalBytesRead = 0; int readLength = 0; byte[] ActiveBuffer = new byte[bufferSize]; byte[] BackBuffer = new byte[bufferSize]; bool readStarted = false; //var transfer = _activeTransfer; IAsyncResult readState = null; do { if (!readStarted) { transfer.GetNextFile(out file, out sourceStream, out destStream, out finishMethod, out updateMethod, out totalBytesRead); } if (file != null) { var thisFile = file; var thisSourceStream = sourceStream; var thisDestStream = destStream; var thisFinishMethod = finishMethod; var thisUpdateMethod = updateMethod; transfer.Status = Interfaces.TransferStatus.TransferingFiles; long fileLength = 0; if (sourceStream.CanSeek) { fileLength = sourceStream.Length; } bool endOfFile = false; if (!readStarted) { //start reading asynchronously readState = sourceStream.BeginRead(ActiveBuffer, 0, ActiveBuffer.Length, null, null); readStarted = true; } do { readLength = sourceStream.EndRead(readState); readStarted = false; totalBytesRead += readLength; //If we haven't reached the end of the file... if (readLength != 0) { var thisTotalBytesRead = totalBytesRead; bool pauseStream = false; bool abortStream = false; //Kick off another read asynchronously while we write. try { if (fileLength > 0 && sourceStream.Position >= fileLength) { endOfFile = true; } else if (ShouldAbortTransfer(transfer)) { abortStream = true; } //If the streams can be paused, and the manager is being paused or this transfer isn't first anymore, then pause the streams and return. else if (transfer.CanPauseMidStream() && (_IsPaused || (transfer != GetFirstTransfer(preferCurrentTransfer: !transfer.CanCopy)))) //If this transfer became locked, mark it preferred because we should probably close the streams before pausing { //Flag the stream for pausing pauseStream = true; } else { readState = sourceStream.BeginRead(BackBuffer, 0, BackBuffer.Length, null, null); readStarted = true; } } catch (IOException) { endOfFile = true; } if (endOfFile && !abortStream && !pauseStream) { transfer.GetNextFile(out file, out sourceStream, out destStream, out finishMethod, out updateMethod, out totalBytesRead); if (file != null) { readState = sourceStream.BeginRead(BackBuffer, 0, BackBuffer.Length, null, null); readStarted = true; } else { file = thisFile; sourceStream = thisSourceStream; destStream = thisDestStream; } } //Write the active buffer to the destination, update progress, and do a buffer swap. thisDestStream.Write(ActiveBuffer, 0, readLength); thisUpdateMethod(thisTotalBytesRead); BackBuffer = Interlocked.Exchange(ref ActiveBuffer, BackBuffer); if (endOfFile == false) { //If there is an abort request, dispose of our active streams and bail out. The fact that we dispose here and nowhere else is a little dirty... if (abortStream) { try { sourceStream.Dispose(); } catch (Exception ex) { Utils.Logging.Logger.Error("An exception occurred while aborting the current file source stream:", ex); } try { destStream.Dispose(); } catch (Exception ex) { Utils.Logging.Logger.Error("An exception occurred while aborting the current file destination stream:", ex); } return; } else if (pauseStream) { transfer.PauseStreaming(thisFile, sourceStream, destStream, thisTotalBytesRead); return; } } } }while ((readLength != 0) && (endOfFile == false)); //Break out once there is no more to read if (thisFinishMethod != null) { thisFinishMethod(thisFile, thisSourceStream, thisDestStream); //Callback to the finish method, } } if (_IsPaused || (transfer != GetFirstTransfer())) { transfer.Pause(); return; } } while (file != null); //If file is null, we've reached the end Steam.SteamBase.UiDispatcher.Invoke((Action)(() => { lock (_transferLock) //Acquire the transfer lock { Transfers.Remove(transfer); } })); Steam.SteamBase.UiDispatcher.BeginInvoke((Action)(() => transfer.RunPostProcessing())); }
//TODO Look into more efficient methods https://designingefficientsoftware.wordpress.com/2011/03/03/efficient-file-io-from-csharp/ private void TransferFiles(TransferBase transfer, long bufferSize = 65535) { string file = null; long fileSize = 0; Stream sourceStream = null; Stream destStream = null; Action <string, Stream, Stream> finishMethod = null; Action <long> updateMethod = null; long totalBytesRead = 0; int readLength = 0; byte[] ActiveBuffer = new byte[bufferSize]; byte[] BackBuffer = new byte[bufferSize]; bool readStarted = false; //var transfer = _activeTransfer; IAsyncResult readState = null; do { if (!readStarted) { transfer.GetNextFile(out file, out fileSize, out sourceStream, out destStream, out finishMethod, out updateMethod, out totalBytesRead); } if (file != null) { var thisFile = file; var thisSourceStream = sourceStream; var thisDestStream = destStream; var thisFinishMethod = finishMethod; var thisUpdateMethod = updateMethod; transfer.Status = Interfaces.TransferStatus.TransferingFiles; long fileLength = 0; //Local file streams can check the file length, however network streams cannot, in which case we read until we get a read length of 0 if (sourceStream.CanSeek) { fileLength = sourceStream.Length; } bool endOfFile = false; //If we already started a read while waiting for the final write on the previous file, then skip this block if (!readStarted) { try { //start reading asynchronously readState = sourceStream.BeginRead(ActiveBuffer, 0, ActiveBuffer.Length, null, null); readStarted = true; } catch (IOException ex) { SafeDisposeStream(sourceStream); SafeDisposeStream(destStream); readStarted = false; Utils.Logging.Logger.Error($"Exception reading from {file}: {ex.Message}. A retry will be attempted."); transfer.RetryFile(file, fileSize); } } if (readStarted) { bool readFailedMidStream = false; Exception readFailedMidStreamException = null; long readFailedMidStreamFileSize = 0; //Loop until we reach the end of file do { readLength = sourceStream.EndRead(readState); readStarted = false; totalBytesRead += readLength; //If we haven't reached the end of the file... if (readLength != 0) { var thisTotalBytesRead = totalBytesRead; readFailedMidStream = false; bool pauseStream = false; bool abortStream = false; if (fileLength > 0 && sourceStream.Position >= fileLength) { endOfFile = true; } else if (ShouldAbortTransfer(transfer)) { abortStream = true; } //If the streams can be paused, and the manager is being paused or this transfer isn't first anymore, then pause the streams and return. else if (transfer.CanPauseMidStream() && (_IsPaused || (transfer != GetFirstTransfer(preferCurrentTransfer: !transfer.GetCanCopyCached())))) //If this transfer became locked, mark it preferred because we should probably close the streams before pausing { //Flag the stream for pausing pauseStream = true; } else { //Kick off another read to run asynchronously while we write. try { readState = sourceStream.BeginRead(BackBuffer, 0, BackBuffer.Length, null, null); readStarted = true; } catch (IOException ex) { endOfFile = true; readFailedMidStream = true; readFailedMidStreamException = ex; readFailedMidStreamFileSize = fileSize; } } if (endOfFile && !abortStream && !pauseStream) { bool tryAgain = false; do { transfer.GetNextFile(out file, out fileSize, out sourceStream, out destStream, out finishMethod, out updateMethod, out totalBytesRead); if (file != null) { try { readState = sourceStream.BeginRead(BackBuffer, 0, BackBuffer.Length, null, null); readStarted = true; tryAgain = false; } catch (IOException ex) { SafeDisposeStream(sourceStream); SafeDisposeStream(destStream); readStarted = false; Utils.Logging.Logger.Error($"Exception reading from {file}: {ex.Message}. A retry will be attempted."); transfer.RetryFile(file, fileSize); tryAgain = true; } } else { file = thisFile; sourceStream = thisSourceStream; destStream = thisDestStream; tryAgain = false; } } while (tryAgain); } //Write the active buffer to the destination, update progress, and do a buffer swap. try { thisDestStream.Write(ActiveBuffer, 0, readLength); } catch (IOException ex) { Utils.Logging.Logger.Error($"IOException writing to {thisFile}. Promting retry or cancel.", ex); string message = $"An exception occurred while writing {file}. Resolve the issue if possible and click OK to resume. Click cancel to abort the transfer.\r\n{ex.Message}"; if (ex.Message.Contains("not enough space")) { message = $"Insufficient disk space, please free up some space and click Ok to continue. Otherwise, click cancel to abort."; } var result = System.Windows.MessageBox.Show(message, "IO Error", System.Windows.MessageBoxButton.OKCancel); if (result == System.Windows.MessageBoxResult.Cancel) { AbortTransfer(transfer); } else { SafeDisposeStream(thisSourceStream); SafeDisposeStream(thisDestStream); transfer.RetryFile(thisFile, readFailedMidStreamFileSize); return; } } thisUpdateMethod(thisTotalBytesRead); BackBuffer = Interlocked.Exchange(ref ActiveBuffer, BackBuffer); if (endOfFile == false) { //If there is an abort request, dispose of our active streams and bail out. The fact that we dispose here and nowhere else is a little dirty... if (abortStream) { SafeDisposeStream(sourceStream); SafeDisposeStream(destStream); return; } else if (pauseStream) { transfer.PauseStreaming(thisFile, sourceStream, destStream, thisTotalBytesRead); return; } } } }while ((readLength != 0) && (endOfFile == false)); //Break out once there is no more to read if (readFailedMidStream) { SafeDisposeStream(thisSourceStream); SafeDisposeStream(thisDestStream); Utils.Logging.Logger.Error($"Exception reading from {thisFile}: {readFailedMidStreamException?.Message}. A retry will be attempted."); transfer.RetryFile(thisFile, readFailedMidStreamFileSize); } else { try { thisFinishMethod?.Invoke(thisFile, thisSourceStream, thisDestStream); //Callback to the finish method, } catch (IOException ex) { Utils.Logging.Logger.Error($"Exception closing {thisFile}", ex); string message = $"An exception occurred while writing {file}. Resolve the issue if possible and click OK to resume. Click cancel to abort the transfer.\r\n{ex.Message}"; if (ex.Message.Contains("not enough space")) { message = $"Insufficient disk space, please free up some space and click Ok to continue. Otherwise, click cancel to abort."; } var result = System.Windows.MessageBox.Show(message, "IO Error", System.Windows.MessageBoxButton.OKCancel); if (result == System.Windows.MessageBoxResult.Cancel) { AbortTransfer(transfer); SafeDisposeStream(thisSourceStream); SafeDisposeStream(thisDestStream); return; } else { SafeDisposeStream(thisSourceStream); SafeDisposeStream(thisDestStream); transfer.RetryFile(thisFile, readFailedMidStreamFileSize); return; } } } } } if (_IsPaused || (transfer != GetFirstTransfer())) { transfer.Pause(); return; } } while (file != null); //If file is null, we've reached the end Steam.SteamBase.UiDispatcher.Invoke((Action)(() => { lock (_transferLock) //Acquire the transfer lock { Transfers.Remove(transfer); } })); Steam.SteamBase.UiDispatcher.BeginInvoke((Action)(() => transfer.RunPostProcessing())); }