Skip to content

Commit 3893c85

Browse files
authored
Fix retain package structure flow where nested directory at root level were lost (#14918)
* optimize * comments and a test * add subdir test and comments * add child test * test updates
1 parent d0bd96e commit 3893c85

File tree

11 files changed

+245
-85
lines changed

11 files changed

+245
-85
lines changed

src/DynamoCoreWpf/ViewModels/PackageManager/PackageItemRootViewModel.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,14 @@ internal void AddChildren(PackageItemRootViewModel item)
131131
if (this.ChildItems.Contains(item)) return;
132132
this.ChildItems.Add(item);
133133
}
134-
135-
internal void AddChild(PackageItemRootViewModel elem)
134+
/// <summary>
135+
/// The methods is used for adding a child item to all the encountered parent folders in a nested path
136+
/// and make sure all the intermediate file paths are created as separate PackageItemRootViewModel.
137+
/// For example if we have a path like "\dir1\dir2\dir3" and we want to add a child item to "dir1", the method will
138+
/// add "dir 3" to "dir2" and then "dir2" to "dir1".
139+
/// </summary>
140+
/// <param name="elem">Child item to be added.</param>
141+
internal void AddChildRecursively(PackageItemRootViewModel elem)
136142
{
137143
if (elem.DependencyType.Equals(DependencyType.CustomNode)) return;
138144

@@ -149,7 +155,7 @@ internal void AddChild(PackageItemRootViewModel elem)
149155

150156
while (di.Parent != null)
151157
{
152-
// if we already have a subfodler item with that name,
158+
// if we already have a subfolder item with that name,
153159
// add this element's children to its children instead of creating a new subfolder branch
154160
if(existingSubFolders.Keys.Contains(elem.DirectoryName))
155161
{

src/DynamoCoreWpf/ViewModels/PackageManager/PublishPackageViewModel.cs

Lines changed: 80 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,10 @@ public bool RetainFolderStructureOverride
893893
}
894894
}
895895
}
896+
/// <summary>
897+
/// The root directory of the package
898+
/// </summary>
899+
private string CurrentPackageDirectory { get; set; }
896900
private static MetadataLoadContext sharedMetaDataLoadContext = null;
897901
/// <summary>
898902
/// A shared MetaDataLoadContext that is used for assembly inspection during package publishing.
@@ -996,7 +1000,7 @@ private void RefreshPackageContents()
9961000

9971001
var items = new Dictionary<string, PackageItemRootViewModel>();
9981002

999-
if(!String.IsNullOrEmpty(RootFolder))
1003+
if (!String.IsNullOrEmpty(RootFolder))
10001004
{
10011005
var root = new PackageItemRootViewModel(RootFolder);
10021006
items[RootFolder] = root;
@@ -1022,7 +1026,7 @@ private void RefreshPackageContents()
10221026
}
10231027
}
10241028

1025-
var updatedItems = BindParentToChild(items);
1029+
var updatedItems = BindParentToChild(items);
10261030

10271031
updatedItems.AddRange(itemsToAdd.Where(pa => pa.DependencyType.Equals(DependencyType.CustomNode)));
10281032

