// Extract.cpp | |
#include "StdAfx.h" | |
#include "../../../../C/Sort.h" | |
#include "../../../Common/StringConvert.h" | |
#include "../../../Windows/FileDir.h" | |
#include "../../../Windows/PropVariant.h" | |
#include "../../../Windows/PropVariantConv.h" | |
#include "../Common/ExtractingFilePath.h" | |
#include "Extract.h" | |
#include "SetProperties.h" | |
using namespace NWindows; | |
using namespace NFile; | |
using namespace NDir; | |
static HRESULT DecompressArchive( | |
CCodecs *codecs, | |
const CArchiveLink &arcLink, | |
UInt64 packSize, | |
const NWildcard::CCensorNode &wildcardCensor, | |
const CExtractOptions &options, | |
bool calcCrc, | |
IExtractCallbackUI *callback, | |
CArchiveExtractCallback *ecs, | |
UString &errorMessage, | |
UInt64 &stdInProcessed) | |
{ | |
const CArc &arc = arcLink.Arcs.Back(); | |
stdInProcessed = 0; | |
IInArchive *archive = arc.Archive; | |
CRecordVector<UInt32> realIndices; | |
UStringVector removePathParts; | |
FString outDir = options.OutputDir; | |
UString replaceName = arc.DefaultName; | |
if (arcLink.Arcs.Size() > 1) | |
{ | |
// Most "pe" archives have same name of archive subfile "[0]" or ".rsrc_1". | |
// So it extracts different archives to one folder. | |
// We will use top level archive name | |
const CArc &arc0 = arcLink.Arcs[0]; | |
if (StringsAreEqualNoCase_Ascii(codecs->Formats[arc0.FormatIndex].Name, "pe")) | |
replaceName = arc0.DefaultName; | |
} | |
outDir.Replace(FString("*"), us2fs(Get_Correct_FsFile_Name(replaceName))); | |
bool elimIsPossible = false; | |
UString elimPrefix; // only pure name without dir delimiter | |
FString outDirReduced = outDir; | |
if (options.ElimDup.Val && options.PathMode != NExtract::NPathMode::kAbsPaths) | |
{ | |
UString dirPrefix; | |
SplitPathToParts_Smart(fs2us(outDir), dirPrefix, elimPrefix); | |
if (!elimPrefix.IsEmpty()) | |
{ | |
if (IsPathSepar(elimPrefix.Back())) | |
elimPrefix.DeleteBack(); | |
if (!elimPrefix.IsEmpty()) | |
{ | |
outDirReduced = us2fs(dirPrefix); | |
elimIsPossible = true; | |
} | |
} | |
} | |
bool allFilesAreAllowed = wildcardCensor.AreAllAllowed(); | |
if (!options.StdInMode) | |
{ | |
UInt32 numItems; | |
RINOK(archive->GetNumberOfItems(&numItems)); | |
CReadArcItem item; | |
for (UInt32 i = 0; i < numItems; i++) | |
{ | |
if (elimIsPossible || !allFilesAreAllowed) | |
{ | |
RINOK(arc.GetItem(i, item)); | |
} | |
else | |
{ | |
#ifdef SUPPORT_ALT_STREAMS | |
item.IsAltStream = false; | |
if (!options.NtOptions.AltStreams.Val && arc.Ask_AltStream) | |
{ | |
RINOK(Archive_IsItem_AltStream(arc.Archive, i, item.IsAltStream)); | |
} | |
#endif | |
} | |
#ifdef SUPPORT_ALT_STREAMS | |
if (!options.NtOptions.AltStreams.Val && item.IsAltStream) | |
continue; | |
#endif | |
if (elimIsPossible) | |
{ | |
const UString &s = | |
#ifdef SUPPORT_ALT_STREAMS | |
item.MainPath; | |
#else | |
item.Path; | |
#endif | |
if (!IsPath1PrefixedByPath2(s, elimPrefix)) | |
elimIsPossible = false; | |
else | |
{ | |
wchar_t c = s[elimPrefix.Len()]; | |
if (c == 0) | |
{ | |
if (!item.MainIsDir) | |
elimIsPossible = false; | |
} | |
else if (!IsPathSepar(c)) | |
elimIsPossible = false; | |
} | |
} | |
if (!allFilesAreAllowed) | |
{ | |
if (!CensorNode_CheckPath(wildcardCensor, item)) | |
continue; | |
} | |
realIndices.Add(i); | |
} | |
if (realIndices.Size() == 0) | |
{ | |
callback->ThereAreNoFiles(); | |
return callback->ExtractResult(S_OK); | |
} | |
} | |
if (elimIsPossible) | |
{ | |
removePathParts.Add(elimPrefix); | |
// outDir = outDirReduced; | |
} | |
#ifdef _WIN32 | |
// GetCorrectFullFsPath doesn't like "..". | |
// outDir.TrimRight(); | |
// outDir = GetCorrectFullFsPath(outDir); | |
#endif | |
if (outDir.IsEmpty()) | |
outDir = "." STRING_PATH_SEPARATOR; | |
/* | |
#ifdef _WIN32 | |
else if (NName::IsAltPathPrefix(outDir)) {} | |
#endif | |
*/ | |
else if (!CreateComplexDir(outDir)) | |
{ | |
HRESULT res = ::GetLastError(); | |
if (res == S_OK) | |
res = E_FAIL; | |
errorMessage = "Can not create output directory: "; | |
errorMessage += fs2us(outDir); | |
return res; | |
} | |
ecs->Init( | |
options.NtOptions, | |
options.StdInMode ? &wildcardCensor : NULL, | |
&arc, | |
callback, | |
options.StdOutMode, options.TestMode, | |
outDir, | |
removePathParts, false, | |
packSize); | |
#ifdef SUPPORT_LINKS | |
if (!options.StdInMode && | |
!options.TestMode && | |
options.NtOptions.HardLinks.Val) | |
{ | |
RINOK(ecs->PrepareHardLinks(&realIndices)); | |
} | |
#endif | |
HRESULT result; | |
Int32 testMode = (options.TestMode && !calcCrc) ? 1: 0; | |
CArchiveExtractCallback_Closer ecsCloser(ecs); | |
if (options.StdInMode) | |
{ | |
result = archive->Extract(NULL, (UInt32)(Int32)-1, testMode, ecs); | |
NCOM::CPropVariant prop; | |
if (archive->GetArchiveProperty(kpidPhySize, &prop) == S_OK) | |
ConvertPropVariantToUInt64(prop, stdInProcessed); | |
} | |
else | |
result = archive->Extract(&realIndices.Front(), realIndices.Size(), testMode, ecs); | |
HRESULT res2 = ecsCloser.Close(); | |
if (result == S_OK) | |
result = res2; | |
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 */ | |
int Find_FileName_InSortedVector(const UStringVector &fileName, const UString &name) | |
{ | |
unsigned left = 0, right = fileName.Size(); | |
while (left != right) | |
{ | |
unsigned mid = (left + right) / 2; | |
const UString &midValue = fileName[mid]; | |
int compare = CompareFileNames(name, midValue); | |
if (compare == 0) | |
return mid; | |
if (compare < 0) | |
right = mid; | |
else | |
left = mid + 1; | |
} | |
return -1; | |
} | |
HRESULT Extract( | |
CCodecs *codecs, | |
const CObjectVector<COpenType> &types, | |
const CIntVector &excludedFormats, | |
UStringVector &arcPaths, UStringVector &arcPathsFull, | |
const NWildcard::CCensorNode &wildcardCensor, | |
const CExtractOptions &options, | |
IOpenCallbackUI *openCallback, | |
IExtractCallbackUI *extractCallback, | |
#ifndef _SFX | |
IHashCalc *hash, | |
#endif | |
UString &errorMessage, | |
CDecompressStat &st) | |
{ | |
st.Clear(); | |
UInt64 totalPackSize = 0; | |
CRecordVector<UInt64> arcSizes; | |
unsigned numArcs = options.StdInMode ? 1 : arcPaths.Size(); | |
unsigned i; | |
for (i = 0; i < numArcs; i++) | |
{ | |
NFind::CFileInfo fi; | |
fi.Size = 0; | |
if (!options.StdInMode) | |
{ | |
const FString &arcPath = us2fs(arcPaths[i]); | |
if (!fi.Find(arcPath)) | |
throw "there is no such archive"; | |
if (fi.IsDir()) | |
throw "can't decompress folder"; | |
} | |
arcSizes.Add(fi.Size); | |
totalPackSize += fi.Size; | |
} | |
CBoolArr skipArcs(numArcs); | |
for (i = 0; i < numArcs; i++) | |
skipArcs[i] = false; | |
CArchiveExtractCallback *ecs = new CArchiveExtractCallback; | |
CMyComPtr<IArchiveExtractCallback> ec(ecs); | |
bool multi = (numArcs > 1); | |
ecs->InitForMulti(multi, options.PathMode, options.OverwriteMode, | |
false // keepEmptyDirParts | |
); | |
#ifndef _SFX | |
ecs->SetHashMethods(hash); | |
#endif | |
if (multi) | |
{ | |
RINOK(extractCallback->SetTotal(totalPackSize)); | |
} | |
UInt64 totalPackProcessed = 0; | |
bool thereAreNotOpenArcs = false; | |
for (i = 0; i < numArcs; i++) | |
{ | |
if (skipArcs[i]) | |
continue; | |
const UString &arcPath = arcPaths[i]; | |
NFind::CFileInfo fi; | |
if (options.StdInMode) | |
{ | |
fi.Size = 0; | |
fi.Attrib = 0; | |
} | |
else | |
{ | |
if (!fi.Find(us2fs(arcPath)) || fi.IsDir()) | |
throw "there is no such archive"; | |
} | |
/* | |
#ifndef _NO_CRYPTO | |
openCallback->Open_Clear_PasswordWasAsked_Flag(); | |
#endif | |
*/ | |
RINOK(extractCallback->BeforeOpen(arcPath, options.TestMode)); | |
CArchiveLink arcLink; | |
CObjectVector<COpenType> types2 = types; | |
/* | |
#ifndef _SFX | |
if (types.IsEmpty()) | |
{ | |
int pos = arcPath.ReverseFind(L'.'); | |
if (pos >= 0) | |
{ | |
UString s = arcPath.Ptr(pos + 1); | |
int index = codecs->FindFormatForExtension(s); | |
if (index >= 0 && s == L"001") | |
{ | |
s = arcPath.Left(pos); | |
pos = s.ReverseFind(L'.'); | |
if (pos >= 0) | |
{ | |
int index2 = codecs->FindFormatForExtension(s.Ptr(pos + 1)); | |
if (index2 >= 0) // && s.CompareNoCase(L"rar") != 0 | |
{ | |
types2.Add(index2); | |
types2.Add(index); | |
} | |
} | |
} | |
} | |
} | |
#endif | |
*/ | |
COpenOptions op; | |
#ifndef _SFX | |
op.props = &options.Properties; | |
#endif | |
op.codecs = codecs; | |
op.types = &types2; | |
op.excludedFormats = &excludedFormats; | |
op.stdInMode = options.StdInMode; | |
op.stream = NULL; | |
op.filePath = arcPath; | |
HRESULT result = arcLink.Open_Strict(op, openCallback); | |
if (result == E_ABORT) | |
return result; | |
// arcLink.Set_ErrorsText(); | |
RINOK(extractCallback->OpenResult(codecs, arcLink, arcPath, result)); | |
if (result != S_OK) | |
{ | |
thereAreNotOpenArcs = true; | |
if (!options.StdInMode) | |
{ | |
NFind::CFileInfo fi2; | |
if (fi2.Find(us2fs(arcPath))) | |
if (!fi2.IsDir()) | |
totalPackProcessed += fi2.Size; | |
} | |
continue; | |
} | |
if (!options.StdInMode) | |
{ | |
// numVolumes += arcLink.VolumePaths.Size(); | |
// arcLink.VolumesSize; | |
// totalPackSize -= DeleteUsedFileNamesFromList(arcLink, i + 1, arcPaths, arcPathsFull, &arcSizes); | |
// numArcs = arcPaths.Size(); | |
if (arcLink.VolumePaths.Size() != 0) | |
{ | |
Int64 correctionSize = arcLink.VolumesSize; | |
FOR_VECTOR (v, arcLink.VolumePaths) | |
{ | |
int index = Find_FileName_InSortedVector(arcPathsFull, arcLink.VolumePaths[v]); | |
if (index >= 0) | |
{ | |
if ((unsigned)index > i) | |
{ | |
skipArcs[(unsigned)index] = true; | |
correctionSize -= arcSizes[(unsigned)index]; | |
} | |
} | |
} | |
if (correctionSize != 0) | |
{ | |
Int64 newPackSize = (Int64)totalPackSize + correctionSize; | |
if (newPackSize < 0) | |
newPackSize = 0; | |
totalPackSize = newPackSize; | |
RINOK(extractCallback->SetTotal(totalPackSize)); | |
} | |
} | |
} | |
/* | |
// Now openCallback and extractCallback use same object. So we don't need to send password. | |
#ifndef _NO_CRYPTO | |
bool passwordIsDefined; | |
UString password; | |
RINOK(openCallback->Open_GetPasswordIfAny(passwordIsDefined, password)); | |
if (passwordIsDefined) | |
{ | |
RINOK(extractCallback->SetPassword(password)); | |
} | |
#endif | |
*/ | |
CArc &arc = arcLink.Arcs.Back(); | |
arc.MTimeDefined = (!options.StdInMode && !fi.IsDevice); | |
arc.MTime = fi.MTime; | |
UInt64 packProcessed; | |
bool calcCrc = | |
#ifndef _SFX | |
(hash != NULL); | |
#else | |
false; | |
#endif | |
RINOK(DecompressArchive( | |
codecs, | |
arcLink, | |
fi.Size + arcLink.VolumesSize, | |
wildcardCensor, | |
options, | |
calcCrc, | |
extractCallback, ecs, errorMessage, packProcessed)); | |
if (!options.StdInMode) | |
packProcessed = fi.Size + arcLink.VolumesSize; | |
totalPackProcessed += packProcessed; | |
ecs->LocalProgressSpec->InSize += packProcessed; | |
ecs->LocalProgressSpec->OutSize = ecs->UnpackSize; | |
if (!errorMessage.IsEmpty()) | |
return E_FAIL; | |
} | |
if (multi || thereAreNotOpenArcs) | |
{ | |
RINOK(extractCallback->SetTotal(totalPackSize)); | |
RINOK(extractCallback->SetCompleted(&totalPackProcessed)); | |
} | |
st.NumFolders = ecs->NumFolders; | |
st.NumFiles = ecs->NumFiles; | |
st.NumAltStreams = ecs->NumAltStreams; | |
st.UnpackSize = ecs->UnpackSize; | |
st.AltStreams_UnpackSize = ecs->AltStreams_UnpackSize; | |
st.NumArchives = arcPaths.Size(); | |
st.PackSize = ecs->LocalProgressSpec->InSize; | |
return S_OK; | |
} |