Skip to content
This repository has been archived by the owner on Jul 5, 2024. It is now read-only.

Commit

Permalink
Pre-cache package files when reading metadata; remove needless memory…
Browse files Browse the repository at this point in the history
… copy when reading lib files
  • Loading branch information
caesay committed Feb 19, 2022
1 parent 8bdf6c3 commit 061904d
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 168 deletions.
141 changes: 41 additions & 100 deletions src/Squirrel/NuGet/ZipPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ internal interface IPackage
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; }
Expand All @@ -46,7 +47,8 @@ internal class ZipPackage : IPackage
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; }
Expand All @@ -56,99 +58,67 @@ internal class ZipPackage : IPackage
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)
{
using var zip = ZipArchive.Open(zipStream, new() { LeaveStreamOpen = true });
using var manifest = GetManifestEntry(zip).OpenEntryStream();
ReadManifest(manifest);
Frameworks = GetFrameworks(zip);
}
public ZipPackage(string filePath) : this(File.OpenRead(filePath))
{ }

public ZipPackage(string filePath)
public ZipPackage(Stream zipStream, bool leaveOpen = false)
{
if (String.IsNullOrEmpty(filePath)) {
throw new ArgumentException("Argument_Cannot_Be_Null_Or_Empty", "filePath");
}

_streamFactory = () => File.OpenRead(filePath);

using var stream = _streamFactory();
using var zip = ZipArchive.Open(stream);
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);
}

private string[] GetFrameworks(ZipArchive zip)
public static void SetSquirrelMetadata(string nuspecPath, RuntimeCpu architecture, IEnumerable<string> runtimes)
{
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();
}
Dictionary<string, string> toSet = new();
if (architecture != RuntimeCpu.Unknown)
toSet.Add("machineArchitecture", architecture.ToString());
if (runtimes.Any())
toSet.Add("runtimeDependencies", String.Join(",", runtimes));

public Stream ReadLibFileStream(string fileName)
{
using var stream = _streamFactory();
using var zip = ZipArchive.Open(stream);
if (!toSet.Any())
return;

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));
XDocument document;
using (var fs = File.OpenRead(nuspecPath))
document = NugetUtil.LoadSafe(fs, ignoreWhiteSpace: true);

if (!files.Any()) {
return null;
var metadataElement = document.Root.ElementsNoNamespace("metadata").FirstOrDefault();
if (metadataElement == null) {
throw new InvalidDataException(
String.Format(CultureInfo.CurrentCulture, "Manifest_RequiredElementMissing", "metadata"));
}

var f = files.First();
using var fstream = f.Entry.OpenEntryStream();
var ms = new MemoryStream();
fstream.CopyTo(ms);
return ms;
}

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));
}
foreach (var el in toSet) {
var elName = XName.Get(el.Key, document.Root.GetDefaultNamespace().NamespaceName);
metadataElement.SetElementValue(elName, el.Value);
}

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();
document.Save(nuspecPath);
}

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)
Expand Down Expand Up @@ -184,35 +154,6 @@ private void ReadManifest(Stream manifestStream)
}
}

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) {
Expand Down
73 changes: 34 additions & 39 deletions src/Squirrel/NuGet/ZipPackageFile.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SharpCompress.Archives.Zip;

namespace Squirrel.NuGet
{
Expand All @@ -8,35 +11,16 @@ internal interface IPackageFile : IFrameworkTargetable
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 {
Expand All @@ -47,22 +31,36 @@ IEnumerable<string> IFrameworkTargetable.SupportedFrameworks {
}
}

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)
Expand All @@ -73,10 +71,7 @@ public override bool Equals(object obj)
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);
}
}
}
33 changes: 18 additions & 15 deletions src/Squirrel/UpdateManager.InstallHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,26 +44,29 @@ public async Task<RegistryKey> CreateUninstallerRegistryEntry(string uninstallCm

// 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[] {
Expand Down
12 changes: 6 additions & 6 deletions test/DeltaPackageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ public void ApplyDeltaPackageSmokeTest()
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)
Expand Down Expand Up @@ -123,7 +123,7 @@ public void CreateDeltaPackageIntegrationTest()
// File Checks
///

var deltaPkgFiles = deltaPkg.GetFiles().ToList();
var deltaPkgFiles = deltaPkg.Files.ToList();
deltaPkgFiles.Count.ShouldBeGreaterThan(0);

this.Log().Info("Files in delta package:");
Expand All @@ -148,13 +148,13 @@ public void CreateDeltaPackageIntegrationTest()
.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);
Expand Down
Loading

0 comments on commit 061904d

Please sign in to comment.