mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Pre-cache package files when reading metadata; remove needless memory copy when reading lib files
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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[] {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user