// BrotliHandler.cpp #include "StdAfx.h" #include "../../../C/CpuArch.h" #include "../../Common/ComTry.h" #include "../../Common/Defs.h" #include "../Common/ProgressUtils.h" #include "../Common/RegisterArc.h" #include "../Common/StreamUtils.h" #include "../Compress/BrotliDecoder.h" #include "../Compress/BrotliEncoder.h" #include "../Compress/CopyCoder.h" #include "Common/DummyOutStream.h" #include "Common/HandlerOut.h" using namespace NWindows; namespace NArchive { namespace NBROTLI { class CHandler: public IInArchive, public IArchiveOpenSeq, public IOutArchive, public ISetProperties, public CMyUnknownImp { CMyComPtr _stream; CMyComPtr _seqStream; bool _isArc; bool _dataAfterEnd; bool _needMoreInput; bool _packSize_Defined; bool _unpackSize_Defined; UInt64 _packSize; UInt64 _unpackSize; UInt64 _numStreams; UInt64 _numBlocks; CSingleMethodProps _props; public: MY_UNKNOWN_IMP4( IInArchive, IArchiveOpenSeq, IOutArchive, ISetProperties) INTERFACE_IInArchive(;) INTERFACE_IOutArchive(;) STDMETHOD(OpenSeq)(ISequentialInStream *stream); STDMETHOD(SetProperties)(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps); CHandler() { } }; static const Byte kProps[] = { kpidSize, kpidPackSize }; static const Byte kArcProps[] = { kpidNumStreams, kpidNumBlocks }; IMP_IInArchive_Props IMP_IInArchive_ArcProps STDMETHODIMP CHandler::GetArchiveProperty(PROPID /*propID*/, PROPVARIANT * /*value*/) { return S_OK; } STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) { *numItems = 1; return S_OK; } STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value) { NCOM::CPropVariant prop; switch (propID) { case kpidPackSize: if (_packSize_Defined) prop = _packSize; break; case kpidSize: if (_unpackSize_Defined) prop = _unpackSize; break; } prop.Detach(value); return S_OK; } // brotli format has no signature // brotli stream can't be veriefied unless errors by unpack #if 0 API_FUNC_static_IsArc IsArc_Brotli(const Byte *p, size_t size) { return k_IsArc_Res_YES; } } #endif STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *) { COM_TRY_BEGIN Close(); { _isArc = true; _stream = stream; _seqStream = stream; } return S_OK; COM_TRY_END } STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream) { Close(); _isArc = true; _seqStream = stream; return S_OK; } STDMETHODIMP CHandler::Close() { _isArc = false; _dataAfterEnd = false; _needMoreInput = false; _packSize_Defined = false; _unpackSize_Defined = false; _packSize = 0; _seqStream.Release(); _stream.Release(); return S_OK; } STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback) { COM_TRY_BEGIN if (numItems == 0) return S_OK; if (numItems != (UInt32)(Int32)-1 && (numItems != 1 || indices[0] != 0)) return E_INVALIDARG; if (_packSize_Defined) extractCallback->SetTotal(_packSize); CMyComPtr realOutStream; Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; RINOK(extractCallback->GetStream(0, &realOutStream, askMode)); if (!testMode && !realOutStream) return S_OK; extractCallback->PrepareOperation(askMode); Int32 opRes; { NCompress::NBROTLI::CDecoder *decoderSpec = new NCompress::NBROTLI::CDecoder; decoderSpec->SetNumberOfThreads(0); /* .br - single threaded processing (without header/mt-frames) */ if (_props._numThreads_WasForced) { decoderSpec->SetNumberOfThreads(_props._numThreads); // translate to decoder (important for -mmt>=2 to use brotli-mt) } CMyComPtr decoder = decoderSpec; decoderSpec->SetInStream(_seqStream); CDummyOutStream *outStreamSpec = new CDummyOutStream; CMyComPtr outStream(outStreamSpec); outStreamSpec->SetStream(realOutStream); outStreamSpec->Init(); realOutStream.Release(); CLocalProgress *lps = new CLocalProgress; CMyComPtr progress = lps; lps->Init(extractCallback, true); UInt64 packSize = 0; UInt64 unpackedSize = 0; HRESULT result = S_OK; for (;;) { lps->InSize = packSize; lps->OutSize = unpackedSize; RINOK(lps->SetCur()); result = decoderSpec->CodeResume(outStream, &unpackedSize, progress); UInt64 streamSize = decoderSpec->GetInputProcessedSize(); if (result != S_FALSE && result != S_OK) return result; if (unpackedSize == 0) break; if (streamSize == packSize) { // no new bytes in input stream, So it's good end of archive. result = S_OK; break; } if (packSize > streamSize) return E_FAIL; if (result != S_OK) break; } decoderSpec->ReleaseInStream(); outStream.Release(); if (!_isArc) opRes = NExtract::NOperationResult::kIsNotArc; else if (_needMoreInput) opRes = NExtract::NOperationResult::kUnexpectedEnd; else if (_dataAfterEnd) opRes = NExtract::NOperationResult::kDataAfterEnd; else if (result == S_FALSE) opRes = NExtract::NOperationResult::kDataError; else if (result == S_OK) opRes = NExtract::NOperationResult::kOK; else return result; } return extractCallback->SetOperationResult(opRes); COM_TRY_END } static HRESULT UpdateArchive( UInt64 unpackSize, ISequentialOutStream *outStream, const CProps &props, IArchiveUpdateCallback *updateCallback) { RINOK(updateCallback->SetTotal(unpackSize)); CMyComPtr fileInStream; RINOK(updateCallback->GetStream(0, &fileInStream)); CLocalProgress *localProgressSpec = new CLocalProgress; CMyComPtr localProgress = localProgressSpec; localProgressSpec->Init(updateCallback, true); NCompress::NBROTLI::CEncoder *encoderSpec = new NCompress::NBROTLI::CEncoder; encoderSpec->unpackSize = unpackSize; encoderSpec->SetNumberOfThreads(0); /* .br - single threaded processing (without header/mt-frames) */ CMyComPtr encoder = encoderSpec; RINOK(props.SetCoderProps(encoderSpec, NULL)); RINOK(encoder->Code(fileInStream, outStream, NULL, NULL, localProgress)); return updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK); } STDMETHODIMP CHandler::GetFileTimeType(UInt32 *type) { *type = NFileTimeType::kUnix; return S_OK; } STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, IArchiveUpdateCallback *updateCallback) { COM_TRY_BEGIN if (numItems != 1) return E_INVALIDARG; Int32 newData, newProps; UInt32 indexInArchive; if (!updateCallback) return E_FAIL; RINOK(updateCallback->GetUpdateItemInfo(0, &newData, &newProps, &indexInArchive)); if ((newProps)) { { NCOM::CPropVariant prop; RINOK(updateCallback->GetProperty(0, kpidIsDir, &prop)); if (prop.vt != VT_EMPTY) if (prop.vt != VT_BOOL || prop.boolVal != VARIANT_FALSE) return E_INVALIDARG; } } if ((newData)) { UInt64 size; { NCOM::CPropVariant prop; RINOK(updateCallback->GetProperty(0, kpidSize, &prop)); if (prop.vt != VT_UI8) return E_INVALIDARG; size = prop.uhVal.QuadPart; } return UpdateArchive(size, outStream, _props, updateCallback); } if (indexInArchive != 0) return E_INVALIDARG; CLocalProgress *lps = new CLocalProgress; CMyComPtr progress = lps; lps->Init(updateCallback, true); CMyComPtr opCallback; updateCallback->QueryInterface(IID_IArchiveUpdateCallbackFile, (void **)&opCallback); if (opCallback) { RINOK(opCallback->ReportOperation( NEventIndexType::kInArcIndex, 0, NUpdateNotifyOp::kReplicate)) } if (_stream) RINOK(_stream->Seek(0, STREAM_SEEK_SET, NULL)); return NCompress::CopyStream(_stream, outStream, progress); COM_TRY_END } STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps) { return _props.SetProperties(names, values, numProps); } IMP_CreateArcIn IMP_CreateArcOut REGISTER_ARC_R( "brotli", "br brotli tbr", 0, 0x1F, 0, NULL, 0, NArcInfoFlags::kKeepName | NArcInfoFlags::kPureStartOpen | NArcInfoFlags::kByExtOnlyOpen, 0, CreateArc, CreateArcOut, /* IsArc_Brotli */) }}