// 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
{
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using WixToolset.Data;
using WixToolset.Extensibility;
#if DEAD_CODE
///
/// Base class for creating a binder file manager.
///
public class BinderFileManager : IBinderFileManager
{
///
/// Gets or sets the file manager core.
///
public IBinderFileManagerCore Core { get; set; }
///
/// Compares two files to determine if they are equivalent.
///
/// The target file.
/// The updated file.
/// true if the files are equal; false otherwise.
public virtual bool? CompareFiles(string targetFile, string updatedFile)
{
FileInfo targetFileInfo = new FileInfo(targetFile);
FileInfo updatedFileInfo = new FileInfo(updatedFile);
if (targetFileInfo.Length != updatedFileInfo.Length)
{
return false;
}
using (FileStream targetStream = File.OpenRead(targetFile))
{
using (FileStream updatedStream = File.OpenRead(updatedFile))
{
if (targetStream.Length != updatedStream.Length)
{
return false;
}
// Using a larger buffer than the default buffer of 4 * 1024 used by FileStream.ReadByte improves performance.
// The buffer size is based on user feedback. Based on performance results, a better buffer size may be determined.
byte[] targetBuffer = new byte[16 * 1024];
byte[] updatedBuffer = new byte[16 * 1024];
int targetReadLength;
int updatedReadLength;
do
{
targetReadLength = targetStream.Read(targetBuffer, 0, targetBuffer.Length);
updatedReadLength = updatedStream.Read(updatedBuffer, 0, updatedBuffer.Length);
if (targetReadLength != updatedReadLength)
{
return false;
}
for (int i = 0; i < targetReadLength; ++i)
{
if (targetBuffer[i] != updatedBuffer[i])
{
return false;
}
}
} while (0 < targetReadLength);
}
}
return true;
}
///
/// Resolves the source path of a file.
///
/// Original source value.
/// Optional type of source file being resolved.
/// Optional source line of source file being resolved.
/// The binding stage used to determine what collection of bind paths will be used
/// Should return a valid path for the stream to be imported.
public virtual string ResolveFile(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage)
{
if (String.IsNullOrEmpty(source))
{
throw new ArgumentNullException("source");
}
if (BinderFileManager.CheckFileExists(source)) // if the file exists, we're good to go.
{
return source;
}
else if (Path.IsPathRooted(source)) // path is rooted so bindpaths won't help, bail since the file apparently doesn't exist.
{
return null;
}
else // not a rooted path so let's try applying all the different source resolution options.
{
const string bindPathOpenString = "!(bindpath.";
string bindName = String.Empty;
string path = source;
string pathWithoutSourceDir = null;
if (source.StartsWith(bindPathOpenString, StringComparison.Ordinal))
{
int closeParen = source.IndexOf(')', bindPathOpenString.Length);
if (-1 != closeParen)
{
bindName = source.Substring(bindPathOpenString.Length, closeParen - bindPathOpenString.Length);
path = source.Substring(bindPathOpenString.Length + bindName.Length + 1); // +1 for the closing brace.
path = path.TrimStart('\\'); // remove starting '\\' char so the path doesn't look rooted.
}
}
else if (source.StartsWith("SourceDir\\", StringComparison.Ordinal) || source.StartsWith("SourceDir/", StringComparison.Ordinal))
{
pathWithoutSourceDir = path.Substring(10);
}
var bindPaths = this.Core.GetBindPaths(bindStage, bindName);
foreach (string bindPath in bindPaths)
{
string filePath;
if (!String.IsNullOrEmpty(pathWithoutSourceDir))
{
filePath = Path.Combine(bindPath, pathWithoutSourceDir);
if (BinderFileManager.CheckFileExists(filePath))
{
return filePath;
}
}
filePath = Path.Combine(bindPath, path);
if (BinderFileManager.CheckFileExists(filePath))
{
return filePath;
}
}
}
// Didn't find the file.
return null;
}
///
/// Resolves the source path of a file related to another file's source.
///
/// Original source value.
/// Source related to original source.
/// Optional type of source file being resolved.
/// Optional source line of source file being resolved.
/// The binding stage used to determine what collection of bind paths will be used
/// Should return a valid path for the stream to be imported.
public virtual string ResolveRelatedFile(string source, string relatedSource, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage)
{
string resolvedSource = this.ResolveFile(source, type, sourceLineNumbers, bindStage);
return Path.Combine(Path.GetDirectoryName(resolvedSource), relatedSource);
}
///
/// Resolves the source path of a cabinet file.
///
/// Default path to cabinet to generate.
/// Collection of files in this cabinet.
/// The CabinetBuildOption and path to build the . By default the cabinet is built and moved to its target location.
[SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")]
public virtual ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable filesWithPath)
{
if (null == filesWithPath)
{
throw new ArgumentNullException("fileRows");
}
// By default cabinet should be built and moved to the suggested location.
ResolvedCabinet resolved = new ResolvedCabinet() { BuildOption = CabinetBuildOption.BuildAndMove, Path = cabinetPath };
// If a cabinet cache path was provided, change the location for the cabinet
// to be built to and check if there is a cabinet that can be reused.
if (!String.IsNullOrEmpty(this.Core.CabCachePath))
{
string cabinetName = Path.GetFileName(cabinetPath);
resolved.Path = Path.Combine(this.Core.CabCachePath, cabinetName);
if (BinderFileManager.CheckFileExists(resolved.Path))
{
// Assume that none of the following are true:
// 1. any files are added or removed
// 2. order of files changed or names changed
// 3. modified time changed
bool cabinetValid = true;
// Need to force garbage collection of WixEnumerateCab to ensure the handle
// associated with it is closed before it is reused.
using (Cab.WixEnumerateCab wixEnumerateCab = new Cab.WixEnumerateCab())
{
List fileList = wixEnumerateCab.Enumerate(resolved.Path);
if (filesWithPath.Count() != fileList.Count)
{
cabinetValid = false;
}
else
{
int i = 0;
foreach (BindFileWithPath file in filesWithPath)
{
// First check that the file identifiers match because that is quick and easy.
CabinetFileInfo cabFileInfo = fileList[i];
cabinetValid = (cabFileInfo.FileId == file.Id);
if (cabinetValid)
{
// Still valid so ensure the file sizes are the same.
FileInfo fileInfo = new FileInfo(file.Path);
cabinetValid = (cabFileInfo.Size == fileInfo.Length);
if (cabinetValid)
{
// Still valid so ensure the source time stamp hasn't changed. Thus we need
// to convert the source file time stamp into a cabinet compatible data/time.
ushort sourceCabDate;
ushort sourceCabTime;
WixToolset.Core.Native.CabInterop.DateTimeToCabDateAndTime(fileInfo.LastWriteTime, out sourceCabDate, out sourceCabTime);
cabinetValid = (cabFileInfo.Date == sourceCabDate && cabFileInfo.Time == sourceCabTime);
}
}
if (!cabinetValid)
{
break;
}
i++;
}
}
}
resolved.BuildOption = cabinetValid ? CabinetBuildOption.Copy : CabinetBuildOption.BuildAndCopy;
}
}
return resolved;
}
///
/// Resolve the layout path of a media.
///
/// The media's row.
/// The layout directory provided by the Media element.
/// The layout directory for the setup image.
/// The layout path for the media.
public virtual string ResolveMedia(MediaRow mediaRow, string mediaLayoutDirectory, string layoutDirectory)
{
return null;
}
///
/// Resolves the URL to a file.
///
/// URL that may be a format string for the id and fileName.
/// Identity of the package (if payload is not part of a package) the URL points to. NULL if not part of a package.
/// Identity of the payload the URL points to.
/// File name the URL points at.
/// Optional URL to use if the URL provided is empty.
/// An absolute URL or null if no URL is provided.
public virtual string ResolveUrl(string url, string fallbackUrl, string packageId, string payloadId, string fileName)
{
// If a URL was not specified but there is a fallback URL that has a format specifier in it
// then use the fallback URL formatter for this URL.
if (String.IsNullOrEmpty(url) && !String.IsNullOrEmpty(fallbackUrl))
{
string formattedFallbackUrl = String.Format(fallbackUrl, packageId, payloadId, fileName);
if (!String.Equals(fallbackUrl, formattedFallbackUrl, StringComparison.OrdinalIgnoreCase))
{
url = fallbackUrl;
}
}
if (!String.IsNullOrEmpty(url))
{
string formattedUrl = String.Format(url, packageId, payloadId, fileName);
Uri canonicalUri;
if (Uri.TryCreate(formattedUrl, UriKind.Absolute, out canonicalUri))
{
url = canonicalUri.AbsoluteUri;
}
else
{
url = null;
}
}
return url;
}
///
/// Copies a file.
///
/// The file to copy.
/// The destination file.
/// true if the destination file can be overwritten; otherwise, false.
public virtual bool CopyFile(string source, string destination, bool overwrite)
{
if (overwrite && File.Exists(destination))
{
File.Delete(destination);
}
if (!CreateHardLink(destination, source, IntPtr.Zero))
{
#if DEBUG
int er = Marshal.GetLastWin32Error();
#endif
File.Copy(source, destination, overwrite);
}
return true;
}
///
/// Moves a file.
///
/// The file to move.
/// The destination file.
public virtual bool MoveFile(string source, string destination, bool overwrite)
{
if (overwrite && File.Exists(destination))
{
File.Delete(destination);
}
var directory = Path.GetDirectoryName(destination);
if (!String.IsNullOrEmpty(directory))
{
Directory.CreateDirectory(directory);
}
File.Move(source, destination);
return true;
}
///
/// Checks if a path exists, and throws a well known error for invalid paths.
///
/// Path to check.
/// True if path exists.
private static bool CheckFileExists(string path)
{
try
{
return File.Exists(path);
}
catch (ArgumentException)
{
throw new WixException(WixErrors.IllegalCharactersInPath(path));
}
}
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes);
}
#endif
}