blob: bd0c8221d8861c895a5250b52f01f32600ad1708 [file] [log] [blame]
// ZipUpdate.cpp
#include "StdAfx.h"
#include "../../../../C/Alloc.h"
#include "Common/AutoPtr.h"
#include "Common/Defs.h"
#include "Common/StringConvert.h"
#include "Windows/Defs.h"
#include "Windows/Thread.h"
#include "../../Common/CreateCoder.h"
#include "../../Common/LimitedStreams.h"
#include "../../Common/OutMemStream.h"
#include "../../Common/ProgressUtils.h"
#ifndef _7ZIP_ST
#include "../../Common/ProgressMt.h"
#endif
#include "../../Common/StreamUtils.h"
#include "../../Compress/CopyCoder.h"
#include "ZipAddCommon.h"
#include "ZipOut.h"
#include "ZipUpdate.h"
using namespace NWindows;
using namespace NSynchronization;
namespace NArchive {
namespace NZip {
static const Byte kHostOS =
#ifdef _WIN32
NFileHeader::NHostOS::kFAT;
#else
NFileHeader::NHostOS::kUnix;
#endif
static const Byte kMadeByHostOS = kHostOS;
static const Byte kExtractHostOS = kHostOS;
static const Byte kMethodForDirectory = NFileHeader::NCompressionMethod::kStored;
static HRESULT CopyBlockToArchive(ISequentialInStream *inStream,
COutArchive &outArchive, ICompressProgressInfo *progress)
{
CMyComPtr<ISequentialOutStream> outStream;
outArchive.CreateStreamForCopying(&outStream);
return NCompress::CopyStream(inStream, outStream, progress);
}
static HRESULT WriteRange(IInStream *inStream, COutArchive &outArchive,
const CUpdateRange &range, ICompressProgressInfo *progress)
{
UInt64 position;
RINOK(inStream->Seek(range.Position, STREAM_SEEK_SET, &position));
CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream;
CMyComPtr<CLimitedSequentialInStream> inStreamLimited(streamSpec);
streamSpec->SetStream(inStream);
streamSpec->Init(range.Size);
RINOK(CopyBlockToArchive(inStreamLimited, outArchive, progress));
return progress->SetRatioInfo(&range.Size, &range.Size);
}
static void SetFileHeader(
COutArchive &archive,
const CCompressionMethodMode &options,
const CUpdateItem &ui,
CItem &item)
{
item.UnPackSize = ui.Size;
bool isDir;
item.ClearFlags();
if (ui.NewProperties)
{
isDir = ui.IsDir;
item.Name = ui.Name;
item.SetUtf8(ui.IsUtf8);
item.ExternalAttributes = ui.Attributes;
item.Time = ui.Time;
item.NtfsMTime = ui.NtfsMTime;
item.NtfsATime = ui.NtfsATime;
item.NtfsCTime = ui.NtfsCTime;
item.NtfsTimeIsDefined = ui.NtfsTimeIsDefined;
}
else
isDir = item.IsDir();
item.LocalHeaderPosition = archive.GetCurrentPosition();
item.MadeByVersion.HostOS = kMadeByHostOS;
item.MadeByVersion.Version = NFileHeader::NCompressionMethod::kMadeByProgramVersion;
item.ExtractVersion.HostOS = kExtractHostOS;
item.InternalAttributes = 0; // test it
item.SetEncrypted(!isDir && options.PasswordIsDefined);
if (isDir)
{
item.ExtractVersion.Version = NFileHeader::NCompressionMethod::kExtractVersion_Dir;
item.CompressionMethod = kMethodForDirectory;
item.PackSize = 0;
item.FileCRC = 0; // test it
}
}
static void SetItemInfoFromCompressingResult(const CCompressingResult &compressingResult,
bool isAesMode, Byte aesKeyMode, CItem &item)
{
item.ExtractVersion.Version = compressingResult.ExtractVersion;
item.CompressionMethod = compressingResult.Method;
item.FileCRC = compressingResult.CRC;
item.UnPackSize = compressingResult.UnpackSize;
item.PackSize = compressingResult.PackSize;
item.LocalExtra.Clear();
item.CentralExtra.Clear();
if (isAesMode)
{
CWzAesExtraField wzAesField;
wzAesField.Strength = aesKeyMode;
wzAesField.Method = compressingResult.Method;
item.CompressionMethod = NFileHeader::NCompressionMethod::kWzAES;
item.FileCRC = 0;
CExtraSubBlock sb;
wzAesField.SetSubBlock(sb);
item.LocalExtra.SubBlocks.Add(sb);
item.CentralExtra.SubBlocks.Add(sb);
}
}
#ifndef _7ZIP_ST
static THREAD_FUNC_DECL CoderThread(void *threadCoderInfo);
struct CThreadInfo
{
#ifdef EXTERNAL_CODECS
CMyComPtr<ICompressCodecsInfo> _codecsInfo;
const CObjectVector<CCodecInfoEx> *_externalCodecs;
#endif
NWindows::CThread Thread;
NWindows::NSynchronization::CAutoResetEvent CompressEvent;
NWindows::NSynchronization::CAutoResetEventWFMO CompressionCompletedEvent;
bool ExitThread;
CMtCompressProgress *ProgressSpec;
CMyComPtr<ICompressProgressInfo> Progress;
COutMemStream *OutStreamSpec;
CMyComPtr<IOutStream> OutStream;
CMyComPtr<ISequentialInStream> InStream;
CAddCommon Coder;
HRESULT Result;
CCompressingResult CompressingResult;
bool IsFree;
UInt32 UpdateIndex;
CThreadInfo(const CCompressionMethodMode &options):
ExitThread(false),
ProgressSpec(0),
OutStreamSpec(0),
Coder(options)
{}
HRESULT CreateEvents(CSynchro *sync)
{
RINOK(CompressEvent.CreateIfNotCreated());
return CompressionCompletedEvent.CreateIfNotCreated(sync);
}
HRes CreateThread() { return Thread.Create(CoderThread, this); }
void WaitAndCode();
void StopWaitClose()
{
ExitThread = true;
if (OutStreamSpec != 0)
OutStreamSpec->StopWriting(E_ABORT);
if (CompressEvent.IsCreated())
CompressEvent.Set();
Thread.Wait();
Thread.Close();
}
};
void CThreadInfo::WaitAndCode()
{
for (;;)
{
CompressEvent.Lock();
if (ExitThread)
return;
Result = Coder.Compress(
#ifdef EXTERNAL_CODECS
_codecsInfo, _externalCodecs,
#endif
InStream, OutStream, Progress, CompressingResult);
if (Result == S_OK && Progress)
Result = Progress->SetRatioInfo(&CompressingResult.UnpackSize, &CompressingResult.PackSize);
CompressionCompletedEvent.Set();
}
}
static THREAD_FUNC_DECL CoderThread(void *threadCoderInfo)
{
((CThreadInfo *)threadCoderInfo)->WaitAndCode();
return 0;
}
class CThreads
{
public:
CObjectVector<CThreadInfo> Threads;
~CThreads()
{
for (int i = 0; i < Threads.Size(); i++)
Threads[i].StopWaitClose();
}
};
struct CMemBlocks2: public CMemLockBlocks
{
CCompressingResult CompressingResult;
bool Defined;
bool Skip;
CMemBlocks2(): Defined(false), Skip(false) {}
};
class CMemRefs
{
public:
CMemBlockManagerMt *Manager;
CObjectVector<CMemBlocks2> Refs;
CMemRefs(CMemBlockManagerMt *manager): Manager(manager) {} ;
~CMemRefs()
{
for (int i = 0; i < Refs.Size(); i++)
Refs[i].FreeOpt(Manager);
}
};
class CMtProgressMixer2:
public ICompressProgressInfo,
public CMyUnknownImp
{
UInt64 ProgressOffset;
UInt64 InSizes[2];
UInt64 OutSizes[2];
CMyComPtr<IProgress> Progress;
CMyComPtr<ICompressProgressInfo> RatioProgress;
bool _inSizeIsMain;
public:
NWindows::NSynchronization::CCriticalSection CriticalSection;
MY_UNKNOWN_IMP
void Create(IProgress *progress, bool inSizeIsMain);
void SetProgressOffset(UInt64 progressOffset);
HRESULT SetRatioInfo(int index, const UInt64 *inSize, const UInt64 *outSize);
STDMETHOD(SetRatioInfo)(const UInt64 *inSize, const UInt64 *outSize);
};
void CMtProgressMixer2::Create(IProgress *progress, bool inSizeIsMain)
{
Progress = progress;
Progress.QueryInterface(IID_ICompressProgressInfo, &RatioProgress);
_inSizeIsMain = inSizeIsMain;
ProgressOffset = InSizes[0] = InSizes[1] = OutSizes[0] = OutSizes[1] = 0;
}
void CMtProgressMixer2::SetProgressOffset(UInt64 progressOffset)
{
CriticalSection.Enter();
InSizes[1] = OutSizes[1] = 0;
ProgressOffset = progressOffset;
CriticalSection.Leave();
}
HRESULT CMtProgressMixer2::SetRatioInfo(int index, const UInt64 *inSize, const UInt64 *outSize)
{
NWindows::NSynchronization::CCriticalSectionLock lock(CriticalSection);
if (index == 0 && RatioProgress)
{
RINOK(RatioProgress->SetRatioInfo(inSize, outSize));
}
if (inSize != 0)
InSizes[index] = *inSize;
if (outSize != 0)
OutSizes[index] = *outSize;
UInt64 v = ProgressOffset + (_inSizeIsMain ?
(InSizes[0] + InSizes[1]) :
(OutSizes[0] + OutSizes[1]));
return Progress->SetCompleted(&v);
}
STDMETHODIMP CMtProgressMixer2::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize)
{
return SetRatioInfo(0, inSize, outSize);
}
class CMtProgressMixer:
public ICompressProgressInfo,
public CMyUnknownImp
{
public:
CMtProgressMixer2 *Mixer2;
CMyComPtr<ICompressProgressInfo> RatioProgress;
void Create(IProgress *progress, bool inSizeIsMain);
MY_UNKNOWN_IMP
STDMETHOD(SetRatioInfo)(const UInt64 *inSize, const UInt64 *outSize);
};
void CMtProgressMixer::Create(IProgress *progress, bool inSizeIsMain)
{
Mixer2 = new CMtProgressMixer2;
RatioProgress = Mixer2;
Mixer2->Create(progress, inSizeIsMain);
}
STDMETHODIMP CMtProgressMixer::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize)
{
return Mixer2->SetRatioInfo(1, inSize, outSize);
}
#endif
static HRESULT UpdateItemOldData(COutArchive &archive,
IInStream *inStream,
const CUpdateItem &ui, CItemEx &item,
/* bool izZip64, */
ICompressProgressInfo *progress,
UInt64 &complexity)
{
if (ui.NewProperties)
{
if (item.HasDescriptor())
return E_NOTIMPL;
// use old name size.
// CUpdateRange range(item.GetLocalExtraPosition(), item.LocalExtraSize + item.PackSize);
CUpdateRange range(item.GetDataPosition(), item.PackSize);
// item.ExternalAttributes = ui.Attributes;
// Test it
item.Name = ui.Name;
item.SetUtf8(ui.IsUtf8);
item.Time = ui.Time;
item.NtfsMTime = ui.NtfsMTime;
item.NtfsATime = ui.NtfsATime;
item.NtfsCTime = ui.NtfsCTime;
item.NtfsTimeIsDefined = ui.NtfsTimeIsDefined;
item.CentralExtra.RemoveUnknownSubBlocks();
item.LocalExtra.RemoveUnknownSubBlocks();
archive.PrepareWriteCompressedData2((UInt16)item.Name.Length(), item.UnPackSize, item.PackSize, item.LocalExtra.HasWzAesField());
item.LocalHeaderPosition = archive.GetCurrentPosition();
archive.SeekToPackedDataPosition();
RINOK(WriteRange(inStream, archive, range, progress));
complexity += range.Size;
archive.WriteLocalHeader(item);
}
else
{
CUpdateRange range(item.LocalHeaderPosition, item.GetLocalFullSize());
// set new header position
item.LocalHeaderPosition = archive.GetCurrentPosition();
RINOK(WriteRange(inStream, archive, range, progress));
complexity += range.Size;
archive.MoveBasePosition(range.Size);
}
return S_OK;
}
static void WriteDirHeader(COutArchive &archive, const CCompressionMethodMode *options,
const CUpdateItem &ui, CItemEx &item)
{
SetFileHeader(archive, *options, ui, item);
archive.PrepareWriteCompressedData((UInt16)item.Name.Length(), ui.Size, options->IsAesMode);
archive.WriteLocalHeader(item);
}
static HRESULT Update2St(
DECL_EXTERNAL_CODECS_LOC_VARS
COutArchive &archive,
CInArchive *inArchive,
IInStream *inStream,
const CObjectVector<CItemEx> &inputItems,
const CObjectVector<CUpdateItem> &updateItems,
const CCompressionMethodMode *options,
const CByteBuffer *comment,
IArchiveUpdateCallback *updateCallback)
{
CLocalProgress *lps = new CLocalProgress;
CMyComPtr<ICompressProgressInfo> progress = lps;
lps->Init(updateCallback, true);
CAddCommon compressor(*options);
CObjectVector<CItem> items;
UInt64 unpackSizeTotal = 0, packSizeTotal = 0;
for (int itemIndex = 0; itemIndex < updateItems.Size(); itemIndex++)
{
lps->InSize = unpackSizeTotal;
lps->OutSize = packSizeTotal;
RINOK(lps->SetCur());
const CUpdateItem &ui = updateItems[itemIndex];
CItemEx item;
if (!ui.NewProperties || !ui.NewData)
{
item = inputItems[ui.IndexInArchive];
if (inArchive->ReadLocalItemAfterCdItemFull(item) != S_OK)
return E_NOTIMPL;
}
if (ui.NewData)
{
bool isDir = ((ui.NewProperties) ? ui.IsDir : item.IsDir());
if (isDir)
{
WriteDirHeader(archive, options, ui, item);
}
else
{
CMyComPtr<ISequentialInStream> fileInStream;
HRESULT res = updateCallback->GetStream(ui.IndexInClient, &fileInStream);
if (res == S_FALSE)
{
lps->ProgressOffset += ui.Size;
RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK));
continue;
}
RINOK(res);
// file Size can be 64-bit !!!
SetFileHeader(archive, *options, ui, item);
archive.PrepareWriteCompressedData((UInt16)item.Name.Length(), ui.Size, options->IsAesMode);
CCompressingResult compressingResult;
CMyComPtr<IOutStream> outStream;
archive.CreateStreamForCompressing(&outStream);
RINOK(compressor.Compress(
EXTERNAL_CODECS_LOC_VARS
fileInStream, outStream, progress, compressingResult));
SetItemInfoFromCompressingResult(compressingResult, options->IsAesMode, options->AesKeyMode, item);
archive.WriteLocalHeader(item);
RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK));
unpackSizeTotal += item.UnPackSize;
packSizeTotal += item.PackSize;
}
}
else
{
UInt64 complexity = 0;
lps->SendRatio = false;
RINOK(UpdateItemOldData(archive, inStream, ui, item, progress, complexity));
lps->SendRatio = true;
lps->ProgressOffset += complexity;
}
items.Add(item);
lps->ProgressOffset += NFileHeader::kLocalBlockSize;
}
archive.WriteCentralDir(items, comment);
return S_OK;
}
static HRESULT Update2(
DECL_EXTERNAL_CODECS_LOC_VARS
COutArchive &archive,
CInArchive *inArchive,
IInStream *inStream,
const CObjectVector<CItemEx> &inputItems,
const CObjectVector<CUpdateItem> &updateItems,
const CCompressionMethodMode *options,
const CByteBuffer *comment,
IArchiveUpdateCallback *updateCallback)
{
UInt64 complexity = 0;
UInt64 numFilesToCompress = 0;
UInt64 numBytesToCompress = 0;
int i;
for(i = 0; i < updateItems.Size(); i++)
{
const CUpdateItem &ui = updateItems[i];
if (ui.NewData)
{
complexity += ui.Size;
numBytesToCompress += ui.Size;
numFilesToCompress++;
/*
if (ui.Commented)
complexity += ui.CommentRange.Size;
*/
}
else
{
CItemEx inputItem = inputItems[ui.IndexInArchive];
if (inArchive->ReadLocalItemAfterCdItemFull(inputItem) != S_OK)
return E_NOTIMPL;
complexity += inputItem.GetLocalFullSize();
// complexity += inputItem.GetCentralExtraPlusCommentSize();
}
complexity += NFileHeader::kLocalBlockSize;
complexity += NFileHeader::kCentralBlockSize;
}
if (comment)
complexity += comment->GetCapacity();
complexity++; // end of central
updateCallback->SetTotal(complexity);
CAddCommon compressor(*options);
complexity = 0;
#ifndef _7ZIP_ST
const size_t kNumMaxThreads = (1 << 10);
UInt32 numThreads = options->NumThreads;
if (numThreads > kNumMaxThreads)
numThreads = kNumMaxThreads;
const size_t kMemPerThread = (1 << 25);
const size_t kBlockSize = 1 << 16;
CCompressionMethodMode options2;
if (options != 0)
options2 = *options;
bool mtMode = ((options != 0) && (numThreads > 1));
if (numFilesToCompress <= 1)
mtMode = false;
if (mtMode)
{
Byte method = options->MethodSequence.Front();
if (method == NFileHeader::NCompressionMethod::kStored && !options->PasswordIsDefined)
mtMode = false;
if (method == NFileHeader::NCompressionMethod::kBZip2)
{
UInt64 averageSize = numBytesToCompress / numFilesToCompress;
UInt32 blockSize = options->DicSize;
if (blockSize == 0)
blockSize = 1;
UInt64 averageNumberOfBlocks = averageSize / blockSize;
UInt32 numBZip2Threads = 32;
if (averageNumberOfBlocks < numBZip2Threads)
numBZip2Threads = (UInt32)averageNumberOfBlocks;
if (numBZip2Threads < 1)
numBZip2Threads = 1;
numThreads = numThreads / numBZip2Threads;
options2.NumThreads = numBZip2Threads;
if (numThreads <= 1)
mtMode = false;
}
if (method == NFileHeader::NCompressionMethod::kLZMA)
{
UInt32 numLZMAThreads = (options->Algo > 0 ? 2 : 1);
numThreads /= numLZMAThreads;
options2.NumThreads = numLZMAThreads;
if (numThreads <= 1)
mtMode = false;
}
}
if (!mtMode)
#endif
return Update2St(
EXTERNAL_CODECS_LOC_VARS
archive, inArchive,inStream,
inputItems, updateItems, options, comment, updateCallback);
#ifndef _7ZIP_ST
// Warning : before memManager, threads and compressingCompletedEvents
// in order to have a "good" order for the destructor
NWindows::NSynchronization::CSynchro synchroForCompressingCompletedEvents;
synchroForCompressingCompletedEvents.Create();
NWindows::NSynchronization::CSynchro synchroForOutStreamSpec;
synchroForOutStreamSpec.Create();
CObjectVector<CItem> items;
CMtProgressMixer *mtProgressMixerSpec = new CMtProgressMixer;
CMyComPtr<ICompressProgressInfo> progress = mtProgressMixerSpec;
mtProgressMixerSpec->Create(updateCallback, true);
CMtCompressProgressMixer mtCompressProgressMixer;
mtCompressProgressMixer.Init(numThreads, mtProgressMixerSpec->RatioProgress);
CMemBlockManagerMt memManager(kBlockSize);
CMemRefs refs(&memManager);
CThreads threads;
CRecordVector<HANDLE> compressingCompletedEvents;
CRecordVector<int> threadIndices; // list threads in order of updateItems
{
RINOK(memManager.AllocateSpaceAlways(&synchroForOutStreamSpec,(size_t)numThreads * (kMemPerThread / kBlockSize)));
for(i = 0; i < updateItems.Size(); i++)
refs.Refs.Add(CMemBlocks2());
UInt32 i;
for (i = 0; i < numThreads; i++)
threads.Threads.Add(CThreadInfo(options2));
for (i = 0; i < numThreads; i++)
{
CThreadInfo &threadInfo = threads.Threads[i];
#ifdef EXTERNAL_CODECS
threadInfo._codecsInfo = codecsInfo;
threadInfo._externalCodecs = externalCodecs;
#endif
RINOK(threadInfo.CreateEvents(&synchroForCompressingCompletedEvents));
threadInfo.OutStreamSpec = new COutMemStream(&memManager);
RINOK(threadInfo.OutStreamSpec->CreateEvents(&synchroForOutStreamSpec));
threadInfo.OutStream = threadInfo.OutStreamSpec;
threadInfo.IsFree = true;
threadInfo.ProgressSpec = new CMtCompressProgress();
threadInfo.Progress = threadInfo.ProgressSpec;
threadInfo.ProgressSpec->Init(&mtCompressProgressMixer, (int)i);
RINOK(threadInfo.CreateThread());
}
}
int mtItemIndex = 0;
int itemIndex = 0;
int lastRealStreamItemIndex = -1;
while (itemIndex < updateItems.Size())
{
if ((UInt32)threadIndices.Size() < numThreads && mtItemIndex < updateItems.Size())
{
const CUpdateItem &ui = updateItems[mtItemIndex++];
if (!ui.NewData)
continue;
CItemEx item;
if (ui.NewProperties)
{
if (ui.IsDir)
continue;
}
else
{
item = inputItems[ui.IndexInArchive];
if (inArchive->ReadLocalItemAfterCdItemFull(item) != S_OK)
return E_NOTIMPL;
if (item.IsDir())
continue;
}
CMyComPtr<ISequentialInStream> fileInStream;
{
NWindows::NSynchronization::CCriticalSectionLock lock(mtProgressMixerSpec->Mixer2->CriticalSection);
HRESULT res = updateCallback->GetStream(ui.IndexInClient, &fileInStream);
if (res == S_FALSE)
{
complexity += ui.Size;
complexity += NFileHeader::kLocalBlockSize;
mtProgressMixerSpec->Mixer2->SetProgressOffset(complexity);
RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK));
refs.Refs[mtItemIndex - 1].Skip = true;
continue;
}
RINOK(res);
RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK));
}
for (UInt32 i = 0; i < numThreads; i++)
{
CThreadInfo &threadInfo = threads.Threads[i];
if (threadInfo.IsFree)
{
threadInfo.IsFree = false;
threadInfo.InStream = fileInStream;
// !!!!! we must release ref before sending event
// BUG was here in v4.43 and v4.44. It could change ref counter in two threads in same time
fileInStream.Release();
threadInfo.OutStreamSpec->Init();
threadInfo.ProgressSpec->Reinit();
threadInfo.CompressEvent.Set();
threadInfo.UpdateIndex = mtItemIndex - 1;
compressingCompletedEvents.Add(threadInfo.CompressionCompletedEvent);
threadIndices.Add(i);
break;
}
}
continue;
}
if (refs.Refs[itemIndex].Skip)
{
itemIndex++;
continue;
}
const CUpdateItem &ui = updateItems[itemIndex];
CItemEx item;
if (!ui.NewProperties || !ui.NewData)
{
item = inputItems[ui.IndexInArchive];
if (inArchive->ReadLocalItemAfterCdItemFull(item) != S_OK)
return E_NOTIMPL;
}
if (ui.NewData)
{
bool isDir = ((ui.NewProperties) ? ui.IsDir : item.IsDir());
if (isDir)
{
WriteDirHeader(archive, options, ui, item);
}
else
{
if (lastRealStreamItemIndex < itemIndex)
{
lastRealStreamItemIndex = itemIndex;
SetFileHeader(archive, *options, ui, item);
// file Size can be 64-bit !!!
archive.PrepareWriteCompressedData((UInt16)item.Name.Length(), ui.Size, options->IsAesMode);
}
CMemBlocks2 &memRef = refs.Refs[itemIndex];
if (memRef.Defined)
{
CMyComPtr<IOutStream> outStream;
archive.CreateStreamForCompressing(&outStream);
memRef.WriteToStream(memManager.GetBlockSize(), outStream);
SetItemInfoFromCompressingResult(memRef.CompressingResult,
options->IsAesMode, options->AesKeyMode, item);
SetFileHeader(archive, *options, ui, item);
archive.WriteLocalHeader(item);
// RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK));
memRef.FreeOpt(&memManager);
}
else
{
{
CThreadInfo &thread = threads.Threads[threadIndices.Front()];
if (!thread.OutStreamSpec->WasUnlockEventSent())
{
CMyComPtr<IOutStream> outStream;
archive.CreateStreamForCompressing(&outStream);
thread.OutStreamSpec->SetOutStream(outStream);
thread.OutStreamSpec->SetRealStreamMode();
}
}
DWORD result = ::WaitForMultipleObjects(compressingCompletedEvents.Size(),
&compressingCompletedEvents.Front(), FALSE, INFINITE);
int t = (int)(result - WAIT_OBJECT_0);
CThreadInfo &threadInfo = threads.Threads[threadIndices[t]];
threadInfo.InStream.Release();
threadInfo.IsFree = true;
RINOK(threadInfo.Result);
threadIndices.Delete(t);
compressingCompletedEvents.Delete(t);
if (t == 0)
{
RINOK(threadInfo.OutStreamSpec->WriteToRealStream());
threadInfo.OutStreamSpec->ReleaseOutStream();
SetItemInfoFromCompressingResult(threadInfo.CompressingResult,
options->IsAesMode, options->AesKeyMode, item);
SetFileHeader(archive, *options, ui, item);
archive.WriteLocalHeader(item);
}
else
{
CMemBlocks2 &memRef = refs.Refs[threadInfo.UpdateIndex];
threadInfo.OutStreamSpec->DetachData(memRef);
memRef.CompressingResult = threadInfo.CompressingResult;
memRef.Defined = true;
continue;
}
}
}
}
else
{
RINOK(UpdateItemOldData(archive, inStream, ui, item, progress, complexity));
}
items.Add(item);
complexity += NFileHeader::kLocalBlockSize;
mtProgressMixerSpec->Mixer2->SetProgressOffset(complexity);
itemIndex++;
}
archive.WriteCentralDir(items, comment);
return S_OK;
#endif
}
static const size_t kCacheBlockSize = (1 << 20);
static const size_t kCacheSize = (kCacheBlockSize << 2);
static const size_t kCacheMask = (kCacheSize - 1);
class CCacheOutStream:
public IOutStream,
public CMyUnknownImp
{
CMyComPtr<IOutStream> _stream;
Byte *_cache;
UInt64 _virtPos;
UInt64 _virtSize;
UInt64 _phyPos;
UInt64 _phySize; // <= _virtSize
UInt64 _cachedPos; // (_cachedPos + _cachedSize) <= _virtSize
size_t _cachedSize;
HRESULT MyWrite(size_t size);
HRESULT MyWriteBlock()
{
return MyWrite(kCacheBlockSize - ((size_t)_cachedPos & (kCacheBlockSize - 1)));
}
HRESULT FlushCache();
public:
CCacheOutStream(): _cache(0) {}
~CCacheOutStream();
bool Allocate();
HRESULT Init(IOutStream *stream);
MY_UNKNOWN_IMP
STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize);
STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition);
STDMETHOD(SetSize)(UInt64 newSize);
};
bool CCacheOutStream::Allocate()
{
if (!_cache)
_cache = (Byte *)::MidAlloc(kCacheSize);
return (_cache != NULL);
}
HRESULT CCacheOutStream::Init(IOutStream *stream)
{
_virtPos = _phyPos = 0;
_stream = stream;
RINOK(_stream->Seek(0, STREAM_SEEK_CUR, &_virtPos));
RINOK(_stream->Seek(0, STREAM_SEEK_END, &_virtSize));
RINOK(_stream->Seek(_virtPos, STREAM_SEEK_SET, &_virtPos));
_phyPos = _virtPos;
_phySize = _virtSize;
_cachedPos = 0;
_cachedSize = 0;
return S_OK;
}
HRESULT CCacheOutStream::MyWrite(size_t size)
{
while (size != 0 && _cachedSize != 0)
{
if (_phyPos != _cachedPos)
{
RINOK(_stream->Seek(_cachedPos, STREAM_SEEK_SET, &_phyPos));
}
size_t pos = (size_t)_cachedPos & kCacheMask;
size_t curSize = MyMin(kCacheSize - pos, _cachedSize);
curSize = MyMin(curSize, size);
RINOK(WriteStream(_stream, _cache + pos, curSize));
_phyPos += curSize;
if (_phySize < _phyPos)
_phySize = _phyPos;
_cachedPos += curSize;
_cachedSize -= curSize;
size -= curSize;
}
return S_OK;
}
HRESULT CCacheOutStream::FlushCache()
{
return MyWrite(_cachedSize);
}
CCacheOutStream::~CCacheOutStream()
{
FlushCache();
if (_virtSize != _phySize)
_stream->SetSize(_virtSize);
if (_virtPos != _phyPos)
_stream->Seek(_virtPos, STREAM_SEEK_SET, NULL);
::MidFree(_cache);
}
STDMETHODIMP CCacheOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)
{
if (processedSize)
*processedSize = 0;
if (size == 0)
return S_OK;
UInt64 zerosStart = _virtPos;
if (_cachedSize != 0)
{
if (_virtPos < _cachedPos)
{
RINOK(FlushCache());
}
else
{
UInt64 cachedEnd = _cachedPos + _cachedSize;
if (cachedEnd < _virtPos)
{
if (cachedEnd < _phySize)
{
RINOK(FlushCache());
}
else
zerosStart = cachedEnd;
}
}
}
if (_cachedSize == 0 && _phySize < _virtPos)
_cachedPos = zerosStart = _phySize;
if (zerosStart != _virtPos)
{
// write zeros to [cachedEnd ... _virtPos)
for (;;)
{
UInt64 cachedEnd = _cachedPos + _cachedSize;
size_t endPos = (size_t)cachedEnd & kCacheMask;
size_t curSize = kCacheSize - endPos;
if (curSize > _virtPos - cachedEnd)
curSize = (size_t)(_virtPos - cachedEnd);
if (curSize == 0)
break;
while (curSize > (kCacheSize - _cachedSize))
{
RINOK(MyWriteBlock());
}
memset(_cache + endPos, 0, curSize);
_cachedSize += curSize;
}
}
if (_cachedSize == 0)
_cachedPos = _virtPos;
size_t pos = (size_t)_virtPos & kCacheMask;
size = (UInt32)MyMin((size_t)size, kCacheSize - pos);
UInt64 cachedEnd = _cachedPos + _cachedSize;
if (_virtPos != cachedEnd) // _virtPos < cachedEnd
size = (UInt32)MyMin((size_t)size, (size_t)(cachedEnd - _virtPos));
else
{
// _virtPos == cachedEnd
if (_cachedSize == kCacheSize)
{
RINOK(MyWriteBlock());
}
size_t startPos = (size_t)_cachedPos & kCacheMask;
if (startPos > pos)
size = (UInt32)MyMin((size_t)size, (size_t)(startPos - pos));
_cachedSize += size;
}
memcpy(_cache + pos, data, size);
if (processedSize)
*processedSize = size;
_virtPos += size;
if (_virtSize < _virtPos)
_virtSize = _virtPos;
return S_OK;
}
STDMETHODIMP CCacheOutStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)
{
switch(seekOrigin)
{
case STREAM_SEEK_SET: _virtPos = offset; break;
case STREAM_SEEK_CUR: _virtPos += offset; break;
case STREAM_SEEK_END: _virtPos = _virtSize + offset; break;
default: return STG_E_INVALIDFUNCTION;
}
if (newPosition)
*newPosition = _virtPos;
return S_OK;
}
STDMETHODIMP CCacheOutStream::SetSize(UInt64 newSize)
{
_virtSize = newSize;
if (newSize < _phySize)
{
RINOK(_stream->SetSize(newSize));
_phySize = newSize;
}
if (newSize <= _cachedPos)
{
_cachedSize = 0;
_cachedPos = newSize;
}
if (newSize < _cachedPos + _cachedSize)
_cachedSize = (size_t)(newSize - _cachedPos);
return S_OK;
}
HRESULT Update(
DECL_EXTERNAL_CODECS_LOC_VARS
const CObjectVector<CItemEx> &inputItems,
const CObjectVector<CUpdateItem> &updateItems,
ISequentialOutStream *seqOutStream,
CInArchive *inArchive,
CCompressionMethodMode *compressionMethodMode,
IArchiveUpdateCallback *updateCallback)
{
CMyComPtr<IOutStream> outStream;
{
CMyComPtr<IOutStream> outStreamReal;
seqOutStream->QueryInterface(IID_IOutStream, (void **)&outStreamReal);
if (!outStreamReal)
return E_NOTIMPL;
CCacheOutStream *cacheStream = new CCacheOutStream();
outStream = cacheStream;
if (!cacheStream->Allocate())
return E_OUTOFMEMORY;
RINOK(cacheStream->Init(outStreamReal));
}
if (inArchive)
{
if (inArchive->ArcInfo.Base != 0 ||
inArchive->ArcInfo.StartPosition != 0 ||
!inArchive->IsOkHeaders)
return E_NOTIMPL;
}
COutArchive outArchive;
outArchive.Create(outStream);
/*
if (inArchive && inArchive->ArcInfo.StartPosition > 0)
{
CMyComPtr<ISequentialInStream> inStream;
inStream.Attach(inArchive->CreateLimitedStream(0, inArchive->ArcInfo.StartPosition));
RINOK(CopyBlockToArchive(inStream, outArchive, NULL));
outArchive.MoveBasePosition(inArchive->ArcInfo.StartPosition);
}
*/
CMyComPtr<IInStream> inStream;
if (inArchive)
inStream.Attach(inArchive->CreateStream());
return Update2(
EXTERNAL_CODECS_LOC_VARS
outArchive, inArchive, inStream,
inputItems, updateItems,
compressionMethodMode,
inArchive ? &inArchive->ArcInfo.Comment : NULL,
updateCallback);
}
}}