/// <summary> /// Add a ZipEntry for which content is written directly by the application. /// </summary> /// /// <remarks> /// <para> /// When the application needs to write the zip entry data, use this /// method to add the ZipEntry. For example, in the case that the /// application wishes to write the XML representation of a DataSet into /// a ZipEntry, the application can use this method to do so. /// </para> /// /// <para> /// For <c>ZipFile</c> properties including <see cref="Encryption"/>, <see /// cref="Password"/>, <see cref="SetCompression"/>, <see /// cref="ProvisionalAlternateEncoding"/>, <see cref="ExtractExistingFile"/>, /// <see cref="ZipErrorAction"/>, and <see cref="CompressionLevel"/>, their /// respective values at the time of this call will be applied to the /// <c>ZipEntry</c> added. /// </para> /// /// <para> /// About progress events: When using the WriteDelegate, DotNetZip does /// not issue any SaveProgress events with <c>EventType</c> = <see /// cref="ZipProgressEventType.Saving_EntryBytesRead"> /// Saving_EntryBytesRead</see>. (This is because it is the /// application's code that runs in WriteDelegate - there's no way for /// DotNetZip to know when to issue a EntryBytesRead event.) /// Applications that want to update a progress bar or similar status /// indicator should do so from within the WriteDelegate /// itself. DotNetZip will issue the other SaveProgress events, /// including <see cref="ZipProgressEventType.Saving_Started"> /// Saving_Started</see>, /// <see cref="ZipProgressEventType.Saving_BeforeWriteEntry"> /// Saving_BeforeWriteEntry</see>, and <see /// cref="ZipProgressEventType.Saving_AfterWriteEntry"> /// Saving_AfterWriteEntry</see>. /// </para> /// /// <para> /// Note: When you use PKZip encryption, it's normally necessary to /// compute the CRC of the content to be encrypted, before compressing or /// encrypting it. Therefore, when using PKZip encryption with a /// WriteDelegate, the WriteDelegate CAN BE called twice: once to compute /// the CRC, and the second time to potentially compress and /// encrypt. Surprising, but true. This is because PKWARE specified that /// the encryption initialization data depends on the CRC. /// If this happens, for each call of the delegate, your /// application must stream the same entry data in its entirety. If your /// application writes different data during the second call, it will /// result in a corrupt zip file. /// </para> /// /// <para> /// The double-read behavior happens with all types of entries, not only /// those that use WriteDelegate. It happens if you add an entry from a /// filesystem file, or using a string, or a stream, or an opener/closer /// pair. But in those cases, DotNetZip takes care of reading twice; in /// the case of the WriteDelegate, the application code gets invoked /// twice. Be aware. /// </para> /// /// <para> /// As you can imagine, this can cause performance problems for large /// streams, and it can lead to correctness problems when you use a /// <c>WriteDelegate</c>. This is a pretty big pitfall. There are two /// ways to avoid it. First, and most preferred: don't use PKZIP /// encryption. If you use the WinZip AES encryption, this problem /// doesn't occur, because the encryption protocol doesn't require the CRC /// up front. Second: if you do choose to use PKZIP encryption, write out /// to a non-seekable stream (like standard output, or the /// Response.OutputStream in an ASP.NET application). In this case, /// DotNetZip will use an alternative encryption protocol that does not /// rely on the CRC of the content. This also implies setting bit 3 in /// the zip entry, which still presents problems for some zip tools. /// </para> /// /// <para> /// In the future I may modify DotNetZip to *always* use bit 3 when PKZIP /// encryption is in use. This seems like a win overall, but there will /// be some work involved. If you feel strongly about it, visit the /// DotNetZip forums and vote up <see /// href="http://dotnetzip.codeplex.com/workitem/13686">the Workitem /// tracking this issue</see>. /// </para> /// /// </remarks> /// /// <param name="entryName">the name of the entry to add</param> /// <param name="writer">the delegate which will write the entry content</param> /// <returns>the ZipEntry added</returns> /// /// <example> /// /// This example shows an application filling a DataSet, then saving the /// contents of that DataSet as XML, into a ZipEntry in a ZipFile, using an /// anonymous delegate in C#. The DataSet XML is never saved to a disk file. /// /// <code lang="C#"> /// var c1= new System.Data.SqlClient.SqlConnection(connstring1); /// var da = new System.Data.SqlClient.SqlDataAdapter() /// { /// SelectCommand= new System.Data.SqlClient.SqlCommand(strSelect, c1) /// }; /// /// DataSet ds1 = new DataSet(); /// da.Fill(ds1, "Invoices"); /// /// using(Ionic.Zip.ZipFile zip = new Ionic.Zip.ZipFile()) /// { /// zip.AddEntry(zipEntryName, (name,stream) => ds1.WriteXml(stream) ); /// zip.Save(zipFileName); /// } /// </code> /// </example> /// /// <example> /// /// This example uses an anonymous method in C# as the WriteDelegate to provide /// the data for the ZipEntry. The example is a bit contrived - the /// <c>AddFile()</c> method is a simpler way to insert the contents of a file /// into an entry in a zip file. On the other hand, if there is some sort of /// processing or transformation of the file contents required before writing, /// the application could use the <c>WriteDelegate</c> to do it, in this way. /// /// <code lang="C#"> /// using (var input = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite )) /// { /// using(Ionic.Zip.ZipFile zip = new Ionic.Zip.ZipFile()) /// { /// zip.AddEntry(zipEntryName, (name,output) => /// { /// byte[] buffer = new byte[BufferSize]; /// int n; /// while ((n = input.Read(buffer, 0, buffer.Length)) != 0) /// { /// // could transform the data here... /// output.Write(buffer, 0, n); /// // could update a progress bar here /// } /// }); /// /// zip.Save(zipFileName); /// } /// } /// </code> /// </example> /// /// <example> /// /// This example uses a named delegate in VB to write data for the given /// ZipEntry (VB9 does not have anonymous delegates). The example here is a bit /// contrived - a simpler way to add the contents of a file to a ZipEntry is to /// simply use the appropriate <c>AddFile()</c> method. The key scenario for /// which the <c>WriteDelegate</c> makes sense is saving a DataSet, in XML /// format, to the zip file. The DataSet can write XML to a stream, and the /// WriteDelegate is the perfect place to write into the zip file. There may be /// other data structures that can write to a stream, but cannot be read as a /// stream. The <c>WriteDelegate</c> would be appropriate for those cases as /// well. /// /// <code lang="VB"> /// Private Sub WriteEntry (ByVal name As String, ByVal output As Stream) /// Using input As FileStream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite) /// Dim n As Integer = -1 /// Dim buffer As Byte() = New Byte(BufferSize){} /// Do While n <> 0 /// n = input.Read(buffer, 0, buffer.Length) /// output.Write(buffer, 0, n) /// Loop /// End Using /// End Sub /// /// Public Sub Run() /// Using zip = New ZipFile /// zip.AddEntry(zipEntryName, New WriteDelegate(AddressOf WriteEntry)) /// zip.Save(zipFileName) /// End Using /// End Sub /// </code> /// </example> public ZipEntry AddEntry(string entryName, WriteDelegate writer) { ZipEntry ze = ZipEntry.CreateForWriter(entryName, writer); if (Verbose) { StatusMessageTextWriter.WriteLine("adding {0}...", entryName); } return(_InternalAddEntry(ze)); }
public ZipEntry AddFile(string fileName, String directoryPathInArchive) { string nameInArchive = ZipEntry.NameInArchive(fileName, directoryPathInArchive); ZipEntry ze = ZipEntry.CreateFromFile(fileName, nameInArchive); if (Verbose) { StatusMessageTextWriter.WriteLine("adding {0}...", fileName); } return(_InternalAddEntry(ze)); }
public ZipEntry AddEntry(string entryName, Stream stream) { ZipEntry ze = ZipEntry.CreateForStream(entryName, stream); ze.SetEntryTimes(DateTime.Now, DateTime.Now, DateTime.Now); if (Verbose) { StatusMessageTextWriter.WriteLine("adding {0}...", entryName); } return(_InternalAddEntry(ze)); }
/// <summary> /// Add an entry, for which the application will provide a stream /// containing the entry data, on a just-in-time basis. /// </summary> /// /// <remarks> /// <para> /// In cases where the application wishes to open the stream that /// holds the content for the ZipEntry, on a just-in-time basis, the /// application can use this method. The application provides an /// opener delegate that will be called by the DotNetZip library to /// obtain a readable stream that can be read to get the bytes for /// the given entry. Typically, this delegate opens a stream. /// Optionally, the application can provide a closer delegate as /// well, which will be called by DotNetZip when all bytes have been /// read from the entry. /// </para> /// /// <para> /// These delegates are called from within the scope of the call to /// ZipFile.Save(). /// </para> /// /// <para> /// For <c>ZipFile</c> properties including <see cref="Encryption"/>, <see /// cref="Password"/>, <see cref="SetCompression"/>, <see /// cref="ProvisionalAlternateEncoding"/>, <see cref="ExtractExistingFile"/>, /// <see cref="ZipErrorAction"/>, and <see cref="CompressionLevel"/>, their /// respective values at the time of this call will be applied to the /// <c>ZipEntry</c> added. /// </para> /// /// </remarks> /// /// <example> /// /// This example uses anonymous methods in C# to open and close the /// source stream for the content for a zip entry. /// /// <code lang="C#"> /// using(Ionic.Zip.ZipFile zip = new Ionic.Zip.ZipFile()) /// { /// zip.AddEntry(zipEntryName, /// (name) => File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite ), /// (name, stream) => stream.Close() /// ); /// /// zip.Save(zipFileName); /// } /// </code> /// /// </example> /// /// <example> /// /// This example uses delegates in VB.NET to open and close the /// the source stream for the content for a zip entry. VB 9.0 lacks /// support for "Sub" lambda expressions, and so the CloseDelegate must /// be an actual, named Sub. /// /// <code lang="VB"> /// /// Function MyStreamOpener(ByVal entryName As String) As Stream /// '' This simply opens a file. You probably want to do somethinig /// '' more involved here: open a stream to read from a database, /// '' open a stream on an HTTP connection, and so on. /// Return File.OpenRead(entryName) /// End Function /// /// Sub MyStreamCloser(entryName As String, stream As Stream) /// stream.Close() /// End Sub /// /// Public Sub Run() /// Dim dirToZip As String = "fodder" /// Dim zipFileToCreate As String = "Archive.zip" /// Dim opener As OpenDelegate = AddressOf MyStreamOpener /// Dim closer As CloseDelegate = AddressOf MyStreamCloser /// Dim numFilestoAdd As Int32 = 4 /// Using zip As ZipFile = New ZipFile /// Dim i As Integer /// For i = 0 To numFilesToAdd - 1 /// zip.AddEntry(String.Format("content-{0:000}.txt"), opener, closer) /// Next i /// zip.Save(zipFileToCreate) /// End Using /// End Sub /// /// </code> /// </example> /// /// <param name="entryName">the name of the entry to add</param> /// <param name="opener"> /// the delegate that will be invoked by ZipFile.Save() to get the /// readable stream for the given entry. ZipFile.Save() will call /// read on this stream to obtain the data for the entry. This data /// will then be compressed and written to the newly created zip /// file. /// </param> /// <param name="closer"> /// the delegate that will be invoked to close the stream. This may /// be null (Nothing in VB), in which case no call is makde to close /// the stream. /// </param> /// <returns>the ZipEntry added</returns> /// public ZipEntry AddEntry(string entryName, OpenDelegate opener, CloseDelegate closer) { ZipEntry ze = ZipEntry.CreateForJitStreamProvider(entryName, opener, closer); ze.SetEntryTimes(DateTime.Now, DateTime.Now, DateTime.Now); if (Verbose) { StatusMessageTextWriter.WriteLine("adding {0}...", entryName); } return(_InternalAddEntry(ze)); }
private string SfxSaveTemporary() { var tempFileName = System.IO.Path.Combine(TempFileFolder, System.IO.Path.GetRandomFileName() + ".zip"); Stream outstream = null; try { bool save_contentsChanged = _contentsChanged; outstream = new System.IO.FileStream(tempFileName, System.IO.FileMode.CreateNew); if (outstream == null) { throw new BadStateException(String.Format("Cannot open the temporary file ({0}) for writing.", tempFileName)); } if (Verbose) { StatusMessageTextWriter.WriteLine("Saving temp zip file...."); } // write an entry in the zip for each file int n = 0; foreach (ZipEntry e in _entries) { OnSaveEntry(n, e, true); e.Write(outstream); n++; OnSaveEntry(n, e, false); if (_saveOperationCanceled) { break; } } if (!_saveOperationCanceled) { WriteCentralDirectoryStructure(outstream); outstream.Close(); outstream = null; } _contentsChanged = save_contentsChanged; } finally { if (outstream != null) { try { outstream.Close(); } catch { } try { outstream.Dispose(); } catch { } } } return(tempFileName); }
private void RemoveTempFile() { try { if (File.Exists(_temporaryFileName)) { File.Delete(_temporaryFileName); } } catch (Exception ex1) { if (Verbose) StatusMessageTextWriter.WriteLine("ZipFile::Save: could not delete temp file: {0}.", ex1.Message); } }
/// <summary> /// Saves the ZipFile instance to a self-extracting zip archive. /// </summary> /// /// <remarks> /// /// <para> /// The generated exe image will execute on any machine that has the .NET Framework 2.0 /// installed on it. /// </para> /// /// <para> /// There are two "flavors" of self-extracting archive. The <c>WinFormsApplication</c> /// version will pop up a GUI and allow the user to select a target directory into which /// to extract. There's also a checkbox allowing the user to specify to overwrite /// existing files, and another checkbox to allow the user to request that Explorer be /// opened to see the extracted files after extraction. The other flavor is /// <c>ConsoleApplication</c>. A self-extractor generated with that flavor setting will /// run from the command line. It accepts command-line options to set the overwrite /// behavior, and to specify the target extraction directory. /// </para> /// /// <para> /// There are a few temporary files created during the saving to a self-extracting zip. /// These files are created in the directory pointed to by /// <see cref="ZipFile.TempFileFolder"/>, which defaults to <see cref="System.IO.Path.GetTempPath"/>. /// These temporary files are removed upon successful completion of this method. /// </para> /// /// <para> /// When a user runs the SFX, the user's personal directory /// (<see cref="Environment.SpecialFolder.Personal"/>) /// will be used as the default extract location. /// The user who runs the SFX will have the opportunity to change the extract /// directory before extracting. /// </para> /// /// <para> /// NB: This method is not available in the version of DotNetZip /// build for the .NET Compact Framework, nor in the "Reduced" DotNEtZip library. /// </para> /// /// </remarks> /// /// <example> /// <code> /// string DirectoryPath = "c:\\Documents\\Project7"; /// using (ZipFile zip = new ZipFile()) /// { /// zip.AddDirectory(DirectoryPath, System.IO.Path.GetFileName(DirectoryPath)); /// zip.Comment = "This will be embedded into a self-extracting console-based exe"; /// zip.SaveSelfExtractor("archive.exe", SelfExtractorFlavor.ConsoleApplication); /// } /// </code> /// <code lang="VB"> /// Dim DirectoryPath As String = "c:\Documents\Project7" /// Using zip As New ZipFile() /// zip.AddDirectory(DirectoryPath, System.IO.Path.GetFileName(DirectoryPath)) /// zip.Comment = "This will be embedded into a self-extracting console-based exe" /// zip.SaveSelfExtractor("archive.exe", SelfExtractorFlavor.ConsoleApplication) /// End Using /// </code> /// </example> /// /// <param name="exeToGenerate">a pathname, possibly fully qualified, to be created. Typically it will end in an .exe extension.</param> /// <param name="flavor">Indicates whether a Winforms or Console self-extractor is desired.</param> public void SaveSelfExtractor(string exeToGenerate, SelfExtractorFlavor flavor) { if (File.Exists(exeToGenerate)) { if (Verbose) { StatusMessageTextWriter.WriteLine("The existing file ({0}) will be overwritten.", exeToGenerate); } } if (!exeToGenerate.EndsWith(".exe")) { if (Verbose) { StatusMessageTextWriter.WriteLine("Warning: The generated self-extracting file will not have an .exe extension."); } } string TempZipFile = SfxSaveTemporary(); OnSaveEvent(ZipProgressEventType.Saving_AfterSaveTempArchive); if (TempZipFile == null) { return; // cancelled } // look for myself (ZipFile will be present in the Ionic.Utils.Zip assembly) Assembly a1 = typeof(ZipFile).Assembly; //Console.WriteLine("DotNetZip assembly loc: {0}", a1.Location); Microsoft.CSharp.CSharpCodeProvider csharp = new Microsoft.CSharp.CSharpCodeProvider(); // Perfect opportunity for a linq query, but I cannot use it. // The DotNetZip library can compile into 2.0, but needs to run on .NET 2.0. // Using LINQ would break that. Here's what it would look like: // // var settings = (from x in SettingsList // where x.Flavor == flavor // select x).First(); ExtractorSettings settings = null; foreach (var x in SettingsList) { if (x.Flavor == flavor) { settings = x; break; } } if (settings == null) { throw new BadStateException(String.Format("While saving a Self-Extracting Zip, Cannot find that flavor ({0})?", flavor)); } // This is the list of referenced assemblies. Ionic.Utils.Zip is needed here. // Also if it is the winforms (gui) extractor, we need other referenced assemblies. System.CodeDom.Compiler.CompilerParameters cp = new System.CodeDom.Compiler.CompilerParameters(); cp.ReferencedAssemblies.Add(a1.Location); if (settings.ReferencedAssemblies != null) { foreach (string ra in settings.ReferencedAssemblies) { cp.ReferencedAssemblies.Add(ra); } } cp.GenerateInMemory = false; cp.GenerateExecutable = true; cp.IncludeDebugInformation = false; cp.OutputAssembly = exeToGenerate; Assembly a2 = Assembly.GetExecutingAssembly(); string TempDir = GenerateUniquePathname("tmp", null); if ((settings.CopyThroughResources != null) && (settings.CopyThroughResources.Count != 0)) { System.IO.Directory.CreateDirectory(TempDir); int n = 0; byte[] bytes = new byte[1024]; foreach (string re in settings.CopyThroughResources) { string filename = Path.Combine(TempDir, re); using (Stream instream = a2.GetManifestResourceStream(re)) { using (FileStream outstream = File.OpenWrite(filename)) { do { n = instream.Read(bytes, 0, bytes.Length); outstream.Write(bytes, 0, n); } while (n > 0); } } // add the embedded resource in our own assembly into the target assembly as an embedded resource cp.EmbeddedResources.Add(filename); } } // add the zip file as an embedded resource cp.EmbeddedResources.Add(TempZipFile); // add the Ionic.Utils.Zip DLL as an embedded resource cp.EmbeddedResources.Add(a1.Location); //Console.WriteLine("Resources in this assembly:"); //foreach (string rsrc in a2.GetManifestResourceNames()) //{ // Console.WriteLine(rsrc); //} //Console.WriteLine(); //Console.WriteLine("reading source code resources:"); // concatenate all the source code resources into a single module var sb = new System.Text.StringBuilder(); // set the default extract location if it is available bool wantCodeReplace = (flavor == SelfExtractorFlavor.WinFormsApplication && _defaultExtractLocation != null); if (wantCodeReplace) { _defaultExtractLocation = _defaultExtractLocation.Replace("\"", ""); } foreach (string rc in settings.ResourcesToCompile) { //Console.WriteLine(" trying to read stream: ({0})", rc); Stream s = a2.GetManifestResourceStream(rc); using (StreamReader sr = new StreamReader(s)) { while (sr.Peek() >= 0) { string line = sr.ReadLine(); if (wantCodeReplace) { line = line.Replace("@@VALUE", _defaultExtractLocation); } sb.Append(line).Append("\n"); } } sb.Append("\n\n"); } string LiteralSource = sb.ToString(); System.CodeDom.Compiler.CompilerResults cr = csharp.CompileAssemblyFromSource(cp, LiteralSource); if (cr == null) { throw new SfxGenerationException("Cannot compile the extraction logic!"); } if (Verbose) { foreach (string output in cr.Output) { StatusMessageTextWriter.WriteLine(output); } } if (cr.Errors.Count != 0) { throw new SfxGenerationException("Errors compiling the extraction logic!"); } OnSaveEvent(ZipProgressEventType.Saving_AfterCompileSelfExtractor); try { if (Directory.Exists(TempDir)) { try { Directory.Delete(TempDir, true); } catch { } } if (File.Exists(TempZipFile)) { try { File.Delete(TempZipFile); } catch { } } } catch { } OnSaveCompleted(); if (Verbose) { StatusMessageTextWriter.WriteLine("Created self-extracting zip file {0}.", cr.PathToAssembly); } return; // catch (Exception e1) // { // StatusMessageTextWriter.WriteLine("****Exception: " + e1); // throw; // } // return; }
/// <summary> /// Saves the Zip archive to a file, specified by the Name property of the <c>ZipFile</c>. /// </summary> /// /// <remarks> /// <para> /// The <c>ZipFile</c> instance is written to storage, typically a zip file in a /// filesystem, only when the caller calls <c>Save</c>. The Save operation writes /// the zip content to a temporary file, and then renames the temporary file /// to the desired name. If necessary, this method will delete a pre-existing file /// before the rename. /// </para> /// /// <para> The <see cref="ZipFile.Name"/> property is specified either /// explicitly, or implicitly using one of the parameterized ZipFile /// constructors. For COM Automation clients, the <c>Name</c> property must be /// set explicitly, because COM Automation clients cannot call parameterized /// constructors. </para> /// /// <para> /// When using a filesystem file for the Zip output, it is possible to call /// <c>Save</c> multiple times on the <c>ZipFile</c> instance. With each call the zip /// content is re-written to the same output file. /// </para> /// /// <para> /// Data for entries that have been added to the <c>ZipFile</c> instance is written /// to the output when the <c>Save</c> method is called. This means that the input /// streams for those entries must be available at the time the application calls /// <c>Save</c>. If, for example, the application adds entries with <c>AddEntry</c> /// using a dynamically-allocated <c>MemoryStream</c>, the memory stream must not /// have been disposed before the call to <c>Save</c>. See the <see /// cref="ZipEntry.InputStream"/> property for more discussion of the availability /// requirements of the input stream for an entry, and an approach for providing /// just-in-time stream lifecycle management. /// </para> /// /// </remarks> /// /// <seealso cref="Ionic.Zip.ZipFile.AddEntry(String, System.IO.Stream)"/> /// /// <exception cref="Ionic.Zip.BadStateException"> /// Thrown if you haven't specified a location or stream for saving the zip, /// either in the constructor or by setting the Name property, or if you try to /// save a regular zip archive to a filename with a .exe extension. /// </exception> /// 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(); 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, ProvisionalAlternateEncoding); OnSaveEvent(ZipProgressEventType.Saving_AfterSaveTempArchive); _hasBeenSaved = true; _contentsChanged = false; thisSaveUsedZip64 |= directoryNeededZip64; _OutputUsesZip64 = new Nullable <bool>(thisSaveUsedZip64); // 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. WriteStream.Close(); #if !NETCF WriteStream.Dispose(); #endif if (_saveOperationCanceled) { return; } if ((_fileAlreadyExists) && (this._readstream != null)) { // This means we opened and read a zip file. // If we are now saving to the same file, 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) { e._archiveStream = null; } } if (_fileAlreadyExists) { // We do not 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. // // Ideally this would be transactional. // // It's possible that the delete succeeds and the move fails. // in that case, we're hosed, and we'll throw. // // Could make this more complicated by moving (renaming) the first file, then // moving the second, then deleting the first file. But the // error handling and unwrap logic just gets more complicated. // // Better to just keep it simple. File.Delete(_name); } OnSaveEvent(ZipProgressEventType.Saving_BeforeRenameTempArchive); File.Move((zss != null) ? zss.CurrentName : _temporaryFileName, _name); OnSaveEvent(ZipProgressEventType.Saving_AfterRenameTempArchive); _fileAlreadyExists = true; } NotifyEntriesSaveComplete(c); OnSaveCompleted(); _JustSaved = true; } // workitem 5043 finally { CleanupAfterSaveOperation(); } return; }
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) { zss1.Dispose(); } 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. WriteStream.Dispose(); if (_saveOperationCanceled) { return; } string tmpName = null; if (File.Exists(_name)) { tmpName = _name + "." + Path.GetRandomFileName(); 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; }
private void _InternalExtractAll(string path, bool overrideExtractExistingProperty) { bool header = Verbose; _inExtractAll = true; try { OnExtractAllStarted(path); int n = 0; foreach (ZipEntry e in _entries.Values) { if (header) { StatusMessageTextWriter.WriteLine("\n{1,-22} {2,-8} {3,4} {4,-8} {0}", "Name", "Modified", "Size", "Ratio", "Packed"); StatusMessageTextWriter.WriteLine(new System.String('-', 72)); header = false; } if (Verbose) { StatusMessageTextWriter.WriteLine("{1,-22} {2,-8} {3,4:F0}% {4,-8} {0}", e.FileName, e.LastModified.ToString("yyyy-MM-dd HH:mm:ss"), e.UncompressedSize, e.CompressionRatio, e.CompressedSize); if (!String.IsNullOrEmpty(e.Comment)) { StatusMessageTextWriter.WriteLine(" Comment: {0}", e.Comment); } } e.Password = _Password; // this may be null OnExtractEntry(n, true, e, path); if (overrideExtractExistingProperty) { e.ExtractExistingFile = this.ExtractExistingFile; } e.Extract(path); n++; OnExtractEntry(n, false, e, path); if (_extractOperationCanceled) { break; } } // workitem 8264: // now, set times on directory entries, again. // The problem is, extracting a file changes the times on the parent // directory. So after all files have been extracted, we have to // run through the directories again. foreach (ZipEntry e in _entries.Values) { // check if it is a directory if ((e.IsDirectory) || (e.FileName.EndsWith("/"))) { string outputFile = (e.FileName.StartsWith("/")) ? Path.Combine(path, e.FileName.Substring(1)) : Path.Combine(path, e.FileName); e._SetTimes(outputFile, false); } } OnExtractAllCompleted(path); } finally { _inExtractAll = false; } }
/// <summary> /// Saves the Zip archive to a file, specified by the Name property of the /// <c>ZipFile</c>. /// </summary> /// /// <remarks> /// <para> /// The <c>ZipFile</c> instance is written to storage, typically a zip file /// in a filesystem, only when the caller calls <c>Save</c>. In the typical /// case, the Save operation writes the zip content to a temporary file, and /// then renames the temporary file to the desired name. If necessary, this /// method will delete a pre-existing file before the rename. /// </para> /// /// <para> /// The <see cref="ZipFile.Name"/> property is specified either explicitly, /// or implicitly using one of the parameterized ZipFile constructors. For /// COM Automation clients, the <c>Name</c> property must be set explicitly, /// because COM Automation clients cannot call parameterized constructors. /// </para> /// /// <para> /// When using a filesystem file for the Zip output, it is possible to call /// <c>Save</c> multiple times on the <c>ZipFile</c> instance. With each /// call the zip content is re-written to the same output file. /// </para> /// /// <para> /// Data for entries that have been added to the <c>ZipFile</c> instance is /// written to the output when the <c>Save</c> method is called. This means /// that the input streams for those entries must be available at the time /// the application calls <c>Save</c>. If, for example, the application /// adds entries with <c>AddEntry</c> using a dynamically-allocated /// <c>MemoryStream</c>, the memory stream must not have been disposed /// before the call to <c>Save</c>. See the <see /// cref="ZipEntry.InputStream"/> property for more discussion of the /// availability requirements of the input stream for an entry, and an /// approach for providing just-in-time stream lifecycle management. /// </para> /// /// </remarks> /// /// <seealso cref="Ionic.Zip.ZipFile.AddEntry(String, System.IO.Stream)"/> /// /// <exception cref="Ionic.Zip.BadStateException"> /// Thrown if you haven't specified a location or stream for saving the zip, /// either in the constructor or by setting the Name property, or if you try /// to save a regular zip archive to a filename with a .exe extension. /// </exception> /// /// <exception cref="System.OverflowException"> /// Thrown if <see cref="MaxOutputSegmentSize"/> is non-zero, and the number /// of segments that would be generated for the spanned zip file during the /// save operation exceeds 99. If this happens, you need to increase the /// segment size. /// </exception> /// internal 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."); } // 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 : 0; bool directoryNeededZip64 = ZipOutput.WriteCentralDirectoryStructure (WriteStream, c, (zss != null) ? zss.CurrentSegment : 1, _zip64, Comment, new ZipContainer(this)); OnSaveEvent(ZipProgressEventType.Saving_AfterSaveTempArchive); _hasBeenSaved = true; _contentsChanged = false; thisSaveUsedZip64 |= directoryNeededZip64; _OutputUsesZip64 = new Nullable <bool>(thisSaveUsedZip64); NotifyEntriesSaveComplete(c); OnSaveCompleted(); _JustSaved = true; } // workitem 5043 finally { CleanupAfterSaveOperation(); } return; }
private ZipEntry AddOrUpdateDirectoryImpl(string directoryName, string rootDirectoryPathInArchive, AddOrUpdateAction action, bool recurse, int level) { if (Verbose) { StatusMessageTextWriter.WriteLine("{0} {1}...", (action == AddOrUpdateAction.AddOnly) ? "adding" : "Adding or updating", directoryName); } if (level == 0) { _addOperationCanceled = false; OnAddStarted(); } // workitem 13371 if (_addOperationCanceled) { return(null); } string dirForEntries = rootDirectoryPathInArchive; ZipEntry baseDir = null; if (level > 0) { int f = directoryName.Length; for (int i = level; i > 0; i--) { f = directoryName.LastIndexOfAny("/\\".ToCharArray(), f - 1, f - 1); } dirForEntries = directoryName.Substring(f + 1); dirForEntries = Path.Combine(rootDirectoryPathInArchive, dirForEntries); } // if not top level, or if the root is non-empty, then explicitly add the directory if (level > 0 || rootDirectoryPathInArchive != "") { baseDir = ZipEntry.CreateFromFile(directoryName, dirForEntries); baseDir._container = new ZipContainer(this); baseDir.AlternateEncoding = this.AlternateEncoding; // workitem 6410 baseDir.AlternateEncodingUsage = this.AlternateEncodingUsage; baseDir.MarkAsDirectory(); baseDir.EmitTimesInWindowsFormatWhenSaving = _emitNtfsTimes; baseDir.EmitTimesInUnixFormatWhenSaving = _emitUnixTimes; // add the directory only if it does not exist. // It's not an error if it already exists. if (!_entries.ContainsKey(baseDir.FileName)) { InternalAddEntry(baseDir.FileName, baseDir); AfterAddEntry(baseDir); } dirForEntries = baseDir.FileName; } if (!_addOperationCanceled) { String[] filenames = Directory.GetFiles(directoryName); if (recurse) { // add the files: foreach (String filename in filenames) { if (_addOperationCanceled) { break; } if (action == AddOrUpdateAction.AddOnly) { AddFile(filename, dirForEntries); } else { UpdateFile(filename, dirForEntries); } } if (!_addOperationCanceled) { // add the subdirectories: String[] dirnames = Directory.GetDirectories(directoryName); foreach (String dir in dirnames) { // workitem 8617: Optionally traverse reparse points FileAttributes fileAttrs = System.IO.File.GetAttributes(dir); if (this.AddDirectoryWillTraverseReparsePoints || ((fileAttrs & FileAttributes.ReparsePoint) == 0) ) { AddOrUpdateDirectoryImpl(dir, rootDirectoryPathInArchive, action, recurse, level + 1); } } } } } if (level == 0) { OnAddCompleted(); } return(baseDir); }
private void _SaveSfxStub(string exeToGenerate, SelfExtractorFlavor flavor, string defaultExtractLocation, string postExtractCmdLine, string nameOfIconFile) { bool removeIconFile = false; string StubExe = null; string TempDir = null; try { if (File.Exists(exeToGenerate)) { if (Verbose) { StatusMessageTextWriter.WriteLine("The existing file ({0}) will be overwritten.", exeToGenerate); } } if (!exeToGenerate.EndsWith(".exe")) { if (Verbose) { StatusMessageTextWriter.WriteLine("Warning: The generated self-extracting file will not have an .exe extension."); } } StubExe = GenerateTempPathname("exe", null); // get the Ionic.Zip assembly Assembly a1 = typeof(ZipFile).Assembly; Microsoft.CSharp.CSharpCodeProvider csharp = new Microsoft.CSharp.CSharpCodeProvider(); // Perfect opportunity for a linq query, but I cannot use it. // The DotNetZip library can compile into 2.0, but needs to run on .NET 2.0. // Using LINQ would break that. Here's what it would look like: // // var settings = (from x in SettingsList // where x.Flavor == flavor // select x).First(); ExtractorSettings settings = null; foreach (var x in SettingsList) { if (x.Flavor == flavor) { settings = x; break; } } if (settings == null) { throw new BadStateException(String.Format("While saving a Self-Extracting Zip, Cannot find that flavor ({0})?", flavor)); } // This is the list of referenced assemblies. Ionic.Zip is needed here. // Also if it is the winforms (gui) extractor, we need other referenced assemblies, // like System.Windows.Forms.dll, etc. System.CodeDom.Compiler.CompilerParameters cp = new System.CodeDom.Compiler.CompilerParameters(); cp.ReferencedAssemblies.Add(a1.Location); if (settings.ReferencedAssemblies != null) { foreach (string ra in settings.ReferencedAssemblies) { cp.ReferencedAssemblies.Add(ra); } } cp.GenerateInMemory = false; cp.GenerateExecutable = true; cp.IncludeDebugInformation = false; cp.CompilerOptions = ""; Assembly a2 = Assembly.GetExecutingAssembly(); if (nameOfIconFile == null) { removeIconFile = true; nameOfIconFile = GenerateTempPathname("ico", null); ExtractResourceToFile(a2, "Ionic.Zip.Resources.zippedFile.ico", nameOfIconFile); cp.CompilerOptions += String.Format("/win32icon:\"{0}\"", nameOfIconFile); } else if (nameOfIconFile != "") { cp.CompilerOptions += String.Format("/win32icon:\"{0}\"", nameOfIconFile); } //cp.IncludeDebugInformation = true; cp.OutputAssembly = StubExe; if (flavor == SelfExtractorFlavor.WinFormsApplication) { cp.CompilerOptions += " /target:winexe"; } if (cp.CompilerOptions == "") { cp.CompilerOptions = null; } TempDir = GenerateTempPathname("tmp", null); if ((settings.CopyThroughResources != null) && (settings.CopyThroughResources.Count != 0)) { System.IO.Directory.CreateDirectory(TempDir); foreach (string re in settings.CopyThroughResources) { string filename = Path.Combine(TempDir, re); ExtractResourceToFile(a2, re, filename); // add the file into the target assembly as an embedded resource cp.EmbeddedResources.Add(filename); } } // add the Ionic.Utils.Zip DLL as an embedded resource cp.EmbeddedResources.Add(a1.Location); //Console.WriteLine("Resources in this assembly:"); //foreach (string rsrc in a2.GetManifestResourceNames()) //{ // Console.WriteLine(rsrc); //} //Console.WriteLine(); //Console.WriteLine("reading source code resources:"); // concatenate all the source code resources into a single module var sb = new System.Text.StringBuilder(); // assembly attributes sb.Append("[assembly: System.Reflection.AssemblyTitle(\"DotNetZip SFX Archive\")]\n"); sb.Append("[assembly: System.Reflection.AssemblyProduct(\"ZipLibrary\")]\n"); sb.Append("[assembly: System.Reflection.AssemblyCopyright(\"Copyright © Dino Chiesa 2008, 2009\")]\n"); sb.Append(String.Format("[assembly: System.Reflection.AssemblyVersion(\"{0}\")]\n\n", ZipFile.LibraryVersion.ToString())); // Set the default extract location if it is available, and if supported. bool haveLocation = (defaultExtractLocation != null); if (haveLocation) { defaultExtractLocation = defaultExtractLocation.Replace("\"", "").Replace("\\", "\\\\"); } foreach (string rc in settings.ResourcesToCompile) { //Console.WriteLine(" trying to read stream: ({0})", rc); Stream s = a2.GetManifestResourceStream(rc); if (s == null) { throw new ZipException(String.Format("missing resource '{0}'", rc)); } using (StreamReader sr = new StreamReader(s)) { while (sr.Peek() >= 0) { string line = sr.ReadLine(); if (haveLocation) { line = line.Replace("@@EXTRACTLOCATION", defaultExtractLocation); } if (postExtractCmdLine != null) { line = line.Replace("@@POST_UNPACK_CMD_LINE", postExtractCmdLine.Replace("\\", "\\\\")); } sb.Append(line).Append("\n"); } } sb.Append("\n\n"); } string LiteralSource = sb.ToString(); #if DEBUGSFX // for debugging only string sourceModule = GenerateTempPathname("cs", null); using (StreamWriter sw = File.CreateText(sourceModule)) { sw.Write(LiteralSource); } Console.WriteLine("source: {0}", sourceModule); #endif System.CodeDom.Compiler.CompilerResults cr = csharp.CompileAssemblyFromSource(cp, LiteralSource); if (cr == null) { throw new SfxGenerationException("Cannot compile the extraction logic!"); } if (Verbose) { foreach (string output in cr.Output) { StatusMessageTextWriter.WriteLine(output); } } if (cr.Errors.Count != 0) { //Console.ReadLine(); string sourcefile = GenerateTempPathname("cs", null); using (TextWriter tw = new StreamWriter(sourcefile)) { tw.Write(LiteralSource); } throw new SfxGenerationException(String.Format("Errors compiling the extraction logic! {0}", sourcefile)); } OnSaveEvent(ZipProgressEventType.Saving_AfterCompileSelfExtractor); // Now, copy the resulting EXE image to the _writestream. // Because this stub exe is being saved first, the effect will be to // concatenate the exe and the zip data together. using (System.IO.Stream input = System.IO.File.OpenRead(StubExe)) { byte[] buffer = new byte[4000]; int n = 1; while (n != 0) { n = input.Read(buffer, 0, buffer.Length); if (n != 0) { WriteStream.Write(buffer, 0, n); } } } OnSaveEvent(ZipProgressEventType.Saving_AfterSaveTempArchive); } finally { try { if (Directory.Exists(TempDir)) { try { Directory.Delete(TempDir, true); } catch { } } if (File.Exists(StubExe)) { try { File.Delete(StubExe); } catch { } } if (removeIconFile && File.Exists(nameOfIconFile)) { try { File.Delete(nameOfIconFile); } catch { } } } catch { } } return; }
private void _SaveSfxStub(string exeToGenerate, SelfExtractorSaveOptions options) { string nameOfIconFile = null; string stubExe = null; string unpackedResourceDir = null; string tmpDir = null; try { if (File.Exists(exeToGenerate)) { if (Verbose) { StatusMessageTextWriter.WriteLine("The existing file ({0}) will be overwritten.", exeToGenerate); } } if (!exeToGenerate.EndsWith(".exe")) { if (Verbose) { StatusMessageTextWriter.WriteLine("Warning: The generated self-extracting file will not have an .exe extension."); } } // workitem 10553 tmpDir = TempFileFolder ?? Path.GetDirectoryName(exeToGenerate); stubExe = GenerateTempPathname(tmpDir, "exe"); // get the Ionic.Zip assembly Assembly a1 = typeof(ZipFile).Assembly; using (var csharp = new Microsoft.CSharp.CSharpCodeProvider (new Dictionary <string, string>() { { "CompilerVersion", "v2.0" } })) { // The following is a perfect opportunity for a linq query, but // I cannot use it. DotNetZip needs to run on .NET 2.0, // and using LINQ would break that. Here's what it would look // like: // // var settings = (from x in SettingsList // where x.Flavor == flavor // select x).First(); ExtractorSettings settings = null; foreach (var x in SettingsList) { if (x.Flavor == options.Flavor) { settings = x; break; } } // sanity check; should never happen if (settings == null) { throw new BadStateException(String.Format("While saving a Self-Extracting Zip, Cannot find that flavor ({0})?", options.Flavor)); } // This is the list of referenced assemblies. Ionic.Zip is // needed here. Also if it is the winforms (gui) extractor, we // need other referenced assemblies, like // System.Windows.Forms.dll, etc. var cp = new System.CodeDom.Compiler.CompilerParameters(); cp.ReferencedAssemblies.Add(a1.Location); if (settings.ReferencedAssemblies != null) { foreach (string ra in settings.ReferencedAssemblies) { cp.ReferencedAssemblies.Add(ra); } } cp.GenerateInMemory = false; cp.GenerateExecutable = true; cp.IncludeDebugInformation = false; cp.CompilerOptions = ""; Assembly a2 = Assembly.GetExecutingAssembly(); // Use this to concatenate all the source code resources into a // single module. var sb = new System.Text.StringBuilder(); // In case there are compiler errors later, we allocate a source // file name now. If errors are detected, we'll spool the source // code as well as the errors (in comments) into that filename, // and throw an exception with the filename. Makes it easier to // diagnose. This should be rare; most errors happen only // during devlpmt of DotNetZip itself, but there are rare // occasions when they occur in other cases. string sourceFile = GenerateTempPathname(tmpDir, "cs"); // // debugging: enumerate the resources in this assembly // Console.WriteLine("Resources in this assembly:"); // foreach (string rsrc in a2.GetManifestResourceNames()) // { // Console.WriteLine(rsrc); // } // Console.WriteLine(); // all the source code is embedded in the DLL as a zip file. using (ZipFile zip = ZipFile.Read(a2.GetManifestResourceStream("Ionic.Zip.Resources.ZippedResources.zip"))) { // // debugging: enumerate the files in the embedded zip // Console.WriteLine("Entries in the embbedded zip:"); // foreach (ZipEntry entry in zip) // { // Console.WriteLine(entry.FileName); // } // Console.WriteLine(); unpackedResourceDir = GenerateTempPathname(tmpDir, "tmp"); if (String.IsNullOrEmpty(options.IconFile)) { // Use the ico file that is embedded into the Ionic.Zip // DLL itself. To do this we must unpack the icon to // the filesystem, in order to specify it on the cmdline // of csc.exe. This method will remove the unpacked // file later. System.IO.Directory.CreateDirectory(unpackedResourceDir); ZipEntry e = zip["zippedFile.ico"]; // Must not extract a readonly file - it will be impossible to // delete later. if ((e.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) { e.Attributes ^= FileAttributes.ReadOnly; } e.Extract(unpackedResourceDir); nameOfIconFile = Path.Combine(unpackedResourceDir, "zippedFile.ico"); cp.CompilerOptions += String.Format("/win32icon:\"{0}\"", nameOfIconFile); } else { cp.CompilerOptions += String.Format("/win32icon:\"{0}\"", options.IconFile); } cp.OutputAssembly = stubExe; if (options.Flavor == SelfExtractorFlavor.WinFormsApplication) { cp.CompilerOptions += " /target:winexe"; } if (!String.IsNullOrEmpty(options.AdditionalCompilerSwitches)) { cp.CompilerOptions += " " + options.AdditionalCompilerSwitches; } if (String.IsNullOrEmpty(cp.CompilerOptions)) { cp.CompilerOptions = null; } if ((settings.CopyThroughResources != null) && (settings.CopyThroughResources.Count != 0)) { if (!Directory.Exists(unpackedResourceDir)) { System.IO.Directory.CreateDirectory(unpackedResourceDir); } foreach (string re in settings.CopyThroughResources) { string filename = Path.Combine(unpackedResourceDir, re); ExtractResourceToFile(a2, re, filename); // add the file into the target assembly as an embedded resource cp.EmbeddedResources.Add(filename); } } // add the Ionic.Utils.Zip DLL as an embedded resource cp.EmbeddedResources.Add(a1.Location); // file header sb.Append("// " + Path.GetFileName(sourceFile) + "\n") .Append("// --------------------------------------------\n//\n") .Append("// This SFX source file was generated by DotNetZip ") .Append(ZipFile.LibraryVersion.ToString()) .Append("\n// at ") .Append(System.DateTime.Now.ToString("yyyy MMMM dd HH:mm:ss")) .Append("\n//\n// --------------------------------------------\n\n\n"); // assembly attributes if (!String.IsNullOrEmpty(options.Description)) { sb.Append("[assembly: System.Reflection.AssemblyTitle(\"" + options.Description.Replace("\"", "") + "\")]\n"); } else { sb.Append("[assembly: System.Reflection.AssemblyTitle(\"DotNetZip SFX Archive\")]\n"); } if (!String.IsNullOrEmpty(options.ProductVersion)) { sb.Append("[assembly: System.Reflection.AssemblyInformationalVersion(\"" + options.ProductVersion.Replace("\"", "") + "\")]\n"); } // workitem string copyright = (String.IsNullOrEmpty(options.Copyright)) ? "Extractor: Copyright � Dino Chiesa 2008-2011" : options.Copyright.Replace("\"", ""); if (!String.IsNullOrEmpty(options.ProductName)) { sb.Append("[assembly: System.Reflection.AssemblyProduct(\"") .Append(options.ProductName.Replace("\"", "")) .Append("\")]\n"); } else { sb.Append("[assembly: System.Reflection.AssemblyProduct(\"DotNetZip\")]\n"); } sb.Append("[assembly: System.Reflection.AssemblyCopyright(\"" + copyright + "\")]\n") .Append(String.Format("[assembly: System.Reflection.AssemblyVersion(\"{0}\")]\n", ZipFile.LibraryVersion.ToString())); if (options.FileVersion != null) { sb.Append(String.Format("[assembly: System.Reflection.AssemblyFileVersion(\"{0}\")]\n", options.FileVersion.ToString())); } sb.Append("\n\n\n"); // Set the default extract location if it is available string extractLoc = options.DefaultExtractDirectory; if (extractLoc != null) { // remove double-quotes and replace slash with double-slash. // This, because the value is going to be embedded into a // cs file as a quoted string, and it needs to be escaped. extractLoc = extractLoc.Replace("\"", "").Replace("\\", "\\\\"); } string postExCmdLine = options.PostExtractCommandLine; if (postExCmdLine != null) { postExCmdLine = postExCmdLine.Replace("\\", "\\\\"); postExCmdLine = postExCmdLine.Replace("\"", "\\\""); } foreach (string rc in settings.ResourcesToCompile) { using (Stream s = zip[rc].OpenReader()) { if (s == null) { throw new ZipException(String.Format("missing resource '{0}'", rc)); } using (StreamReader sr = new StreamReader(s)) { while (sr.Peek() >= 0) { string line = sr.ReadLine(); if (extractLoc != null) { line = line.Replace("@@EXTRACTLOCATION", extractLoc); } line = line.Replace("@@REMOVE_AFTER_EXECUTE", options.RemoveUnpackedFilesAfterExecute.ToString()); line = line.Replace("@@QUIET", options.Quiet.ToString()); if (!String.IsNullOrEmpty(options.SfxExeWindowTitle)) { line = line.Replace("@@SFX_EXE_WINDOW_TITLE", options.SfxExeWindowTitle); } line = line.Replace("@@EXTRACT_EXISTING_FILE", ((int)options.ExtractExistingFile).ToString()); if (postExCmdLine != null) { line = line.Replace("@@POST_UNPACK_CMD_LINE", postExCmdLine); } sb.Append(line).Append("\n"); } } sb.Append("\n\n"); } } } string LiteralSource = sb.ToString(); #if DEBUGSFX // for debugging only string sourceModule = GenerateTempPathname(tmpDir, "cs"); using (StreamWriter sw = File.CreateText(sourceModule)) { sw.Write(LiteralSource); } Console.WriteLine("source: {0}", sourceModule); #endif var cr = csharp.CompileAssemblyFromSource(cp, LiteralSource); if (cr == null) { throw new SfxGenerationException("Cannot compile the extraction logic!"); } if (Verbose) { foreach (string output in cr.Output) { StatusMessageTextWriter.WriteLine(output); } } if (cr.Errors.Count != 0) { using (TextWriter tw = new StreamWriter(sourceFile)) { // first, the source we compiled tw.Write(LiteralSource); // now, append the compile errors tw.Write("\n\n\n// ------------------------------------------------------------------\n"); tw.Write("// Errors during compilation: \n//\n"); string p = Path.GetFileName(sourceFile); foreach (System.CodeDom.Compiler.CompilerError error in cr.Errors) { tw.Write(String.Format("// {0}({1},{2}): {3} {4}: {5}\n//\n", p, // 0 error.Line, // 1 error.Column, // 2 error.IsWarning ? "Warning" : "error", // 3 error.ErrorNumber, // 4 error.ErrorText)); // 5 } } throw new SfxGenerationException(String.Format("Errors compiling the extraction logic! {0}", sourceFile)); } OnSaveEvent(ZipProgressEventType.Saving_AfterCompileSelfExtractor); // Now, copy the resulting EXE image to the _writestream. // Because this stub exe is being saved first, the effect will be to // concatenate the exe and the zip data together. using (System.IO.Stream input = System.IO.File.OpenRead(stubExe)) { byte[] buffer = new byte[4000]; int n = 1; while (n != 0) { n = input.Read(buffer, 0, buffer.Length); if (n != 0) { WriteStream.Write(buffer, 0, n); } } } } OnSaveEvent(ZipProgressEventType.Saving_AfterSaveTempArchive); } finally { try { if (Directory.Exists(unpackedResourceDir)) { try { Directory.Delete(unpackedResourceDir, true); } catch (System.IO.IOException exc1) { StatusMessageTextWriter.WriteLine("Warning: Exception: {0}", exc1); } } if (File.Exists(stubExe)) { try { File.Delete(stubExe); } catch (System.IO.IOException exc1) { StatusMessageTextWriter.WriteLine("Warning: Exception: {0}", exc1); } } } catch (System.IO.IOException) { } } return; }
/// <summary> /// Saves the Zip archive to a file, specified by the Name property of the /// <c>ZipFile</c>. /// </summary> /// /// <remarks> /// <para> /// The <c>ZipFile</c> instance is written to storage, typically a zip file /// in a filesystem, only when the caller calls <c>Save</c>. In the typical /// case, the Save operation writes the zip content to a temporary file, and /// then renames the temporary file to the desired name. If necessary, this /// method will delete a pre-existing file before the rename. /// </para> /// /// <para> /// The <see cref="ZipFile.Name"/> property is specified either explicitly, /// or implicitly using one of the parameterized ZipFile constructors. For /// COM Automation clients, the <c>Name</c> property must be set explicitly, /// because COM Automation clients cannot call parameterized constructors. /// </para> /// /// <para> /// When using a filesystem file for the Zip output, it is possible to call /// <c>Save</c> multiple times on the <c>ZipFile</c> instance. With each /// call the zip content is re-written to the same output file. /// </para> /// /// <para> /// Data for entries that have been added to the <c>ZipFile</c> instance is /// written to the output when the <c>Save</c> method is called. This means /// that the input streams for those entries must be available at the time /// the application calls <c>Save</c>. If, for example, the application /// adds entries with <c>AddEntry</c> using a dynamically-allocated /// <c>MemoryStream</c>, the memory stream must not have been disposed /// before the call to <c>Save</c>. See the <see /// cref="ZipEntry.InputStream"/> property for more discussion of the /// availability requirements of the input stream for an entry, and an /// approach for providing just-in-time stream lifecycle management. /// </para> /// /// </remarks> /// /// <seealso cref="Ionic.Zip.ZipFile.AddEntry(String, System.IO.Stream)"/> /// /// <exception cref="Ionic.Zip.BadStateException"> /// Thrown if you haven't specified a location or stream for saving the zip, /// either in the constructor or by setting the Name property, or if you try /// to save a regular zip archive to a filename with a .exe extension. /// </exception> /// /// <exception cref="System.OverflowException"> /// Thrown if <see cref="MaxOutputSegmentSize"/> is non-zero, and the number /// of segments that would be generated for the spanned zip file during the /// save operation exceeds 99. If this happens, you need to increase the /// segment size. /// </exception> /// 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); // 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; } if (_fileAlreadyExists && this._readstream != null) { // This means we opened and read a zip file. // If we are now saving to the same file, we need to close the // orig file, first. this._readstream.Close(); this._readstream.Dispose(); 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; } } if (_fileAlreadyExists && this._writestream != null) { this.WriteStream.Close(); } 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); } try { File.Move(_name, tmpName); } catch { if (System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debugger.Break(); //Look at the comment below. Added this to force a break here instead of your catch so you don't miss the cause and waste your time like I did } throw; } //Oh boy. You've just hit an exception on that File.Move line right above this stating that the template file can't be renamed because it's open //in another process, right? //Congratulation! You've probably run into the same gibberish I ran into! //Check to make sure your file streams are closed, and ITpipes/Template Editor/Winrar/7-Zip don't have the template file open. I'm betting you won't find anything. //If you try closing Visual Studio and running the binary directly I'm betting you won't have this crash occur. Go ahead--try it. //What the hell, right? //The XAML designer process (XDesProc.exe) is configured, by default, to populate its ViewModel in the designer UI. This creates a copy of the model--which creates a second //Ionic.Zip.ZipFile object using the same template file. So when *this* ZipFile object attempts to move the existing template to replace it with an updated file //the exception is thrown. //To fix this, disable the following option in the Visual Studio toolbar above: // Tools -> Options -> XAML Designer -> "Run project code in XAML Designer (if supported)" //Once that's disabled, restart Visual Studio. Problem solved. //Hopefully these comments will prevent someone else from wasting their time obsessing over their streams and AntiVirus. - Vincent Nary } 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; } NotifyEntriesSaveComplete(c); OnSaveCompleted(); _JustSaved = true; } // workitem 5043 finally { CleanupAfterSaveOperation(); } return; }
/// <summary> /// Saves the Zip archive to a file, specified by the Name property of the /// <c>ZipFile</c>. /// </summary> /// /// <remarks> /// <para> /// The <c>ZipFile</c> instance is written to storage, typically a zip file /// in a filesystem, only when the caller calls <c>Save</c>. In the typical /// case, the Save operation writes the zip content to a temporary file, and /// then renames the temporary file to the desired name. If necessary, this /// method will delete a pre-existing file before the rename. /// </para> /// /// <para> /// The <see cref="ZipFile.Name"/> property is specified either explicitly, /// or implicitly using one of the parameterized ZipFile constructors. For /// COM Automation clients, the <c>Name</c> property must be set explicitly, /// because COM Automation clients cannot call parameterized constructors. /// </para> /// /// <para> /// When using a filesystem file for the Zip output, it is possible to call /// <c>Save</c> multiple times on the <c>ZipFile</c> instance. With each /// call the zip content is re-written to the same output file. /// </para> /// /// <para> /// Data for entries that have been added to the <c>ZipFile</c> instance is /// written to the output when the <c>Save</c> method is called. This means /// that the input streams for those entries must be available at the time /// the application calls <c>Save</c>. If, for example, the application /// adds entries with <c>AddEntry</c> using a dynamically-allocated /// <c>MemoryStream</c>, the memory stream must not have been disposed /// before the call to <c>Save</c>. See the <see /// cref="ZipEntry.InputStream"/> property for more discussion of the /// availability requirements of the input stream for an entry, and an /// approach for providing just-in-time stream lifecycle management. /// </para> /// /// </remarks> /// /// <seealso cref="Ionic.Zip.ZipFile.AddEntry(String, System.IO.Stream)"/> /// /// <exception cref="Ionic.Zip.BadStateException"> /// Thrown if you haven't specified a location or stream for saving the zip, /// either in the constructor or by setting the Name property, or if you try /// to save a regular zip archive to a filename with a .exe extension. /// </exception> /// /// <exception cref="System.OverflowException"> /// Thrown if <see cref="MaxOutputSegmentSize"/> is non-zero, and the number /// of segments that would be generated for the spanned zip file during the /// save operation exceeds 99. If this happens, you need to increase the /// segment size. /// </exception> /// 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; }
private void _AddOrUpdateSelectedFiles(String selectionCriteria, String directoryOnDisk, String directoryPathInArchive, bool recurseDirectories, bool wantUpdate) { if (directoryOnDisk == null && (Directory.Exists(selectionCriteria))) { directoryOnDisk = selectionCriteria; selectionCriteria = "*.*"; } else if (String.IsNullOrEmpty(directoryOnDisk)) { directoryOnDisk = "."; } // workitem 9176 while (directoryOnDisk.EndsWith("\\")) { directoryOnDisk = directoryOnDisk.Substring(0, directoryOnDisk.Length - 1); } if (Verbose) { StatusMessageTextWriter.WriteLine("adding selection '{0}' from dir '{1}'...", selectionCriteria, directoryOnDisk); } Ionic.FileSelector ff = new Ionic.FileSelector(selectionCriteria, AddDirectoryWillTraverseReparsePoints); var itemsToAdd = ff.SelectFiles(directoryOnDisk, recurseDirectories); if (Verbose) { StatusMessageTextWriter.WriteLine("found {0} files...", itemsToAdd.Count); } OnAddStarted(); AddOrUpdateAction action = (wantUpdate) ? AddOrUpdateAction.AddOrUpdate : AddOrUpdateAction.AddOnly; foreach (var item in itemsToAdd) { // workitem 10153 string dirInArchive = (directoryPathInArchive == null) ? null // workitem 12260 : ReplaceLeadingDirectory(Path.GetDirectoryName(item), directoryOnDisk, directoryPathInArchive); if (File.Exists(item)) { if (wantUpdate) { this.UpdateFile(item, dirInArchive); } else { this.AddFile(item, dirInArchive); } } else { // this adds "just" the directory, without recursing to the contained files AddOrUpdateDirectoryImpl(item, dirInArchive, action, false, 0); } } OnAddCompleted(); }