Initial work on new netstandard symlink

This commit is contained in:
Caelan
2024-09-21 11:27:08 -06:00
parent 89955cad94
commit cb9447821d
12 changed files with 148 additions and 101 deletions

View File

@@ -114,7 +114,7 @@ namespace Velopack.Json
internal static class CompiledJson
{
public static readonly JsonSerializerSettings Options = new JsonSerializerSettings {
private static readonly JsonSerializerSettings Options = new JsonSerializerSettings {
Converters = { new StringEnumConverter(), new SemanticVersionConverter() },
ContractResolver = new JsonNameContractResolver(),
NullValueHandling = NullValueHandling.Ignore,

View File

@@ -39,14 +39,30 @@ namespace Velopack
: targetPath;
if (Directory.Exists(targetPath)) {
#if NETFRAMEWORK || NETSTANDARD
#if NETSTANDARD
if (VelopackRuntimeInfo.IsWindows) {
if (!CreateSymbolicLink(linkPath, finalTarget, SYMBOLIC_LINK_FLAG_DIRECTORY | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE))
ThrowLastWin32Error("Unable to create junction point / symlink.");
} else {
var linkInfo = new Mono.Unix.UnixSymbolicLinkInfo(linkPath);
linkInfo.CreateSymbolicLinkTo(targetPath);
}
#elif NETFRAMEWORK
if (!CreateSymbolicLink(linkPath, finalTarget, SYMBOLIC_LINK_FLAG_DIRECTORY | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE))
ThrowLastWin32Error("Unable to create junction point / symlink.");
#else
Directory.CreateSymbolicLink(linkPath, finalTarget);
#endif
} else if (File.Exists(targetPath)) {
#if NETFRAMEWORK || NETSTANDARD
#if NETSTANDARD
if (VelopackRuntimeInfo.IsWindows) {
if (!CreateSymbolicLink(linkPath, finalTarget, SYMBOLIC_LINK_FLAG_FILE | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE))
ThrowLastWin32Error("Unable to create junction point / symlink.");
} else {
var fileInfo = new Mono.Unix.UnixFileInfo(targetPath);
fileInfo.CreateSymbolicLink(linkPath);
}
#elif NETFRAMEWORK
if (!CreateSymbolicLink(linkPath, finalTarget, SYMBOLIC_LINK_FLAG_FILE | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE))
ThrowLastWin32Error("Unable to create junction point / symlink.");
#else
@@ -74,7 +90,7 @@ namespace Velopack
{
var isLink = TryGetLinkFsi(linkPath, out var fsi);
if (fsi != null && !isLink) {
throw new IOException("Path is not a junction point.");
throw new IOException("Path is not a junction point / symlink.");
} else {
fsi?.Delete();
}
@@ -106,13 +122,19 @@ namespace Velopack
private static string GetUnresolvedTarget(string linkPath)
{
if (TryGetLinkFsi(linkPath, out var fsi)) {
#if NETFRAMEWORK || NETSTANDARD
#if NETSTANDARD
if (VelopackRuntimeInfo.IsWindows) {
return GetTargetWin32(linkPath);
} else {
return Mono.Unix.UnixPath.ReadLink(linkPath);
}
#elif NETFRAMEWORK
return GetTargetWin32(linkPath);
#else
return fsi.LinkTarget!;
#endif
}
throw new IOException("Path does not exist or is not a junction point / symlink.");
}
@@ -140,6 +162,67 @@ namespace Velopack
}
#if NETFRAMEWORK || NETSTANDARD
private static string ToggleRelative(string basePath, string toggledPath)
{
// from https://github.com/RT-Projects/RT.Util/blob/master/RT.Util.Core/Paths/PathUtil.cs#L297
if (basePath.Length == 0)
throw new Exception("InvalidBasePath");
if (toggledPath.Length == 0)
throw new Exception("InvalidToggledPath");
if (!Path.IsPathRooted(basePath))
throw new Exception("BasePathNotAbsolute");
try { basePath = Path.GetFullPath(basePath + "\\"); } catch { throw new Exception("InvalidBasePath"); }
if (!Path.IsPathRooted(toggledPath)) {
try {
return StripTrailingSeparator(Path.GetFullPath(Path.Combine(basePath, toggledPath)));
} catch {
throw new Exception("InvalidToggledPath");
}
}
// Both basePath and toggledPath are absolute. Need to relativize toggledPath.
try { toggledPath = Path.GetFullPath(toggledPath + "\\"); } catch { throw new Exception("InvalidToggledPath"); }
int prevPos = -1;
int pos = toggledPath.IndexOf(Path.DirectorySeparatorChar);
while (pos != -1 && pos < basePath.Length &&
basePath.Substring(0, pos + 1).Equals(toggledPath.Substring(0, pos + 1), StringComparison.OrdinalIgnoreCase)) {
prevPos = pos;
pos = toggledPath.IndexOf(Path.DirectorySeparatorChar, pos + 1);
}
if (prevPos == -1)
throw new Exception("PathsOnDifferentDrives");
var piece = basePath.Substring(prevPos + 1);
var result = StripTrailingSeparator(
(".." + Path.DirectorySeparatorChar).Repeat(piece.Count(ch => ch == Path.DirectorySeparatorChar))
+ toggledPath.Substring(prevPos + 1));
return result.Length == 0 ? "." : result;
}
private static string Repeat(this string input, int numTimes)
{
if (numTimes == 0) return "";
if (numTimes == 1) return input;
if (numTimes == 2) return input + input;
var sb = new StringBuilder();
for (int i = 0; i < numTimes; i++)
sb.Append(input);
return sb.ToString();
}
private static string StripTrailingSeparator(string path)
{
if (path.Length < 1)
return path;
if (path[path.Length - 1] == '/' || path[path.Length - 1] == '\\')
return (path.Length == 3 && path[1] == ':') ? path : path.Substring(0, path.Length - 1);
else
return path;
}
[Flags]
private enum EFileAttributes : uint
{
@@ -194,7 +277,8 @@ namespace Velopack
private const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003;
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern uint GetFinalPathNameByHandle(IntPtr hFile, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpszFilePath, uint cchFilePath, uint dwFlags);
private static extern uint GetFinalPathNameByHandle(IntPtr hFile, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpszFilePath, uint cchFilePath,
uint dwFlags);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
@@ -281,63 +365,6 @@ namespace Velopack
{
throw new IOException(message, Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()));
}
private static string ToggleRelative(string basePath, string toggledPath)
{
// from https://github.com/RT-Projects/RT.Util/blob/master/RT.Util.Core/Paths/PathUtil.cs#L297
if (basePath.Length == 0)
throw new Exception("InvalidBasePath");
if (toggledPath.Length == 0)
throw new Exception("InvalidToggledPath");
if (!Path.IsPathRooted(basePath))
throw new Exception("BasePathNotAbsolute");
try { basePath = Path.GetFullPath(basePath + "\\"); } catch { throw new Exception("InvalidBasePath"); }
if (!Path.IsPathRooted(toggledPath)) {
try {
return StripTrailingSeparator(Path.GetFullPath(Path.Combine(basePath, toggledPath)));
} catch {
throw new Exception("InvalidToggledPath");
}
}
// Both basePath and toggledPath are absolute. Need to relativize toggledPath.
try { toggledPath = Path.GetFullPath(toggledPath + "\\"); } catch { throw new Exception("InvalidToggledPath"); }
int prevPos = -1;
int pos = toggledPath.IndexOf(Path.DirectorySeparatorChar);
while (pos != -1 && pos < basePath.Length && basePath.Substring(0, pos + 1).Equals(toggledPath.Substring(0, pos + 1), StringComparison.OrdinalIgnoreCase)) {
prevPos = pos;
pos = toggledPath.IndexOf(Path.DirectorySeparatorChar, pos + 1);
}
if (prevPos == -1)
throw new Exception("PathsOnDifferentDrives");
var piece = basePath.Substring(prevPos + 1);
var result = StripTrailingSeparator((".." + Path.DirectorySeparatorChar).Repeat(piece.Count(ch => ch == Path.DirectorySeparatorChar))
+ toggledPath.Substring(prevPos + 1));
return result.Length == 0 ? "." : result;
}
private static string Repeat(this string input, int numTimes)
{
if (numTimes == 0) return "";
if (numTimes == 1) return input;
if (numTimes == 2) return input + input;
var sb = new StringBuilder();
for (int i = 0; i < numTimes; i++)
sb.Append(input);
return sb.ToString();
}
private static string StripTrailingSeparator(string path)
{
if (path.Length < 1)
return path;
if (path[path.Length - 1] == '/' || path[path.Length - 1] == '\\')
return (path.Length == 3 && path[1] == ':') ? path : path.Substring(0, path.Length - 1);
else
return path;
}
#endif
}
}
}

View File

@@ -32,6 +32,7 @@
<PackageReference Include="Newtonsoft.Json" Version="[13.0.1,)" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="[2.2.0,)" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
</ItemGroup>
<ItemGroup Condition=" $(TargetFramework) == 'net6.0' ">

View File

@@ -1,36 +1,43 @@
namespace Velopack.Json;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using NuGet.Versioning;
namespace Velopack.Packaging;
public class SimpleJson
{
#if NET6_0_OR_GREATER
private static readonly System.Text.Json.JsonSerializerOptions Options = new System.Text.Json.JsonSerializerOptions {
AllowTrailingCommas = true,
ReadCommentHandling = System.Text.Json.JsonCommentHandling.Skip,
PropertyNameCaseInsensitive = true,
WriteIndented = true,
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
Converters = {
new System.Text.Json.Serialization.JsonStringEnumConverter(),
new SemanticVersionConverter(),
},
private static readonly JsonSerializerSettings Options = new JsonSerializerSettings {
Converters = { new StringEnumConverter(), new SemanticVersionConverter() },
NullValueHandling = NullValueHandling.Ignore,
};
#endif
public static T DeserializeObject<T>(string json)
{
#if NET6_0_OR_GREATER
return System.Text.Json.JsonSerializer.Deserialize<T>(json, Options);
#else
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(json, CompiledJson.Options);
#endif
return JsonConvert.DeserializeObject<T>(json, Options);
}
public static string SerializeObject<T>(T obj)
{
#if NET6_0_OR_GREATER
return System.Text.Json.JsonSerializer.Serialize(obj, Options);
#else
return Newtonsoft.Json.JsonConvert.SerializeObject(obj, CompiledJson.Options);
#endif
return JsonConvert.SerializeObject(obj, Options);
}
}
private class SemanticVersionConverter : JsonConverter<SemanticVersion>
{
public override SemanticVersion ReadJson(JsonReader reader, Type objectType, SemanticVersion existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
string s = reader.Value as string;
if (s == null) return null;
return SemanticVersion.Parse(s);
}
public override void WriteJson(JsonWriter writer, SemanticVersion value, JsonSerializer serializer)
{
if (value != null) {
writer.WriteValue(value.ToFullString());
} else {
writer.WriteNull();
}
}
}
}

View File

@@ -12,6 +12,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="Markdig" Version="0.37.0" />

View File

@@ -2,6 +2,7 @@
using System.Text.Json;
using NuGet.Versioning;
using Velopack.Json;
using Velopack.Packaging;
using Velopack.Sources;
using JsonPropertyNameAttribute = System.Text.Json.Serialization.JsonPropertyNameAttribute;

View File

@@ -5,7 +5,6 @@ using Velopack.Windows;
namespace Velopack.Tests;
[SupportedOSPlatform("windows")]
public class ShortcutTests
{
private readonly ITestOutputHelper _output;

View File

@@ -74,8 +74,8 @@ public class SymbolicLinkTests
SymbolicLink.Create(symFile, tmpFile, true);
Assert.True(File.Exists(symFile), "Symfile point exists now.");
Assert.True(SymbolicLink.Exists(symFile), "Junction point exists now.");
Assert.True(File.Exists(symFile), "Symlink should exist now.");
Assert.True(SymbolicLink.Exists(symFile), "Symlink should exist now.");
Assert.Equal(tmpFile, SymbolicLink.GetTarget(symFile));

View File

@@ -1,7 +1,7 @@
#pragma warning disable CS0618 // Type or member is obsolete
#pragma warning disable CS0612 // Type or member is obsolete
using System.Text;
using Velopack.Json;
using Velopack.Packaging;
using Velopack.Sources;
namespace Velopack.Tests.TestHelpers;

View File

@@ -1,7 +1,7 @@
using System.Text;
using NuGet.Versioning;
using Velopack.Compression;
using Velopack.Json;
using Velopack.Packaging;
using Velopack.Locators;
using Velopack.Sources;
using Velopack.Tests.TestHelpers;

View File

@@ -48,7 +48,6 @@ public class UtilityTests
}
[SkippableFact]
[SupportedOSPlatform("windows")]
public void SetAppIdOnShortcutTest()
{
Skip.IfNot(VelopackRuntimeInfo.IsWindows);

View File

@@ -1,15 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<NoWarn>$(NoWarn);CA1416</NoWarn>
</PropertyGroup>
<Choose>
<When Condition="$([MSBuild]::IsOSPlatform('Windows'))">
<PropertyGroup>
<TargetFrameworks>net8.0;net48</TargetFrameworks>
<TargetFrameworks>net6.0;net8.0;net48</TargetFrameworks>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
</PropertyGroup>
</Otherwise>
</Choose>
@@ -20,7 +24,6 @@
<ItemGroup>
<PackageReference Include="System.IO.Packaging" Version="8.0.0" />
<PackageReference Include="System.Text.Json" Version="8.0.4" />
</ItemGroup>
<ItemGroup Condition=" $(TargetFramework.StartsWith('net4')) ">
@@ -30,8 +33,17 @@
<Reference Include="System.IO.Compression.FileSystem" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\lib-csharp\Velopack.csproj" />
</ItemGroup>
<Choose>
<When Condition="'$(TargetFramework)' == 'net6.0'">
<ItemGroup>
<ProjectReference Include="..\..\src\lib-csharp\Velopack.csproj" SetTargetFramework="TargetFramework=netstandard2.0" />
</ItemGroup>
</When>
<Otherwise>
<ItemGroup>
<ProjectReference Include="..\..\src\lib-csharp\Velopack.csproj" />
</ItemGroup>
</Otherwise>
</Choose>
</Project>