private TempFile coreDoGetSequential(FileEntryItem item, Interface.IEncryption useDecrypter, out long retDownloadSize, out string retHashcode) { retHashcode = null; retDownloadSize = -1; TempFile retTarget, dlTarget = null, decryptTarget = null; try { dlTarget = new Library.Utility.TempFile(); if (m_backend is Library.Interface.IStreamingBackend && !m_options.DisableStreamingTransfers) { Func<string> getFileHash; // extended to use stacked streams using (var fs = System.IO.File.OpenWrite(dlTarget)) using (var hs = GetFileHasherStream(fs, System.Security.Cryptography.CryptoStreamMode.Write, out getFileHash)) using (var ss = new ShaderStream(hs, true)) { using (var ts = new ThrottledStream(ss, m_options.MaxUploadPrSecond, m_options.MaxDownloadPrSecond)) using (var pgs = new Library.Utility.ProgressReportingStream(ts, item.Size, HandleProgress)) { ((Library.Interface.IStreamingBackend)m_backend).Get(item.RemoteFilename, pgs); } ss.Flush(); retDownloadSize = ss.TotalBytesWritten; retHashcode = getFileHash(); } } else { m_backend.Get(item.RemoteFilename, dlTarget); retDownloadSize = new System.IO.FileInfo(dlTarget).Length; retHashcode = CalculateFileHash(dlTarget); } // Decryption is not placed in the stream stack because there seemed to be an effort // to throw a CryptographicException on fail. If in main stack, we cannot differentiate // in which part of the stack the source of an exception resides. if (useDecrypter != null) { decryptTarget = new Library.Utility.TempFile(); lock (m_encryptionLock) { try { useDecrypter.Decrypt(dlTarget, decryptTarget); } // If we fail here, make sure that we throw a crypto exception catch (System.Security.Cryptography.CryptographicException) { throw; } catch (Exception ex) { throw new System.Security.Cryptography.CryptographicException(ex.Message, ex); } } retTarget = decryptTarget; decryptTarget = null; } else { retTarget = dlTarget; dlTarget = null; } } finally { if (dlTarget != null) dlTarget.Dispose(); if (decryptTarget != null) decryptTarget.Dispose(); } return retTarget; }
private TempFile coreDoGetPiping(FileEntryItem item, Interface.IEncryption useDecrypter, out long retDownloadSize, out string retHashcode) { // With piping allowed, we will parallelize the operation with buffered pipes to maximize throughput: // Separated: Download (only for streaming) - Hashing - Decryption // The idea is to use DirectStreamLink's that are inserted in the stream stack, creating a fork to run // the crypto operations on. retDownloadSize = -1; retHashcode = null; bool enableStreaming = (m_backend is Library.Interface.IStreamingBackend && !m_options.DisableStreamingTransfers); System.Threading.Tasks.Task<string> taskHasher = null; DirectStreamLink linkForkHasher = null; System.Threading.Tasks.Task taskDecrypter = null; DirectStreamLink linkForkDecryptor = null; // keep potential temp files and their streams for cleanup (cannot use using here). TempFile retTarget = null, dlTarget = null, decryptTarget = null; System.IO.Stream dlToStream = null, decryptToStream = null; try { System.IO.Stream nextTierWriter = null; // target of our stacked streams if (!enableStreaming) // we will always need dlTarget if not streaming... dlTarget = new TempFile(); else if (enableStreaming && useDecrypter == null) { dlTarget = new TempFile(); dlToStream = System.IO.File.OpenWrite(dlTarget); nextTierWriter = dlToStream; // actually write through to file. } // setup decryption: fork off a StreamLink from stack, and setup decryptor task if (useDecrypter != null) { linkForkDecryptor = new DirectStreamLink(1 << 16, false, false, nextTierWriter); nextTierWriter = linkForkDecryptor.WriterStream; linkForkDecryptor.SetKnownLength(item.Size, false); // Set length to allow AES-decryption (not streamable yet) decryptTarget = new TempFile(); decryptToStream = System.IO.File.OpenWrite(decryptTarget); taskDecrypter = new System.Threading.Tasks.Task(() => { using (var input = linkForkDecryptor.ReaderStream) using (var output = decryptToStream) lock (m_encryptionLock) { useDecrypter.Decrypt(input, output); } } ); } // setup hashing: fork off a StreamLink from stack, then task computes hash linkForkHasher = new DirectStreamLink(1 << 16, false, false, nextTierWriter); nextTierWriter = linkForkHasher.WriterStream; taskHasher = new System.Threading.Tasks.Task<string>(() => { using (var input = linkForkHasher.ReaderStream) return CalculateFileHash(input); } ); // OK, forks with tasks are set up, so let's do the download which is performed in main thread. bool hadException = false; try { if (enableStreaming) { using (var ss = new ShaderStream(nextTierWriter, false)) { using (var ts = new ThrottledStream(ss, m_options.MaxUploadPrSecond, m_options.MaxDownloadPrSecond)) using (var pgs = new Library.Utility.ProgressReportingStream(ts, item.Size, HandleProgress)) { taskHasher.Start(); // We do not start tasks earlier to be sure the input always gets closed. if (taskDecrypter != null) taskDecrypter.Start(); ((Library.Interface.IStreamingBackend)m_backend).Get(item.RemoteFilename, pgs); } retDownloadSize = ss.TotalBytesWritten; } } else { m_backend.Get(item.RemoteFilename, dlTarget); retDownloadSize = new System.IO.FileInfo(dlTarget).Length; using (dlToStream = System.IO.File.OpenRead(dlTarget)) { taskHasher.Start(); // We do not start tasks earlier to be sure the input always gets closed. if (taskDecrypter != null) taskDecrypter.Start(); new DirectStreamLink.DataPump(dlToStream, nextTierWriter).Run(); } } } catch (Exception) { hadException = true; throw; } finally { // This nested try-catch-finally blocks will make sure we do not miss any exceptions ans all started tasks // are properly ended and tidied up. For what is thrown: If exceptions in main thread occured (download) it is thrown, // then hasher task is checked and last decryption. This resembles old logic. try { retHashcode = taskHasher.Result; } catch (AggregateException ex) { if (!hadException) { hadException = true; throw ex.InnerExceptions[0]; } } finally { if (taskDecrypter != null) { try { taskDecrypter.Wait(); } catch (AggregateException ex) { if (!hadException) { hadException = true; if (ex.InnerExceptions[0] is System.Security.Cryptography.CryptographicException) throw ex.InnerExceptions[0]; else throw new System.Security.Cryptography.CryptographicException(ex.InnerExceptions[0].Message, ex.InnerExceptions[0]); } } } } } if (useDecrypter != null) // return decrypted temp file { retTarget = decryptTarget; decryptTarget = null; } else // return downloaded file { retTarget = dlTarget; dlTarget = null; } } finally { // Be tidy: manually do some cleanup to temp files, as we could not use using's. // Unclosed streams should only occur if we failed even before tasks were started. if (dlToStream != null) dlToStream.Dispose(); if (dlTarget != null) dlTarget.Dispose(); if (decryptToStream != null) decryptToStream.Dispose(); if (decryptTarget != null) decryptTarget.Dispose(); } return retTarget; }