diff --git a/docs/doxygen/mainpages/cat_classes.h b/docs/doxygen/mainpages/cat_classes.h index 66bad14a00..f48a4b032a 100644 --- a/docs/doxygen/mainpages/cat_classes.h +++ b/docs/doxygen/mainpages/cat_classes.h @@ -704,6 +704,7 @@ Related overview: @ref overview_stream @li wxStringInputStream: String input stream class @li wxStringOutputStream: String output stream class @li wxLZMAInputStream: LZMA decompression stream class +@li wxLZMAOutputStream: LZMA compression stream class @li wxZlibInputStream: Zlib and gzip (compression) input stream class @li wxZlibOutputStream: Zlib and gzip (compression) output stream class @li wxZipInputStream: Input stream for reading from ZIP archives diff --git a/include/wx/lzmastream.h b/include/wx/lzmastream.h index 604a2543a2..5164d67f86 100644 --- a/include/wx/lzmastream.h +++ b/include/wx/lzmastream.h @@ -71,6 +71,52 @@ private: void Init(); }; +// ---------------------------------------------------------------------------- +// Filter for compressing data using LZMA(2) algorithm +// ---------------------------------------------------------------------------- + +class WXDLLIMPEXP_BASE wxLZMAOutputStream : public wxFilterOutputStream, + private wxPrivate::wxLZMAData +{ +public: + explicit wxLZMAOutputStream(wxOutputStream& stream, int level = -1) + : wxFilterOutputStream(stream) + { + Init(level); + } + + explicit wxLZMAOutputStream(wxOutputStream* stream, int level = -1) + : wxFilterOutputStream(stream) + { + Init(level); + } + + virtual ~wxLZMAOutputStream() { Close(); } + + void Sync() wxOVERRIDE { DoFlush(false); } + bool Close() wxOVERRIDE; + wxFileOffset GetLength() const wxOVERRIDE { return m_pos; } + +protected: + size_t OnSysWrite(const void *buffer, size_t size) wxOVERRIDE; + wxFileOffset OnSysTell() const wxOVERRIDE { return m_pos; } + +private: + void Init(int level); + + // Write the contents of the internal buffer to the output stream. + bool UpdateOutput(); + + // Write out the current buffer if necessary, i.e. if no space remains in + // it, and reinitialize m_stream to point to it. Returns false on success + // or false on error, in which case m_lasterror is updated. + bool UpdateOutputIfNecessary(); + + // Run LZMA_FINISH (if argument is true) or LZMA_FULL_FLUSH, return true on + // success or false on error. + bool DoFlush(bool finish); +}; + WXDLLIMPEXP_BASE wxVersionInfo wxGetLibLZMAVersionInfo(); #endif // wxUSE_LIBLZMA && wxUSE_STREAMS diff --git a/interface/wx/lzmastream.h b/interface/wx/lzmastream.h index 7ca8fb5d32..58c5ad5288 100644 --- a/interface/wx/lzmastream.h +++ b/interface/wx/lzmastream.h @@ -33,7 +33,7 @@ @library{wxbase} @category{archive,streams} - @see wxInputStream, wxZlibInputStream + @see wxInputStream, wxZlibInputStream, wxLZMAOutputStream. @since 3.1.2 */ @@ -58,3 +58,41 @@ public: */ wxLZMAInputStream(wxInputStream* stream); }; + +/** + @class wxLZMAOutputStream + + This filter stream compresses data using XZ format. + + XZ format uses LZMA compression, making it (significantly) more efficient + than Gzip format used by wxZlibOutputStream. Output generated by this class + is compatible with xz utilities working with .xz files and also supported + by 7-Zip program, even though it is different from its native .7z format. + + @library{wxbase} + @category{archive,streams} + + @see wxOutputStream, wxZlibOutputStream, wxLZMAInputStream + + @since 3.1.2 +*/ +class wxLZMAOutputStream : public wxFilterOutputStream +{ + /** + Create compressing stream associated with the given underlying + stream. + + This overload does not take ownership of the @a stream. + */ + wxLZMAOutputStream(wxOutputStream& stream); + + /** + Create compressing stream associated with the given underlying + stream and takes ownership of it. + + As with the base wxFilterOutputStream class, passing @a stream by + pointer indicates that this object takes ownership of it and will + delete it when it is itself destroyed. + */ + wxLZMAOutputStream(wxOutputStream* stream); +}; diff --git a/src/common/lzmastream.cpp b/src/common/lzmastream.cpp index e07460b7df..f931c83f6e 100644 --- a/src/common/lzmastream.cpp +++ b/src/common/lzmastream.cpp @@ -209,4 +209,164 @@ size_t wxLZMAInputStream::OnSysRead(void* outbuf, size_t size) return size; } +// ---------------------------------------------------------------------------- +// wxLZMAOutputStream: compression +// ---------------------------------------------------------------------------- + +void wxLZMAOutputStream::Init(int level) +{ + if ( level == -1 ) + level = LZMA_PRESET_DEFAULT; + + // Use the check type recommended by liblzma documentation. + const lzma_ret rc = lzma_easy_encoder(m_stream, level, LZMA_CHECK_CRC64); + switch ( rc ) + { + case LZMA_OK: + // Prepare for the first call to OnSysWrite(). + m_stream->next_out = m_streamBuf; + m_stream->avail_out = wxLZMA_BUF_SIZE; + + // Skip setting m_lasterror below. + return; + + case LZMA_MEM_ERROR: + wxLogError(_("Failed to allocate memory for LZMA compression.")); + break; + + default: + wxLogError(_("Failed to initialize LZMA compression: " + "unexpected error %u."), + rc); + break; + } + + m_lasterror = wxSTREAM_WRITE_ERROR; +} + +size_t wxLZMAOutputStream::OnSysWrite(const void *inbuf, size_t size) +{ + m_stream->next_in = static_cast(inbuf); + m_stream->avail_in = size; + + // Compress as long as we have any input data, but stop at first error as + // it's useless to try to continue after it (or even starting if the stream + // had already been in an error state). + while ( m_lasterror == wxSTREAM_NO_ERROR && m_stream->avail_in > 0 ) + { + // Flush the output buffer if necessary. + if ( !UpdateOutputIfNecessary() ) + return 0; + + const lzma_ret rc = lzma_code(m_stream, LZMA_RUN); + + wxString err; + switch ( rc ) + { + case LZMA_OK: + continue; + + case LZMA_MEM_ERROR: + err = wxTRANSLATE("out of memory"); + break; + + case LZMA_STREAM_END: + // This is unexpected as we don't use LZMA_FINISH here. + wxFAIL_MSG( "Unexpected LZMA stream end" ); + wxFALLTHROUGH; + + default: + err = wxTRANSLATE("unknown compression error"); + break; + } + + wxLogError(_("LZMA compression error: %s"), wxGetTranslation(err)); + + m_lasterror = wxSTREAM_WRITE_ERROR; + return 0; + } + + m_pos += size; + return size; +} + +bool wxLZMAOutputStream::UpdateOutput() +{ + // Write the buffer contents to the real output, taking care only to write + // as much of it as we actually have, as the buffer can (and very likely + // will) be incomplete. + const size_t numOut = wxLZMA_BUF_SIZE - m_stream->avail_out; + m_parent_o_stream->Write(m_streamBuf, numOut); + if ( m_parent_o_stream->LastWrite() != numOut ) + { + m_lasterror = wxSTREAM_WRITE_ERROR; + return false; + } + + return true; +} + +bool wxLZMAOutputStream::UpdateOutputIfNecessary() +{ + if ( !m_stream->avail_out ) + { + if ( !UpdateOutput() ) + return false; + + m_stream->next_out = m_streamBuf; + m_stream->avail_out = wxLZMA_BUF_SIZE; + } + + return true; +} + +bool wxLZMAOutputStream::DoFlush(bool finish) +{ + const lzma_action action = finish ? LZMA_FINISH : LZMA_FULL_FLUSH; + + while ( m_lasterror == wxSTREAM_NO_ERROR ) + { + if ( !UpdateOutputIfNecessary() ) + break; + + const lzma_ret rc = lzma_code(m_stream, action); + + wxString err; + switch ( rc ) + { + case LZMA_OK: + continue; + + case LZMA_STREAM_END: + // Don't forget to output the last part of the data. + return UpdateOutput(); + + case LZMA_MEM_ERROR: + err = wxTRANSLATE("out of memory"); + + default: + err = wxTRANSLATE("unknown compression error"); + break; + } + + wxLogError(_("LZMA compression error when flushing output: %s"), + wxGetTranslation(err)); + + m_lasterror = wxSTREAM_WRITE_ERROR; + } + + return false; +} + +bool wxLZMAOutputStream::Close() +{ + if ( !DoFlush(true) ) + return false; + + m_stream->next_out = m_streamBuf; + m_stream->avail_out = wxLZMA_BUF_SIZE; + + return wxFilterOutputStream::Close() && IsOk(); +} + #endif // wxUSE_LIBLZMA && wxUSE_STREAMS