// // // // // $Revision$ // using System; using System.IO; using System.Text; namespace ICSharpCode.WixBinding { /// /// Utility class that converts from long filenames to DOS 8.3 filenames based on the /// rough algorithm outlined here: http://support.microsoft.com/kb/142982/EN-US/ /// apart from using an underscore instead of a '~'. /// public sealed class ShortFileName { const int MaximumFileNameWithoutExtensionLength = 8; /// /// The filename length to use when appending "_1" to it. /// const int FileNameWithoutExtensionTruncatedLength = 6; /// /// Maximum short filename extension length including dot. /// const int MaximumFileNameExtensionLength = 4; /// /// Maximum short filename length including extension. /// public const int MaximumFileNameLength = MaximumFileNameWithoutExtensionLength + MaximumFileNameExtensionLength; /// /// Called by Convert to check whether the specified filename exists. If it /// does then the FileNameExists delegate is called repeatedly until it /// finds a filename that does not exist. /// public delegate bool NameExists(string name); ShortFileName() { } /// /// Converts a long filename to a short 8.3 filename. /// /// The long filename. /// The converted 8.3 filename. If the filename includes a /// starting path then this is stripped from the returned value. public static string Convert(string fileName, NameExists getFileNameExists) { if (fileName == null) { return String.Empty; } // Remove invalid characters. string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName); string extension = Path.GetExtension(fileName); StringBuilder shortFileName = new StringBuilder(); int length = 0; foreach (char ch in fileNameWithoutExtension) { if (IsValidShortFileNameChar(ch)) { shortFileName.Append(ch); ++length; } } // Get truncated extension. if (extension.Length > MaximumFileNameExtensionLength) { extension = extension.Substring(0, MaximumFileNameExtensionLength); } // Truncate filename without extension if it is too long. if (length > MaximumFileNameWithoutExtensionLength) { shortFileName.Remove(FileNameWithoutExtensionTruncatedLength, length - FileNameWithoutExtensionTruncatedLength); return GetTruncatedFileName(shortFileName.ToString(), extension, getFileNameExists); } // Add extension. shortFileName.Append(extension); return shortFileName.ToString().ToUpperInvariant(); } /// /// Converts a long filename to a short 8.3 filename. /// /// The long filename /// The converted 8.3 filename. If the filename includes a /// starting path then this is stripped from the returned value. public static string Convert(string fileName) { return Convert(fileName, FileNameNeverExists); } /// /// Gets the unique name string by adding a digit until a unique name is /// produced. The name string itself will be truncated as the number /// increases in size to make sure the total length of the string is /// always equal to start.Length + 1. /// /// The string to be used at the start of the /// Method called to check that the /// name does not already exist. public static string GetUniqueName(string start, NameExists getNameExists) { return GetUniqueName(start, String.Empty, String.Empty, getNameExists); } /// /// Gets the unique name string by adding a digit until a unique name is /// produced. The name string itself will be truncated as the number /// increases in size to make sure the total length of the string is /// always equal to start.Length + numberPrefix.Length + 1 + end.Length. /// /// The string to be used at the start of the /// The string to be prefixed to the number. /// The string to be added after the number. /// Method called to check that the /// name does not already exist. public static string GetUniqueName(string start, string numberPrefix, string end, NameExists getNameExists) { int count = 0; string truncatedName; int divisor = 10; do { ++count; int removeCharCount = count / divisor; if (removeCharCount > 0) { start = start.Substring(0, start.Length - 1); divisor *= divisor; } truncatedName = String.Concat(start, numberPrefix, count.ToString(), end); } while (getNameExists(truncatedName)); return truncatedName; } /// /// Determines whether the specified filename is a long name /// and should be converted into a short name. /// public static bool IsLongFileName(string fileName) { if (fileName == null) { return false; } string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName); string extension = Path.GetExtension(fileName); return fileNameWithoutExtension.Length > MaximumFileNameWithoutExtensionLength || extension.Length > MaximumFileNameExtensionLength; } /// /// Truncates the filename start and adds "_N" where N produces a filename that /// does not exist. /// static string GetTruncatedFileName(string fileNameStart, string extension, NameExists getFileNameExists) { return GetUniqueName(fileNameStart.ToUpperInvariant(), "_", extension.ToUpperInvariant(), getFileNameExists); } /// /// Default delegate that always returns false to the Convert method. /// static bool FileNameNeverExists(string fileName) { return false; } /// /// Returns whether the character is considered a valid character /// for the starting part of the filename (i.e. not including the extension). /// /// /// We include the period character here to avoid ICE03 invalid filename /// errors. /// /// http://msdn.microsoft.com/library/en-us/msi/setup/ice03.asp /// static bool IsValidShortFileNameChar(char ch) { switch (ch) { case ' ': case '[': case ']': case ';': case '=': case ',': case '.': return false; } return true; } } }