1// SplitHandler.cpp
2
3#include "StdAfx.h"
4
5#include "../../Common/ComTry.h"
6#include "../../Common/MyString.h"
7
8#include "../../Windows/PropVariant.h"
9
10#include "../Common/ProgressUtils.h"
11#include "../Common/RegisterArc.h"
12
13#include "../Compress/CopyCoder.h"
14
15#include "Common/MultiStream.h"
16
17using namespace NWindows;
18
19namespace NArchive {
20namespace NSplit {
21
22static const Byte kProps[] =
23{
24  kpidPath,
25  kpidSize
26};
27
28static const Byte kArcProps[] =
29{
30  kpidNumVolumes,
31  kpidTotalPhySize
32};
33
34class CHandler:
35  public IInArchive,
36  public IInArchiveGetStream,
37  public CMyUnknownImp
38{
39  CObjectVector<CMyComPtr<IInStream> > _streams;
40  CRecordVector<UInt64> _sizes;
41  UString _subName;
42  UInt64 _totalSize;
43
44  HRESULT Open2(IInStream *stream, IArchiveOpenCallback *callback);
45public:
46  MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream)
47  INTERFACE_IInArchive(;)
48  STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream);
49};
50
51IMP_IInArchive_Props
52IMP_IInArchive_ArcProps
53
54STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
55{
56  NCOM::CPropVariant prop;
57  switch (propID)
58  {
59    case kpidMainSubfile: prop = (UInt32)0; break;
60    case kpidPhySize: if (!_sizes.IsEmpty()) prop = _sizes[0]; break;
61    case kpidTotalPhySize: prop = _totalSize; break;
62    case kpidNumVolumes: prop = (UInt32)_streams.Size(); break;
63  }
64  prop.Detach(value);
65  return S_OK;
66}
67
68struct CSeqName
69{
70  UString _unchangedPart;
71  UString _changedPart;
72  bool _splitStyle;
73
74  UString GetNextName()
75  {
76    UString newName;
77    if (_splitStyle)
78    {
79      int i;
80      int numLetters = _changedPart.Len();
81      for (i = numLetters - 1; i >= 0; i--)
82      {
83        wchar_t c = _changedPart[i];
84        if (c == 'z')
85        {
86          newName.InsertAtFront('a');
87          continue;
88        }
89        else if (c == 'Z')
90        {
91          newName.InsertAtFront('A');
92          continue;
93        }
94        c++;
95        if ((c == 'z' || c == 'Z') && i == 0)
96        {
97          _unchangedPart += c;
98          wchar_t newChar = (c == 'z') ? L'a' : L'A';
99          newName.Empty();
100          numLetters++;
101          for (int k = 0; k < numLetters; k++)
102            newName += newChar;
103          break;
104        }
105        newName.InsertAtFront(c);
106        i--;
107        for (; i >= 0; i--)
108          newName.InsertAtFront(_changedPart[i]);
109        break;
110      }
111    }
112    else
113    {
114      int i;
115      int numLetters = _changedPart.Len();
116      for (i = numLetters - 1; i >= 0; i--)
117      {
118        wchar_t c = _changedPart[i];
119        if (c == '9')
120        {
121          newName.InsertAtFront('0');
122          if (i == 0)
123            newName.InsertAtFront('1');
124          continue;
125        }
126        c++;
127        newName.InsertAtFront(c);
128        i--;
129        for (; i >= 0; i--)
130          newName.InsertAtFront(_changedPart[i]);
131        break;
132      }
133    }
134    _changedPart = newName;
135    return _unchangedPart + _changedPart;
136  }
137};
138
139HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback)
140{
141  Close();
142  if (!callback)
143    return S_FALSE;
144
145  CMyComPtr<IArchiveOpenVolumeCallback> volumeCallback;
146  callback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&volumeCallback);
147  if (!volumeCallback)
148    return S_FALSE;
149
150  UString name;
151  {
152    NCOM::CPropVariant prop;
153    RINOK(volumeCallback->GetProperty(kpidName, &prop));
154    if (prop.vt != VT_BSTR)
155      return S_FALSE;
156    name = prop.bstrVal;
157  }
158
159  int dotPos = name.ReverseFind('.');
160  const UString prefix = name.Left(dotPos + 1);
161  const UString ext = name.Ptr(dotPos + 1);
162  UString ext2 = ext;
163  ext2.MakeLower_Ascii();
164
165  CSeqName seqName;
166
167  unsigned numLetters = 2;
168  bool splitStyle = false;
169
170  if (ext2.Len() >= 2 && StringsAreEqual_Ascii(ext2.RightPtr(2), "aa"))
171  {
172    splitStyle = true;
173    while (numLetters < ext2.Len())
174    {
175      if (ext2[ext2.Len() - numLetters - 1] != 'a')
176        break;
177      numLetters++;
178    }
179  }
180  else if (ext.Len() >= 2 && StringsAreEqual_Ascii(ext2.RightPtr(2), "01"))
181  {
182    while (numLetters < ext2.Len())
183    {
184      if (ext2[ext2.Len() - numLetters - 1] != '0')
185        break;
186      numLetters++;
187    }
188    if (numLetters != ext.Len())
189      return S_FALSE;
190  }
191  else
192    return S_FALSE;
193
194  seqName._unchangedPart = prefix + ext.Left(ext2.Len() - numLetters);
195  seqName._changedPart = ext.RightPtr(numLetters);
196  seqName._splitStyle = splitStyle;
197
198  if (prefix.Len() < 1)
199    _subName = L"file";
200  else
201    _subName.SetFrom(prefix, prefix.Len() - 1);
202
203  UInt64 size;
204  {
205    NCOM::CPropVariant prop;
206    RINOK(volumeCallback->GetProperty(kpidSize, &prop));
207    if (prop.vt != VT_UI8)
208      return E_INVALIDARG;
209    size = prop.uhVal.QuadPart;
210  }
211
212  _totalSize += size;
213  _sizes.Add(size);
214  _streams.Add(stream);
215
216  {
217    UInt64 numFiles = _streams.Size();
218    RINOK(callback->SetCompleted(&numFiles, NULL));
219  }
220
221  for (;;)
222  {
223    const UString fullName = seqName.GetNextName();
224    CMyComPtr<IInStream> nextStream;
225    HRESULT result = volumeCallback->GetStream(fullName, &nextStream);
226    if (result == S_FALSE)
227      break;
228    if (result != S_OK)
229      return result;
230    if (!stream)
231      break;
232    {
233      NCOM::CPropVariant prop;
234      RINOK(volumeCallback->GetProperty(kpidSize, &prop));
235      if (prop.vt != VT_UI8)
236        return E_INVALIDARG;
237      size = prop.uhVal.QuadPart;
238    }
239    _totalSize += size;
240    _sizes.Add(size);
241    _streams.Add(nextStream);
242    {
243      UInt64 numFiles = _streams.Size();
244      RINOK(callback->SetCompleted(&numFiles, NULL));
245    }
246  }
247
248  if (_streams.Size() == 1)
249  {
250    if (splitStyle)
251      return S_FALSE;
252  }
253  return S_OK;
254}
255
256STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback)
257{
258  COM_TRY_BEGIN
259  HRESULT res = Open2(stream, callback);
260  if (res != S_OK)
261    Close();
262  return res;
263  COM_TRY_END
264}
265
266STDMETHODIMP CHandler::Close()
267{
268  _totalSize = 0;
269  _subName.Empty();
270  _streams.Clear();
271  _sizes.Clear();
272  return S_OK;
273}
274
275STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
276{
277  *numItems = _streams.IsEmpty() ? 0 : 1;
278  return S_OK;
279}
280
281STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)
282{
283  NCOM::CPropVariant prop;
284  switch (propID)
285  {
286    case kpidPath: prop = _subName; break;
287    case kpidSize:
288    case kpidPackSize:
289      prop = _totalSize;
290      break;
291  }
292  prop.Detach(value);
293  return S_OK;
294}
295
296STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
297    Int32 testMode, IArchiveExtractCallback *extractCallback)
298{
299  COM_TRY_BEGIN
300  if (numItems == 0)
301    return S_OK;
302  if (numItems != (UInt32)(Int32)-1 && (numItems != 1 || indices[0] != 0))
303    return E_INVALIDARG;
304
305  UInt64 currentTotalSize = 0;
306  RINOK(extractCallback->SetTotal(_totalSize));
307  CMyComPtr<ISequentialOutStream> outStream;
308  Int32 askMode = testMode ?
309      NExtract::NAskMode::kTest :
310      NExtract::NAskMode::kExtract;
311  RINOK(extractCallback->GetStream(0, &outStream, askMode));
312  if (!testMode && !outStream)
313    return S_OK;
314  RINOK(extractCallback->PrepareOperation(askMode));
315
316  NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder;
317  CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;
318
319  CLocalProgress *lps = new CLocalProgress;
320  CMyComPtr<ICompressProgressInfo> progress = lps;
321  lps->Init(extractCallback, false);
322
323  FOR_VECTOR (i, _streams)
324  {
325    lps->InSize = lps->OutSize = currentTotalSize;
326    RINOK(lps->SetCur());
327    IInStream *inStream = _streams[i];
328    RINOK(inStream->Seek(0, STREAM_SEEK_SET, NULL));
329    RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress));
330    currentTotalSize += copyCoderSpec->TotalSize;
331  }
332  outStream.Release();
333  return extractCallback->SetOperationResult(NExtract::NOperationResult::kOK);
334  COM_TRY_END
335}
336
337STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream)
338{
339  COM_TRY_BEGIN
340  if (index != 0)
341    return E_INVALIDARG;
342  *stream = 0;
343  CMultiStream *streamSpec = new CMultiStream;
344  CMyComPtr<ISequentialInStream> streamTemp = streamSpec;
345  FOR_VECTOR (i, _streams)
346  {
347    CMultiStream::CSubStreamInfo subStreamInfo;
348    subStreamInfo.Stream = _streams[i];
349    subStreamInfo.Size = _sizes[i];
350    streamSpec->Streams.Add(subStreamInfo);
351  }
352  streamSpec->Init();
353  *stream = streamTemp.Detach();
354  return S_OK;
355  COM_TRY_END
356}
357
358IMP_CreateArcIn
359
360static CArcInfo g_ArcInfo =
361  { "Split", "001", 0, 0xEA,
362  0, { 0 },
363  0,
364  0,
365  CreateArc };
366
367REGISTER_ARC(Split)
368
369}}
370