Files
UnrealEngine/Engine/Source/Programs/UnrealGameSync/UnrealGameSyncCmd/Program.cs
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

248 lines
7.9 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using EpicGames.Core;
using EpicGames.Horde;
using EpicGames.Perforce;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;
using UnrealGameSync;
using UnrealGameSyncCmd.Commands;
using UnrealGameSyncCmd.Utils;
using UserErrorException = UnrealGameSyncCmd.Exceptions.UserErrorException;
namespace UnrealGameSyncCmd
{
using ILogger = Microsoft.Extensions.Logging.ILogger;
public static class Program
{
static readonly CommandInfo[] _commands = CommandsFactory.GetCommands();
public static async Task<int> Main(string[] rawArgs)
{
DirectoryReference globalConfigFolder;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
globalConfigFolder = DirectoryReference.Combine(DirectoryReference.GetSpecialFolder(Environment.SpecialFolder.LocalApplicationData)!, "UnrealGameSync");
}
else
{
globalConfigFolder = DirectoryReference.Combine(DirectoryReference.GetSpecialFolder(Environment.SpecialFolder.UserProfile)!, ".config", "UnrealGameSync");
}
DirectoryReference.CreateDirectory(globalConfigFolder);
string logName;
DirectoryReference logFolder;
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
logFolder = DirectoryReference.Combine(DirectoryReference.GetSpecialFolder(Environment.SpecialFolder.UserProfile)!, "Library", "Logs", "Unreal Engine", "UnrealGameSync");
logName = "UnrealGameSync-.log";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
logFolder = DirectoryReference.GetSpecialFolder(Environment.SpecialFolder.UserProfile)!;
logName = ".ugs-.log";
}
else
{
logFolder = globalConfigFolder;
logName = "UnrealGameSyncCmd-.log";
}
Serilog.ILogger serilogLogger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console(Serilog.Events.LogEventLevel.Information, outputTemplate: "{Message:lj}{NewLine}")
.WriteTo.File(FileReference.Combine(logFolder, logName).FullName, Serilog.Events.LogEventLevel.Debug, rollingInterval: RollingInterval.Day, rollOnFileSizeLimit: true, fileSizeLimitBytes: 20 * 1024 * 1024, retainedFileCountLimit: 10)
.CreateLogger();
using ILoggerFactory loggerFactory = new Serilog.Extensions.Logging.SerilogLoggerFactory(serilogLogger, true);
ILogger logger = loggerFactory.CreateLogger("Main");
try
{
LauncherSettings launcherSettings = new LauncherSettings();
launcherSettings.Read();
ServiceCollection services = new ServiceCollection();
if (launcherSettings.HordeServer != null)
{
services.AddHorde(options => options.ServerUrl = new Uri(launcherSettings.HordeServer));
}
services.AddCloudStorage();
ServiceProvider serviceProvider = services.BuildServiceProvider();
IHordeClient? hordeClient = serviceProvider.GetService<IHordeClient>();
ICloudStorage? cloudStorage = serviceProvider.GetService<ICloudStorage>();
UserSettings? globalSettings = null;
GlobalSettingsFile settings;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
globalSettings = UserSettings.Create(globalConfigFolder, logger);
settings = globalSettings as GlobalSettingsFile;
}
else
{
settings = GlobalSettingsFile.Create(FileReference.Combine(globalConfigFolder, "Global.json"));
}
CommandLineArguments args = new CommandLineArguments(rawArgs);
string? commandName;
if (!args.TryGetPositionalArgument(out commandName))
{
PrintHelp();
return 0;
}
CommandInfo? command = _commands.FirstOrDefault(x => x.Name.Equals(commandName, StringComparison.OrdinalIgnoreCase));
if (command == null)
{
logger.LogError("Unknown command '{Command}'", commandName);
Console.WriteLine();
PrintHelp();
return 1;
}
// On Windows this is distributed with the GUI client, so we don't need to check for upgrades
if (command.Type != typeof(InstallCommand) && command.Type != typeof(UpgradeCommand) && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
string version = VersionUtils.GetVersion();
DateTime utcNow = DateTime.UtcNow;
if (settings.Global.LastVersionCheck < utcNow - TimeSpan.FromDays(1.0) || IsUpgradeAvailable(settings, version))
{
using (CancellationTokenSource cancellationSource = new CancellationTokenSource(TimeSpan.FromSeconds(10.0)))
{
Task<string?> latestVersionTask = VersionUtils.GetLatestVersionAsync(null, cancellationSource.Token);
Task delay = Task.Delay(TimeSpan.FromSeconds(2.0));
await Task.WhenAny(latestVersionTask, delay);
if (!latestVersionTask.IsCompleted)
{
logger.LogInformation("Checking for UGS updates...");
}
try
{
settings.Global.LatestVersion = await latestVersionTask;
}
catch (OperationCanceledException)
{
logger.LogInformation("Request timed out.");
}
catch (Exception ex)
{
logger.LogInformation(ex, "Upgrade check failed: {Message}", ex.Message);
}
}
settings.Global.LastVersionCheck = utcNow;
settings.Save(logger);
}
if (IsUpgradeAvailable(settings, version))
{
logger.LogWarning("A newer version of UGS is available ({LatestVersion} > {Version}). Run {Command} to update.", settings.Global.LatestVersion, version, "ugs upgrade");
logger.LogInformation("");
}
}
Command instance = (Command)Activator.CreateInstance(command.Type)!;
await instance.ExecuteAsync(new CommandContext(args, logger, loggerFactory, settings, globalSettings, hordeClient, cloudStorage));
return 0;
}
catch (UserErrorException ex)
{
logger.Log(ex.Event.Level, "{Message}", ex.Event.ToString());
return ex.Code;
}
catch (PerforceException ex)
{
logger.LogError(ex, "{Message}", ex.Message);
return 1;
}
catch (Exception ex)
{
logger.LogError(ex, "Unhandled exception.\n{Str}", ex);
return 1;
}
}
static bool IsUpgradeAvailable(GlobalSettingsFile settings, string version)
{
return settings.Global.LatestVersion != null && !settings.Global.LatestVersion.Equals(version, StringComparison.OrdinalIgnoreCase);
}
static void PrintHelp()
{
string appName = "UnrealGameSync Command-Line Tool";
string? productVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion;
if (productVersion != null)
{
appName = $"{appName} ({productVersion})";
}
Console.WriteLine(appName);
Console.WriteLine("");
Console.WriteLine("Usage:");
foreach (CommandInfo command in _commands)
{
if (command.Usage != null && command.Brief != null)
{
Console.WriteLine();
ConsoleUtils.WriteLineWithWordWrap(GetUsage(command), 2, 8);
ConsoleUtils.WriteLineWithWordWrap(command.Brief, 4, 4);
}
}
}
static string GetUsage(CommandInfo commandInfo)
{
StringBuilder result = new StringBuilder(commandInfo.Usage);
if (commandInfo.OptionsType != null)
{
foreach (PropertyInfo propertyInfo in commandInfo.OptionsType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
List<string> names = new List<string>();
foreach (CommandLineAttribute attribute in propertyInfo.GetCustomAttributes<CommandLineAttribute>())
{
string name = (attribute.Prefix ?? propertyInfo.Name).ToLower(CultureInfo.InvariantCulture);
if (propertyInfo.PropertyType == typeof(bool) || propertyInfo.PropertyType == typeof(bool?))
{
names.Add(name);
}
else
{
names.Add($"{name}..");
}
}
if (names.Count > 0)
{
result.Append($" [{String.Join('|', names)}]");
}
}
}
return result.ToString();
}
}
}