summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2010-02-20 10:23:02 -0800
committerAndrew Arnott <andrewarnott@gmail.com>2010-02-20 10:23:02 -0800
commit2fe1ac92e477dc14c3e51a9ca768e42144ed81ae (patch)
tree7733679f5cf99402f207b89473131026ce1dda19
parent48fd92dd1dacfc0b8e5ee301ea61097b0accfe0f (diff)
downloadDotNetOpenAuth-2fe1ac92e477dc14c3e51a9ca768e42144ed81ae.zip
DotNetOpenAuth-2fe1ac92e477dc14c3e51a9ca768e42144ed81ae.tar.gz
DotNetOpenAuth-2fe1ac92e477dc14c3e51a9ca768e42144ed81ae.tar.bz2
Fixes project template creation's long filenames and lib dependencies.
-rw-r--r--projecttemplates/projecttemplates.proj38
-rw-r--r--src/DotNetOpenAuth.BuildTasks/AddProjectItems.cs7
-rw-r--r--src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.csproj1
-rw-r--r--src/DotNetOpenAuth.BuildTasks/MergeProjectWithVSTemplate.cs111
-rw-r--r--src/DotNetOpenAuth.BuildTasks/PathSegment.cs282
5 files changed, 337 insertions, 102 deletions
diff --git a/projecttemplates/projecttemplates.proj b/projecttemplates/projecttemplates.proj
index 16b939d..5494463 100644
--- a/projecttemplates/projecttemplates.proj
+++ b/projecttemplates/projecttemplates.proj
@@ -55,10 +55,22 @@
<!-- ... and log4net -->
<ProjectTemplateLibraries Include="$(ProjectRoot)lib\log4net.dll" />
<ProjectTemplateLibraries Include="$(ProjectRoot)lib\log4net.xml" />
- <ProjectTemplateLibrariesTargets Include="@(ProjectTemplateLibraries->'$(ProjectTemplatesLayoutPath)RelyingPartyLogic\lib\%(CultureDir)%(FileName)%(Extension)')" />
+ <ProjectTemplateLibrariesTargets Include="@(ProjectTemplateLibraries->'$(ProjectTemplatesLayoutPath)RelyingPartyLogic\lib\%(CultureDir)%(FileName)%(Extension)')">
+ <ApparentSource>RelyingPartyLogic\lib\%(ProjectTemplateLibraries.CultureDir)%(FileName)%(Extension)</ApparentSource>
+ <ActualSource>%(Identity)</ActualSource>
+ </ProjectTemplateLibrariesTargets>
+ <ProjectTemplateLibrariesSourceExceptions Include="@(ProjectTemplateLibrariesTargets->'%(ApparentSource)')"/>
<FixupReferenceAssemblies Include="@(ProjectTemplateLibrariesTargets)" Condition="'%(Extension)' == '.dll'" />
- <InjectedLibraryItems Include="@(ProjectTemplateLibrariesTargets->'lib\%(FileName)%(Extension)')" />
+ <InjectedLibraryItems Include="@(ProjectTemplateLibraries->'lib\%(CultureDir)%(FileName)%(Extension)')">
+ <Visible/>
+ <UnsignedAssemblyPath/>
+ <SymbolPath/>
+ <OriginalItemSpec/>
+ <MSBuildSourceProjectFile/>
+ <MSBuildSourceTargetName/>
+ <CultureDir/>
+ </InjectedLibraryItems>
<VSProjectTemplates Include="**\*.vstemplate" Exclude="*.vstemplate" />
<VSProjectTemplatesLayout Include="@(VSProjectTemplates->'$(ProjectTemplatesLayoutPath)%(RecursiveDir)%(FileName)%(Extension)')" />
@@ -90,6 +102,7 @@
SourceTemplates="@(VSProjectTemplates)"
SourceProjects="@(TemplateProjectsLayout)"
DestinationTemplates="@(VSProjectTemplatesLayout)"
+ SourcePathExceptions="@(ProjectTemplateLibrariesSourceExceptions)"
MaximumRelativePathLength="$(ProjectTemplateMaxPath)"
>
<Output TaskParameter="ProjectItems" ItemName="TemplateProjectItems"/>
@@ -98,34 +111,19 @@
<Target Name="Layout" DependsOnTargets="$(LayoutDependsOn)">
<ItemGroup>
- <TemplateProjectItems Condition="
- '%(Extension)' == '.cs'
- or '%(Extension)' == '.csproj'
- or '%(Extension)' == '.sql'
- or '%(Extension)' == '.config'
- or '%(Extension)' == '.Master'
- or '%(Extension)' == '.aspx'
- or '%(Extension)' == '.ascx'
- or '%(Extension)' == '.asax'
- or '%(Extension)' == '.ashx'
- ">
+ <TemplateProjectItems Condition=" '%(Transform)' == 'true' ">
<BeforeTokens>%(RecursiveDir)</BeforeTokens>
<AfterTokens>$safeprojectname$</AfterTokens>
</TemplateProjectItems>
<TemplateProjectItems>
<SkipUnchangedFiles>true</SkipUnchangedFiles>
</TemplateProjectItems>
- <TemplateProjectItemsForTransformSource Include="@(TemplateProjectItems->'%(SourceFullPath)')"
- Condition=" '%(TemplateProjectItems.RelativeDir)' != 'RelyingPartyLogic\lib\' "/>
- <TemplateProjectItemsForTransformLayout Include="@(TemplateProjectItems->'%(DestinationFullPath)')"
- Condition=" '%(TemplateProjectItems.RelativeDir)' != 'RelyingPartyLogic\lib\' "/>
+ <TemplateProjectItemsForTransformSource Include="@(TemplateProjectItems->'%(SourceFullPath)')" />
+ <TemplateProjectItemsForTransformLayout Include="@(TemplateProjectItems->'%(DestinationFullPath)')" />
<RootVsTemplateSource Include="*.vstemplate" />
<ProjectTemplatesSource Include="@(RootVsTemplateSource)" />
<ProjectTemplatesLayout Include="@(RootVsTemplateSource->'$(ProjectTemplatesLayoutPath)%(FileName)%(Extension)')" />
- <ProjectTemplatesSource Include="@(ProjectTemplateLibraries)" />
- <ProjectTemplatesLayout Include="@(ProjectTemplateLibrariesTargets)" />
-
<!-- Include the template icon -->
<ProjectTemplatesSource Include="$(ProjectRoot)doc\logo\favicon.ico" />
<ProjectTemplatesLayout Include="$(ProjectTemplatesLayoutPath)__TemplateIcon.ico" />
diff --git a/src/DotNetOpenAuth.BuildTasks/AddProjectItems.cs b/src/DotNetOpenAuth.BuildTasks/AddProjectItems.cs
index 30fa284..0b84398 100644
--- a/src/DotNetOpenAuth.BuildTasks/AddProjectItems.cs
+++ b/src/DotNetOpenAuth.BuildTasks/AddProjectItems.cs
@@ -6,13 +6,13 @@
namespace DotNetOpenAuth.BuildTasks {
using System;
+ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Build.BuildEngine;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
- using System.Collections;
public class AddProjectItems : Task {
/// <summary>
@@ -49,7 +49,10 @@ namespace DotNetOpenAuth.BuildTasks {
BuildItem newItem = project.AddNewItem(itemType, projectItem.ItemSpec, false);
var customMetadata = projectItem.CloneCustomMetadata();
foreach (DictionaryEntry entry in customMetadata) {
- newItem.SetMetadata((string)entry.Key, (string)entry.Value);
+ string value = (string)entry.Value;
+ if (value.Length > 0) {
+ newItem.SetMetadata((string)entry.Key, value);
+ }
}
}
diff --git a/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.csproj b/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.csproj
index f107e81..365bec5 100644
--- a/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.csproj
+++ b/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.csproj
@@ -118,6 +118,7 @@
<Compile Include="JsPack.cs" />
<Compile Include="NativeMethods.cs" />
<Compile Include="ParseMaster.cs" />
+ <Compile Include="PathSegment.cs" />
<Compile Include="Publicize.cs" />
<Compile Include="Purge.cs" />
<Compile Include="ReSignDelaySignedAssemblies.cs" />
diff --git a/src/DotNetOpenAuth.BuildTasks/MergeProjectWithVSTemplate.cs b/src/DotNetOpenAuth.BuildTasks/MergeProjectWithVSTemplate.cs
index 601ff6f..d162cd6 100644
--- a/src/DotNetOpenAuth.BuildTasks/MergeProjectWithVSTemplate.cs
+++ b/src/DotNetOpenAuth.BuildTasks/MergeProjectWithVSTemplate.cs
@@ -7,6 +7,7 @@
namespace DotNetOpenAuth.BuildTasks {
using System;
using System.Collections.Generic;
+ using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
@@ -42,6 +43,8 @@ namespace DotNetOpenAuth.BuildTasks {
[Required]
public ITaskItem[] DestinationTemplates { get; set; }
+ public ITaskItem[] SourcePathExceptions { get; set; }
+
/// <summary>
/// Gets or sets the maximum length a project item's relative path should
/// be limited to, artificially renaming them as necessary.
@@ -87,81 +90,50 @@ namespace DotNetOpenAuth.BuildTasks {
// Figure out where every project item is in source, and where it will go in the destination,
// taking into account a given maximum path length that may require that we shorten the path.
- var sourceToDestinationProjectItemMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- //var oversizedItemPaths = projectItems.Where(item => item.Include.Length > this.MaximumRelativePathLength);
- foreach (var item in projectItems) {
- var source = item.Include;
- var dest = item.Include;
-
- // if (this.MaximumRelativePathLength > 0) {
- // if (item.Include.Length > this.MaximumRelativePathLength) {
- // string leafName = Path.GetFileName(item.Include);
- // int targetLeafLength = leafName.Length - (item.Include.Length - this.MaximumRelativePathLength);
- // string shortenedFileName = CreateUniqueShortFileName(leafName, targetLeafLength);
- // string shortenedRelativePath = Path.Combine(Path.GetDirectoryName(item.Include), shortenedFileName);
- // if (shortenedRelativePath.Length <= this.MaximumRelativePathLength) {
- // this.Log.LogMessage(
- // "Renaming long project item '{0}' to '{1}' within project template to avoid MAX_PATH issues. The instantiated project will remain unchanged.",
- // item.Include,
- // shortenedRelativePath);
- // projectItem.SetAttributeValue("TargetFileName", Path.GetFileName(item.Include));
- // projectItem.Value = shortenedFileName;
- // string originalFullPath = Path.Combine(projectDirectory, item.Include);
- // string shortenedFullPath = Path.Combine(projectDirectory, shortenedRelativePath);
- // if (File.Exists(shortenedFullPath)) {
- // File.Delete(shortenedFullPath); // File.Move can't overwrite files
- // }
- // File.Move(originalFullPath, shortenedFullPath);
-
- // // Document the change so the build system can account for it.
- // TaskItem shortChange = new TaskItem(originalFullPath);
- // shortChange.SetMetadata("ShortPath", shortenedFullPath);
- // shortenedItems.Add(shortChange);
- // } else {
- // this.Log.LogError(
- // "Project item '{0}' exceeds maximum allowable length {1} by {2} characters and it cannot be sufficiently shortened. Estimated full path is: '{3}'.",
- // item.Include,
- // this.MaximumRelativePathLength,
- // item.Include.Length - this.MaximumRelativePathLength,
- // item.Include);
- // }
- // }
- //}
-
- sourceToDestinationProjectItemMap[source] = dest;
- }
+ PathSegment root = new PathSegment();
+ root.Add(projectItems.Select(item => item.Include));
+ root.EnsureSelfAndChildrenNoLongerThan(this.MaximumRelativePathLength);
// Collect the project items from the project that are appropriate
// to include in the .vstemplate file.
- var itemsByFolder = from item in projectItems
- orderby item.Include
- group item by Path.GetDirectoryName(item.Include);
-
- foreach (var folder in itemsByFolder) {
+ foreach (var folder in root.SelfAndDescendents.Where(path => !path.IsLeaf && path.LeafChildren.Any())) {
XElement parentNode = projectElement;
- parentNode = FindOrCreateParent(folder.Key, projectElement);
- //parentNode.SetAttributeValue("TargetFolderName", folder.Key);
+ parentNode = FindOrCreateParent(folder.CurrentPath, projectElement);
+ if (folder.NameChanged) {
+ parentNode.SetAttributeValue("TargetFolderName", folder.OriginalName);
+ }
- foreach (var item in folder) {
- bool replaceParameters = this.ReplaceParametersExtensions.Contains(Path.GetExtension(item.Include));
+ foreach (var item in folder.LeafChildren) {
var itemName = XName.Get("ProjectItem", VSTemplateNamespace);
- var projectItem = parentNode.Elements(itemName).FirstOrDefault(el => string.Equals(el.Value, Path.GetFileName(item.Include), StringComparison.OrdinalIgnoreCase));
+ // The project item MAY be hard-coded in the .vstemplate file, under the original name.
+ var projectItem = parentNode.Elements(itemName).FirstOrDefault(el => string.Equals(el.Value, Path.GetFileName(item.OriginalName), StringComparison.OrdinalIgnoreCase));
if (projectItem == null) {
- projectItem = new XElement(itemName, Path.GetFileName(item.Include));
+ projectItem = new XElement(itemName, item.CurrentName);
parentNode.Add(projectItem);
}
- if (replaceParameters) {
+ if (item.NameChanged) {
+ projectItem.Value = item.CurrentName; // set Value in case it was a hard-coded item in the .vstemplate file.
+ projectItem.SetAttributeValue("TargetFileName", item.OriginalName);
+ }
+ if (this.ReplaceParametersExtensions.Contains(Path.GetExtension(item.OriginalPath))) {
projectItem.SetAttributeValue("ReplaceParameters", "true");
}
}
}
template.Save(this.DestinationTemplates[iTemplate].ItemSpec);
- foreach (var pair in sourceToDestinationProjectItemMap) {
- TaskItem item = new TaskItem(Path.Combine(Path.GetDirectoryName(this.SourceTemplates[iTemplate].ItemSpec), pair.Key));
- item.SetMetadata("SourceFullPath", Path.GetFullPath(Path.Combine(Path.GetDirectoryName(this.SourceTemplates[iTemplate].ItemSpec), pair.Key)));
- item.SetMetadata("DestinationFullPath", Path.GetFullPath(Path.Combine(Path.GetDirectoryName(this.DestinationTemplates[iTemplate].ItemSpec), pair.Value)));
+ foreach (var pair in root.LeafDescendents) {
+ TaskItem item = new TaskItem(Path.Combine(Path.GetDirectoryName(this.SourceTemplates[iTemplate].ItemSpec), pair.OriginalPath));
+ string apparentSource = Path.Combine(Path.GetDirectoryName(this.SourceTemplates[iTemplate].ItemSpec), pair.OriginalPath);
+ var sourcePathException = this.SourcePathExceptions.FirstOrDefault(ex => string.Equals(ex.ItemSpec, apparentSource));
+ if (sourcePathException != null) {
+ item.SetMetadata("SourceFullPath", sourcePathException.GetMetadata("ActualSource"));
+ } else {
+ item.SetMetadata("SourceFullPath", Path.GetFullPath(apparentSource));
+ }
+ item.SetMetadata("DestinationFullPath", Path.GetFullPath(Path.Combine(Path.GetDirectoryName(this.DestinationTemplates[iTemplate].ItemSpec), pair.CurrentPath)));
item.SetMetadata("RecursiveDir", Path.GetDirectoryName(this.SourceTemplates[iTemplate].ItemSpec));
+ item.SetMetadata("Transform", this.ReplaceParametersExtensions.Contains(Path.GetExtension(pair.OriginalName)) ? "true" : "false");
projectItemsToCopy.Add(item);
}
}
@@ -171,27 +143,6 @@ namespace DotNetOpenAuth.BuildTasks {
return !Log.HasLoggedErrors;
}
- private static string CreateUniqueShortFileName(string fileName, int targetLength) {
- Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(fileName));
- Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()));
-
- // The filename may already full within the target length.
- if (fileName.Length <= targetLength) {
- return fileName;
- }
-
- string hashSuffix = Utilities.SuppressCharacters(Math.Abs(fileName.GetHashCode() % 0xfff).ToString("x"), Path.GetInvalidFileNameChars(), '_');
- string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
- string extension = Path.GetExtension(fileName);
- string hashSuffixWithExtension = hashSuffix + extension;
-
- // If the target length is itself shorter than the hash code, then we won't meet our goal,
- // but at least put the hash in there so it's unique, and we'll return a string that's too long.
- string shortenedFileName = fileName.Substring(0, Math.Max(0, targetLength - hashSuffixWithExtension.Length)) + hashSuffixWithExtension;
-
- return shortenedFileName;
- }
-
private static XElement FindOrCreateParent(string directoryName, XElement projectElement) {
Contract.Requires<ArgumentNullException>(projectElement != null);
diff --git a/src/DotNetOpenAuth.BuildTasks/PathSegment.cs b/src/DotNetOpenAuth.BuildTasks/PathSegment.cs
new file mode 100644
index 0000000..a67fa1b
--- /dev/null
+++ b/src/DotNetOpenAuth.BuildTasks/PathSegment.cs
@@ -0,0 +1,282 @@
+//-----------------------------------------------------------------------
+// <copyright file="PathSegment.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.BuildTasks {
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.ObjectModel;
+ using System.Diagnostics;
+ using System.Diagnostics.Contracts;
+ using System.IO;
+ using System.Linq;
+ using System.Text;
+
+ internal class PathSegment {
+ private const float ParentChildResizeThreshold = 0.30f;
+ private readonly PathSegment parent;
+ private readonly string originalName;
+ private string currentName;
+
+ internal PathSegment() {
+ this.currentName = string.Empty;
+ this.originalName = string.Empty;
+ this.Children = new Collection<PathSegment>();
+ }
+
+ private PathSegment(string originalName, PathSegment parent)
+ : this() {
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(originalName));
+ Contract.Requires<ArgumentNullException>(parent != null);
+ this.currentName = this.originalName = originalName;
+ this.parent = parent;
+ }
+
+ internal string OriginalPath {
+ get {
+ if (this.parent != null) {
+ return Path.Combine(this.parent.OriginalPath, this.originalName);
+ } else {
+ return this.originalName;
+ }
+ }
+ }
+
+ internal string CurrentPath {
+ get {
+ if (this.parent != null) {
+ return Path.Combine(this.parent.CurrentPath, this.currentName);
+ } else {
+ return this.currentName;
+ }
+ }
+ }
+
+ internal string CurrentName {
+ get { return this.currentName; }
+ }
+
+ internal string OriginalName {
+ get { return this.originalName; }
+ }
+
+ private int SegmentCount {
+ get {
+ int parents = this.parent != null ? this.parent.SegmentCount : 0;
+ return parents + 1;
+ }
+ }
+
+ internal int FullLength {
+ get {
+ if (this.parent != null) {
+ return this.parent.FullLength + 1/*slash*/ + this.currentName.Length;
+ } else {
+ return this.currentName.Length;
+ }
+ }
+ }
+
+ internal bool NameChanged {
+ get { return !string.Equals(this.currentName, this.originalName, StringComparison.OrdinalIgnoreCase); }
+ }
+
+ internal bool IsLeaf {
+ get { return this.Children.Count == 0; }
+ }
+
+ internal IEnumerable<PathSegment> Descendents {
+ get {
+ IEnumerable<PathSegment> result = this.Children;
+ foreach (PathSegment child in this.Children) {
+ result = result.Concat(child.Descendents);
+ }
+
+ return result;
+ }
+ }
+
+ internal IEnumerable<PathSegment> SelfAndDescendents {
+ get {
+ yield return this;
+ foreach (var child in this.Descendents) {
+ yield return child;
+ }
+ }
+ }
+
+ internal IEnumerable<PathSegment> LeafChildren {
+ get { return this.Children.Where(child => child.IsLeaf); }
+ }
+
+ internal IEnumerable<PathSegment> LeafDescendents {
+ get { return this.Descendents.Where(child => child.IsLeaf); }
+ }
+
+ internal IEnumerable<PathSegment> Siblings {
+ get { return this.parent != null ? this.parent.Children : Enumerable.Empty<PathSegment>(); }
+ }
+
+ internal Collection<PathSegment> Children { get; private set; }
+
+ public override string ToString() {
+ string path;
+ if (this.NameChanged) {
+ path = "{" + this.originalName + " => " + this.currentName + "}";
+ } else {
+ path = this.currentName;
+ }
+
+ if (path.Length > 0 && !this.IsLeaf) {
+ path += "\\";
+ }
+
+ if (this.parent != null) {
+ path = parent.ToString() + path;
+ }
+
+ return path;
+ }
+
+ internal PathSegment Add(string originalPath) {
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(originalPath));
+ Contract.Ensures(Contract.Result<PathSegment>() != null);
+ string[] segments = originalPath.Split(Path.DirectorySeparatorChar);
+ return this.Add(segments, 0);
+ }
+
+ internal void Add(IEnumerable<string> originalPaths) {
+ foreach (string path in originalPaths) {
+ this.Add(path);
+ }
+ }
+
+ internal int EnsureSelfAndChildrenNoLongerThan(int maxLength) {
+ Contract.Requires<ArgumentOutOfRangeException>(maxLength > 0, "A path can only have a positive length.");
+ Contract.Requires<ArgumentOutOfRangeException>(this.parent == null || maxLength > this.parent.FullLength + 1, "A child path cannot possibly be made shorter than its parent.");
+ Contract.Ensures(Contract.Result<int>() <= maxLength);
+
+ // First check whether this segment itself is too long.
+ if (this.FullLength > maxLength) {
+ int tooLongBy = this.FullLength - maxLength;
+ this.currentName = CreateUniqueShortFileName(this.originalName, this.currentName.Length - tooLongBy);
+ }
+
+ // Now check whether children are too long.
+ if (this.Children.Count > 0) {
+ var longChildren = this.Children.Where(path => path.FullLength > maxLength).ToList();
+ if (longChildren.Any()) {
+ // If this segment's name is longer than the longest child that is too long, we need to
+ // shorten THIS segment's name.
+ if (longChildren.Max(child => child.CurrentName.Length) < this.CurrentName.Length) {
+ int tooLongBy = this.FullLength - maxLength;
+ this.currentName = CreateUniqueShortFileName(this.originalName, this.currentName.Length - tooLongBy);
+ } else {
+ // The children need to be shortened.
+ longChildren.ForEach(child => child.EnsureSelfAndChildrenNoLongerThan(maxLength));
+ }
+ }
+ }
+
+ // Give each child a chance to check on their own children.
+ foreach (var child in this.Children) {
+ child.EnsureSelfAndChildrenNoLongerThan(maxLength);
+ }
+
+ // Return the total length of self or longest child.
+ return this.SelfAndDescendents.Max(c => c.FullLength);
+ }
+
+ internal PathSegment FindByOriginalPath(string originalPath) {
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(originalPath));
+ string[] segments = originalPath.Split(Path.DirectorySeparatorChar);
+ return this.FindByOriginalPath(segments, 0);
+ }
+
+ private string GetUniqueShortName(string preferredPrefix, string preferredSuffix, int allowableLength) {
+ Contract.Requires<ArgumentNullException>(preferredPrefix != null);
+ Contract.Requires<ArgumentNullException>(preferredSuffix != null);
+ Contract.Requires<ArgumentException>(allowableLength > 0);
+ Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()));
+ Contract.Ensures(Contract.Result<string>().Length <= allowableLength);
+ string candidateName = string.Empty;
+ int i;
+ for (i = -1; i < 0 || this.Siblings.Any(child => string.Equals(child.CurrentName, candidateName, StringComparison.OrdinalIgnoreCase)); i++) {
+ string unique = i < 0 ? string.Empty : i.ToString("x");
+ if (allowableLength < unique.Length) {
+ throw new InvalidOperationException("Unable to shorten path sufficiently to fit constraints.");
+ }
+
+ candidateName = unique;
+
+ // Suffix gets higher priority than the prefix, but only if the entire suffix can be appended.
+ if (candidateName.Length + preferredSuffix.Length <= allowableLength) {
+ candidateName += preferredSuffix;
+ }
+
+ // Now prepend as much of the prefix as fits.
+ candidateName = preferredPrefix.Substring(0, Math.Min(allowableLength - candidateName.Length, preferredPrefix.Length)) + candidateName;
+ }
+
+ return candidateName;
+ }
+
+ private string CreateUniqueShortFileName(string fileName, int targetLength) {
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(fileName));
+ Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()));
+ Contract.Ensures(Contract.Result<string>().Length <= targetLength);
+
+ // The filename may already full within the target length.
+ if (fileName.Length <= targetLength) {
+ return fileName;
+ }
+
+ string preferredPrefix = Path.GetFileNameWithoutExtension(fileName);
+ string preferredSuffix = Path.GetExtension(fileName);
+
+ string shortenedFileName = GetUniqueShortName(preferredPrefix, preferredSuffix, targetLength);
+ return shortenedFileName;
+ }
+
+ private void ShortenThis(int targetLength) {
+ this.currentName = CreateUniqueShortFileName(this.originalName, targetLength);
+ }
+
+ private PathSegment Add(string[] segments, int segmentIndex) {
+ Contract.Requires<ArgumentNullException>(segments != null);
+ Contract.Requires<ArgumentOutOfRangeException>(segmentIndex < segments.Length);
+ Contract.Ensures(Contract.Result<PathSegment>() != null);
+ var match = this.Children.SingleOrDefault(child => String.Equals(child.originalName, segments[segmentIndex]));
+ if (match == null) {
+ match = new PathSegment(segments[segmentIndex], this);
+ this.Children.Add(match);
+ if (segments.Length == segmentIndex + 1) {
+ return match;
+ }
+ }
+
+ return match.Add(segments, segmentIndex + 1);
+ }
+
+ private PathSegment FindByOriginalPath(string[] segments, int segmentIndex) {
+ Contract.Requires<ArgumentNullException>(segments != null);
+ Contract.Requires<ArgumentOutOfRangeException>(segmentIndex < segments.Length);
+ if (string.Equals(this.originalName, segments[segmentIndex], StringComparison.OrdinalIgnoreCase)) {
+ if (segmentIndex == segments.Length - 1) {
+ return this;
+ }
+
+ foreach (var child in this.Children) {
+ var match = child.FindByOriginalPath(segments, segmentIndex + 1);
+ if (match != null) {
+ return match;
+ }
+ }
+ }
+
+ return null;
+ }
+ }
+}