internal void Write(Stream s)
{
var cs1 = s as CountingStream;
var zss1 = s as ZipSegmentedStream;
bool done = false;
do
{
try
{
// When the app is updating a zip file, it may be possible to
// just copy data for a ZipEntry from the source zipfile to the
// destination, as a block, without decompressing and
// recompressing, etc. But, in some cases the app modifies the
// properties on a ZipEntry prior to calling Save(). A change to
// any of the metadata - the FileName, CompressioLeve and so on,
// means DotNetZip cannot simply copy through the existing
// ZipEntry data unchanged.
//
// There are two cases:
//
// 1. Changes to only metadata, which means the header and
// central directory must be changed.
//
// 2. Changes to the properties that affect the compressed
// stream, such as CompressionMethod, CompressionLevel, or
// EncryptionAlgorithm. In this case, DotNetZip must
// "re-stream" the data: the old entry data must be maybe
// decrypted, maybe decompressed, then maybe re-compressed
// and maybe re-encrypted.
//
// This test checks if the source for the entry data is a zip file, and
// if a restream is necessary. If NOT, then it just copies through
// one entry, potentially changing the metadata.
if (_Source == ZipEntrySource.ZipFile && !_restreamRequiredOnSave)
{
CopyThroughOneEntry(s);
return;
}
// Is the entry a directory? If so, the write is relatively simple.
if (IsDirectory)
{
WriteHeader(s, 1);
StoreRelativeOffset();
_entryRequiresZip64 = new Nullable<bool>(_RelativeOffsetOfLocalHeader >= 0xFFFFFFFF);
_OutputUsesZip64 = new Nullable<bool>(_container.Zip64 == Zip64Option.Always || _entryRequiresZip64.Value);
// handle case for split archives
if (zss1 != null)
_diskNumber = zss1.CurrentSegment;
return;
}
// At this point, the source for this entry is not a directory, and
// not a previously created zip file, or the source for the entry IS
// a previously created zip but the settings whave changed in
// important ways and therefore we will need to process the
// bytestream (compute crc, maybe compress, maybe encrypt) in order
// to write the content into the new zip.
//
// We do this in potentially 2 passes: The first time we do it as
// requested, maybe with compression and maybe encryption. If that
// causes the bytestream to inflate in size, and if compression was
// on, then we turn off compression and do it again.
bool readAgain = true;
int nCycles = 0;
do
{
nCycles++;
WriteHeader(s, nCycles);
// write the encrypted header
WriteSecurityMetadata(s);
// write the (potentially compressed, potentially encrypted) file data
_WriteEntryData(s);
// track total entry size (including the trailing descriptor and MAC)
_TotalEntrySize = _LengthOfHeader + _CompressedFileDataSize + _LengthOfTrailer;
// The file data has now been written to the stream, and
// the file pointer is positioned directly after file data.
if (nCycles > 1) readAgain = false;
else if (!s.CanSeek) readAgain = false;
else readAgain = WantReadAgain();
if (readAgain)
{
// Seek back in the raw output stream, to the beginning of the file
// data for this entry.
// handle case for split archives
if (zss1 != null)
{
// Console.WriteLine("***_diskNumber/first: {0}", _diskNumber);
// Console.WriteLine("***_diskNumber/current: {0}", zss.CurrentSegment);
zss1.TruncateBackward(_diskNumber, _RelativeOffsetOfLocalHeader);
}
else
// workitem 8098: ok (output).
s.Seek(_RelativeOffsetOfLocalHeader, SeekOrigin.Begin);
// If the last entry expands, we read again; but here, we must
// truncate the stream to prevent garbage data after the
// end-of-central-directory.
// workitem 8098: ok (output).
s.SetLength(s.Position);
// Adjust the count on the CountingStream as necessary.
if (cs1 != null) cs1.Adjust(_TotalEntrySize);
}
}
while (readAgain);
_skippedDuringSave = false;
done = true;
}
catch (System.Exception exc1)
{
ZipErrorAction orig = this.ZipErrorAction;
int loop = 0;
do
{
if (ZipErrorAction == ZipErrorAction.Throw)
throw;
if (ZipErrorAction == ZipErrorAction.Skip ||
ZipErrorAction == ZipErrorAction.Retry)
{
// must reset file pointer here.
// workitem 13903 - seek back only when necessary
long p1 = (cs1 != null)
? cs1.ComputedPosition
: s.Position;
long delta = p1 - _future_ROLH;
if (delta > 0)
{
s.Seek(delta, SeekOrigin.Current); // may throw
long p2 = s.Position;
s.SetLength(s.Position); // to prevent garbage if this is the last entry
if (cs1 != null) cs1.Adjust(p1 - p2);
}
if (ZipErrorAction == ZipErrorAction.Skip)
{
WriteStatus("Skipping file {0} (exception: {1})", LocalFileName, exc1.ToString());
_skippedDuringSave = true;
done = true;
}
else
this.ZipErrorAction = orig;
break;
}
if (loop > 0) throw;
if (ZipErrorAction == ZipErrorAction.InvokeErrorEvent)
{
OnZipErrorWhileSaving(exc1);
if (_ioOperationCanceled)
{
done = true;
break;
}
}
loop++;
}
while (true);
}
}
while (!done);
}