public void Save()
{
try
{
bool thisSaveUsedZip64 = false;
_saveOperationCanceled = false;
_numberOfSegmentsForMostRecentSave = 0;
OnSaveStarted();
if (WriteStream == null)
throw new BadStateException("You haven't specified where to save the zip.");
if (_name != null && _name.EndsWith(".exe") && !_SavingSfx)
throw new BadStateException("You specified an EXE for a plain zip file.");
// check if modified, before saving.
if (!_contentsChanged)
{
OnSaveCompleted();
if (Verbose) StatusMessageTextWriter.WriteLine("No save is necessary....");
return;
}
Reset(true);
if (Verbose) StatusMessageTextWriter.WriteLine("saving....");
// validate the number of entries
if (_entries.Count >= 0xFFFF && _zip64 == Zip64Option.Never)
throw new ZipException("The number of entries is 65535 or greater. Consider setting the UseZip64WhenSaving property on the ZipFile instance.");
// write an entry in the zip for each file
int n = 0;
// workitem 9831
ICollection<ZipEntry> c = (SortEntriesBeforeSaving) ? EntriesSorted : Entries;
foreach (ZipEntry e in c) // _entries.Values
{
OnSaveEntry(n, e, true);
e.Write(WriteStream);
if (_saveOperationCanceled)
break;
n++;
OnSaveEntry(n, e, false);
if (_saveOperationCanceled)
break;
// Some entries can be skipped during the save.
if (e.IncludedInMostRecentSave)
thisSaveUsedZip64 |= e.OutputUsedZip64.Value;
}
if (_saveOperationCanceled)
return;
var zss = WriteStream as ZipSegmentedStream;
_numberOfSegmentsForMostRecentSave = (zss!=null)
? zss.CurrentSegment
: 1;
bool directoryNeededZip64 =
ZipOutput.WriteCentralDirectoryStructure
(WriteStream,
c,
_numberOfSegmentsForMostRecentSave,
_zip64,
Comment,
new ZipContainer(this));
OnSaveEvent(ZipProgressEventType.Saving_AfterSaveTempArchive);
_hasBeenSaved = true;
_contentsChanged = false;
thisSaveUsedZip64 |= directoryNeededZip64;
_OutputUsesZip64 = new Nullable<bool>(thisSaveUsedZip64);
if (_fileAlreadyExists && this._readstream != null)
{
// This means we opened and read a zip file.
// If we are now saving, we need to close the orig file, first.
this._readstream.Close();
this._readstream = null;
}
// the archiveStream for each entry needs to be null
foreach (var e in c)
{
var zss1 = e._archiveStream as ZipSegmentedStream;
if (zss1 != null)
#if NETCF
zss1.Close();
#else
zss1.Dispose();
#endif
e._archiveStream = null;
}
// do the rename as necessary
if (_name != null &&
(_temporaryFileName!=null || zss != null))
{
// _temporaryFileName may remain null if we are writing to a stream.
// only close the stream if there is a file behind it.
#if NETCF
WriteStream.Close();
#else
WriteStream.Dispose();
#endif
if (_saveOperationCanceled)
return;
string tmpName = null;
if (File.Exists(_name))
{
// the steps:
//
// 1. Delete tmpName
// 2. move existing zip to tmpName
// 3. rename (File.Move) working file to name of existing zip
// 4. delete tmpName
//
// This series of steps avoids the exception,
// System.IO.IOException:
// "Cannot create a file when that file already exists."
//
// Cannot just call File.Replace() here because
// there is a possibility that the TEMP volume is different
// that the volume for the final file (c:\ vs d:\).
// So we need to do a Delete+Move pair.
//
// But, when doing the delete, Windows allows a process to
// delete the file, even though it is held open by, say, a
// virus scanner. It gets internally marked as "delete
// pending". The file does not actually get removed from the
// file system, it is still there after the File.Delete
// call.
//
// Therefore, we need to move the existing zip, which may be
// held open, to some other name. Then rename our working
// file to the desired name, then delete (possibly delete
// pending) the "other name".
//
// Ideally this would be transactional. It's possible that the
// delete succeeds and the move fails. Lacking transactions, if
// this kind of failure happens, we're hosed, and this logic will
// throw on the next File.Move().
//
//File.Delete(_name);
// workitem 10447
#if NETCF || SILVERLIGHT
tmpName = _name + "." + SharedUtilities.GenerateRandomStringImpl(8,0) + ".tmp";
#else
tmpName = _name + "." + Path.GetRandomFileName();
#endif
if (File.Exists(tmpName))
DeleteFileWithRetry(tmpName);
File.Move(_name, tmpName);
}
OnSaveEvent(ZipProgressEventType.Saving_BeforeRenameTempArchive);
File.Move((zss != null) ? zss.CurrentTempName : _temporaryFileName,
_name);
OnSaveEvent(ZipProgressEventType.Saving_AfterRenameTempArchive);
if (tmpName != null)
{
try
{
// not critical
if (File.Exists(tmpName))
File.Delete(tmpName);
}
catch
{
// don't care about exceptions here.
}
}
_fileAlreadyExists = true;
}
_readName = _name;
NotifyEntriesSaveComplete(c);
OnSaveCompleted();
_JustSaved = true;
}
// workitem 5043
finally
{
CleanupAfterSaveOperation();
}
return;
}