// SplitHandler.cpp
#include "StdAfx.h"
#include "../../Common/ComTry.h"
#include "../../Common/MyString.h"
#include "../../Windows/PropVariant.h"
#include "../Common/ProgressUtils.h"
#include "../Common/RegisterArc.h"
#include "../Compress/CopyCoder.h"
#include "Common/MultiStream.h"
using namespace NWindows;
namespace NArchive {
namespace NSplit {
static const Byte kProps[] =
{
kpidPath,
kpidSize
};
static const Byte kArcProps[] =
{
kpidNumVolumes,
kpidTotalPhySize
};
class CHandler:
public IInArchive,
public IInArchiveGetStream,
public CMyUnknownImp
{
CObjectVector<CMyComPtr<IInStream> > _streams;
CRecordVector<UInt64> _sizes;
UString _subName;
UInt64 _totalSize;
HRESULT Open2(IInStream *stream, IArchiveOpenCallback *callback);
public:
MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream)
INTERFACE_IInArchive(;)
STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream);
};
IMP_IInArchive_Props
IMP_IInArchive_ArcProps
STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
{
NCOM::CPropVariant prop;
switch (propID)
{
case kpidMainSubfile: prop = (UInt32)0; break;
case kpidPhySize: if (!_sizes.IsEmpty()) prop = _sizes[0]; break;
case kpidTotalPhySize: prop = _totalSize; break;
case kpidNumVolumes: prop = (UInt32)_streams.Size(); break;
}
prop.Detach(value);
return S_OK;
}
struct CSeqName
{
UString _unchangedPart;
UString _changedPart;
bool _splitStyle;
UString GetNextName()
{
UString newName;
if (_splitStyle)
{
int i;
int numLetters = _changedPart.Len();
for (i = numLetters - 1; i >= 0; i--)
{
wchar_t c = _changedPart[i];
if (c == 'z')
{
newName.InsertAtFront('a');
continue;
}
else if (c == 'Z')
{
newName.InsertAtFront('A');
continue;
}
c++;
if ((c == 'z' || c == 'Z') && i == 0)
{
_unchangedPart += c;
wchar_t newChar = (c == 'z') ? L'a' : L'A';
newName.Empty();
numLetters++;
for (int k = 0; k < numLetters; k++)
newName += newChar;
break;
}
newName.InsertAtFront(c);
i--;
for (; i >= 0; i--)
newName.InsertAtFront(_changedPart[i]);
break;
}
}
else
{
int i;
int numLetters = _changedPart.Len();
for (i = numLetters - 1; i >= 0; i--)
{
wchar_t c = _changedPart[i];
if (c == '9')
{
newName.InsertAtFront('0');
if (i == 0)
newName.InsertAtFront('1');
continue;
}
c++;
newName.InsertAtFront(c);
i--;
for (; i >= 0; i--)
newName.InsertAtFront(_changedPart[i]);
break;
}
}
_changedPart = newName;
return _unchangedPart + _changedPart;
}
};
HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback)
{
Close();
if (!callback)
return S_FALSE;
CMyComPtr<IArchiveOpenVolumeCallback> volumeCallback;
callback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&volumeCallback);
if (!volumeCallback)
return S_FALSE;
UString name;
{
NCOM::CPropVariant prop;
RINOK(volumeCallback->GetProperty(kpidName, &prop));
if (prop.vt != VT_BSTR)
return S_FALSE;
name = prop.bstrVal;
}
int dotPos = name.ReverseFind('.');
const UString prefix = name.Left(dotPos + 1);
const UString ext = name.Ptr(dotPos + 1);
UString ext2 = ext;
ext2.MakeLower_Ascii();
CSeqName seqName;
unsigned numLetters = 2;
bool splitStyle = false;
if (ext2.Len() >= 2 && StringsAreEqual_Ascii(ext2.RightPtr(2), "aa"))
{
splitStyle = true;
while (numLetters < ext2.Len())
{
if (ext2[ext2.Len() - numLetters - 1] != 'a')
break;
numLetters++;
}
}
else if (ext.Len() >= 2 && StringsAreEqual_Ascii(ext2.RightPtr(2), "01"))
{
while (numLetters < ext2.Len())
{
if (ext2[ext2.Len() - numLetters - 1] != '0')
break;
numLetters++;
}
if (numLetters != ext.Len())
return S_FALSE;
}
else
return S_FALSE;
seqName._unchangedPart = prefix + ext.Left(ext2.Len() - numLetters);
seqName._changedPart = ext.RightPtr(numLetters);
seqName._splitStyle = splitStyle;
if (prefix.Len() < 1)
_subName = L"file";
else
_subName.SetFrom(prefix, prefix.Len() - 1);
UInt64 size;
{
NCOM::CPropVariant prop;
RINOK(volumeCallback->GetProperty(kpidSize, &prop));
if (prop.vt != VT_UI8)
return E_INVALIDARG;
size = prop.uhVal.QuadPart;
}
_totalSize += size;
_sizes.Add(size);
_streams.Add(stream);
{
UInt64 numFiles = _streams.Size();
RINOK(callback->SetCompleted(&numFiles, NULL));
}
for (;;)
{
const UString fullName = seqName.GetNextName();
CMyComPtr<IInStream> nextStream;
HRESULT result = volumeCallback->GetStream(fullName, &nextStream);
if (result == S_FALSE)
break;
if (result != S_OK)
return result;
if (!stream)
break;
{
NCOM::CPropVariant prop;
RINOK(volumeCallback->GetProperty(kpidSize, &prop));
if (prop.vt != VT_UI8)
return E_INVALIDARG;
size = prop.uhVal.QuadPart;
}
_totalSize += size;
_sizes.Add(size);
_streams.Add(nextStream);
{
UInt64 numFiles = _streams.Size();
RINOK(callback->SetCompleted(&numFiles, NULL));
}
}
if (_streams.Size() == 1)
{
if (splitStyle)
return S_FALSE;
}
return S_OK;
}
STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback)
{
COM_TRY_BEGIN
HRESULT res = Open2(stream, callback);
if (res != S_OK)
Close();
return res;
COM_TRY_END
}
STDMETHODIMP CHandler::Close()
{
_totalSize = 0;
_subName.Empty();
_streams.Clear();
_sizes.Clear();
return S_OK;
}
STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
{
*numItems = _streams.IsEmpty() ? 0 : 1;
return S_OK;
}
STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)
{
NCOM::CPropVariant prop;
switch (propID)
{
case kpidPath: prop = _subName; break;
case kpidSize:
case kpidPackSize:
prop = _totalSize;
break;
}
prop.Detach(value);
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;
UInt64 currentTotalSize = 0;
RINOK(extractCallback->SetTotal(_totalSize));
CMyComPtr<ISequentialOutStream> outStream;
Int32 askMode = testMode ?
NExtract::NAskMode::kTest :
NExtract::NAskMode::kExtract;
RINOK(extractCallback->GetStream(0, &outStream, askMode));
if (!testMode && !outStream)
return S_OK;
RINOK(extractCallback->PrepareOperation(askMode));
NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder;
CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;
CLocalProgress *lps = new CLocalProgress;
CMyComPtr<ICompressProgressInfo> progress = lps;
lps->Init(extractCallback, false);
FOR_VECTOR (i, _streams)
{
lps->InSize = lps->OutSize = currentTotalSize;
RINOK(lps->SetCur());
IInStream *inStream = _streams[i];
RINOK(inStream->Seek(0, STREAM_SEEK_SET, NULL));
RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress));
currentTotalSize += copyCoderSpec->TotalSize;
}
outStream.Release();
return extractCallback->SetOperationResult(NExtract::NOperationResult::kOK);
COM_TRY_END
}
STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream)
{
COM_TRY_BEGIN
if (index != 0)
return E_INVALIDARG;
*stream = 0;
CMultiStream *streamSpec = new CMultiStream;
CMyComPtr<ISequentialInStream> streamTemp = streamSpec;
FOR_VECTOR (i, _streams)
{
CMultiStream::CSubStreamInfo subStreamInfo;
subStreamInfo.Stream = _streams[i];
subStreamInfo.Size = _sizes[i];
streamSpec->Streams.Add(subStreamInfo);
}
streamSpec->Init();
*stream = streamTemp.Detach();
return S_OK;
COM_TRY_END
}
IMP_CreateArcIn
static CArcInfo g_ArcInfo =
{ "Split", "001", 0, 0xEA,
0, { 0 },
0,
0,
CreateArc };
REGISTER_ARC(Split)
}}