@@ -1050,8 +1054,6 @@ private bool IsDuplicateFile(PackageItemRootViewModel item1, PackageItemRootView
10501054

10511055
private List<PackageItemRootViewModel> BindParentToChild(Dictionary<string, PackageItemRootViewModel> items)
10521056
{
1053-
var updatedItems = new List<PackageItemRootViewModel>();
1054-
10551057
foreach (var parent in items)
10561058
{
10571059
foreach(var child in items)
@@ -1060,25 +1062,53 @@ private List<PackageItemRootViewModel> BindParentToChild(Dictionary<string, Pack
10601062
if (IsSubPathOfDeep(parent.Value, child.Value))
10611063
{
10621064
if (child.Value.isChild) continue; // if this was picked up already, don't add it again
1063-
parent.Value.AddChild(child.Value);
1065+
parent.Value.AddChildRecursively(child.Value);
10641066
child.Value.isChild = true;
10651067
}
10661068
}
10671069
}
10681070

10691071
// Only add the folder items, they contain the files
1070-
updatedItems = items.Values.Where(x => !x.isChild).ToList();
1072+
var updatedItems = GetRootItems(items);
10711073
return updatedItems;
10721074
}
10731075

1076+
/// <summary>
1077+
/// Gets the list PackageItemRootViewModel items which will be at the root directory of the package with all the child items.
1078+
/// </summary>
1079+
/// <param name="items"></param>
1080+
/// <returns></returns>
1081+
private List<PackageItemRootViewModel> GetRootItems(Dictionary<string, PackageItemRootViewModel> items)
1082+
{
1083+
var rootItems = items.Values.Where(x => !x.isChild).ToList();
1084+
if (!rootItems.Any()) return rootItems;
1085+
var packageSourceDir = CurrentPackageDirectory ??= GetLongestCommonPrefix(items.Keys.ToArray());
1086+
1087+
var root = new PackageItemRootViewModel(packageSourceDir);
1088+
var updatedItems = new List<PackageItemRootViewModel>();
1089+
//check each root item and create any missing connections
1090+
foreach (var item in rootItems)
1091+
{
1092+
var itemDir = new DirectoryInfo(item.DirectoryName);
1093+
if (!itemDir.Parent.FullName.Equals(packageSourceDir))
1094+
{
1095+
root.AddChildRecursively(item);
1096+
}
1097+
else
1098+
{
1099+
root.ChildItems.Add(item);
1100+
}
1101+
}
1102+
return root.ChildItems.ToList();
1103+
}
1104+
10741105
/// <summary>
10751106
/// Test if path2 is subpath of path1
1076-
/// If it is, make sure all the intermediate file paths are created as separte PackageItemRootViewModel
10771107
/// </summary>
10781108
/// <param name="path1"></param>
10791109
/// <param name="path2"></param>
10801110
/// <returns></returns>
1081-
private bool IsSubPathOfDeep(PackageItemRootViewModel path1, PackageItemRootViewModel path2)
1111+
internal bool IsSubPathOfDeep(PackageItemRootViewModel path1, PackageItemRootViewModel path2)
10821112
{
10831113
var di1 = new DirectoryInfo(path1.DirectoryName);
10841114
var di2 = new DirectoryInfo(path2.DirectoryName);
@@ -1089,12 +1119,37 @@ private bool IsSubPathOfDeep(PackageItemRootViewModel path1, PackageItemRootView
10891119
{
10901120
return true;
10911121
}
1092-
else di2 = di2.Parent;
1122+
else
1123+
{
1124+
if (di2.Parent.FullName.Length < di1.FullName.Length) return false;
1125+
di2 = di2.Parent;
1126+
}
10931127
}
10941128

10951129
return false;
10961130
}
10971131

1132+
/// <summary>
1133+
/// Utility method to get the common file path, this may fail for files with the same partial name.
1134+
/// </summary>
1135+
/// <param name="s">A collection of filepaths</param>
1136+
/// <returns></returns>
1137+
internal string GetLongestCommonPrefix(string[] s)
1138+
{
1139+
int k = s[0].Length;
1140+
for (int i = 1; i < s.Length; i++)
1141+
{
1142+
k = Math.Min(k, s[i].Length);
1143+
for (int j = 0; j < k; j++)
1144+
if (s[i][j] != s[0][j])
1145+
{
1146+
k = j;
1147+
break;
1148+
}
1149+
}
1150+
return Path.GetDirectoryName(s[0].Substring(0, k));
1151+
}
1152+
10981153
/// <summary>
10991154
/// Return a list of HostComboboxEntry describing known hosts from PM.
11001155
/// Return an empty list if PM is down.
@@ -1403,6 +1458,7 @@ internal static PublishPackageViewModel FromLocalPackage(DynamoViewModel dynamoV
14031458
CopyrightHolder = pkg.CopyrightHolder,
14041459
CopyrightYear = pkg.CopyrightYear,
14051460
IsPublishFromLocalPackage = true,
1461+
CurrentPackageDirectory = pkg.RootDirectory,
14061462
//default retain folder structure to true when publishing a new version from local.
14071463
RetainFolderStructureOverride = retainFolderStructure
14081464
};
@@ -1636,7 +1692,7 @@ internal IEnumerable<string> GetAllFiles()
16361692
// union with additional files
16371693
files = files.Union(AdditionalFiles);
16381694
// if we retain the folder structure, we don't want to lose assemblies in sub-folders
1639-
// othrewise we need to delete duplicate assemblies which will end up in the same `dll` folder
1695+
// otherwise we need to delete duplicate assemblies which will end up in the same `dll` folder
16401696
files = RetainFolderStructureOverride && !IsPublishFromLocalPackage ?
16411697
files.Union(Assemblies.Select(x => x.LocalFilePath)) :
16421698
files.Union(Assemblies.Select(x => x.Assembly.Location));
@@ -2232,6 +2288,10 @@ private void PublishLocally()
22322288
var remapper = new CustomNodePathRemapper(DynamoViewModel.Model.CustomNodeManager,
22332289
DynamoModel.IsTestMode);
22342290
var builder = new PackageDirectoryBuilder(new MutatingFileSystem(), remapper);
2291+
if (string.IsNullOrEmpty(Package.RootDirectory))
2292+
{
2293+
Package.RootDirectory = CurrentPackageDirectory;
2294+
}
22352295
builder.BuildRetainDirectory(Package, publishPath, updatedFiles, MarkdownFiles);
22362296
UploadState = PackageUploadHandle.State.Uploaded;
22372297
}
@@ -2647,7 +2707,7 @@ internal PackageItemRootViewModel GetPreBuildRootItemViewModel(string publishPat
26472707
var docItemPreview = new PackageItemRootViewModel(docDir) { isChild = true };
26482708

26492709
var pkg = new PackageItemRootViewModel(new FileInfo(Path.Combine(rootDir, "pkg.json")));
2650-
rootItemPreview.AddChild(pkg);
2710+
rootItemPreview.AddChildRecursively(pkg);
26512711

26522712
foreach (var file in files)
26532713
{
@@ -2657,12 +2717,12 @@ internal PackageItemRootViewModel GetPreBuildRootItemViewModel(string publishPat
26572717
if (Path.GetDirectoryName(file).EndsWith(PackageDirectoryBuilder.DocumentationDirectoryName))
26582718
{
26592719
var doc = new PackageItemRootViewModel(new FileInfo(Path.Combine(docDir, fileName)));
2660-
docItemPreview.AddChild(doc);
2720+
docItemPreview.AddChildRecursively(doc);
26612721
}
26622722
else if (file.EndsWith(".dyf"))
26632723
{
26642724
var dyfPreview = new PackageItemRootViewModel(fileName, Path.Combine(dyfDir, fileName));
2665-
dyfItemPreview.AddChild(dyfPreview);
2725+
dyfItemPreview.AddChildRecursively(dyfPreview);
26662726
}
26672727
else if (file.EndsWith(".dll") || PackageDirectoryBuilder.IsXmlDocFile(file, files) || PackageDirectoryBuilder.IsDynamoCustomizationFile(file, files))
26682728
{
@@ -2677,27 +2737,27 @@ internal PackageItemRootViewModel GetPreBuildRootItemViewModel(string publishPat
26772737
else
26782738
{
26792739
var dll = new PackageItemRootViewModel(new FileInfo(Path.Combine(binDir, fileName)));
2680-
binItemPreview.AddChild(dll);
2740+
binItemPreview.AddChildRecursively(dll);
26812741
}
26822742
}
26832743
else
26842744
{
26852745
var extra = new PackageItemRootViewModel(new FileInfo(Path.Combine(extraDir, fileName)));
2686-
extraItemPreview.AddChild(extra);
2746+
extraItemPreview.AddChildRecursively(extra);
26872747
}
26882748
}
26892749

26902750
foreach(var docFile in MarkdownFiles)
26912751
{
26922752
var fileName = Path.GetFileName(docFile);
26932753
var doc = new PackageItemRootViewModel(new FileInfo(Path.Combine(docDir, fileName)));
2694-
docItemPreview.AddChild(doc);
2754+
docItemPreview.AddChildRecursively(doc);
26952755
}
26962756

2697-
rootItemPreview.AddChild(dyfItemPreview);
2698-
rootItemPreview.AddChild(binItemPreview);
2699-
rootItemPreview.AddChild(extraItemPreview);
2700-
rootItemPreview.AddChild(docItemPreview);
2757+
rootItemPreview.AddChildRecursively(dyfItemPreview);
2758+
rootItemPreview.AddChildRecursively(binItemPreview);
2759+
rootItemPreview.AddChildRecursively(extraItemPreview);
2760+
rootItemPreview.AddChildRecursively(docItemPreview);
27012761

27022762
return rootItemPreview;
27032763
}

src/DynamoPackages/PackageDirectoryBuilder.cs

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,13 @@ public IDirectoryInfo BuildRetainDirectory(Package package, string packagesDirec
6969

7070
var rootPath = Path.Combine(packagesDirectory, package.Name);
7171
var rootDir = fileSystem.TryCreateDirectory(rootPath);
72+
var sourcePackageDir = package.RootDirectory;
7273
package.RootDirectory = rootDir.FullName;
7374

7475
var dyfFiles = new List<string>();
7576

7677
RemoveUnselectedFiles(contentFiles.SelectMany(files => files).ToList(), rootDir);
77-
CopyFilesIntoRetainedPackageDirectory(contentFiles, markdownFiles, rootDir, out dyfFiles);
78+
CopyFilesIntoRetainedPackageDirectory(contentFiles, markdownFiles, sourcePackageDir, rootDir, out dyfFiles);
7879
RemoveRetainDyfFiles(contentFiles.SelectMany(files => files).ToList(), dyfFiles);
7980

8081
RemapRetainCustomNodeFilePaths(contentFiles.SelectMany(files => files).ToList(), dyfFiles);
@@ -212,19 +213,12 @@ private void WritePackageHeader(Package package, IDirectoryInfo rootDir)
212213
fileSystem.WriteAllText(headerPath, pkgHeaderStr);
213214
}
214215

215-
internal void CopyFilesIntoRetainedPackageDirectory(IEnumerable<IEnumerable<string>> contentFiles, IEnumerable<string> markdownFiles, IDirectoryInfo rootDir, out List<string> dyfFiles)
216+
internal void CopyFilesIntoRetainedPackageDirectory(IEnumerable<IEnumerable<string>> contentFiles, IEnumerable<string> markdownFiles, string sourcePackageDir, IDirectoryInfo rootDir, out List<string> dyfFiles)
216217
{
217218
dyfFiles = new List<string>();
218219

219220
foreach (var files in contentFiles)
220221
{
221-
// We expect that files are bundled in root folders
222-
// For single files, just get its folder
223-
var commonPath = files.Count() > 1 ? GetLongestCommonPrefix(files.ToArray()) : Path.GetDirectoryName(files.FirstOrDefault());
224-
commonPath = commonPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
225-
var commonRootPath = Path.GetDirectoryName(commonPath);
226-
if (commonRootPath == null) commonRootPath = commonPath; // already at the root
227-
228222
foreach (var file in files.Where(x => x != null))
229223
{
230224
// If the file doesn't actually exist, don't copy it
@@ -233,11 +227,12 @@ internal void CopyFilesIntoRetainedPackageDirectory(IEnumerable<IEnumerable<stri
233227
continue;
234228
}
235229

236-
var relativePath = file.Substring(commonRootPath.Length);
230+
var relativePath = file.Substring(sourcePackageDir.Length);
237231

238232
// Ensure the relative path starts with a directory separator.
239233
if (!string.IsNullOrEmpty(relativePath) && relativePath[0] != Path.DirectorySeparatorChar)
240234
{
235+
relativePath = relativePath.TrimStart(new char[] { '/', '\\' });
241236
relativePath = Path.DirectorySeparatorChar + relativePath;
242237
}
243238

@@ -387,29 +382,6 @@ public static string NormalizePath(string path)
387382
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
388383
.ToUpperInvariant();
389384
}
390-
391-
392-
/// <summary>
393-
/// Utility method to get the common file path, this may fail for files with the same partial name.
394-
/// </summary>
395-
/// <param name="s">A collection of filepaths</param>
396-
/// <returns></returns>
397-
public static string GetLongestCommonPrefix(string[] s)
398-
{
399-
int k = s[0].Length;
400-
for (int i = 1; i < s.Length; i++)
401-
{
402-
k = Math.Min(k, s[i].Length);
403-
for (int j = 0; j < k; j++)
404-
if (s[i][j] != s[0][j])
405-
{
406-
k = j;
407-
break;
408-
}
409-
}
410-
return Path.GetDirectoryName(s[0].Substring(0, k));
411-
}
412-
413385
#endregion
414386

415387
}

test/DynamoCoreWpfTests/PackageManager/PackageManagerUITests.cs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1829,7 +1829,7 @@ public void AddsFilesAndFoldersFromFilePathsCorrectly()
18291829
Assert.AreEqual(additionalFiles.Count, allFiles.Count(f => !f.EndsWith(".dll")));
18301830

18311831
var packageContents = vm.PackageContents;
1832-
Assert.AreEqual(packageContents.Count, 1); // We expect only 1 root item here
1832+
Assert.AreEqual(1, packageContents.Count); // We expect only 1 root item here
18331833

18341834
// Assert that the PackageContents contains the correct number of items
18351835
var allFilesAndFoldres = PackageItemRootViewModel.GetFiles(packageContents.First());
@@ -2162,7 +2162,7 @@ public void CanRemoveAllDependencyTypes()
21622162
// This makes sense as we don't want to try to establish 'common parent' for folders that maybe too far apart in a tree structure
21632163
rootFolder = vm.PackageContents.Where(x => x.DependencyType.Equals(DependencyType.Folder));
21642164
Assert.AreEqual(1, rootFolder.Count());
2165-
Assert.AreEqual(3, PackageItemRootViewModel.GetFiles(rootFolder.First()).Count());
2165+
Assert.AreEqual(4, PackageItemRootViewModel.GetFiles(rootFolder.First()).Count());
21662166

21672167
Assert.DoesNotThrow(() => vm.RemoveItemCommand.Execute(rootFolder.First()));
21682168
Assert.IsFalse(vm.PackageContents.Any());
@@ -2272,6 +2272,46 @@ public void AssertPreviewPackageRetainFolderStructureEqualsPublishLocalPackageRe
22722272
Directory.Delete(publishPath, true);
22732273
}
22742274

2275+
[Test]
2276+
public void AssertPreviewPackageRetainFolderStructureEqualsPublishLocalPackageResultsForNestedFolders()
2277+
{
2278+
var packageName = "NestedPackage";
2279+
var pathManager = this.ViewModel.Model.PathManager as PathManager;
2280+
var publishPath = Path.Combine(pathManager.DefaultPackagesDirectory, packageName);
2281+
2282+
string nodePath = Path.Combine(TestDirectory, "pkgs", packageName);
2283+
var allFiles = Directory.GetFiles(nodePath, "*", SearchOption.AllDirectories).ToList();
2284+
var allFolders = Directory.GetDirectories(nodePath, "*", SearchOption.AllDirectories).ToList();
2285+
2286+
//now lets publish this package.
2287+
var newPkgVm = new PublishPackageViewModel(this.ViewModel);
2288+
newPkgVm.RetainFolderStructureOverride = true;
2289+
newPkgVm.AddAllFilesAfterSelection(allFiles);
2290+
2291+
var previewFilesAndFolders = PackageItemRootViewModel.GetFiles(newPkgVm.PreviewPackageContents.ToList());
2292+
var previewFiles = previewFilesAndFolders.Where(x => !x.DependencyType.Equals(DependencyType.Folder));
2293+
var previewFolders = previewFilesAndFolders.Where(x => x.DependencyType.Equals(DependencyType.Folder));
2294+
2295+
newPkgVm.Name = packageName;
2296+
newPkgVm.MajorVersion = "0";
2297+
newPkgVm.MinorVersion = "0";
2298+
newPkgVm.BuildVersion = "1";
2299+
newPkgVm.PublishLocallyCommand.Execute();
2300+
2301+
Assert.IsTrue(Directory.Exists(publishPath));
2302+
2303+
// Arrange
2304+
var createdFiles = Directory.GetFiles(publishPath, "*", SearchOption.AllDirectories).ToList();
2305+
var createdFolders = Directory.GetDirectories(publishPath, "*", SearchOption.AllDirectories).ToList();
2306+
2307+
// Assert
2308+
Assert.AreEqual(createdFiles.Count(), previewFiles.Count() + 1);
2309+
Assert.AreEqual(1, createdFolders.Count(), previewFolders.Count()); // One subfolder was created
2310+
2311+
// Clean up
2312+
Directory.Delete(publishPath, true);
2313+
}
2314+
22752315
[Test]
22762316
public void AssertPublishLocalHandleType()
22772317
{

0 commit comments

Comments
 (0)