// 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.Dtf.WindowsInstaller.Package { using System; using System.IO; using System.Collections; using System.Collections.Generic; using System.Globalization; /// /// Represents the installation path of a file or directory from an installer product database. /// public class InstallPath { /// /// Creates a new InstallPath, specifying a filename. /// /// The name of the file or directory. Not a full path. public InstallPath(string name) : this(name, false) { } /// /// Creates a new InstallPath, parsing out either the short or long filename. /// /// The name of the file or directory, in short|long syntax for a filename /// or targetshort|targetlong:sourceshort|sourcelong syntax for a directory. /// true to parse the short part of the combined filename; false to parse the long part public InstallPath(string name, bool useShortNames) { if(name == null) { throw new ArgumentNullException(); } this.parentPath = null; ParseName(name, useShortNames); } private void ParseName(string name, bool useShortNames) { string[] parse = name.Split(new char[] { ':' }, 3); if(parse.Length == 3) { // Syntax was targetshort:sourceshort|targetlong:sourcelong. // Chnage it to targetshort|targetlong:sourceshort|sourcelong. parse = name.Split(new char[] { ':', '|' }, 4); if(parse.Length == 4) parse = new string[] { parse[0] + '|' + parse[2], parse[1] + '|' + parse[3] }; else parse = new string[] { parse[0] + '|' + parse[1], parse[1] + '|' + parse[2] }; } string targetName = parse[0]; string sourceName = (parse.Length == 2 ? parse[1] : parse[0]); parse = targetName.Split(new char[] { '|' }, 2); if(parse.Length == 2) targetName = (useShortNames ? parse[0] : parse[1]); parse = sourceName.Split(new char[] { '|' }, 2); if(parse.Length == 2) sourceName = (useShortNames ? parse[0] : parse[1]); this.SourceName = sourceName; this.TargetName = targetName; } /// /// Gets the path of the parent directory. /// public InstallPath ParentPath { get { return parentPath; } } internal void SetParentPath(InstallPath value) { parentPath = value; ResetSourcePath(); ResetTargetPath(); } private InstallPath parentPath; /// /// Gets the set of child paths if this InstallPath object represents a a directory. /// public InstallPathCollection ChildPaths { get { if(childPaths == null) { childPaths = new InstallPathCollection(this); } return childPaths; } } private InstallPathCollection childPaths; /// /// Gets or sets the source name of the InstallPath. /// public string SourceName { get { return sourceName; } set { if(value == null) { throw new ArgumentNullException(); } sourceName = value; ResetSourcePath(); } } private string sourceName; /// /// Gets or sets the target name of the install path. /// public string TargetName { get { return targetName; } set { if(value == null) { throw new ArgumentNullException(); } targetName = value; ResetTargetPath(); } } private string targetName; /// /// Gets the full source path. /// public string SourcePath { get { if(sourcePath == null) { if(parentPath != null) { sourcePath = (sourceName.Equals(".") ? parentPath.SourcePath : Path.Combine(parentPath.SourcePath, sourceName)); } else { sourcePath = sourceName; } } return sourcePath; } set { ResetSourcePath(); sourcePath = value; } } private string sourcePath; /// /// Gets the full target path. /// public string TargetPath { get { if(targetPath == null) { if(parentPath != null) { targetPath = (targetName.Equals(".") ? parentPath.TargetPath : Path.Combine(parentPath.TargetPath, targetName)); } else { targetPath = targetName; } } return targetPath; } set { ResetTargetPath(); targetPath = value; } } private string targetPath; private void ResetSourcePath() { if(sourcePath != null) { sourcePath = null; if(childPaths != null) { foreach(InstallPath ip in childPaths) { ip.ResetSourcePath(); } } } } private void ResetTargetPath() { if(targetPath != null) { targetPath = null; if(childPaths != null) { foreach(InstallPath ip in childPaths) { ip.ResetTargetPath(); } } } } /// /// Gets the full source path. /// /// public override String ToString() { return SourcePath; } } /// /// Represents a collection of InstallPaths that are the child paths of the same parent directory. /// public class InstallPathCollection : IList { private InstallPath parentPath; private List items; internal InstallPathCollection(InstallPath parentPath) { this.parentPath = parentPath; this.items = new List(); } /// /// Gets or sets the element at the specified index. /// public InstallPath this[int index] { get { return this.items[index]; } set { this.OnSet(this.items[index], value); this.items[index] = value; } } /// /// Adds a new child path to the collection. /// /// The InstallPath to add. public void Add(InstallPath item) { this.OnInsert(item); this.items.Add(item); } /// /// Removes a child path to the collection. /// /// The InstallPath to remove. public bool Remove(InstallPath item) { int index = this.items.IndexOf(item); if (index >= 0) { this.OnRemove(item); this.items.RemoveAt(index); return true; } else { return false; } } /// /// Gets the index of a child path in the collection. /// /// The InstallPath to search for. /// The index of the item, or -1 if not found. public int IndexOf(InstallPath item) { return this.items.IndexOf(item); } /// /// Inserts a child path into the collection. /// /// The insertion index. /// The InstallPath to insert. public void Insert(int index, InstallPath item) { this.OnInsert(item); this.items.Insert(index, item); } /// /// Tests if the collection contains a child path. /// /// The InstallPath to search for. /// true if the item is found; false otherwise public bool Contains(InstallPath item) { return this.items.Contains(item); } /// /// Copies the collection into an array. /// /// The array to copy into. /// The starting index in the destination array. public void CopyTo(InstallPath[] array, int index) { this.items.CopyTo(array, index); } private void OnInsert(InstallPath item) { if (item.ParentPath != null) { item.ParentPath.ChildPaths.Remove(item); } item.SetParentPath(this.parentPath); } private void OnRemove(InstallPath item) { item.SetParentPath(null); } private void OnSet(InstallPath oldItem, InstallPath newItem) { this.OnRemove(oldItem); this.OnInsert(newItem); } /// /// Removes an item from the collection. /// /// The index of the item to remove. public void RemoveAt(int index) { this.OnRemove(this[index]); this.items.RemoveAt(index); } /// /// Removes all items from the collection. /// public void Clear() { foreach (InstallPath item in this) { this.OnRemove(item); } this.items.Clear(); } /// /// Gets the number of items in the collection. /// public int Count { get { return this.items.Count; } } bool ICollection.IsReadOnly { get { return false; } } /// /// Gets an enumerator over all items in the collection. /// /// An enumerator for the collection. public IEnumerator GetEnumerator() { return this.items.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable) this).GetEnumerator(); } } /// /// Represents a mapping of install paths for all directories, components, or files in /// an installation database. /// public class InstallPathMap : IDictionary { /// /// Builds a mapping from File keys to installation paths. /// /// Installation database. /// Component mapping returned by . /// true to use short file names; false to use long names /// An InstallPathMap with the described mapping. public static InstallPathMap BuildFilePathMap(Database db, InstallPathMap componentPathMap, bool useShortNames) { if(db == null) { throw new ArgumentNullException("db"); } if(componentPathMap == null) { componentPathMap = BuildComponentPathMap(db, BuildDirectoryPathMap(db, useShortNames)); } InstallPathMap filePathMap = new InstallPathMap(); using (View fileView = db.OpenView("SELECT `File`, `Component_`, `FileName` FROM `File`")) { fileView.Execute(); foreach (Record fileRec in fileView) { using (fileRec) { string file = (string) fileRec[1]; string comp = (string) fileRec[2]; string fileName = (string) fileRec[3]; InstallPath compPath = (InstallPath) componentPathMap[comp]; if(compPath != null) { InstallPath filePath = new InstallPath(fileName, useShortNames); compPath.ChildPaths.Add(filePath); filePathMap[file] = filePath; } } } } return filePathMap; } /// /// Builds a mapping from Component keys to installation paths. /// /// Installation database. /// Directory mapping returned by /// . /// An InstallPathMap with the described mapping. public static InstallPathMap BuildComponentPathMap(Database db, InstallPathMap directoryPathMap) { if(db == null) { throw new ArgumentNullException("db"); } InstallPathMap compPathMap = new InstallPathMap(); using (View compView = db.OpenView("SELECT `Component`, `Directory_` FROM `Component`")) { compView.Execute(); foreach (Record compRec in compView) { using (compRec) { string comp = (string) compRec[1]; InstallPath dirPath = (InstallPath) directoryPathMap[(string) compRec[2]]; if (dirPath != null) { compPathMap[comp] = dirPath; } } } } return compPathMap; } /// /// Builds a mapping from Directory keys to installation paths. /// /// Installation database. /// true to use short directory names; false to use long names /// An InstallPathMap with the described mapping. public static InstallPathMap BuildDirectoryPathMap(Database db, bool useShortNames) { return BuildDirectoryPathMap(db, useShortNames, null, null); } /// /// Builds a mapping of Directory keys to directory paths, specifying root directories /// for the source and target paths. /// /// Database containing the Directory table. /// true to use short directory names; false to use long names /// The root directory path of all source paths, or null to leave them relative. /// The root directory path of all source paths, or null to leave them relative. /// An InstallPathMap with the described mapping. public static InstallPathMap BuildDirectoryPathMap(Database db, bool useShortNames, string sourceRootDir, string targetRootDir) { if(db == null) { throw new ArgumentNullException("db"); } if(sourceRootDir == null) sourceRootDir = ""; if(targetRootDir == null) targetRootDir = ""; InstallPathMap dirMap = new InstallPathMap(); IDictionary dirTreeMap = new Hashtable(); using (View dirView = db.OpenView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) { dirView.Execute(); foreach (Record dirRec in dirView) using (dirRec) { string key = (string) dirRec[1]; string parentKey = (string) dirRec[2]; InstallPath dir = new InstallPath((string) dirRec[3], useShortNames); dirMap[key] = dir; InstallPathMap siblingDirs = (InstallPathMap) dirTreeMap[parentKey]; if (siblingDirs == null) { siblingDirs = new InstallPathMap(); dirTreeMap[parentKey] = siblingDirs; } siblingDirs.Add(key, dir); } } foreach (KeyValuePair entry in (InstallPathMap) dirTreeMap[""]) { string key = (string) entry.Key; InstallPath dir = (InstallPath) entry.Value; LinkSubdirectories(key, dir, dirTreeMap); } InstallPath targetDirPath = (InstallPath) dirMap["TARGETDIR"]; if(targetDirPath != null) { targetDirPath.SourcePath = sourceRootDir; targetDirPath.TargetPath = targetRootDir; } return dirMap; } private static void LinkSubdirectories(string key, InstallPath dir, IDictionary dirTreeMap) { InstallPathMap subDirs = (InstallPathMap) dirTreeMap[key]; if(subDirs != null) { foreach (KeyValuePair entry in subDirs) { string subKey = (string) entry.Key; InstallPath subDir = (InstallPath) entry.Value; dir.ChildPaths.Add(subDir); LinkSubdirectories(subKey, subDir, dirTreeMap); } } } private Dictionary items; /// /// Creates a new empty InstallPathMap. /// public InstallPathMap() { this.items = new Dictionary(StringComparer.Ordinal); } /// /// Gets a mapping from keys to source paths. /// public IDictionary SourcePaths { get { return new SourcePathMap(this); } } /// /// Gets a mapping from keys to target paths. /// public IDictionary TargetPaths { get { return new TargetPathMap(this); } } /// /// Gets or sets an install path for a direcotry, component, or file key. /// /// Depending on the type of InstallPathMap, this is the primary key from the /// either the Directory, Component, or File table. /// /// Changing an install path does not modify the Database used to generate this InstallPathMap. /// public InstallPath this[string key] { get { InstallPath value = null; this.items.TryGetValue(key, out value); return value; } set { this.items[key] = value; } } /// /// Gets the collection of keys in the InstallPathMap. Depending on the type of InstallPathMap, /// they are all directory, component, or file key strings. /// public ICollection Keys { get { return this.items.Keys; } } /// /// Gets the collection of InstallPath values in the InstallPathMap. /// public ICollection Values { get { return this.items.Values; } } /// /// Sets an install path for a direcotry, component, or file key. /// /// Depending on the type of InstallPathMap, this is the primary key from the /// either the Directory, Component, or File table. /// The install path of the key item. /// /// Changing an install path does not modify the Database used to generate this InstallPathMap. /// public void Add(string key, InstallPath installPath) { this.items.Add(key, installPath); } /// /// Removes an install path from the map. /// /// Depending on the type of InstallPathMap, this is the primary key from the /// either the Directory, Component, or File table. /// true if the item was removed, false if it did not exist /// /// Changing an install path does not modify the Database used to generate this InstallPathMap. /// public bool Remove(string key) { return this.items.Remove(key); } /// /// Tests whether a direcotry, component, or file key exists in the map. /// /// Depending on the type of InstallPathMap, this is the primary key from the /// either the Directory, Component, or File table. /// true if the key is found; false otherwise public bool ContainsKey(string key) { return this.items.ContainsKey(key); } /* public override string ToString() { System.Text.StringBuilder buf = new System.Text.StringBuilder(); foreach(KeyValuePair entry in this) { buf.AppendFormat("{0}={1}", entry.Key, entry.Value); buf.Append("\n"); } return buf.ToString(); } */ /// /// Attempts to get a value from the dictionary. /// /// The key to lookup. /// Receives the value, or null if they key was not found. /// True if the value was found, else false. public bool TryGetValue(string key, out InstallPath value) { return this.items.TryGetValue(key, out value); } void ICollection>.Add(KeyValuePair item) { ((ICollection>) this.items).Add(item); } /// /// Removes all entries from the dictionary. /// public void Clear() { this.items.Clear(); } bool ICollection>.Contains(KeyValuePair item) { return ((ICollection>) this.items).Contains(item); } void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { ((ICollection>) this.items).CopyTo(array, arrayIndex); } /// /// Gets the number of entries in the dictionary. /// public int Count { get { return this.items.Count; } } bool ICollection>.IsReadOnly { get { return false; } } bool ICollection>.Remove(KeyValuePair item) { return ((ICollection>) this.items).Remove(item); } /// /// Gets an enumerator over all entries in the dictionary. /// /// An enumerator for the dictionary. public IEnumerator> GetEnumerator() { return this.items.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return this.items.GetEnumerator(); } } internal class SourcePathMap : IDictionary { private const string RO_MSG = "The SourcePathMap collection is read-only. " + "Modify the InstallPathMap instead."; private InstallPathMap map; internal SourcePathMap(InstallPathMap map) { this.map = map; } public void Add(string key, string value) { throw new InvalidOperationException(RO_MSG); } public bool ContainsKey(string key) { return this.map.ContainsKey(key); } public ICollection Keys { get { return this.map.Keys; } } public bool Remove(string key) { throw new InvalidOperationException(RO_MSG); } public bool TryGetValue(string key, out string value) { InstallPath installPath; if (this.map.TryGetValue(key, out installPath)) { value = installPath.SourcePath; return true; } else { value = null; return false; } } public ICollection Values { get { List values = new List(this.Count); foreach (KeyValuePair entry in this.map) { values.Add(entry.Value.SourcePath); } return values; } } public string this[string key] { get { string value = null; this.TryGetValue(key, out value); return value; } set { throw new InvalidOperationException(RO_MSG); } } public void Add(KeyValuePair item) { throw new InvalidOperationException(RO_MSG); } public void Clear() { throw new InvalidOperationException(RO_MSG); } public bool Contains(KeyValuePair item) { string value = this[item.Key]; return value == item.Value; } public void CopyTo(KeyValuePair[] array, int arrayIndex) { foreach (KeyValuePair entry in this) { array[arrayIndex] = entry; arrayIndex++; } } public int Count { get { return this.map.Count; } } public bool IsReadOnly { get { return true; } } public bool Remove(KeyValuePair item) { throw new InvalidOperationException(RO_MSG); } public IEnumerator> GetEnumerator() { foreach (KeyValuePair entry in this.map) { yield return new KeyValuePair( entry.Key, entry.Value.SourcePath); } } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } } internal class TargetPathMap : IDictionary { private const string RO_MSG = "The TargetPathMap collection is read-only. " + "Modify the InstallPathMap instead."; private InstallPathMap map; internal TargetPathMap(InstallPathMap map) { this.map = map; } public void Add(string key, string value) { throw new InvalidOperationException(RO_MSG); } public bool ContainsKey(string key) { return this.map.ContainsKey(key); } public ICollection Keys { get { return this.map.Keys; } } public bool Remove(string key) { throw new InvalidOperationException(RO_MSG); } public bool TryGetValue(string key, out string value) { InstallPath installPath; if (this.map.TryGetValue(key, out installPath)) { value = installPath.TargetPath; return true; } else { value = null; return false; } } public ICollection Values { get { List values = new List(this.Count); foreach (KeyValuePair entry in this.map) { values.Add(entry.Value.TargetPath); } return values; } } public string this[string key] { get { string value = null; this.TryGetValue(key, out value); return value; } set { throw new InvalidOperationException(RO_MSG); } } public void Add(KeyValuePair item) { throw new InvalidOperationException(RO_MSG); } public void Clear() { throw new InvalidOperationException(RO_MSG); } public bool Contains(KeyValuePair item) { string value = this[item.Key]; return value == item.Value; } public void CopyTo(KeyValuePair[] array, int arrayIndex) { foreach (KeyValuePair entry in this) { array[arrayIndex] = entry; arrayIndex++; } } public int Count { get { return this.map.Count; } } public bool IsReadOnly { get { return true; } } public bool Remove(KeyValuePair item) { throw new InvalidOperationException(RO_MSG); } public IEnumerator> GetEnumerator() { foreach (KeyValuePair entry in this.map) { yield return new KeyValuePair( entry.Key, entry.Value.TargetPath); } } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } } }