Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -264,11 +264,6 @@ jobs:
with:
dotnet-version: ${{ env.DOTNET_VERSION }}

- name: Install Linux Dependencies
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libx11-dev

- name: Cache NuGet Packages
uses: actions/cache@v3
with:
Expand Down
3 changes: 0 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,6 @@ jobs:
with:
dotnet-version: ${{ env.DOTNET_VERSION }}

- name: Install Linux Dependencies
run: sudo apt-get update && sudo apt-get install -y libgtk-3-dev libx11-dev

- name: Extract Build Info
id: buildinfo
run: |
Expand Down
110 changes: 110 additions & 0 deletions GenHub/GenHub.Core/Constants/AppUpdateConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@ public static class AppUpdateConstants
/// </summary>
public const int MaxHttpRetries = 3;

/// <summary>
/// Index for the Update tab in update notification views.
/// </summary>
public const int UpdateTabIndex = 0;

/// <summary>
/// Index for the Browse Builds tab in update notification views.
/// </summary>
public const int BrowseBuildsTabIndex = 1;

/// <summary>
/// Maximum valid tab index in update notification views.
/// </summary>
public const int MaxTabIndex = 1;

/// <summary>
/// Velopack directory name.
/// </summary>
Expand Down Expand Up @@ -153,6 +168,101 @@ public static class AppUpdateConstants
"3. Launch the installed version (will be in %LOCALAPPDATA%\\GenHub)\n\n" +
"Update available: v{1}";

/// <summary>
/// Update available notification title for release channel.
/// </summary>
public const string UpdateAvailableNotificationTitle = "Update Available";

/// <summary>
/// Update available notification title for branch subscriptions.
/// </summary>
public const string BranchUpdateAvailableNotificationTitle = "Branch Update Available";

/// <summary>
/// Update available notification title for PR subscriptions.
/// </summary>
public const string PrUpdateAvailableNotificationTitle = "PR Update Available";

/// <summary>
/// Update action button text.
/// </summary>
public const string UpdateAction = "Update";

/// <summary>
/// Title for the update in progress notification.
/// </summary>
public const string UpdatingAppNotificationTitle = "Updating GenHub";

/// <summary>
/// Starting update progress message.
/// </summary>
public const string UpdateStartingMessage = "Starting update...";

/// <summary>
/// Title for update failed notification.
/// </summary>
public const string UpdateFailedNotificationTitle = "Update Failed";

/// <summary>
/// Update failed notification body format string ({0}: error message).
/// </summary>
public const string UpdateFailedNotificationFormat = "Failed to install update: {0}";

/// <summary>
/// View updates action button text.
/// </summary>
public const string ViewUpdatesAction = "View Updates";

/// <summary>
/// Release update notification body format string ({0}: version).
/// </summary>
public const string ReleaseUpdateNotificationFormat = "A new version ({0}) is available.";

/// <summary>
/// Branch update notification body format string ({0}: version, {1}: branch name).
/// </summary>
public const string BranchUpdateNotificationFormat = "A new build ({0}) is available on branch '{1}'.";

/// <summary>
/// PR update notification body format string ({0}: version, {1}: PR number).
/// </summary>
public const string PrUpdateNotificationFormat = "A new build ({0}) is available for PR #{1}.";

/// <summary>
/// Sort option: sort by last updated date descending.
/// </summary>
public const string SortOptionLastUpdated = "Last Updated";

/// <summary>
/// Sort option: sort by pull request number descending.
/// </summary>
public const string SortOptionPrNumberDesc = "PR Number (Highest)";

/// <summary>
/// Sort option: sort by pull request number ascending.
/// </summary>
public const string SortOptionPrNumberAsc = "PR Number (Lowest)";

/// <summary>
/// Default interval in minutes for periodic update checks (30 minutes).
/// </summary>
public const int DefaultPeriodicUpdateCheckIntervalMinutes = 30;

/// <summary>
/// Minimum interval in minutes for periodic update checks (5 minutes).
/// </summary>
public const int MinPeriodicUpdateCheckIntervalMinutes = 5;

/// <summary>
/// Maximum interval in minutes for periodic update checks (10080 minutes / 7 days).
/// </summary>
public const int MaxPeriodicUpdateCheckIntervalMinutes = 10080;

/// <summary>
/// Increment step in minutes for periodic update check interval setting (5 minutes).
/// </summary>
public const int PeriodicUpdateCheckIntervalIncrementMinutes = 5;

/// <summary>
/// Delay before exit after applying update (5 seconds).
/// </summary>
Expand Down
22 changes: 16 additions & 6 deletions GenHub/GenHub.Core/Constants/CommandLineConstants.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
namespace GenHub.Core.Constants;

/// <summary>
/// Constants for command line arguments and URI schemes.
/// Constants for command line arguments and the <c>genhub://</c> URI scheme.
/// </summary>
/// <remarks>
/// Subscription links use <c>genhub://subscribe?url=&lt;absolute-url&gt;</c>.
/// Today <c>url</c> is a hosted GenHub <c>catalog.json</c>. Publisher Studio will also share
/// Provider Definition URLs via the same scheme; GenHub will detect payload type at fetch time.
/// </remarks>
public static class CommandLineConstants
{
/// <summary>
Expand All @@ -16,22 +21,27 @@ public static class CommandLineConstants
public const string LaunchProfileInlinePrefix = "--launch-profile=";

/// <summary>
/// URI scheme used for protocol handling.
/// Scheme name for custom protocol registration.
/// </summary>
public const string UriScheme = "genhub://";
public const string SchemeName = "genhub";

/// <summary>
/// Command for subscribing to a catalog via URI.
/// Custom URI scheme registered so OS/browser links can open GenHub.
/// </summary>
public const string UriScheme = SchemeName + "://";

/// <summary>
/// URI path segment for content subscription (<c>genhub://subscribe?url=...</c>).
/// </summary>
public const string SubscribeCommand = "subscribe";

/// <summary>
/// Full prefix for subscription URI.
/// Full prefix for subscription URIs (<c>genhub://subscribe</c>).
/// </summary>
public const string SubscribeUriPrefix = UriScheme + SubscribeCommand;

/// <summary>
/// Query parameter name for the catalog URL in a subscription URI.
/// Query parameter carrying the absolute URL of a catalog (or future provider definition).
/// </summary>
public const string SubscribeUrlParam = "?url=";
}
3 changes: 2 additions & 1 deletion GenHub/GenHub.Core/Constants/IpcCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ public static class IpcCommands
public const string LaunchProfilePrefix = "launch-profile:";

/// <summary>
/// Command prefix used to subscribe to a catalog via IPC.
/// Command prefix used to forward a subscribe URL to the primary instance
/// (<c>subscribe:&lt;absolute-url&gt;</c>). Same payload as <c>genhub://subscribe?url=...</c>.
/// </summary>
public const string SubscribePrefix = "subscribe:";
}
15 changes: 15 additions & 0 deletions GenHub/GenHub.Core/Constants/SuperHackersConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,21 @@ public static class SuperHackersConstants
/// </summary>
public const string GeneralsGameCodeRepo = "GeneralsGameCode";

/// <summary>
/// GitHub owner for Generals game patch 2.
/// </summary>
public const string GeneralsGamePatch2Owner = "TheSuperHackers";

/// <summary>
/// GitHub repo for Generals game patch 2.
/// </summary>
public const string GeneralsGamePatch2Repo = "GeneralsGamePatch2";

/// <summary>
/// Display name for Generals game patch 2.
/// </summary>
public const string GeneralsGamePatch2DisplayName = "Community Patch 2";

// ===== Service Configuration =====

/// <summary>
Expand Down
101 changes: 101 additions & 0 deletions GenHub/GenHub.Core/Helpers/AppUpdateVersionHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;

namespace GenHub.Core.Helpers;

/// <summary>
/// Helper class for application update version comparison and parsing.
/// </summary>
public static partial class AppUpdateVersionHelper
{
/// <summary>
/// Extracts the workflow run number from a version string (e.g., "0.0.641-pr241" -> 641).
/// Returns 0 for plain semantic versions without CI run markers.
/// </summary>
/// <param name="version">The version string to extract the run number from.</param>
/// <returns>The extracted run number, or 0 if extraction fails or not a CI build.</returns>
public static int ExtractRunNumber(string? version)
{
if (string.IsNullOrWhiteSpace(version))
{
return 0;
}

var match = CiRunNumberRegex().Match(version);
if (match.Success && int.TryParse(match.Groups[1].Value, out var runNumber) && runNumber > 0)
{
return runNumber;
}

var ciMatch = CiMarkerRegex().Match(version);
if (ciMatch.Success && int.TryParse(ciMatch.Groups[1].Value, out var ciRunNumber) && ciRunNumber > 0)
{
return ciRunNumber;
}

return 0;
}

/// <summary>
/// Checks whether an available artifact version is newer than the currently installed version.
/// </summary>
/// <param name="newVersion">The new artifact version string.</param>
/// <param name="currentVersion">The current version string.</param>
/// <returns>True if newVersion is newer than currentVersion; otherwise false.</returns>
public static bool IsArtifactVersionNewer(string? newVersion, string? currentVersion)
{
if (string.IsNullOrWhiteSpace(newVersion))
{
return false;
}

if (string.IsNullOrWhiteSpace(currentVersion))
{
return true;
}

var newVersionBase = newVersion.Split('+')[0].Trim();
var currentVersionBase = currentVersion.Split('+')[0].Trim();

var newRun = ExtractRunNumber(newVersionBase);
var currentRun = ExtractRunNumber(currentVersionBase);

if (newRun > 0 && currentRun > 0)
{
return newRun > currentRun;
}

if (newRun == 0 && currentRun > 0)
{
return false;
}

if (newRun > 0 && currentRun == 0)
{
return true;
}

var newClean = newVersionBase.Split('-')[0];
var currentClean = currentVersionBase.Split('-')[0];
if (Version.TryParse(newClean, out var newVer) && Version.TryParse(currentClean, out var currentVer))
{
return newVer > currentVer;
}

return false;
}

/// <summary>
/// Regex for extracting workflow run number from a 0.0.X CI version string.
/// Matches patterns like "0.0.1282-pr265", "0.0.1282-main", "0.0.1282".
/// </summary>
[GeneratedRegex(@"^0\.0\.(\d+)(?:-[a-zA-Z0-9_.-]+)?$", RegexOptions.IgnoreCase)]
private static partial Regex CiRunNumberRegex();

/// <summary>
/// Regex for extracting workflow run number from a -ci.X marker.
/// </summary>
[GeneratedRegex(@"-ci\.(\d+)", RegexOptions.IgnoreCase)]
private static partial Regex CiMarkerRegex();
}
45 changes: 35 additions & 10 deletions GenHub/GenHub.Core/Helpers/CommandLineParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ public static class CommandLineParser
/// <returns>The extracted profile identifier if present; otherwise, <c>null</c>.</returns>
public static string? ExtractProfileId(string[] args)
{
for (var i = 0; i < args.Length; i++)
for (int i = 0; i < args.Length; i++)
{
var arg = args[i];
string arg = args[i];

if (arg.Equals(CommandLineConstants.LaunchProfileArg, StringComparison.OrdinalIgnoreCase) && i + 1 < args.Length)
{
Expand All @@ -34,23 +34,48 @@ public static class CommandLineParser
}

/// <summary>
/// Extracts a subscription URL from command line arguments.
/// Supports the URI scheme format: <c>genhub://subscribe?url=&lt;url&gt;</c>.
/// Extracts the absolute URL from a <c>genhub://subscribe?url=...</c> startup argument.
/// </summary>
/// <remarks>
/// The returned value is the <c>url</c> query value only (not the <c>genhub://</c> wrapper).
/// Callers treat it as a GenHub catalog JSON URL today; later it may also be a Provider
/// Definition URL without changing this parser.
/// </remarks>
/// <param name="args">The command line arguments.</param>
/// <returns>The extracted catalog URL if present; otherwise, <c>null</c>.</returns>
/// <returns>The decoded absolute URL if present; otherwise, <c>null</c>.</returns>
public static string? ExtractSubscriptionUrl(string[] args)
{
foreach (var arg in args)
foreach (string arg in args)
{
if (arg.StartsWith(CommandLineConstants.SubscribeUriPrefix, StringComparison.OrdinalIgnoreCase))
{
// Simple parsing for ?url=...
var queryStart = arg.IndexOf(CommandLineConstants.SubscribeUrlParam, StringComparison.OrdinalIgnoreCase);
string remainder = arg[CommandLineConstants.SubscribeUriPrefix.Length..];
if (!remainder.StartsWith('?') && !remainder.StartsWith("/?", StringComparison.Ordinal))
{
continue;
}

int queryStart = arg.IndexOf(CommandLineConstants.SubscribeUrlParam, StringComparison.OrdinalIgnoreCase);
if (queryStart != -1)
{
var url = arg[(queryStart + CommandLineConstants.SubscribeUrlParam.Length)..];
return Uri.UnescapeDataString(url).Trim('"');
string url = arg[(queryStart + CommandLineConstants.SubscribeUrlParam.Length)..];
string unescaped = Uri.UnescapeDataString(url)
.Replace("\r", string.Empty)
.Replace("\n", string.Empty)
.Trim('"', '\'', ' ', '\t');

if (string.IsNullOrWhiteSpace(unescaped))
{
return null;
}

if (Uri.TryCreate(unescaped, UriKind.Absolute, out var uri) &&
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps))
{
return unescaped;
}

return null;
}
}
}
Expand Down
Loading
Loading