Pre-cache package files when reading metadata; remove needless memory copy when reading lib files

This commit is contained in:
Caelan Sayler
2022-02-19 13:07:17 +00:00
parent 8bdf6c3044
commit 061904dc23
6 changed files with 110 additions and 171 deletions

View File

@@ -21,6 +21,7 @@ namespace Squirrel.NuGet
IEnumerable<string> Frameworks { get; }
IEnumerable<FrameworkAssemblyReference> FrameworkAssemblies { get; }
IEnumerable<PackageDependencySet> DependencySets { get; }
IEnumerable<ZipPackageFile> Files { get; }
Uri ProjectUrl { get; }
string ReleaseNotes { get; }
Uri IconUrl { get; }
@@ -46,7 +47,8 @@ namespace Squirrel.NuGet
public string Language { get; private set; }
public IEnumerable<string> Tags { get; private set; } = Enumerable.Empty<string>();
public IEnumerable<string> RuntimeDependencies { get; private set; } = Enumerable.Empty<string>();
public IEnumerable<string> Frameworks { get; private set; }
public IEnumerable<string> Frameworks { get; private set; } = Enumerable.Empty<string>();
public IEnumerable<ZipPackageFile> Files { get; private set; } = Enumerable.Empty<ZipPackageFile>();
public RuntimeCpu MachineArchitecture { get; private set; }
protected string Description { get; private set; }
@@ -56,99 +58,67 @@ namespace Squirrel.NuGet
protected string Summary { get; private set; }
protected string Copyright { get; private set; }
private readonly Func<Stream> _streamFactory;
private static readonly string[] ExcludePaths = new[] { "_rels", "package" };
public ZipPackage(Stream zipStream)
public ZipPackage(string filePath) : this(File.OpenRead(filePath))
{ }
public ZipPackage(Stream zipStream, bool leaveOpen = false)
{
using var zip = ZipArchive.Open(zipStream, new() { LeaveStreamOpen = true });
using var zip = ZipArchive.Open(zipStream, new() { LeaveStreamOpen = leaveOpen });
using var manifest = GetManifestEntry(zip).OpenEntryStream();
ReadManifest(manifest);
Frameworks = GetFrameworks(zip);
Files = GetPackageFiles(zip).ToArray();
Frameworks = GetFrameworks(Files);
}
public ZipPackage(string filePath)
public static void SetSquirrelMetadata(string nuspecPath, RuntimeCpu architecture, IEnumerable<string> runtimes)
{
if (String.IsNullOrEmpty(filePath)) {
throw new ArgumentException("Argument_Cannot_Be_Null_Or_Empty", "filePath");
Dictionary<string, string> toSet = new();
if (architecture != RuntimeCpu.Unknown)
toSet.Add("machineArchitecture", architecture.ToString());
if (runtimes.Any())
toSet.Add("runtimeDependencies", String.Join(",", runtimes));
if (!toSet.Any())
return;
XDocument document;
using (var fs = File.OpenRead(nuspecPath))
document = NugetUtil.LoadSafe(fs, ignoreWhiteSpace: true);
var metadataElement = document.Root.ElementsNoNamespace("metadata").FirstOrDefault();
if (metadataElement == null) {
throw new InvalidDataException(
String.Format(CultureInfo.CurrentCulture, "Manifest_RequiredElementMissing", "metadata"));
}
_streamFactory = () => File.OpenRead(filePath);
using var stream = _streamFactory();
using var zip = ZipArchive.Open(stream);
using var manifest = GetManifestEntry(zip).OpenEntryStream();
ReadManifest(manifest);
Frameworks = GetFrameworks(zip);
}
private string[] GetFrameworks(ZipArchive zip)
{
var fileFrameworks = GetPackageFiles(zip)
.Select(z => NugetUtil.ParseFrameworkNameFromFilePath(z.Path, out var _));
return FrameworkAssemblies
.SelectMany(f => f.SupportedFrameworks)
.Concat(fileFrameworks)
.Where(f => f != null)
.Distinct()
.ToArray();
}
public Stream ReadLibFileStream(string fileName)
{
using var stream = _streamFactory();
using var zip = ZipArchive.Open(stream);
string folderPrefix = NugetUtil.LibDirectory + Path.DirectorySeparatorChar;
var files = GetPackageFiles(zip)
.Where(z => z.Path.StartsWith(folderPrefix, StringComparison.OrdinalIgnoreCase))
.Where(z => Path.GetFileName(z.Path).Equals(fileName, StringComparison.OrdinalIgnoreCase));
if (!files.Any()) {
return null;
foreach (var el in toSet) {
var elName = XName.Get(el.Key, document.Root.GetDefaultNamespace().NamespaceName);
metadataElement.SetElementValue(elName, el.Value);
}
var f = files.First();
using var fstream = f.Entry.OpenEntryStream();
var ms = new MemoryStream();
fstream.CopyTo(ms);
return ms;
document.Save(nuspecPath);
}
public IEnumerable<IPackageFile> GetLibFiles()
{
return GetFiles(NugetUtil.LibDirectory);
}
public IEnumerable<IPackageFile> GetContentFiles()
{
return GetFiles(NugetUtil.ContentDirectory);
}
public IEnumerable<IPackageFile> GetFiles(string directory)
{
string folderPrefix = directory + Path.DirectorySeparatorChar;
return GetFiles().Where(file => file.Path.StartsWith(folderPrefix, StringComparison.OrdinalIgnoreCase));
}
public IEnumerable<IPackageFile> GetFiles()
{
using var stream = _streamFactory();
using var zip = ZipArchive.Open(stream);
return GetPackageFiles(zip)
.Select(z => (IPackageFile) new ZipPackageFile(z.Path))
.ToArray();
}
private IEnumerable<(string Path, ZipArchiveEntry Entry)> GetPackageFiles(ZipArchive zip)
private IEnumerable<ZipPackageFile> GetPackageFiles(ZipArchive zip)
{
return from entry in zip.Entries
where !entry.IsDirectory
let uri = new Uri(entry.Key, UriKind.Relative)
let path = NugetUtil.GetPath(uri)
where IsPackageFile(path)
select (path, entry);
select new ZipPackageFile(uri);
}
private string[] GetFrameworks(IEnumerable<ZipPackageFile> files)
{
return FrameworkAssemblies
.SelectMany(f => f.SupportedFrameworks)
.Concat(files.Select(z => z.TargetFramework))
.Where(f => f != null)
.Distinct()
.ToArray();
}
private ZipArchiveEntry GetManifestEntry(ZipArchive zip)
@@ -184,35 +154,6 @@ namespace Squirrel.NuGet
}
}
public static void SetSquirrelMetadata(string nuspecPath, RuntimeCpu architecture, IEnumerable<string> runtimes)
{
Dictionary<string, string> toSet = new();
if (architecture != RuntimeCpu.Unknown)
toSet.Add("machineArchitecture", architecture.ToString());
if (runtimes.Any())
toSet.Add("runtimeDependencies", String.Join(",", runtimes));
if (!toSet.Any())
return;
XDocument document;
using (var fs = File.OpenRead(nuspecPath))
document = NugetUtil.LoadSafe(fs, ignoreWhiteSpace: true);
var metadataElement = document.Root.ElementsNoNamespace("metadata").FirstOrDefault();
if (metadataElement == null) {
throw new InvalidDataException(
String.Format(CultureInfo.CurrentCulture, "Manifest_RequiredElementMissing", "metadata"));
}
foreach (var el in toSet) {
var elName = XName.Get(el.Key, document.Root.GetDefaultNamespace().NamespaceName);
metadataElement.SetElementValue(elName, el.Value);
}
document.Save(nuspecPath);
}
private void ReadMetadataValue(XElement element, HashSet<string> allElements)
{
if (element.Value == null) {

View File

@@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SharpCompress.Archives.Zip;
namespace Squirrel.NuGet
{
@@ -8,35 +11,16 @@ namespace Squirrel.NuGet
string Path { get; }
string EffectivePath { get; }
string TargetFramework { get; }
bool IsLibFile();
bool IsContentFile();
Stream GetEntryStream(Stream archiveStream);
}
internal class ZipPackageFile : IPackageFile, IEquatable<ZipPackageFile>
{
private readonly string _targetFramework;
public ZipPackageFile(string path)
{
Path = path;
string effectivePath;
_targetFramework = NugetUtil.ParseFrameworkNameFromFilePath(path, out effectivePath);
EffectivePath = effectivePath;
}
public string Path {
get;
private set;
}
public string EffectivePath {
get;
private set;
}
public string TargetFramework {
get {
return _targetFramework;
}
}
public string EffectivePath { get; }
public string TargetFramework { get; }
public string Path { get; }
IEnumerable<string> IFrameworkTargetable.SupportedFrameworks {
get {
@@ -47,22 +31,36 @@ namespace Squirrel.NuGet
}
}
public override string ToString()
private readonly Uri _entryKey;
public ZipPackageFile(Uri relpath)
{
return Path;
_entryKey = relpath;
Path = NugetUtil.GetPath(relpath);
TargetFramework = NugetUtil.ParseFrameworkNameFromFilePath(Path, out var effectivePath);
EffectivePath = effectivePath;
}
public override int GetHashCode()
public Stream GetEntryStream(Stream archiveStream)
{
unchecked {
int hash = 17;
hash = hash * 23 + Path.GetHashCode();
hash = hash * 23 + EffectivePath.GetHashCode();
hash = hash * 23 + TargetFramework.GetHashCode();
return hash;
}
using var zip = ZipArchive.Open(archiveStream, new() { LeaveStreamOpen = true });
var entry = zip.Entries.FirstOrDefault(f => new Uri(f.Key, UriKind.Relative) == _entryKey);
return entry?.OpenEntryStream();
}
public bool IsLibFile() => IsFileInTopDirectory(NugetUtil.LibDirectory);
public bool IsContentFile() => IsFileInTopDirectory(NugetUtil.ContentDirectory);
public bool IsFileInTopDirectory(string directory)
{
string folderPrefix = directory + System.IO.Path.DirectorySeparatorChar;
return Path.StartsWith(folderPrefix, StringComparison.OrdinalIgnoreCase);
}
public override string ToString() => Path;
public override int GetHashCode() => Path.GetHashCode();
public override bool Equals(object obj)
{
if (obj is ZipPackageFile zpf)
@@ -73,10 +71,7 @@ namespace Squirrel.NuGet
public bool Equals(ZipPackageFile other)
{
if (other == null) return false;
return
Path.Equals(other.Path) &&
EffectivePath.Equals(other.EffectivePath) &&
TargetFramework.Equals(other.TargetFramework);
return Path.Equals(other.Path);
}
}
}

View File

@@ -44,26 +44,29 @@ namespace Squirrel
// we will try to find an "app.ico" from the package, write it to the local app dir, and then
// use it for the uninstaller icon. If an app.ico does not exist, it will use a SquirrelAwareApp exe icon instead.
using var iconFileStream = zp.ReadLibFileStream("app.ico");
if (iconFileStream != null) {
try {
try {
var appIconEntry = zp.Files
.FirstOrDefault(f => f.IsLibFile() && f.EffectivePath.Equals("app.ico", StringComparison.InvariantCultureIgnoreCase));
if (appIconEntry != null) {
var targetIco = Path.Combine(rootAppDirectory, "app.ico");
using (var pkgStream = File.OpenRead(pkgPath))
using (var iconStream = appIconEntry.GetEntryStream(pkgStream))
using (var targetStream = File.Open(targetIco, FileMode.Create, FileAccess.Write))
await iconFileStream.CopyToAsync(targetStream).ConfigureAwait(false);
await iconStream.CopyToAsync(targetStream).ConfigureAwait(false);
this.Log().Info($"File '{targetIco}' is being used for uninstall icon.");
key.SetValue("DisplayIcon", targetIco, RegistryValueKind.String);
} catch (Exception ex) {
this.Log().InfoException("Couldn't write uninstall icon, don't care", ex);
}
} else {
// DisplayIcon can be a path to an exe instead of an ico if an icon was not provided.
var appDir = new DirectoryInfo(Utility.AppDirForRelease(rootAppDirectory, latest));
var appIconExe = SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(appDir.FullName).FirstOrDefault()
?? appDir.GetFiles("*.exe").Select(x => x.FullName).FirstOrDefault();
if (appIconExe != null) {
this.Log().Info($"There was no icon found, will use '{appIconExe}' for uninstall icon.");
key.SetValue("DisplayIcon", appIconExe, RegistryValueKind.String);
} else {
// DisplayIcon can be a path to an exe instead of an ico if an icon was not provided.
var appDir = new DirectoryInfo(Utility.AppDirForRelease(rootAppDirectory, latest));
var appIconExe = SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(appDir.FullName).FirstOrDefault()
?? appDir.GetFiles("*.exe").Select(x => x.FullName).FirstOrDefault();
if (appIconExe != null) {
this.Log().Info($"There was no icon found, will use '{appIconExe}' for uninstall icon.");
key.SetValue("DisplayIcon", appIconExe, RegistryValueKind.String);
}
}
} catch (Exception ex) {
this.Log().InfoException("Couldn't write uninstall icon, don't care", ex);
}
var stringsToWrite = new[] {

View File

@@ -31,11 +31,11 @@ namespace Squirrel.Tests
result.Version.ShouldEqual(expected.Version);
this.Log().Info("Expected file list:");
var expectedList = expected.GetFiles().Select(x => x.Path).OrderBy(x => x).ToList();
var expectedList = expected.Files.Select(x => x.Path).OrderBy(x => x).ToList();
expectedList.ForEach(x => this.Log().Info(x));
this.Log().Info("Actual file list:");
var actualList = result.GetFiles().Select(x => x.Path).OrderBy(x => x).ToList();
var actualList = result.Files.Select(x => x.Path).OrderBy(x => x).ToList();
actualList.ForEach(x => this.Log().Info(x));
Enumerable.Zip(expectedList, actualList, (e, a) => e == a)
@@ -123,7 +123,7 @@ namespace Squirrel.Tests
// File Checks
///
var deltaPkgFiles = deltaPkg.GetFiles().ToList();
var deltaPkgFiles = deltaPkg.Files.ToList();
deltaPkgFiles.Count.ShouldBeGreaterThan(0);
this.Log().Info("Files in delta package:");
@@ -148,13 +148,13 @@ namespace Squirrel.Tests
.ShouldBeTrue();
// Every .diff file should have a shasum file
deltaPkg.GetFiles().Any(x => x.Path.ToLowerInvariant().EndsWith(".diff")).ShouldBeTrue();
deltaPkg.GetFiles()
deltaPkg.Files.Any(x => x.Path.ToLowerInvariant().EndsWith(".diff")).ShouldBeTrue();
deltaPkg.Files
.Where(x => x.Path.ToLowerInvariant().EndsWith(".diff"))
.ForEach(x => {
var lookingFor = x.Path.Replace(".diff", ".shasum");
this.Log().Info("Looking for corresponding shasum file: {0}", lookingFor);
deltaPkg.GetFiles().Any(y => y.Path == lookingFor).ShouldBeTrue();
deltaPkg.Files.Any(y => y.Path == lookingFor).ShouldBeTrue();
});
} finally {
tempFiles.ForEach(File.Delete);

View File

@@ -63,7 +63,7 @@ namespace Squirrel.Tests
this.Log().Info("Files in release package:");
List<IPackageFile> files = pkg.GetFiles().ToList();
List<ZipPackageFile> files = pkg.Files.ToList();
files.ForEach(x => this.Log().Info(x.Path));
List<string> nonDesktopPaths = new[] { "sl", "winrt", "netcore", "win8", "windows8", "MonoAndroid", "MonoTouch", "MonoMac", "wp", }
@@ -156,7 +156,7 @@ namespace Squirrel.Tests
this.Log().Info("Files in release package:");
var contentFiles = pkg.GetContentFiles();
var contentFiles = pkg.Files.Where(f => f.IsContentFile()).ToArray();
Assert.Equal(2, contentFiles.Count());
var contentFilePaths = contentFiles.Select(f => f.EffectivePath);
@@ -164,7 +164,7 @@ namespace Squirrel.Tests
Assert.Contains("some-words.txt", contentFilePaths);
Assert.Contains("dir\\item-in-subdirectory.txt", contentFilePaths);
Assert.Equal(1, pkg.GetLibFiles().Count());
Assert.Equal(1, pkg.Files.Where(f => f.IsLibFile()).Count());
} finally {
File.Delete(outputPackage);
}

View File

@@ -21,8 +21,8 @@ namespace Squirrel.Tests
var zp = new ZipPackage(inputPackage);
var zipfw = zp.Frameworks;
var zipf = zp.GetFiles().OrderBy(f => f.Path).ToArray();
var zipfLib = zp.GetLibFiles().OrderBy(f => f.Path).ToArray();
var zipf = zp.Files.OrderBy(f => f.Path).ToArray();
var zipfLib = zp.Files.Where(f => f.IsLibFile()).OrderBy(f => f.Path).ToArray();
using Package package = Package.Open(inputPackage);
var packagingfw = GetSupportedFrameworks(zp, package);
@@ -49,7 +49,7 @@ namespace Squirrel.Tests
Assert.Equal("FullNuspec", zp.Id);
Assert.Equal(new SemanticVersion("1.0"), zp.Version);
Assert.Equal(new [] { "Anaïs Betts", "Caelan Sayler" }, dyn.Authors);
Assert.Equal(new[] { "Anaïs Betts", "Caelan Sayler" }, dyn.Authors);
Assert.Equal(new Uri("https://github.com/clowd/Clowd.Squirrel"), zp.ProjectUrl);
Assert.Equal(new Uri("https://user-images.githubusercontent.com/1287295/131249078-9e131e51-0b66-4dc7-8c0a-99cbea6bcf80.png"), zp.IconUrl);
Assert.Equal("A test description", dyn.Description);
@@ -74,7 +74,7 @@ namespace Squirrel.Tests
Assert.NotEmpty(zp.FrameworkAssemblies);
var fw = zp.FrameworkAssemblies.First();
Assert.Equal("System.Net.Http", fw.AssemblyName);
Assert.Equal(new [] { ".NETFramework4.6.1" }, fw.SupportedFrameworks);
Assert.Equal(new[] { ".NETFramework4.6.1" }, fw.SupportedFrameworks);
}
IEnumerable<string> GetSupportedFrameworks(ZipPackage zp, Package package)
@@ -104,7 +104,7 @@ namespace Squirrel.Tests
{
return (from part in package.GetParts()
where IsPackageFile(part)
select (IPackageFile) new ZipPackageFile(NugetUtil.GetPath(part.Uri))).ToList();
select (IPackageFile) new ZipPackageFile(part.Uri)).ToList();
}
bool IsPackageFile(PackagePart part)