// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
namespace WixToolset.Data
{
using System;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using System.Text;
///
/// Class that understands the standard file structure of the WiX toolset.
///
public class WixOutput : IDisposable
{
private readonly Stream stream;
private ZipArchive archive;
private bool disposed;
private WixOutput(Uri uri, ZipArchive archive, Stream stream)
{
this.Uri = uri;
this.archive = archive;
this.stream = stream;
}
///
///
///
public Uri Uri { get; }
///
/// Creates a new file structure in memory.
///
/// Newly created WixOutput.
public static WixOutput Create()
{
var uri = new Uri("memorystream:");
var stream = new MemoryStream();
return WixOutput.Create(uri, stream);
}
///
/// Creates a new file structure on disk.
///
/// Path to write file structure to.
/// Newly created WixOutput.
public static WixOutput Create(string path)
{
var fullPath = Path.GetFullPath(path);
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
var uri = new Uri(fullPath);
var stream = File.Create(path);
return WixOutput.Create(uri, stream);
}
///
/// Creates a new file structure.
///
///
/// Stream to write the file structure to.
/// Newly created WixOutput.
public static WixOutput Create(Uri uri, Stream stream)
{
var archive = new ZipArchive(stream, ZipArchiveMode.Update, leaveOpen: true);
return new WixOutput(uri, archive, stream);
}
///
/// Loads a wixout from a path on disk.
///
/// Path to wixout file saved on disk.
/// Loaded created WixOutput.
public static WixOutput Read(string path)
{
var uri = new Uri(Path.GetFullPath(path));
var stream = File.OpenRead(path);
return Read(uri, stream);
}
///
/// Loads a wixout from a path on disk or embedded resource in assembly.
///
/// Uri with local path to wixout file saved on disk or embedded resource in assembly.
/// Loaded created WixOutput.
public static WixOutput Read(Uri baseUri)
{
// If the embedded files are stored in an assembly resource stream (usually
// a .wixlib embedded in a WixExtension).
if ("embeddedresource" == baseUri.Scheme)
{
var assemblyPath = Path.GetFullPath(baseUri.LocalPath);
var resourceName = baseUri.Fragment.TrimStart('#');
var assembly = Assembly.LoadFile(assemblyPath);
return WixOutput.Read(assembly, resourceName);
}
else // normal file (usually a binary .wixlib on disk).
{
var stream = File.OpenRead(baseUri.LocalPath);
return WixOutput.Read(baseUri, stream);
}
}
///
/// Loads a wixout from an assembly resource stream.
///
///
///
/// Loaded created WixOutput.
public static WixOutput Read(Assembly assembly, string resourceName)
{
var resourceStream = assembly.GetManifestResourceStream(resourceName);
var uriBuilder = new UriBuilder(assembly.CodeBase)
{
Scheme = "embeddedresource",
Fragment = resourceName
};
return Read(uriBuilder.Uri, resourceStream);
}
///
/// Reads a file structure from an open stream.
///
///
/// Stream to read from.
/// Loaded created WixOutput.
public static WixOutput Read(Uri uri, Stream stream)
{
try
{
var archive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen: true);
return new WixOutput(uri, archive, stream);
}
catch (InvalidDataException)
{
throw new WixException(ErrorMessages.CorruptFileFormat(uri.AbsoluteUri, "wixout"));
}
}
///
/// Reopen the underlying archive for read-only or read-write access.
///
/// Indicates whether the output can be modified. Defaults to false.
public void Reopen(bool writable = false)
{
this.archive?.Dispose();
this.archive = null;
this.archive = new ZipArchive(this.stream, writable ? ZipArchiveMode.Update : ZipArchiveMode.Read, leaveOpen: true);
}
///
/// Extracts an embedded file.
///
/// Id to the file to extract.
/// Path to write the extracted file to.
public void ExtractEmbeddedFile(string embeddedId, string outputPath)
{
var entry = this.archive.GetEntry(embeddedId);
if (entry == null)
{
throw new ArgumentOutOfRangeException(nameof(embeddedId));
}
var folder = Path.GetDirectoryName(outputPath);
Directory.CreateDirectory(folder);
entry.ExtractToFile(outputPath, overwrite: true);
}
///
/// Creates a data stream in the wixout.
///
/// Stream to the data of the file.
public Stream CreateDataStream(string name)
{
this.DeleteExistingEntry(name);
var entry = this.archive.CreateEntry(name);
return entry.Open();
}
///
/// Imports a file from disk into the output.
///
/// Name of the stream in the output.
/// Path to file on disk to include in the output.
public void ImportDataStream(string name, string path)
{
this.DeleteExistingEntry(name);
this.archive.CreateEntryFromFile(path, name, System.IO.Compression.CompressionLevel.Optimal);
}
///
/// Gets a non-closing stream to the data of the file.
///
/// Stream to the data of the file.
public Stream GetDataStream(string name)
{
var entry = this.archive.GetEntry(name);
if (entry == null)
{
throw new ArgumentOutOfRangeException(nameof(name));
}
return entry.Open();
}
///
/// Gets the data of the file as a string.
///
/// String contents data of the file.
public string GetData(string name)
{
var entry = this.archive.GetEntry(name);
// Use StreamReader to "swallow" BOM if present.
using (var stream = entry.Open())
using (var streamReader = new StreamReader(stream, Encoding.UTF8))
{
return streamReader.ReadToEnd();
}
}
///
/// Disposes of the internal state of the file structure.
///
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Disposes of the internsl state of the file structure.
///
/// True if disposing.
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
this.archive?.Dispose();
this.stream?.Dispose();
}
}
this.disposed = true;
}
private void DeleteExistingEntry(string name)
{
var entry = this.archive.GetEntry(name);
if (entry != null)
{
entry.Delete();
}
}
}
}