// 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_ANY_MASK, us2fs(GetCorrectFsPath(replaceName)));
bool elimIsPossible = false;
UString elimPrefix; // only pure name without dir delimiter
FString outDirReduced = outDir;
if (options.ElimDup.Val)
{
UString dirPrefix;
SplitPathToParts_Smart(fs2us(outDir), dirPrefix, elimPrefix);
if (!elimPrefix.IsEmpty())
{
if (IsCharDirLimiter(elimPrefix.Back()))
elimPrefix.DeleteBack();
if (!elimPrefix.IsEmpty())
{
outDirReduced = us2fs(dirPrefix);
elimIsPossible = true;
}
}
}
if (!options.StdInMode)
{
UInt32 numItems;
RINOK(archive->GetNumberOfItems(&numItems));
UString filePath;
for (UInt32 i = 0; i < numItems; i++)
{
RINOK(arc.GetItemPath(i, filePath));
if (elimIsPossible && options.ElimDup.Val)
{
if (!IsPath1PrefixedByPath2(filePath, elimPrefix))
elimIsPossible = false;
else
{
wchar_t c = filePath[elimPrefix.Len()];
if (c != 0 && !IsCharDirLimiter(c))
elimIsPossible = false;
}
}
bool isFolder;
RINOK(Archive_IsItem_Folder(archive, i, isFolder));
bool isAltStream;
RINOK(Archive_IsItem_AltStream(archive, i, isAltStream));
if (!options.NtOptions.AltStreams.Val && isAltStream)
continue;
if (!wildcardCensor.CheckPath(isAltStream, filePath, !isFolder))
continue;
realIndices.Add(i);
}
if (realIndices.Size() == 0)
{
callback->ThereAreNoFiles();
return callback->ExtractResult(S_OK);
}
}
if (elimIsPossible)
outDir = outDirReduced;
#ifdef _WIN32
// GetCorrectFullFsPath doesn't like "..".
// outDir.TrimRight();
// outDir = GetCorrectFullFsPath(outDir);
#endif
if (outDir.IsEmpty())
outDir = FString(FTEXT(".")) + FString(FSTRING_PATH_SEPARATOR);
else
if (!CreateComplexDir(outDir))
{
HRESULT res = ::GetLastError();
if (res == S_OK)
res = E_FAIL;
errorMessage = ((UString)L"Can not create output directory ") + fs2us(outDir);
return res;
}
ecs->Init(
options.NtOptions,
options.StdInMode ? &wildcardCensor : NULL,
&arc,
callback,
options.StdOutMode, options.TestMode,
outDir,
removePathParts,
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;
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);
if (result == S_OK && !options.StdInMode)
result = ecs->SetDirsTimes();
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 &stat)
{
stat.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);
#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_ClearPasswordWasAskedFlag();
#endif
RINOK(extractCallback->BeforeOpen(arcPath));
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.Open2(op, openCallback);
if (result == E_ABORT)
return result;
bool crypted = false;
#ifndef _NO_CRYPTO
crypted = openCallback->Open_WasPasswordAsked();
#endif
if (arcLink.NonOpen_ErrorInfo.ErrorFormatIndex >= 0)
result = S_FALSE;
// arcLink.Set_ErrorsText();
RINOK(extractCallback->OpenResult(arcPath, result, crypted));
{
FOR_VECTOR (r, arcLink.Arcs)
{
const CArc &arc = arcLink.Arcs[r];
const CArcErrorInfo &er = arc.ErrorInfo;
if (er.IsThereErrorOrWarning())
{
RINOK(extractCallback->SetError(r, arc.Path,
er.GetErrorFlags(), er.ErrorMessage,
er.GetWarningFlags(), er.WarningMessage));
}
}
}
if (result != S_OK)
{
thereAreNotOpenArcs = true;
if (!options.StdInMode)
{
NFind::CFileInfo fi;
if (fi.Find(us2fs(arcPath)))
if (!fi.IsDir())
totalPackProcessed += fi.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[index] = true;
correctionSize -= arcSizes[index];
}
}
}
if (correctionSize != 0)
{
Int64 newPackSize = (Int64)totalPackSize + correctionSize;
if (newPackSize < 0)
newPackSize = 0;
totalPackSize = newPackSize;
RINOK(extractCallback->SetTotal(totalPackSize));
}
}
}
#ifndef _NO_CRYPTO
bool passwordIsDefined;
UString password;
RINOK(openCallback->Open_GetPasswordIfAny(passwordIsDefined, password));
if (passwordIsDefined)
{
RINOK(extractCallback->SetPassword(password));
}
#endif
FOR_VECTOR (k, arcLink.Arcs)
{
const CArc &arc = arcLink.Arcs[k];
const CArcErrorInfo &er = arc.ErrorInfo;
if (er.ErrorFormatIndex >= 0)
{
RINOK(extractCallback->OpenTypeWarning(arc.Path,
codecs->GetFormatNamePtr(arc.FormatIndex),
codecs->GetFormatNamePtr(er.ErrorFormatIndex)))
/*
UString s = L"Can not open the file as [" + codecs->Formats[arc.ErrorFormatIndex].Name + L"] archive\n";
s += L"The file is open as [" + codecs->Formats[arc.FormatIndex].Name + L"] archive";
RINOK(extractCallback->MessageError(s));
*/
}
{
const UString &s = er.ErrorMessage;
if (!s.IsEmpty())
{
RINOK(extractCallback->MessageError(s));
}
}
}
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));
}
stat.NumFolders = ecs->NumFolders;
stat.NumFiles = ecs->NumFiles;
stat.NumAltStreams = ecs->NumAltStreams;
stat.UnpackSize = ecs->UnpackSize;
stat.AltStreams_UnpackSize = ecs->AltStreams_UnpackSize;
stat.NumArchives = arcPaths.Size();
stat.PackSize = ecs->LocalProgressSpec->InSize;
return S_OK;
}