diff --git a/src/lib-csharp/Internal/CompiledJson.cs b/src/lib-csharp/Internal/CompiledJson.cs index 8a278469..f5028d3d 100644 --- a/src/lib-csharp/Internal/CompiledJson.cs +++ b/src/lib-csharp/Internal/CompiledJson.cs @@ -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, diff --git a/src/lib-csharp/Internal/SymbolicLink.cs b/src/lib-csharp/Internal/SymbolicLink.cs index a52ee850..4aa80d8a 100644 --- a/src/lib-csharp/Internal/SymbolicLink.cs +++ b/src/lib-csharp/Internal/SymbolicLink.cs @@ -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 } -} +} \ No newline at end of file diff --git a/src/lib-csharp/Velopack.csproj b/src/lib-csharp/Velopack.csproj index 4a15cc98..c977d9ab 100644 --- a/src/lib-csharp/Velopack.csproj +++ b/src/lib-csharp/Velopack.csproj @@ -32,6 +32,7 @@ + diff --git a/src/vpk/Velopack.Packaging/SimpleJson.cs b/src/vpk/Velopack.Packaging/SimpleJson.cs index 6f11b8ad..8d9a22d2 100644 --- a/src/vpk/Velopack.Packaging/SimpleJson.cs +++ b/src/vpk/Velopack.Packaging/SimpleJson.cs @@ -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(string json) { -#if NET6_0_OR_GREATER - return System.Text.Json.JsonSerializer.Deserialize(json, Options); -#else - return Newtonsoft.Json.JsonConvert.DeserializeObject(json, CompiledJson.Options); -#endif + return JsonConvert.DeserializeObject(json, Options); } public static string SerializeObject(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 + { + 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(); + } + } + } +} \ No newline at end of file diff --git a/src/vpk/Velopack.Packaging/Velopack.Packaging.csproj b/src/vpk/Velopack.Packaging/Velopack.Packaging.csproj index 7e66f7d4..0db84bf9 100644 --- a/src/vpk/Velopack.Packaging/Velopack.Packaging.csproj +++ b/src/vpk/Velopack.Packaging/Velopack.Packaging.csproj @@ -12,6 +12,7 @@ + diff --git a/test/Velopack.Packaging.Tests/SimpleJsonTests.cs b/test/Velopack.Packaging.Tests/SimpleJsonTests.cs index 1e2ecd31..60f470e5 100644 --- a/test/Velopack.Packaging.Tests/SimpleJsonTests.cs +++ b/test/Velopack.Packaging.Tests/SimpleJsonTests.cs @@ -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; diff --git a/test/Velopack.Tests/ShortcutTests.cs b/test/Velopack.Tests/ShortcutTests.cs index 3177988e..658167b9 100644 --- a/test/Velopack.Tests/ShortcutTests.cs +++ b/test/Velopack.Tests/ShortcutTests.cs @@ -5,7 +5,6 @@ using Velopack.Windows; namespace Velopack.Tests; -[SupportedOSPlatform("windows")] public class ShortcutTests { private readonly ITestOutputHelper _output; diff --git a/test/Velopack.Tests/SymbolicLinkTests.cs b/test/Velopack.Tests/SymbolicLinkTests.cs index 7ea003c9..bff4264e 100644 --- a/test/Velopack.Tests/SymbolicLinkTests.cs +++ b/test/Velopack.Tests/SymbolicLinkTests.cs @@ -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)); diff --git a/test/Velopack.Tests/TestHelpers/FakeFixtureRepository.cs b/test/Velopack.Tests/TestHelpers/FakeFixtureRepository.cs index 676b0bb8..611d43ee 100644 --- a/test/Velopack.Tests/TestHelpers/FakeFixtureRepository.cs +++ b/test/Velopack.Tests/TestHelpers/FakeFixtureRepository.cs @@ -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; diff --git a/test/Velopack.Tests/UpdateManagerTests.cs b/test/Velopack.Tests/UpdateManagerTests.cs index 0396d3d4..9fcfc8e2 100644 --- a/test/Velopack.Tests/UpdateManagerTests.cs +++ b/test/Velopack.Tests/UpdateManagerTests.cs @@ -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; diff --git a/test/Velopack.Tests/UtilityTests.cs b/test/Velopack.Tests/UtilityTests.cs index 51e1c8b7..98900553 100644 --- a/test/Velopack.Tests/UtilityTests.cs +++ b/test/Velopack.Tests/UtilityTests.cs @@ -48,7 +48,6 @@ public class UtilityTests } [SkippableFact] - [SupportedOSPlatform("windows")] public void SetAppIdOnShortcutTest() { Skip.IfNot(VelopackRuntimeInfo.IsWindows); diff --git a/test/Velopack.Tests/Velopack.Tests.csproj b/test/Velopack.Tests/Velopack.Tests.csproj index 8f01bee2..5662d354 100644 --- a/test/Velopack.Tests/Velopack.Tests.csproj +++ b/test/Velopack.Tests/Velopack.Tests.csproj @@ -1,15 +1,19 @@ + + + $(NoWarn);CA1416 + - net8.0;net48 + net6.0;net8.0;net48 - net8.0 + net6.0;net8.0 @@ -20,7 +24,6 @@ - @@ -30,8 +33,17 @@ - - - + + + + + + + + + + + +