#ifndef IOUTILITIES_COPY_H #define IOUTILITIES_COPY_H #include "./nativefilestream.h" #if defined(CPP_UTILITIES_USE_PLATFORM_SPECIFIC_API_FOR_OPTIMIZING_COPY_HELPER) && defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER) \ && defined(PLATFORM_LINUX) #define CPP_UTILITIES_USE_SEND_FILE #include "../conversion/stringbuilder.h" #endif #ifdef CPP_UTILITIES_USE_SEND_FILE #include #include #endif #include #include #ifdef CPP_UTILITIES_USE_SEND_FILE #include #include #endif namespace CppUtilities { /*! * \class CopyHelper * \brief The CopyHelper class helps to copy bytes from one stream to another. * \tparam Specifies the chunk/buffer size. */ template class CPP_UTILITIES_EXPORT CopyHelper { public: CopyHelper(); void copy(std::istream &input, std::ostream &output, std::uint64_t count); void callbackCopy(std::istream &input, std::ostream &output, std::uint64_t count, const std::function &isAborted, const std::function &callback); void copy(NativeFileStream &input, NativeFileStream &output, std::uint64_t count); void callbackCopy(NativeFileStream &input, NativeFileStream &output, std::uint64_t count, const std::function &isAborted, const std::function &callback); char *buffer(); private: char m_buffer[bufferSize]; }; /*! * \brief Constructs a new copy helper. */ template CopyHelper::CopyHelper() { } /*! * \brief Copies \a count bytes from \a input to \a output. * \remarks Set an exception mask using std::ios::exceptions() to get a std::ios_base::failure exception * when an IO error occurs. */ template void CopyHelper::copy(std::istream &input, std::ostream &output, std::uint64_t count) { while (count > bufferSize) { input.read(m_buffer, bufferSize); output.write(m_buffer, bufferSize); count -= bufferSize; } input.read(m_buffer, static_cast(count)); output.write(m_buffer, static_cast(count)); } /*! * \brief Copies \a count bytes from \a input to \a output. The procedure might be aborted and * progress updates will be reported. * * Before processing the next chunk \a isAborted is checked and the copying aborted if it returns true. Before processing the next chunk * \a callback is invoked to report the current progress. * * \remarks Set an exception mask using std::ios::exceptions() to get a std::ios_base::failure exception * when an IO error occurs. */ template void CopyHelper::callbackCopy(std::istream &input, std::ostream &output, std::uint64_t count, const std::function &isAborted, const std::function &callback) { const auto totalBytes = count; while (count > bufferSize) { input.read(m_buffer, bufferSize); output.write(m_buffer, bufferSize); count -= bufferSize; if (isAborted()) { return; } callback(static_cast(totalBytes - count) / static_cast(totalBytes)); } input.read(m_buffer, static_cast(count)); output.write(m_buffer, static_cast(count)); callback(1.0); } /*! * \brief Copies \a count bytes from \a input to \a output. * \remarks * - Set an exception mask using std::ios::exceptions() to get a std::ios_base::failure exception * when an IO error occurs. * - Possibly uses native APIs such as POSIX sendfile() to improve the speed. */ template void CopyHelper::copy(NativeFileStream &input, NativeFileStream &output, std::uint64_t count) { #ifdef CPP_UTILITIES_USE_SEND_FILE if (output.fileDescriptor() != -1 && input.fileDescriptor() != -1 && output.fileDescriptor() != input.fileDescriptor()) { const auto inputTellg = output.tellg(); const auto inputTellp = output.tellp(); const auto outputTellg = output.tellg(); const auto outputTellp = output.tellp(); input.flush(); output.flush(); const auto totalBytes = static_cast(count); while (count) { const auto bytesCopied = ::sendfile64(output.fileDescriptor(), input.fileDescriptor(), nullptr, count); if (bytesCopied < 0) { if ((errno == EINVAL || errno == ENOSYS) && static_cast(totalBytes) == count) { // try again the unoptimized version, maybe the filesystem doesn't support sendfile goto unoptimized_version; } throw std::ios_base::failure(argsToString("sendfile64() failed: ", std::strerror(errno))); } count -= static_cast(bytesCopied); } input.sync(); output.sync(); output.seekg(outputTellg + totalBytes); output.seekp(outputTellp + totalBytes); input.seekg(inputTellg + totalBytes); input.seekp(inputTellp + totalBytes); return; } unoptimized_version: #endif copy(static_cast(input), static_cast(output), count); } /*! * \brief Copies \a count bytes from \a input to \a output. The procedure might be aborted and * progress updates will be reported. * * Before processing the next chunk \a isAborted is checked and the copying aborted if it returns true. Before processing the next chunk * \a callback is invoked to report the current progress. * * - Set an exception mask using std::ios::exceptions() to get a std::ios_base::failure exception * when an IO error occurs. * - Possibly uses native APIs such as POSIX sendfile() to improve the speed. */ template void CopyHelper::callbackCopy(NativeFileStream &input, NativeFileStream &output, std::uint64_t count, const std::function &isAborted, const std::function &callback) { #ifdef CPP_UTILITIES_USE_SEND_FILE if (output.fileDescriptor() != -1 && input.fileDescriptor() != -1 && output.fileDescriptor() != input.fileDescriptor()) { const auto inputTellg = output.tellg(); const auto inputTellp = output.tellp(); const auto outputTellg = output.tellg(); const auto outputTellp = output.tellp(); input.flush(); output.flush(); const auto totalBytes = static_cast(count); while (count) { const auto bytesToCopy = static_cast(std::min(count, static_cast(bufferSize))); const auto bytesCopied = ::sendfile64(output.fileDescriptor(), input.fileDescriptor(), nullptr, bytesToCopy); if (bytesCopied < 0) { if ((errno == EINVAL || errno == ENOSYS) && static_cast(totalBytes) == count) { // try again the unoptimized version, maybe the filesystem doesn't support sendfile goto unoptimized_version; } throw std::ios_base::failure(argsToString("sendfile64() failed: ", std::strerror(errno))); } count -= static_cast(bytesCopied); if (isAborted()) { return; } callback(static_cast(totalBytes - static_cast(count)) / static_cast(totalBytes)); } input.sync(); output.sync(); output.seekg(outputTellg + totalBytes); output.seekp(outputTellp + totalBytes); input.seekg(inputTellg + totalBytes); input.seekp(inputTellp + totalBytes); callback(1.0); return; } unoptimized_version: #endif callbackCopy(static_cast(input), static_cast(output), count, isAborted, callback); } /*! * \brief Returns the internal buffer. */ template char *CopyHelper::buffer() { return m_buffer; } } // namespace CppUtilities #endif // IOUTILITIES_COPY_H