From 2f4adde213048c980f800c71edac194c9f8a8a33 Mon Sep 17 00:00:00 2001 From: softworkz Date: Fri, 17 Apr 2026 07:25:45 +0200 Subject: [PATCH 1/2] Chained Archive Extraction This implements single-pass extraction for archives like .tar.gz, .tar.xz, .tgz etc. and adds a new switch "-sce" for command-line use --- CPP/7zip/7zip_gcc.mak | 2 + CPP/7zip/Archive/Tar/TarHandler.cpp | 11 +- CPP/7zip/Archive/Tar/TarIn.cpp | 4 + CPP/7zip/Bundles/Alone/makefile.gcc | 1 + CPP/7zip/Bundles/Alone7z/makefile.gcc | 1 + CPP/7zip/Bundles/Fm/makefile | 1 + CPP/7zip/Bundles/SFXCon/makefile | 1 + CPP/7zip/Bundles/SFXCon/makefile.gcc | 1 + CPP/7zip/Bundles/SFXWin/makefile | 1 + CPP/7zip/Common/StreamBinder.h | 1 + CPP/7zip/UI/Common/ArchiveCommandLine.cpp | 5 + CPP/7zip/UI/Common/ArchiveExtractCallback.cpp | 11 +- CPP/7zip/UI/Common/ArchiveExtractCallback.h | 6 + CPP/7zip/UI/Common/ChainedExtract.cpp | 294 ++++++++++++++++++ CPP/7zip/UI/Common/ChainedExtract.h | 22 ++ CPP/7zip/UI/Common/CompressCall.cpp | 5 +- CPP/7zip/UI/Common/CompressCall.h | 2 +- CPP/7zip/UI/Common/CompressCall2.cpp | 3 +- CPP/7zip/UI/Common/Extract.cpp | 73 +++-- CPP/7zip/UI/Common/Extract.h | 16 + CPP/7zip/UI/Common/LoadCodecs.h | 10 + CPP/7zip/UI/Common/OpenArchive.cpp | 3 +- CPP/7zip/UI/Console/Console.mak | 1 + CPP/7zip/UI/Console/Main.cpp | 1 + CPP/7zip/UI/Console/makefile | 2 + CPP/7zip/UI/FileManager/Panel.cpp | 1 + CPP/7zip/UI/GUI/GUI.cpp | 6 + CPP/7zip/UI/GUI/GUI.dsp | 12 + CPP/7zip/UI/GUI/makefile | 3 + 29 files changed, 470 insertions(+), 30 deletions(-) create mode 100644 CPP/7zip/UI/Common/ChainedExtract.cpp create mode 100644 CPP/7zip/UI/Common/ChainedExtract.h diff --git a/CPP/7zip/7zip_gcc.mak b/CPP/7zip/7zip_gcc.mak index a78c0fa..d571f68 100644 --- a/CPP/7zip/7zip_gcc.mak +++ b/CPP/7zip/7zip_gcc.mak @@ -896,6 +896,8 @@ $O/DefaultName.o: ../../UI/Common/DefaultName.cpp $(CXX) $(CXXFLAGS) $< $O/EnumDirItems.o: ../../UI/Common/EnumDirItems.cpp $(CXX) $(CXXFLAGS) $< +$O/ChainedExtract.o: ../../UI/Common/ChainedExtract.cpp + $(CXX) $(CXXFLAGS) $< $O/Extract.o: ../../UI/Common/Extract.cpp $(CXX) $(CXXFLAGS) $< $O/ExtractingFilePath.o: ../../UI/Common/ExtractingFilePath.cpp diff --git a/CPP/7zip/Archive/Tar/TarHandler.cpp b/CPP/7zip/Archive/Tar/TarHandler.cpp index 5761ea3..93a8d18 100644 --- a/CPP/7zip/Archive/Tar/TarHandler.cpp +++ b/CPP/7zip/Archive/Tar/TarHandler.cpp @@ -730,15 +730,18 @@ Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, stream = _stream; const bool allFilesMode = (numItems == (UInt32)(Int32)-1); - if (allFilesMode) + if (allFilesMode && !seqMode) numItems = _items.Size(); if (_stream && numItems == 0) return S_OK; UInt64 totalSize = 0; UInt32 i; - for (i = 0; i < numItems; i++) - totalSize += _items[allFilesMode ? i : indices[i]].Get_UnpackSize(); - RINOK(extractCallback->SetTotal(totalSize)) + if (!(seqMode && allFilesMode)) + { + for (i = 0; i < numItems; i++) + totalSize += _items[allFilesMode ? i : indices[i]].Get_UnpackSize(); + RINOK(extractCallback->SetTotal(totalSize)) + } UInt64 totalPackSize; totalSize = totalPackSize = 0; diff --git a/CPP/7zip/Archive/Tar/TarIn.cpp b/CPP/7zip/Archive/Tar/TarIn.cpp index e702b68..092dd04 100644 --- a/CPP/7zip/Archive/Tar/TarIn.cpp +++ b/CPP/7zip/Archive/Tar/TarIn.cpp @@ -186,6 +186,7 @@ HRESULT CArchive::GetNextItemReal(CItemEx &item) filled = false; bool thereAreEmptyRecords = false; + unsigned numEmptyRecords = 0; for (;;) { size_t processedSize = NFileHeader::kRecordSize; @@ -219,6 +220,9 @@ HRESULT CArchive::GetNextItemReal(CItemEx &item) break; item.HeaderSize += NFileHeader::kRecordSize; thereAreEmptyRecords = true; + numEmptyRecords++; + if (!InStream && numEmptyRecords >= 2) + return S_OK; RINOK(Progress(item, 0)) } if (thereAreEmptyRecords) diff --git a/CPP/7zip/Bundles/Alone/makefile.gcc b/CPP/7zip/Bundles/Alone/makefile.gcc index 621c0ce..1cbeb9b 100644 --- a/CPP/7zip/Bundles/Alone/makefile.gcc +++ b/CPP/7zip/Bundles/Alone/makefile.gcc @@ -101,6 +101,7 @@ UI_COMMON_OBJS = \ $O/Bench.o \ $O/DefaultName.o \ $O/EnumDirItems.o \ + $O/ChainedExtract.o \ $O/Extract.o \ $O/ExtractingFilePath.o \ $O/HashCalc.o \ diff --git a/CPP/7zip/Bundles/Alone7z/makefile.gcc b/CPP/7zip/Bundles/Alone7z/makefile.gcc index a31475e..5d50380 100644 --- a/CPP/7zip/Bundles/Alone7z/makefile.gcc +++ b/CPP/7zip/Bundles/Alone7z/makefile.gcc @@ -99,6 +99,7 @@ UI_COMMON_OBJS = \ $O/Bench.o \ $O/DefaultName.o \ $O/EnumDirItems.o \ + $O/ChainedExtract.o \ $O/Extract.o \ $O/ExtractingFilePath.o \ $O/HashCalc.o \ diff --git a/CPP/7zip/Bundles/Fm/makefile b/CPP/7zip/Bundles/Fm/makefile index de74f76..8cf619b 100644 --- a/CPP/7zip/Bundles/Fm/makefile +++ b/CPP/7zip/Bundles/Fm/makefile @@ -48,6 +48,7 @@ UI_COMMON_OBJS = \ $O\CompressCall2.obj \ $O\DefaultName.obj \ $O\EnumDirItems.obj \ + $O\ChainedExtract.obj \ $O\Extract.obj \ $O\ExtractingFilePath.obj \ $O\HashCalc.obj \ diff --git a/CPP/7zip/Bundles/SFXCon/makefile b/CPP/7zip/Bundles/SFXCon/makefile index a72e3f8..c319f68 100644 --- a/CPP/7zip/Bundles/SFXCon/makefile +++ b/CPP/7zip/Bundles/SFXCon/makefile @@ -67,6 +67,7 @@ UI_COMMON_OBJS = \ $O\ArchiveExtractCallback.obj \ $O\ArchiveOpenCallback.obj \ $O\DefaultName.obj \ + $O\ChainedExtract.obj \ $O\Extract.obj \ $O\ExtractingFilePath.obj \ $O\LoadCodecs.obj \ diff --git a/CPP/7zip/Bundles/SFXCon/makefile.gcc b/CPP/7zip/Bundles/SFXCon/makefile.gcc index 87d78a9..7fe3366 100644 --- a/CPP/7zip/Bundles/SFXCon/makefile.gcc +++ b/CPP/7zip/Bundles/SFXCon/makefile.gcc @@ -129,6 +129,7 @@ UI_COMMON_OBJS = \ $O/ArchiveExtractCallback.o \ $O/ArchiveOpenCallback.o \ $O/DefaultName.o \ + $O/ChainedExtract.o \ $O/Extract.o \ $O/ExtractingFilePath.o \ $O/LoadCodecs.o \ diff --git a/CPP/7zip/Bundles/SFXWin/makefile b/CPP/7zip/Bundles/SFXWin/makefile index 806bd07..73e8b0c 100644 --- a/CPP/7zip/Bundles/SFXWin/makefile +++ b/CPP/7zip/Bundles/SFXWin/makefile @@ -76,6 +76,7 @@ UI_COMMON_OBJS = \ $O\ArchiveExtractCallback.obj \ $O\ArchiveOpenCallback.obj \ $O\DefaultName.obj \ + $O\ChainedExtract.obj \ $O\Extract.obj \ $O\ExtractingFilePath.obj \ $O\LoadCodecs.obj \ diff --git a/CPP/7zip/Common/StreamBinder.h b/CPP/7zip/Common/StreamBinder.h index c0a7079..45d468b 100644 --- a/CPP/7zip/Common/StreamBinder.h +++ b/CPP/7zip/Common/StreamBinder.h @@ -3,6 +3,7 @@ #ifndef ZIP7_INC_STREAM_BINDER_H #define ZIP7_INC_STREAM_BINDER_H +#include "../../Common/MyCom.h" #include "../../Windows/Synchronization.h" #include "../IStream.h" diff --git a/CPP/7zip/UI/Common/ArchiveCommandLine.cpp b/CPP/7zip/UI/Common/ArchiveCommandLine.cpp index 73974e6..817c833 100644 --- a/CPP/7zip/UI/Common/ArchiveCommandLine.cpp +++ b/CPP/7zip/UI/Common/ArchiveCommandLine.cpp @@ -187,6 +187,7 @@ enum Enum kUseSlashMark, kDisableWildcardParsing, kElimDup, + kChainedExtract, kFullPathMode, kHardLinks, @@ -338,6 +339,7 @@ static const CSwitchForm kSwitchForms[] = { "spm", SWFRM_STRING_SINGL(0) }, { "spd", SWFRM_SIMPLE }, { "spe", SWFRM_MINUS }, + { "sce", SWFRM_MINUS }, { "spf", SWFRM_STRING_SINGL(0) }, { "snh", SWFRM_MINUS }, @@ -1358,6 +1360,9 @@ void CArcCmdLineParser::Parse2(CArcCmdLineOptions &options) options.ExtractOptions.ElimDup.Def = true; options.ExtractOptions.ElimDup.Val = !parser[NKey::kElimDup].WithMinus; } + + if (parser[NKey::kChainedExtract].ThereIs) + options.ExtractOptions.EnableChainedExtract = !parser[NKey::kChainedExtract].WithMinus; NWildcard::ECensorPathMode censorPathMode = NWildcard::k_RelatPath; bool fullPathMode = parser[NKey::kFullPathMode].ThereIs; diff --git a/CPP/7zip/UI/Common/ArchiveExtractCallback.cpp b/CPP/7zip/UI/Common/ArchiveExtractCallback.cpp index 6631629..b7a26de 100644 --- a/CPP/7zip/UI/Common/ArchiveExtractCallback.cpp +++ b/CPP/7zip/UI/Common/ArchiveExtractCallback.cpp @@ -301,7 +301,8 @@ CArchiveExtractCallback::CArchiveExtractCallback(): // Write_MTime(true), Is_elimPrefix_Mode(false), _arc(NULL), - _multiArchives(false) + _multiArchives(false), + _disableProgress(false) { #ifdef Z7_USE_SECURITY_CODE _saclEnabled = InitLocalPrivileges(); @@ -311,6 +312,7 @@ CArchiveExtractCallback::CArchiveExtractCallback(): void CArchiveExtractCallback::InitBeforeNewArchive() { + _disableProgress = false; #if defined(_WIN32) && !defined(UNDER_CE) && !defined(Z7_SFX) ZoneBuf.Free(); #endif @@ -403,6 +405,8 @@ Z7_COM7F_IMF(CArchiveExtractCallback::SetTotal(UInt64 size)) COM_TRY_BEGIN _progressTotal = size; // _progressTotal_Defined = true; + if (_disableProgress) + return S_OK; if (!_multiArchives && _extractCallback2) return _extractCallback2->SetTotal(size); return S_OK; @@ -438,6 +442,9 @@ Z7_COM7F_IMF(CArchiveExtractCallback::SetCompleted(const UInt64 *completeValue)) if (!_extractCallback2) return S_OK; + if (_disableProgress) + return S_OK; + UInt64 packCur; if (_multiArchives) { @@ -455,6 +462,8 @@ Z7_COM7F_IMF(CArchiveExtractCallback::SetCompleted(const UInt64 *completeValue)) Z7_COM7F_IMF(CArchiveExtractCallback::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize)) { COM_TRY_BEGIN + if (_disableProgress) + return S_OK; return LocalProgressSpec.Interface()->SetRatioInfo(inSize, outSize); COM_TRY_END } diff --git a/CPP/7zip/UI/Common/ArchiveExtractCallback.h b/CPP/7zip/UI/Common/ArchiveExtractCallback.h index 3c62763..ab6b1e3 100644 --- a/CPP/7zip/UI/Common/ArchiveExtractCallback.h +++ b/CPP/7zip/UI/Common/ArchiveExtractCallback.h @@ -391,6 +391,7 @@ private: bool _some_pathParts_wereRemoved; bool _multiArchives; + bool _disableProgress; bool _keepAndReplaceEmptyDirPrefixes; // replace them to "_"; #if defined(_WIN32) && !defined(UNDER_CE) && !defined(Z7_SFX) bool _saclEnabled; @@ -529,6 +530,11 @@ public: void InitBeforeNewArchive(); + void DisableProgress() + { + _disableProgress = true; + } + void Init( const CExtractNtOptions &ntOptions, const NWildcard::CCensorNode *wildcardCensor, diff --git a/CPP/7zip/UI/Common/ChainedExtract.cpp b/CPP/7zip/UI/Common/ChainedExtract.cpp new file mode 100644 index 0000000..986ee6d --- /dev/null +++ b/CPP/7zip/UI/Common/ChainedExtract.cpp @@ -0,0 +1,294 @@ +// ChainedExtract.cpp + +#include "StdAfx.h" + +#include "../../Common/StreamBinder.h" +#include "../../Common/VirtThread.h" + +#include "ChainedExtract.h" + + +static void SetExtractOpResMessage(Int32 opRes, UString &message) +{ + message.Empty(); + + switch (opRes) + { + case NArchive::NExtract::NOperationResult::kUnsupportedMethod: + message = "Unsupported Method"; + break; + case NArchive::NExtract::NOperationResult::kDataError: + message = "Data Error"; + break; + case NArchive::NExtract::NOperationResult::kCRCError: + message = "CRC Failed"; + break; + case NArchive::NExtract::NOperationResult::kUnavailable: + message = "Unavailable data"; + break; + case NArchive::NExtract::NOperationResult::kUnexpectedEnd: + message = "Unexpected end of data"; + break; + case NArchive::NExtract::NOperationResult::kDataAfterEnd: + message = "There are some data after the end of the payload data"; + break; + case NArchive::NExtract::NOperationResult::kIsNotArc: + message = "Is not archive"; + break; + case NArchive::NExtract::NOperationResult::kHeadersError: + message = "Headers Error"; + break; + case NArchive::NExtract::NOperationResult::kWrongPassword: + message = "Wrong password"; + break; + } +} + + +static HRESULT CheckOuterStreamResult(Int32 opRes, UString &errorMessage) +{ + if (opRes == NArchive::NExtract::NOperationResult::kOK) + return S_OK; + + SetExtractOpResMessage(opRes, errorMessage); + if (errorMessage.IsEmpty()) + errorMessage = "Error"; + errorMessage = UString("Outer stream: ") + errorMessage; + return E_FAIL; +} + + +static HRESULT DrainChainedStream(ISequentialInStream *stream) +{ + Byte buf[1 << 15]; + + for (;;) + { + UInt32 processed = 0; + RINOK(stream->Read(buf, (UInt32)sizeof(buf), &processed)) + if (processed == 0) + return S_OK; + } +} + + +Z7_CLASS_IMP_COM_1( + CChainedStreamExtractCallback + , IArchiveExtractCallback +) + Z7_IFACE_COM7_IMP(IProgress) +public: + CMyComPtr Progress; + CMyComPtr Stream; + Int32 OperationResult; + bool MultiMode; + UInt64 CompletedBeforeArc; + + void Init(IFolderArchiveExtractCallback *progress, ISequentialOutStream *stream, + bool multiMode, UInt64 completedBeforeArc) + { + Progress = progress; + Stream = stream; + OperationResult = NArchive::NExtract::NOperationResult::kOK; + MultiMode = multiMode; + CompletedBeforeArc = completedBeforeArc; + } +}; + + +Z7_COM7F_IMF(CChainedStreamExtractCallback::SetTotal(UInt64 size)) +{ + if (MultiMode) + return S_OK; + return Progress ? Progress->SetTotal(size) : S_OK; +} + + +Z7_COM7F_IMF(CChainedStreamExtractCallback::SetCompleted(const UInt64 *completeValue)) +{ + if (MultiMode) + { + if (!Progress || !completeValue) + return S_OK; + const UInt64 completed = CompletedBeforeArc + *completeValue; + return Progress->SetCompleted(&completed); + } + return Progress ? Progress->SetCompleted(completeValue) : S_OK; +} + + +Z7_COM7F_IMF(CChainedStreamExtractCallback::GetStream( + UInt32 index, ISequentialOutStream **outStream, Int32 /* askExtractMode */)) +{ + *outStream = NULL; + if (index != 0 || !Stream) + return E_FAIL; + *outStream = Stream.Detach(); + return S_OK; +} + + +Z7_COM7F_IMF(CChainedStreamExtractCallback::PrepareOperation(Int32 /* askExtractMode */)) +{ + return S_OK; +} + + +Z7_COM7F_IMF(CChainedStreamExtractCallback::SetOperationResult(Int32 opRes)) +{ + OperationResult = opRes; + return S_OK; +} + + +struct CChainedStreamExtractThread Z7_final: + public CVirtThread +{ + const CArc *Arc; + CMyComPtr Callback; + HRESULT Result; + + CChainedStreamExtractThread(): + Arc(NULL), + Result(S_OK) + {} + + void Execute() Z7_override + { + Result = Arc->Archive->Extract(NULL, (UInt32)(Int32)-1, 0, Callback); + } +}; + + +static int FindChainedFormatIndex( + const CCodecs *codecs, + const CArc &arc, + const CExtractOptions &options) +{ + if (!options.EnableChainedExtract) + return -1; + if (options.StdInMode || options.StdOutMode) + return -1; + if (arc.FormatIndex < 0) + return -1; + const CArcInfoEx &ai = codecs->Formats[(unsigned)arc.FormatIndex]; + const UString wrappedExt = ai.GetWrappedExt(); + if (wrappedExt.IsEmpty()) + return -1; + UInt32 numItems = 0; + if (arc.Archive->GetNumberOfItems(&numItems) != S_OK || numItems != 1) + return -1; + UString ext(wrappedExt); + if (ext.IsPrefixedBy(L".")) + ext.DeleteFrontal(1); + + const int dotPos = arc.DefaultName.ReverseFind_Dot(); + if (dotPos < 0) + return -1; + if (!ext.IsEqualTo_NoCase(arc.DefaultName.Ptr((unsigned)(dotPos + 1)))) + return -1; + FOR_VECTOR (i, codecs->Formats) + if (codecs->Formats[i].FindExtension(ext) >= 0) + return (int)i; + return -1; +} + + +HRESULT TryChainedExtract( + CCodecs *codecs, + const CArchiveLink &arcLink, + UInt64 packSize, + UInt64 completedBeforeArc, + bool multi, + const NWildcard::CCensorNode &wildcardCensor, + const CExtractOptions &options, + bool calcCrc, + IExtractCallbackUI *callback, + IFolderArchiveExtractCallback *callbackFAE, + CArchiveExtractCallback *ecs, + UString &errorMessage) +{ + const CArc &arc = arcLink.Arcs.Back(); + const int innerFormatIndex = FindChainedFormatIndex(codecs, arc, options); + if (innerFormatIndex < 0) + return S_FALSE; + + CStreamBinder binder; + RINOK(binder.Create_ReInit()) + + CMyComPtr binderInStream; + CMyComPtr binderOutStream; + binder.CreateStreams2(binderInStream, binderOutStream); + + CMyComPtr2_Create outerExtractCallback; + outerExtractCallback->Init(callbackFAE, binderOutStream, multi, completedBeforeArc); + binderOutStream.Release(); + + CChainedStreamExtractThread outerExtractThread; + outerExtractThread.Arc = &arc; + outerExtractThread.Callback = outerExtractCallback; + RINOK(HRESULT_FROM_WIN32(outerExtractThread.Create())) + RINOK(HRESULT_FROM_WIN32(outerExtractThread.Start())) + + HRESULT result = S_OK; + + { + CArchiveLink innerArcLink; + CObjectVector innerTypes; + COpenType innerType; + innerType.FormatIndex = innerFormatIndex; + innerTypes.Add(innerType); + CIntVector excludedFormats; + + COpenOptions op; + #ifndef Z7_SFX + op.props = &options.Properties; + #endif + op.codecs = codecs; + op.types = &innerTypes; + op.excludedFormats = &excludedFormats; + op.seqStream = binderInStream; + op.filePath = arc.DefaultName; + + result = innerArcLink.Open(op); + if (result == S_OK) + { + UInt64 innerProcessed = 0; + ecs->DisableProgress(); + result = DecompressArchive( + codecs, + innerArcLink, + packSize, + wildcardCensor, + options, + calcCrc, + callback, + callbackFAE, + ecs, + errorMessage, + innerProcessed, + true); + } + innerArcLink.Release(); + } + + if (result == S_OK) + { + const HRESULT drainResult = DrainChainedStream(binderInStream); + if (drainResult != S_OK) + result = drainResult; + } + + binderInStream.Release(); + + RINOK(HRESULT_FROM_WIN32(outerExtractThread.WaitExecuteFinish())) + + const HRESULT outerResult = outerExtractThread.Result; + + if (outerResult != S_OK && outerResult != k_My_HRESULT_WritingWasCut) + return outerResult; + if (result != S_OK) + return result; + + return CheckOuterStreamResult(outerExtractCallback->OperationResult, errorMessage); +} diff --git a/CPP/7zip/UI/Common/ChainedExtract.h b/CPP/7zip/UI/Common/ChainedExtract.h new file mode 100644 index 0000000..d5be670 --- /dev/null +++ b/CPP/7zip/UI/Common/ChainedExtract.h @@ -0,0 +1,22 @@ +// ChainedExtract.h + +#ifndef ZIP7_INC_CHAINED_EXTRACT_H +#define ZIP7_INC_CHAINED_EXTRACT_H + +#include "Extract.h" + +HRESULT TryChainedExtract( + CCodecs *codecs, + const CArchiveLink &arcLink, + UInt64 packSize, + UInt64 completedBeforeArc, + bool multi, + const NWildcard::CCensorNode &wildcardCensor, + const CExtractOptions &options, + bool calcCrc, + IExtractCallbackUI *callback, + IFolderArchiveExtractCallback *callbackFAE, + CArchiveExtractCallback *ecs, + UString &errorMessage); + +#endif diff --git a/CPP/7zip/UI/Common/CompressCall.cpp b/CPP/7zip/UI/Common/CompressCall.cpp index 1ded1b3..c872aae 100644 --- a/CPP/7zip/UI/Common/CompressCall.cpp +++ b/CPP/7zip/UI/Common/CompressCall.cpp @@ -44,6 +44,7 @@ using namespace NWindows; #define kArcIncludeSwitches " -an -ai" ISWITCH_NO_WILDCARD_POSTFIX #define kHashIncludeSwitches kIncludeSwitch #define kStopSwitchParsing " --" +#define kChainedExtractSwitch " -sce" extern HWND g_HWND; @@ -252,7 +253,7 @@ static void ExtractGroupCommand(const UStringVector &arcPaths, UString ¶ms, ErrorMessageHRESULT(result); } -void ExtractArchives(const UStringVector &arcPaths, const UString &outFolder, bool showDialog, bool elimDup, UInt32 writeZone) +void ExtractArchives(const UStringVector &arcPaths, const UString &outFolder, bool showDialog, bool elimDup, UInt32 writeZone, bool enableChainedExtract) { MY_TRY_BEGIN UString params ('x'); @@ -268,6 +269,8 @@ void ExtractArchives(const UStringVector &arcPaths, const UString &outFolder, bo params += " -snz"; params.Add_UInt32(writeZone); } + if (enableChainedExtract) + params += kChainedExtractSwitch; if (showDialog) params += kShowDialogSwitch; ExtractGroupCommand(arcPaths, params, false); diff --git a/CPP/7zip/UI/Common/CompressCall.h b/CPP/7zip/UI/Common/CompressCall.h index f2da163..0be3075 100644 --- a/CPP/7zip/UI/Common/CompressCall.h +++ b/CPP/7zip/UI/Common/CompressCall.h @@ -15,7 +15,7 @@ HRESULT CompressFiles( const UStringVector &names, bool email, bool showDialog, bool waitFinish); -void ExtractArchives(const UStringVector &arcPaths, const UString &outFolder, bool showDialog, bool elimDup, UInt32 writeZone); +void ExtractArchives(const UStringVector &arcPaths, const UString &outFolder, bool showDialog, bool elimDup, UInt32 writeZone, bool enableChainedExtract); void TestArchives(const UStringVector &arcPaths, bool hashMode = false); void CalcChecksum(const UStringVector &paths, diff --git a/CPP/7zip/UI/Common/CompressCall2.cpp b/CPP/7zip/UI/Common/CompressCall2.cpp index fef0877..89d6fd5 100644 --- a/CPP/7zip/UI/Common/CompressCall2.cpp +++ b/CPP/7zip/UI/Common/CompressCall2.cpp @@ -224,11 +224,12 @@ static HRESULT ExtractGroupCommand(const UStringVector &arcPaths, } void ExtractArchives(const UStringVector &arcPaths, const UString &outFolder, - bool showDialog, bool elimDup, UInt32 writeZone) + bool showDialog, bool elimDup, UInt32 writeZone, bool enableChainedExtract) { CExtractOptions eo; eo.OutputDir = us2fs(outFolder); eo.TestMode = false; + eo.EnableChainedExtract = enableChainedExtract; eo.ElimDup.Val = elimDup; eo.ElimDup.Def = elimDup; if (writeZone != (UInt32)(Int32)-1) diff --git a/CPP/7zip/UI/Common/Extract.cpp b/CPP/7zip/UI/Common/Extract.cpp index 0301976..b759298 100644 --- a/CPP/7zip/UI/Common/Extract.cpp +++ b/CPP/7zip/UI/Common/Extract.cpp @@ -13,6 +13,7 @@ #include "../Common/ExtractingFilePath.h" #include "../Common/HashCalc.h" +#include "ChainedExtract.h" #include "Extract.h" #include "SetProperties.h" @@ -33,7 +34,7 @@ static void SetErrorMessage(const char *message, } -static HRESULT DecompressArchive( +HRESULT DecompressArchive( CCodecs *codecs, const CArchiveLink &arcLink, UInt64 packSize, @@ -44,12 +45,14 @@ static HRESULT DecompressArchive( IFolderArchiveExtractCallback *callbackFAE, CArchiveExtractCallback *ecs, UString &errorMessage, - UInt64 &stdInProcessed) + UInt64 &stdInProcessed, + bool chainedMode) { const CArc &arc = arcLink.Arcs.Back(); stdInProcessed = 0; IInArchive *archive = arc.Archive; CRecordVector realIndices; + const bool singlePassMode = (options.StdInMode || chainedMode); UStringVector removePathParts; @@ -90,7 +93,7 @@ static HRESULT DecompressArchive( const bool allFilesAreAllowed = wildcardCensor.AreAllAllowed(); - if (!options.StdInMode) + if (!singlePassMode) { UInt32 numItems; RINOK(archive->GetNumberOfItems(&numItems)) @@ -163,7 +166,7 @@ static HRESULT DecompressArchive( } } - if (elimIsPossible) + if (elimIsPossible && !singlePassMode) { removePathParts.Add(elimPrefix); // outDir = outDirReduced; @@ -191,7 +194,7 @@ static HRESULT DecompressArchive( ecs->Init( options.NtOptions, - options.StdInMode ? &wildcardCensor : NULL, + singlePassMode ? &wildcardCensor : NULL, &arc, callbackFAE, options.StdOutMode, options.TestMode, @@ -199,12 +202,12 @@ static HRESULT DecompressArchive( removePathParts, false, packSize); - ecs->Is_elimPrefix_Mode = elimIsPossible; + ecs->Is_elimPrefix_Mode = (elimIsPossible && !singlePassMode); #ifdef SUPPORT_LINKS - if (!options.StdInMode && + if (!singlePassMode && !options.TestMode && options.NtOptions.HardLinks.Val) { @@ -219,12 +222,15 @@ static HRESULT DecompressArchive( CArchiveExtractCallback_Closer ecsCloser(ecs); - if (options.StdInMode) + if (singlePassMode) { result = archive->Extract(NULL, (UInt32)(Int32)-1, testMode, ecs); - NCOM::CPropVariant prop; - if (archive->GetArchiveProperty(kpidPhySize, &prop) == S_OK) - ConvertPropVariantToUInt64(prop, stdInProcessed); + if (options.StdInMode) + { + NCOM::CPropVariant prop; + if (archive->GetArchiveProperty(kpidPhySize, &prop) == S_OK) + ConvertPropVariantToUInt64(prop, stdInProcessed); + } } else { @@ -242,6 +248,7 @@ static HRESULT DecompressArchive( return callback->ExtractResult(result); } + /* v9.31: BUG was fixed: Sorted list for file paths was sorted with case insensitive compare function. But FindInSorted function did binary search via case sensitive compare function */ @@ -539,15 +546,41 @@ HRESULT Extract( false; #endif - RINOK(DecompressArchive( - codecs, - arcLink, - fi.Size + arcLink.VolumesSize, - wildcardCensor, - options, - calcCrc, - extractCallback, faeCallback, ecs, - errorMessage, packProcessed)) + HRESULT chainRes = S_FALSE; + + chainRes = TryChainedExtract( + codecs, + arcLink, + fi.Size + arcLink.VolumesSize, + totalPackProcessed, + multi, + wildcardCensor, + options, + calcCrc, + extractCallback, + faeCallback, + ecs, + errorMessage); + + if (chainRes != S_FALSE) + { + RINOK(chainRes) + packProcessed = fi.Size + arcLink.VolumesSize; + } + else + RINOK(DecompressArchive( + codecs, + arcLink, + fi.Size + arcLink.VolumesSize, + wildcardCensor, + options, + calcCrc, + extractCallback, + faeCallback, + ecs, + errorMessage, + packProcessed, + false)) if (!options.StdInMode) packProcessed = fi.Size + arcLink.VolumesSize; diff --git a/CPP/7zip/UI/Common/Extract.h b/CPP/7zip/UI/Common/Extract.h index defaa27..9fc7677 100644 --- a/CPP/7zip/UI/Common/Extract.h +++ b/CPP/7zip/UI/Common/Extract.h @@ -17,6 +17,7 @@ struct CExtractOptionsBase { CBoolPair ElimDup; + bool EnableChainedExtract; bool ExcludeDirItems; bool ExcludeFileItems; @@ -33,6 +34,7 @@ struct CExtractOptionsBase UString HashDir; CExtractOptionsBase(): + EnableChainedExtract(false), ExcludeDirItems(false), ExcludeFileItems(false), PathMode_Force(false), @@ -87,6 +89,20 @@ struct CDecompressStat } }; +HRESULT DecompressArchive( + CCodecs *codecs, + const CArchiveLink &arcLink, + UInt64 packSize, + const NWildcard::CCensorNode &wildcardCensor, + const CExtractOptions &options, + bool calcCrc, + IExtractCallbackUI *callback, + IFolderArchiveExtractCallback *callbackFAE, + CArchiveExtractCallback *ecs, + UString &errorMessage, + UInt64 &stdInProcessed, + bool chainedMode); + HRESULT Extract( // DECL_EXTERNAL_CODECS_LOC_VARS CCodecs *codecs, diff --git a/CPP/7zip/UI/Common/LoadCodecs.h b/CPP/7zip/UI/Common/LoadCodecs.h index a81bb5c..3a6a08c 100644 --- a/CPP/7zip/UI/Common/LoadCodecs.h +++ b/CPP/7zip/UI/Common/LoadCodecs.h @@ -184,6 +184,16 @@ struct CArcInfoEx return UString(); return Exts[0].Ext; } + + UString GetWrappedExt() const + { + if (Flags_KeepName()) + FOR_VECTOR (i, Exts) + if (!Exts[i].AddExt.IsEmpty()) + return Exts[i].AddExt; + return UString(); + } + int FindExtension(const UString &ext) const; bool Is_7z() const { return Name.IsEqualTo_Ascii_NoCase("7z"); } diff --git a/CPP/7zip/UI/Common/OpenArchive.cpp b/CPP/7zip/UI/Common/OpenArchive.cpp index c26d4c0..dd6384a 100644 --- a/CPP/7zip/UI/Common/OpenArchive.cpp +++ b/CPP/7zip/UI/Common/OpenArchive.cpp @@ -3094,11 +3094,10 @@ HRESULT CArc::OpenStreamOrFile(COpenOptions &op) #endif op.seqStream = seqStream; } - else if (!op.stream) + else if (!op.stream && !op.seqStream) { fileStreamSpec = new CInFileStream; fileStream = fileStreamSpec; - Path = filePath; if (!fileStreamSpec->Open(us2fs(Path))) return GetLastError_noZero_HRESULT(); op.stream = fileStream; diff --git a/CPP/7zip/UI/Console/Console.mak b/CPP/7zip/UI/Console/Console.mak index 4effea6..fe5f931 100644 --- a/CPP/7zip/UI/Console/Console.mak +++ b/CPP/7zip/UI/Console/Console.mak @@ -25,6 +25,7 @@ UI_COMMON_OBJS = \ $O\Bench.obj \ $O\DefaultName.obj \ $O\EnumDirItems.obj \ + $O\ChainedExtract.obj \ $O\Extract.obj \ $O\ExtractingFilePath.obj \ $O\HashCalc.obj \ diff --git a/CPP/7zip/UI/Console/Main.cpp b/CPP/7zip/UI/Console/Main.cpp index 90e00a4..c4499a2 100644 --- a/CPP/7zip/UI/Console/Main.cpp +++ b/CPP/7zip/UI/Console/Main.cpp @@ -165,6 +165,7 @@ static const char * const kHelpString = " -p{Password} : set Password\n" #endif " -r[-|0] : Recurse subdirectories for name search\n" + " -sce : chained extract archives with tar wrapper in a single operation\n" " -sa{a|e|s} : set Archive name mode\n" " -scc{UTF-8|WIN|DOS} : set charset for console input/output\n" " -scs{UTF-8|UTF-16LE|UTF-16BE|WIN|DOS|{id}} : set charset for list files\n" diff --git a/CPP/7zip/UI/Console/makefile b/CPP/7zip/UI/Console/makefile index d449b38..cfa2cd4 100644 --- a/CPP/7zip/UI/Console/makefile +++ b/CPP/7zip/UI/Console/makefile @@ -45,9 +45,11 @@ WIN_OBJS = \ $O\MultiOutStream.obj \ $O\ProgressUtils.obj \ $O\PropId.obj \ + $O\StreamBinder.obj \ $O\StreamObjects.obj \ $O\StreamUtils.obj \ $O\UniqBlocks.obj \ + $O\VirtThread.obj \ AR_COMMON_OBJS = \ $O\ItemNameUtils.obj \ diff --git a/CPP/7zip/UI/FileManager/Panel.cpp b/CPP/7zip/UI/FileManager/Panel.cpp index 84bd88c..9d72934 100644 --- a/CPP/7zip/UI/FileManager/Panel.cpp +++ b/CPP/7zip/UI/FileManager/Panel.cpp @@ -1035,6 +1035,7 @@ void CPanel::ExtractArchives() , true // showDialog , false // elimDup , ci.WriteZone + , false // enableChainedExtract ); } diff --git a/CPP/7zip/UI/GUI/GUI.cpp b/CPP/7zip/UI/GUI/GUI.cpp index 6bb6693..1a13f63 100644 --- a/CPP/7zip/UI/GUI/GUI.cpp +++ b/CPP/7zip/UI/GUI/GUI.cpp @@ -282,6 +282,12 @@ static int Main2() } #endif + if (options.StdInMode) + { + ArchivePathsSorted.Add(options.ArcName_for_StdInMode); + ArchivePathsFullSorted.Add(options.ArcName_for_StdInMode); + } + else { CDirItemsStat st; HRESULT hresultMain = EnumerateDirItemsAndSort( diff --git a/CPP/7zip/UI/GUI/GUI.dsp b/CPP/7zip/UI/GUI/GUI.dsp index 1742f26..4d7e20d 100644 --- a/CPP/7zip/UI/GUI/GUI.dsp +++ b/CPP/7zip/UI/GUI/GUI.dsp @@ -693,6 +693,10 @@ SOURCE=..\..\Common\PropId.cpp # End Source File # Begin Source File +SOURCE=..\..\Common\StreamBinder.cpp +# End Source File +# Begin Source File + SOURCE=..\..\Common\StreamObjects.cpp # End Source File # Begin Source File @@ -715,6 +719,14 @@ SOURCE=..\..\Common\UniqBlocks.cpp SOURCE=..\..\Common\UniqBlocks.h # End Source File +# Begin Source File + +SOURCE=..\..\Common\VirtThread.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\VirtThread.h +# End Source File # End Group # Begin Group "Compress" diff --git a/CPP/7zip/UI/GUI/makefile b/CPP/7zip/UI/GUI/makefile index b879a5d..7298612 100644 --- a/CPP/7zip/UI/GUI/makefile +++ b/CPP/7zip/UI/GUI/makefile @@ -76,9 +76,11 @@ WIN_CTRL_OBJS = \ $O\MultiOutStream.obj \ $O\ProgressUtils.obj \ $O\PropId.obj \ + $O\StreamBinder.obj \ $O\StreamObjects.obj \ $O\StreamUtils.obj \ $O\UniqBlocks.obj \ + $O\VirtThread.obj \ UI_COMMON_OBJS = \ $O\ArchiveCommandLine.obj \ @@ -87,6 +89,7 @@ UI_COMMON_OBJS = \ $O\Bench.obj \ $O\DefaultName.obj \ $O\EnumDirItems.obj \ + $O\ChainedExtract.obj \ $O\Extract.obj \ $O\ExtractingFilePath.obj \ $O\HashCalc.obj \ From 6128789b97f3cba634ea2d26146d1a83e53b99cd Mon Sep 17 00:00:00 2001 From: softworkz Date: Fri, 17 Apr 2026 07:26:21 +0200 Subject: [PATCH 2/2] Use Chained Extraction in Explorer context menu actions This commit unconditionally enables chained extraction for explorer integration (context menus). It's still possible to choose "Open" instead and extract the tar only. --- CPP/7zip/UI/Explorer/ContextMenu.cpp | 64 ++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/CPP/7zip/UI/Explorer/ContextMenu.cpp b/CPP/7zip/UI/Explorer/ContextMenu.cpp index 0630d78..25f384d 100644 --- a/CPP/7zip/UI/Explorer/ContextMenu.cpp +++ b/CPP/7zip/UI/Explorer/ContextMenu.cpp @@ -444,6 +444,56 @@ static bool IsItArcExt(const UString &ext) return false; } +static bool IsTarCompoundExtension(const UString &ext) +{ + return ext.IsEqualTo_Ascii_NoCase("gz") + || ext.IsEqualTo_Ascii_NoCase("gzip") + || ext.IsEqualTo_Ascii_NoCase("bz2") + || ext.IsEqualTo_Ascii_NoCase("bzip2") + || ext.IsEqualTo_Ascii_NoCase("xz") + || ext.IsEqualTo_Ascii_NoCase("zst") + || ext.IsEqualTo_Ascii_NoCase("zstd") + || ext.IsEqualTo_Ascii_NoCase("z"); +} + +static bool GetChainedExtractFolderName(const UString &arcName, UString &folderName) +{ + int dotPos = arcName.ReverseFind_Dot(); + if (dotPos < 0) + return false; + + const UString ext = arcName.Ptr(dotPos + 1); + UString base = arcName.Left(dotPos); + + if (ext.IsEqualTo_Ascii_NoCase("tgz") + || ext.IsEqualTo_Ascii_NoCase("tpz") + || ext.IsEqualTo_Ascii_NoCase("tbz") + || ext.IsEqualTo_Ascii_NoCase("tbz2") + || ext.IsEqualTo_Ascii_NoCase("txz") + || ext.IsEqualTo_Ascii_NoCase("tzst") + || ext.IsEqualTo_Ascii_NoCase("taz")) + { + base.TrimRight(); + folderName = Get_Correct_FsFile_Name(base); + return true; + } + + if (!IsTarCompoundExtension(ext)) + return false; + + dotPos = base.ReverseFind_Dot(); + if (dotPos < 0) + return false; + + if (!StringsAreEqualNoCase_Ascii(base.Ptr((unsigned)(dotPos + 1)), "tar")) + return false; + + base.DeleteFrom((unsigned)dotPos); + base.TrimRight(); + folderName = Get_Correct_FsFile_Name(base); + return true; +} + UString GetSubFolderNameForExtract(const UString &arcName); UString GetSubFolderNameForExtract(const UString &arcName) { @@ -831,9 +881,14 @@ Z7_COMWF_B CZipContextMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu, if (_dropMode) baseFolder = _dropPath; - UString specFolder ('*'); + UString specFolderName ('*'); if (_fileNames.Size() == 1) - specFolder = GetSubFolderNameForExtract(fs2us(fi0.Name)); + { + const UString arcName = fs2us(fi0.Name); + if (!GetChainedExtractFolderName(arcName, specFolderName)) + specFolderName = GetSubFolderNameForExtract(arcName); + } + UString specFolder = specFolderName; specFolder.Add_PathSepar(); if ((contextMenuFlags & NContextMenuFlags::kExtract) != 0) @@ -861,7 +916,7 @@ Z7_COMWF_B CZipContextMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu, UString s; cmi.Folder = baseFolder + specFolder; AddCommand(kExtractTo, s, cmi); - MyFormatNew_ReducedName(s, specFolder); + MyFormatNew_ReducedName(s, specFolderName); Set_UserString_in_LastCommand(s); MyInsertMenu(popupMenu, subIndex++, currentCommandID++, s, bitmap); } @@ -1285,7 +1340,8 @@ HRESULT CZipContextMenu::InvokeCommandCommon(const CCommandMapItem &cmi) ExtractArchives(_fileNames, cmi.Folder, (cmdID == kExtract), // showDialog (cmdID == kExtractTo) && _elimDup.Val, // elimDup - _writeZone + _writeZone, + true ); break; }