From d713c32373bccb6f886dfd38786d978f4bd9929d Mon Sep 17 00:00:00 2001 From: Paul Hebble Date: Tue, 9 Jan 2024 21:14:08 -0600 Subject: [PATCH] Support dev builds for auto updater --- .github/workflows/build.yml | 8 +- .github/workflows/deploy.yml | 7 +- .github/workflows/release.yml | 4 +- Cmdline/Action/Upgrade.cs | 63 +++-- Cmdline/Options.cs | 8 + Cmdline/Properties/Resources.resx | 3 + Core/AutoUpdate/AutoUpdate.cs | 118 ++++++++++ Core/AutoUpdate/CkanUpdate.cs | 26 +++ Core/AutoUpdate/GithubReleaseCkanUpdate.cs | 80 +++++++ Core/AutoUpdate/GithubReleaseInfo.cs | 19 ++ Core/AutoUpdate/S3BuildCkanUpdate.cs | 44 ++++ Core/AutoUpdate/S3BuildVersionInfo.cs | 10 + Core/Configuration/IConfiguration.cs | 6 +- Core/Configuration/JsonConfiguration.cs | 21 ++ .../Win32RegistryConfiguration.cs | 5 + Core/Meta.cs | 4 +- Core/Net/AutoUpdate.cs | 198 ---------------- Core/Relationships/RelationshipResolver.cs | 1 - Core/Versioning/ModuleVersion.cs | 8 +- GUI/Controls/Changeset.Designer.cs | 4 +- GUI/Controls/ChooseProvidedMods.Designer.cs | 4 +- .../ChooseRecommendedMods.Designer.cs | 4 +- GUI/Controls/DeleteDirectories.Designer.cs | 4 +- GUI/Controls/EditModpack.Designer.cs | 6 +- GUI/Controls/InstallationHistory.Designer.cs | 4 +- GUI/Controls/LeftRightRowPanel.cs | 2 +- GUI/Controls/PlayTime.Designer.cs | 12 +- GUI/Controls/UnmanagedFiles.Designer.cs | 4 +- GUI/Controls/Wait.Designer.cs | 4 +- GUI/Dialogs/DownloadsFailedDialog.Designer.cs | 4 +- GUI/Dialogs/DownloadsFailedDialog.cs | 1 - GUI/Dialogs/SettingsDialog.Designer.cs | 216 ++++++++++-------- GUI/Dialogs/SettingsDialog.cs | 173 ++++++++------ GUI/Dialogs/SettingsDialog.resx | 1 + GUI/Dialogs/YesNoDialog.Designer.cs | 27 ++- GUI/Main/Main.cs | 31 ++- GUI/Main/MainAutoUpdate.cs | 57 +++-- GUI/Main/MainInstall.cs | 5 +- GUI/Main/MainTrayIcon.cs | 7 +- GUI/Model/GUIConfiguration.cs | 1 + GUI/Properties/Resources.resx | 7 + Tests/Core/Configuration/FakeConfiguration.cs | 2 + Tests/Core/Net/AutoUpdateTests.cs | 70 ++++-- Tests/Data/version.json | 4 + bin/version_info.py | 32 +++ build.cake | 48 ++-- 46 files changed, 842 insertions(+), 525 deletions(-) create mode 100644 Core/AutoUpdate/AutoUpdate.cs create mode 100644 Core/AutoUpdate/CkanUpdate.cs create mode 100644 Core/AutoUpdate/GithubReleaseCkanUpdate.cs create mode 100644 Core/AutoUpdate/GithubReleaseInfo.cs create mode 100644 Core/AutoUpdate/S3BuildCkanUpdate.cs create mode 100644 Core/AutoUpdate/S3BuildVersionInfo.cs delete mode 100644 Core/Net/AutoUpdate.cs create mode 100644 Tests/Data/version.json create mode 100644 bin/version_info.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c115693af7..84f26ffa56 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,18 +30,18 @@ jobs: image: mono:${{ matrix.mono }} steps: - - uses: actions/checkout@v3 - - name: Adding HTTPS support to APT for old Mono images run: | apt-get update || true apt-get install -y apt-transport-https + - name: Installing checkout/build dependencies + run: apt-get update && apt-get install -y git + - uses: actions/checkout@v3 + - name: Setup .NET Core uses: actions/setup-dotnet@v3 with: dotnet-version: '7' - - name: Installing build dependencies - run: apt-get update && apt-get install -y git - name: Install runtime dependencies run: apt-get install -y xvfb - name: Restore cache for _build/tools diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9b6e93b8c4..8a3fbfe129 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -19,6 +19,8 @@ jobs: image: mono:latest steps: + - name: Installing checkout/build dependencies + run: apt-get update && apt-get install -y git make sed gzip fakeroot lintian dpkg-dev gpg createrepo - uses: actions/checkout@v3 - name: Check version @@ -35,8 +37,6 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: '7' - - name: Installing build dependencies - run: apt-get update && apt-get install -y git make sed gzip fakeroot lintian dpkg-dev gpg createrepo - name: Installing runtime dependencies run: apt-get install -y xvfb - name: Install Docker @@ -119,6 +119,9 @@ jobs: echo "$DOCKERHUB_PASSWORD" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin ./build docker-metadata --exclusive + - name: Create a version.json file for S3 + shell: bash + run: python bin/version_info.py > _build/repack/Release/version.json - name: Push ckan.exe and netkan.exe to S3 # Send ckan.exe and netkan.exe to https://ksp-ckan.s3-us-west-2.amazonaws.com/ uses: jakejarvis/s3-sync-action@master diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 719ae366bf..a283ad2dbb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,14 +13,14 @@ jobs: image: mono:latest steps: + - name: Installing checkout/build dependencies + run: apt-get update && apt-get install -y git make sed libplist-utils xorriso gzip fakeroot lintian rpm wget jq dpkg-dev gpg createrepo - uses: actions/checkout@v3 - name: Setup .NET Core uses: actions/setup-dotnet@v3 with: dotnet-version: '7' - - name: Installing build dependencies - run: apt-get update && apt-get install -y git make sed libplist-utils xorriso gzip fakeroot lintian rpm wget jq dpkg-dev gpg createrepo - name: Installing runtime dependencies run: apt-get install -y xvfb diff --git a/Cmdline/Action/Upgrade.cs b/Cmdline/Action/Upgrade.cs index 46b631900d..ba5069c2ec 100644 --- a/Cmdline/Action/Upgrade.cs +++ b/Cmdline/Action/Upgrade.cs @@ -3,9 +3,11 @@ using System.Collections.Generic; using System.Transactions; +using Autofac; using log4net; using CKAN.Versioning; +using CKAN.Configuration; namespace CKAN.CmdLine { @@ -47,38 +49,63 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options) user.RaiseMessage(" or ckan upgrade --all"); if (AutoUpdate.CanUpdate) { - user.RaiseMessage(" or ckan upgrade ckan"); + user.RaiseMessage(" or ckan upgrade ckan [--stable-release|--dev-build]"); } return Exit.BADOPT; } if (!options.upgrade_all && options.modules[0] == "ckan" && AutoUpdate.CanUpdate) { - user.RaiseMessage(Properties.Resources.UpgradeQueryingCKAN); - AutoUpdate.Instance.FetchLatestReleaseInfo(); - var latestVersion = AutoUpdate.Instance.latestUpdate.Version; - var currentVersion = new ModuleVersion(Meta.GetVersion(VersionFormat.Short)); + if (options.dev_build && options.stable_release) + { + user.RaiseMessage(Properties.Resources.UpgradeCannotCombineFlags); + return Exit.BADOPT; + } + var config = ServiceLocator.Container.Resolve(); + var devBuild = options.dev_build + || (!options.stable_release && config.DevBuilds); + if (devBuild != config.DevBuilds) + { + config.DevBuilds = devBuild; + user.RaiseMessage( + config.DevBuilds + ? Properties.Resources.UpgradeSwitchingToDevBuilds + : Properties.Resources.UpgradeSwitchingToStableReleases); + } - if (latestVersion.IsGreaterThan(currentVersion)) + user.RaiseMessage(Properties.Resources.UpgradeQueryingCKAN); + try { - user.RaiseMessage(Properties.Resources.UpgradeNewCKANAvailable, latestVersion); - var releaseNotes = AutoUpdate.Instance.latestUpdate.ReleaseNotes; - user.RaiseMessage(releaseNotes); - user.RaiseMessage(""); - user.RaiseMessage(""); + var upd = new AutoUpdate(); + var update = upd.GetUpdate(config.DevBuilds); + var latestVersion = update.Version; + var currentVersion = new ModuleVersion(Meta.GetVersion()); - if (user.RaiseYesNoDialog(Properties.Resources.UpgradeProceed)) + if (!latestVersion.Equals(currentVersion)) { - user.RaiseMessage(Properties.Resources.UpgradePleaseWait); - AutoUpdate.Instance.StartUpdateProcess(false); + user.RaiseMessage(Properties.Resources.UpgradeNewCKANAvailable, latestVersion); + var releaseNotes = update.ReleaseNotes; + user.RaiseMessage(releaseNotes); + user.RaiseMessage(""); + user.RaiseMessage(""); + + if (user.RaiseYesNoDialog(Properties.Resources.UpgradeProceed)) + { + user.RaiseMessage(Properties.Resources.UpgradePleaseWait); + upd.StartUpdateProcess(false, config.DevBuilds, user); + } + } + else + { + user.RaiseMessage(Properties.Resources.UpgradeAlreadyHaveLatest); } + return Exit.OK; } - else + catch (Exception exc) { - user.RaiseMessage(Properties.Resources.UpgradeAlreadyHaveLatest); + user.RaiseError("Upgrade failed: {0}", exc.Message); + return Exit.ERROR; } - - return Exit.OK; } try diff --git a/Cmdline/Options.cs b/Cmdline/Options.cs index 4ea9019b33..13499591e4 100644 --- a/Cmdline/Options.cs +++ b/Cmdline/Options.cs @@ -405,6 +405,14 @@ internal class UpgradeOptions : InstanceSpecificOptions [Option("all", DefaultValue = false, HelpText = "Upgrade all available updated modules")] public bool upgrade_all { get; set; } + [Option("dev-build", DefaultValue = false, + HelpText = "For `ckan` option only, use dev builds")] + public bool dev_build { get; set; } + + [Option("stable-release", DefaultValue = false, + HelpText = "For `ckan` option only, use stable releases")] + public bool stable_release { get; set; } + [ValueList(typeof (List))] [InstalledIdentifiers] public List modules { get; set; } diff --git a/Cmdline/Properties/Resources.resx b/Cmdline/Properties/Resources.resx index f1e19db9dd..1dcf548b05 100644 --- a/Cmdline/Properties/Resources.resx +++ b/Cmdline/Properties/Resources.resx @@ -362,6 +362,9 @@ Try `ckan list` for a list of installed mods. Removed modules [Name (CKAN identifier)]: Updated modules [Name (CKAN identifier)]: Updated information on {0} compatible modules + The `--dev-build` and `--stable-release` flags cannot be combined! + Switching to dev builds + Switching to stable releases Querying the latest CKAN version New CKAN version available - {0} Proceed with install? diff --git a/Core/AutoUpdate/AutoUpdate.cs b/Core/AutoUpdate/AutoUpdate.cs new file mode 100644 index 0000000000..44c4fdc094 --- /dev/null +++ b/Core/AutoUpdate/AutoUpdate.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Diagnostics; +using System.Reflection; + +using Autofac; +using Newtonsoft.Json; + +using CKAN.Versioning; +using CKAN.Configuration; + +namespace CKAN +{ + /// + /// CKAN client auto-updating routines. This works in conjunction with the + /// auto-update helper to allow users to upgrade. + /// + public class AutoUpdate + { + public AutoUpdate() + { + } + + public CkanUpdate GetUpdate(bool devBuild) + { + if (updates.TryGetValue(devBuild, out CkanUpdate update)) + { + return update; + } + var newUpdate = devBuild + ? new S3BuildCkanUpdate() as CkanUpdate + : new GitHubReleaseCkanUpdate(); + updates.Add(devBuild, newUpdate); + return newUpdate; + } + + private Dictionary updates = new Dictionary(); + + /// + /// Report whether it's possible to run the auto-updater. + /// Checks whether we can overwrite the running ckan.exe. + /// Windows doesn't let us check this because it locks the EXE + /// for a running process, so assume we can always overwrite on Windows. + /// + public static readonly bool CanUpdate = Platform.IsWindows || CanWrite(exePath); + + /// + /// Downloads the new ckan.exe version, as well as the updater helper, + /// and then launches the helper allowing us to upgrade. + /// + /// If set to true launch CKAN after update. + public void StartUpdateProcess(bool launchCKANAfterUpdate, bool devBuild, IUser user = null) + { + var pid = Process.GetCurrentProcess().Id; + + var update = GetUpdate(devBuild); + + // download updater app and new ckan.exe + NetAsyncDownloader.DownloadWithProgress(update.Targets, user); + + // run updater + SetExecutable(update.updaterFilename); + Process.Start(new ProcessStartInfo + { + Verb = "runas", + FileName = update.updaterFilename, + Arguments = string.Format(@"{0} ""{1}"" ""{2}"" {3}", + -pid, exePath, + update.ckanFilename, + launchCKANAfterUpdate ? "launch" : "nolaunch"), + UseShellExecute = false, + // Make child's stdin a pipe so it can tell when we exit + RedirectStandardInput = true, + CreateNoWindow = true, + }); + + // Caller should now exit. Let them do it safely. + } + + public static void SetExecutable(string fileName) + { + // mark as executable if on Linux or Mac + if (Platform.IsUnix || Platform.IsMac) + { + // TODO: It would be really lovely (and safer!) to use the native system + // call here: http://docs.go-mono.com/index.aspx?link=M:Mono.Unix.Native.Syscall.chmod + + string command = string.Format("+x \"{0}\"", fileName); + + ProcessStartInfo permsinfo = new ProcessStartInfo("chmod", command) + { + UseShellExecute = false + }; + Process permsprocess = Process.Start(permsinfo); + permsprocess.WaitForExit(); + } + } + + private static bool CanWrite(string path) + { + try + { + // Try to open the file for writing. + // We won't actually write, but we expect the OS to stop us if we don't have permissions. + using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite)) { } + return true; + } + catch + { + return false; + } + } + + // This is null when running tests, seemingly. + private static readonly string exePath = Assembly.GetEntryAssembly()?.Location ?? ""; + } +} diff --git a/Core/AutoUpdate/CkanUpdate.cs b/Core/AutoUpdate/CkanUpdate.cs new file mode 100644 index 0000000000..8ef171b033 --- /dev/null +++ b/Core/AutoUpdate/CkanUpdate.cs @@ -0,0 +1,26 @@ +using System; +using System.IO; +using System.Collections.Generic; + +using CKAN.Versioning; + +namespace CKAN +{ + /// + /// Object representing a CKAN release + /// + public abstract class CkanUpdate + { + public CkanModuleVersion Version { get; protected set; } + public Uri ReleaseDownload { get; protected set; } + public long ReleaseSize { get; protected set; } + public Uri UpdaterDownload { get; protected set; } + public long UpdaterSize { get; protected set; } + public string ReleaseNotes { get; protected set; } + + public string updaterFilename = $"{Path.GetTempPath()}{Guid.NewGuid()}.exe"; + public string ckanFilename = $"{Path.GetTempPath()}{Guid.NewGuid()}.exe"; + + public abstract IList Targets { get; } + } +} diff --git a/Core/AutoUpdate/GithubReleaseCkanUpdate.cs b/Core/AutoUpdate/GithubReleaseCkanUpdate.cs new file mode 100644 index 0000000000..8fb461b388 --- /dev/null +++ b/Core/AutoUpdate/GithubReleaseCkanUpdate.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; + +using Autofac; +using Newtonsoft.Json; + +using CKAN.Configuration; +using CKAN.Versioning; + +namespace CKAN +{ + /// + /// Represents a CKAN release on GitHub + /// + public class GitHubReleaseCkanUpdate : CkanUpdate + { + /// + /// Initialize the Object + /// + /// JSON representation of release + public GitHubReleaseCkanUpdate(GitHubReleaseInfo releaseJson = null) + { + if (releaseJson == null) + { + var coreConfig = ServiceLocator.Container.Resolve(); + var token = coreConfig.TryGetAuthToken(latestCKANReleaseApiUrl.Host, out string t) + ? t : null; + releaseJson = JsonConvert.DeserializeObject( + Net.DownloadText(latestCKANReleaseApiUrl, token)); + if (releaseJson == null) + { + throw new Kraken(Properties.Resources.AutoUpdateNotFetched); + } + } + + Version = new CkanModuleVersion(releaseJson.tag_name.ToString(), + releaseJson.name.ToString()); + ReleaseNotes = ExtractReleaseNotes(releaseJson.body.ToString()); + foreach (var asset in releaseJson.assets) + { + string url = asset.browser_download_url.ToString(); + if (url.EndsWith("ckan.exe")) + { + ReleaseDownload = asset.browser_download_url; + ReleaseSize = (long)asset.size; + } + else if (url.EndsWith("AutoUpdater.exe")) + { + UpdaterDownload = asset.browser_download_url; + UpdaterSize = (long)asset.size; + } + } + } + + public override IList Targets => new[] + { + new NetAsyncDownloader.DownloadTarget( + UpdaterDownload, updaterFilename, UpdaterSize), + new NetAsyncDownloader.DownloadTarget( + ReleaseDownload, ckanFilename, ReleaseSize), + }; + + /// + /// Extracts release notes from the body of text provided by the github API. + /// By default this is everything after the first three dashes on a line by + /// itself, but as a fallback we'll use the whole body if not found. + /// + /// The release notes. + internal static string ExtractReleaseNotes(string releaseBody) + { + const string divider = "\r\n---\r\n"; + // Get at most two pieces, the first is the image, the second is the release notes + string[] notesArray = releaseBody.Split(new string[] { divider }, 2, StringSplitOptions.None); + return notesArray.Length > 1 ? notesArray[1] : notesArray[0]; + } + + private static readonly Uri latestCKANReleaseApiUrl = + new Uri("https://api.github.com/repos/KSP-CKAN/CKAN/releases/latest"); + } +} diff --git a/Core/AutoUpdate/GithubReleaseInfo.cs b/Core/AutoUpdate/GithubReleaseInfo.cs new file mode 100644 index 0000000000..8216ca892e --- /dev/null +++ b/Core/AutoUpdate/GithubReleaseInfo.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +namespace CKAN +{ + public class GitHubReleaseInfo + { + public string tag_name; + public string name; + public string body; + public List assets; + + public sealed class Asset + { + public Uri browser_download_url; + public long size; + } + } +} diff --git a/Core/AutoUpdate/S3BuildCkanUpdate.cs b/Core/AutoUpdate/S3BuildCkanUpdate.cs new file mode 100644 index 0000000000..19def760e3 --- /dev/null +++ b/Core/AutoUpdate/S3BuildCkanUpdate.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; + +using Newtonsoft.Json; + +using CKAN.Versioning; + +namespace CKAN +{ + public class S3BuildCkanUpdate : CkanUpdate + { + public S3BuildCkanUpdate(S3BuildVersionInfo versionJson = null) + { + if (versionJson == null) + { + versionJson = JsonConvert.DeserializeObject( + Net.DownloadText(new Uri(S3BaseUrl, VersionJsonUrlPiece))); + if (versionJson == null) + { + throw new Kraken(Properties.Resources.AutoUpdateNotFetched); + } + } + + Version = new CkanModuleVersion(versionJson.version.ToString(), "dev"); + ReleaseNotes = versionJson.changelog; + UpdaterDownload = new Uri(S3BaseUrl, AutoUpdaterUrlPiece); + ReleaseDownload = new Uri(S3BaseUrl, CkanUrlPiece); + } + + public override IList Targets => new[] + { + new NetAsyncDownloader.DownloadTarget( + UpdaterDownload, updaterFilename), + new NetAsyncDownloader.DownloadTarget( + ReleaseDownload, ckanFilename), + }; + + private static readonly Uri S3BaseUrl = + new Uri("https://ksp-ckan.s3-us-west-2.amazonaws.com/"); + private const string VersionJsonUrlPiece = "version.json"; + private const string AutoUpdaterUrlPiece = "AutoUpdater.exe"; + private const string CkanUrlPiece = "ckan.exe"; + } +} diff --git a/Core/AutoUpdate/S3BuildVersionInfo.cs b/Core/AutoUpdate/S3BuildVersionInfo.cs new file mode 100644 index 0000000000..0b5e504f7b --- /dev/null +++ b/Core/AutoUpdate/S3BuildVersionInfo.cs @@ -0,0 +1,10 @@ +using CKAN.Versioning; + +namespace CKAN +{ + public class S3BuildVersionInfo + { + public ModuleVersion version; + public string changelog; + } +} diff --git a/Core/Configuration/IConfiguration.cs b/Core/Configuration/IConfiguration.cs index 7637fb1d2e..1537ac0fad 100644 --- a/Core/Configuration/IConfiguration.cs +++ b/Core/Configuration/IConfiguration.cs @@ -63,7 +63,11 @@ public interface IConfiguration /// List of hosts in order of priority when there are multiple URLs to choose from. /// The first null value represents where all other hosts should go. /// - /// string[] PreferredHosts { get; set; } + + /// + /// true if user wants to use nightly builds from S3, false to use releases from GitHub + /// + bool DevBuilds { get; set; } } } diff --git a/Core/Configuration/JsonConfiguration.cs b/Core/Configuration/JsonConfiguration.cs index cf47850d74..f7e20a4169 100644 --- a/Core/Configuration/JsonConfiguration.cs +++ b/Core/Configuration/JsonConfiguration.cs @@ -29,6 +29,7 @@ private class Config public IDictionary AuthTokens { get; set; } = new Dictionary(); public string[] GlobalInstallFilters { get; set; } = new string[] { }; public string[] PreferredHosts { get; set; } = new string[] { }; + public bool DevBuilds { get; set; } } public class ConfigConverter : JsonPropertyNamesChangedConverter @@ -323,6 +324,26 @@ public string[] PreferredHosts } } + public bool DevBuilds + { + get + { + lock (_lock) + { + return config.DevBuilds; + } + } + + set + { + lock (_lock) + { + config.DevBuilds = value; + SaveConfig(); + } + } + } + // // Save the JSON configuration file. // diff --git a/Core/Configuration/Win32RegistryConfiguration.cs b/Core/Configuration/Win32RegistryConfiguration.cs index 2b2cf6fff9..dab16876ab 100644 --- a/Core/Configuration/Win32RegistryConfiguration.cs +++ b/Core/Configuration/Win32RegistryConfiguration.cs @@ -183,6 +183,11 @@ public void SetAuthToken(string host, string token) /// public string[] PreferredHosts { get; set; } + /// + /// Not implemented because the Windows registry is deprecated + /// + public bool DevBuilds { get; set; } + public static bool DoesRegistryConfigurationExist() { RegistryKey key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(CKAN_KEY_NO_PREFIX); diff --git a/Core/Meta.cs b/Core/Meta.cs index 396144c3d1..fedc50b640 100644 --- a/Core/Meta.cs +++ b/Core/Meta.cs @@ -28,7 +28,9 @@ public static string GetVersion(VersionFormat format = VersionFormat.Normal) case VersionFormat.Short: return $"v{version.UpToCharacters(shortDelimiters)}"; case VersionFormat.Normal: - return $"v{version.UpToCharacter('+')}"; + return "v" + Assembly.GetExecutingAssembly() + .GetAssemblyAttribute() + .Version; case VersionFormat.Full: return $"v{version}"; default: diff --git a/Core/Net/AutoUpdate.cs b/Core/Net/AutoUpdate.cs deleted file mode 100644 index 7f297415aa..0000000000 --- a/Core/Net/AutoUpdate.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System; -using System.IO; -using System.Diagnostics; -using System.Reflection; - -using Newtonsoft.Json; - -using CKAN.Versioning; - -namespace CKAN -{ - /// - /// Object representing a CKAN release - /// - public class CkanUpdate - { - /// - /// Initialize the Object - /// - /// JSON representation of release - public CkanUpdate(string json) - { - dynamic response = JsonConvert.DeserializeObject(json); - - Version = new CkanModuleVersion( - response.tag_name.ToString(), - response.name.ToString() - ); - ReleaseNotes = ExtractReleaseNotes(response.body.ToString()); - - foreach (var asset in response.assets) - { - string url = asset.browser_download_url.ToString(); - if (url.EndsWith("ckan.exe")) - { - ReleaseDownload = asset.browser_download_url; - ReleaseSize = (long)asset.size; - } - else if (url.EndsWith("AutoUpdater.exe")) - { - UpdaterDownload = asset.browser_download_url; - UpdaterSize = (long)asset.size; - } - } - } - - /// - /// Extracts release notes from the body of text provided by the github API. - /// By default this is everything after the first three dashes on a line by - /// itself, but as a fallback we'll use the whole body if not found. - /// - /// The release notes. - public static string ExtractReleaseNotes(string releaseBody) - { - const string divider = "\r\n---\r\n"; - // Get at most two pieces, the first is the image, the second is the release notes - string[] notesArray = releaseBody.Split(new string[] { divider }, 2, StringSplitOptions.None); - return notesArray.Length > 1 ? notesArray[1] : notesArray[0]; - } - - public readonly CkanModuleVersion Version; - public readonly Uri ReleaseDownload; - public readonly long ReleaseSize; - public readonly Uri UpdaterDownload; - public readonly long UpdaterSize; - public readonly string ReleaseNotes; - } - - /// - /// CKAN client auto-updating routines. This works in conjunction with the - /// auto-update helper to allow users to upgrade. - /// - public class AutoUpdate - { - /// - /// The list of releases containing ckan.exe and AutoUpdater.exe - /// - private static readonly Uri latestCKANReleaseApiUrl = new Uri("https://api.github.com/repos/KSP-CKAN/CKAN/releases/latest"); - - public static readonly AutoUpdate Instance = new AutoUpdate(); - - public CkanUpdate latestUpdate; - - // This is private so we can enforce our class being a singleton. - private AutoUpdate() { } - - private static bool CanWrite(string path) - { - try - { - // Try to open the file for writing. - // We won't actually write, but we expect the OS to stop us if we don't have permissions. - using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite)) { } - return true; - } - catch - { - return false; - } - } - - // This is null when running tests, seemingly. - private static readonly string exePath = Assembly.GetEntryAssembly()?.Location ?? ""; - - /// - /// Report whether it's possible to run the auto-updater. - /// Checks whether we can overwrite the running ckan.exe. - /// Windows doesn't let us check this because it locks the EXE - /// for a running process, so assume we can always overwrite on Windows. - /// - public static readonly bool CanUpdate = Platform.IsWindows || CanWrite(exePath); - - /// - /// Our metadata is considered fetched if we have a latest version, release notes, - /// and download URLs for the ckan executable and helper. - /// - public bool IsFetched() - { - return latestUpdate != null; - } - - /// - /// Fetches all the latest release info, populating our attributes in - /// the process. - /// - public void FetchLatestReleaseInfo() - { - latestUpdate = new CkanUpdate(Net.DownloadText(latestCKANReleaseApiUrl)); - } - - /// - /// Downloads the new ckan.exe version, as well as the updater helper, - /// and then launches the helper allowing us to upgrade. - /// - /// If set to true launch CKAN after update. - public void StartUpdateProcess(bool launchCKANAfterUpdate, IUser user = null) - { - if (!IsFetched()) - { - throw new Kraken(Properties.Resources.AutoUpdateNotFetched); - } - - var pid = Process.GetCurrentProcess().Id; - - // download updater app and new ckan.exe - string updaterFilename = Path.GetTempPath() + Guid.NewGuid().ToString() + ".exe"; - string ckanFilename = Path.GetTempPath() + Guid.NewGuid().ToString() + ".exe"; - NetAsyncDownloader.DownloadWithProgress( - new[] - { - new NetAsyncDownloader.DownloadTarget( - latestUpdate.UpdaterDownload, - updaterFilename, - latestUpdate.UpdaterSize), - new NetAsyncDownloader.DownloadTarget( - latestUpdate.ReleaseDownload, - ckanFilename, - latestUpdate.ReleaseSize), - }, - user - ); - - // run updater - SetExecutable(updaterFilename); - Process.Start(new ProcessStartInfo - { - Verb = "runas", - FileName = updaterFilename, - Arguments = string.Format(@"{0} ""{1}"" ""{2}"" {3}", -pid, exePath, ckanFilename, launchCKANAfterUpdate ? "launch" : "nolaunch"), - UseShellExecute = false, - // Make child's stdin a pipe so it can tell when we exit - RedirectStandardInput = true, - CreateNoWindow = true, - }); - - // Caller should now exit. Let them do it safely. - } - - public static void SetExecutable(string fileName) - { - // mark as executable if on Linux or Mac - if (Platform.IsUnix || Platform.IsMac) - { - // TODO: It would be really lovely (and safer!) to use the native system - // call here: http://docs.go-mono.com/index.aspx?link=M:Mono.Unix.Native.Syscall.chmod - - string command = string.Format("+x \"{0}\"", fileName); - - ProcessStartInfo permsinfo = new ProcessStartInfo("chmod", command) - { - UseShellExecute = false - }; - Process permsprocess = Process.Start(permsinfo); - permsprocess.WaitForExit(); - } - } - } -} diff --git a/Core/Relationships/RelationshipResolver.cs b/Core/Relationships/RelationshipResolver.cs index 827e837ddc..ba0ed23f66 100644 --- a/Core/Relationships/RelationshipResolver.cs +++ b/Core/Relationships/RelationshipResolver.cs @@ -628,7 +628,6 @@ public Dictionary ConflictList /// Avoids duplicates and explains why dependencies are in the list. /// Use for reporting the conflicts to the user, use ConflictsList for coloring rows. /// - /// public IEnumerable ConflictDescriptions => conflicts.Where(kvp => kvp.Value == null // Pick the pair with the least distantly selected one first diff --git a/Core/Versioning/ModuleVersion.cs b/Core/Versioning/ModuleVersion.cs index e09abfde8a..dde88bbfa2 100644 --- a/Core/Versioning/ModuleVersion.cs +++ b/Core/Versioning/ModuleVersion.cs @@ -65,17 +65,13 @@ public ModuleVersion(string version) /// true if versions have the same epoch, false if different /// public bool EpochEquals(ModuleVersion other) - { - return _epoch == other._epoch; - } + => _epoch == other._epoch; /// /// New module version with same version as 'this' but with one greater epoch /// public ModuleVersion IncrementEpoch() - { - return new ModuleVersion($"{_epoch + 1}:{_version}"); - } + => new ModuleVersion($"{_epoch + 1}:{_version}"); /// /// Converts the value of the current object to its equivalent diff --git a/GUI/Controls/Changeset.Designer.cs b/GUI/Controls/Changeset.Designer.cs index 87e767fdda..ace396195c 100644 --- a/GUI/Controls/Changeset.Designer.cs +++ b/GUI/Controls/Changeset.Designer.cs @@ -35,7 +35,7 @@ private void InitializeComponent() this.ChangeType = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); this.Reason = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); this.CloseTheGameLabel = new System.Windows.Forms.Label(); - this.BottomButtonPanel = new LeftRightRowPanel(); + this.BottomButtonPanel = new CKAN.GUI.LeftRightRowPanel(); this.ConfirmChangesButton = new System.Windows.Forms.Button(); this.BackButton = new System.Windows.Forms.Button(); this.CancelChangesButton = new System.Windows.Forms.Button(); @@ -157,7 +157,7 @@ private void InitializeComponent() private System.Windows.Forms.ColumnHeader ChangeType; private System.Windows.Forms.ColumnHeader Reason; private System.Windows.Forms.Label CloseTheGameLabel; - private LeftRightRowPanel BottomButtonPanel; + private CKAN.GUI.LeftRightRowPanel BottomButtonPanel; private System.Windows.Forms.Button BackButton; private System.Windows.Forms.Button CancelChangesButton; private System.Windows.Forms.Button ConfirmChangesButton; diff --git a/GUI/Controls/ChooseProvidedMods.Designer.cs b/GUI/Controls/ChooseProvidedMods.Designer.cs index 7f4a268df6..f0da91877b 100644 --- a/GUI/Controls/ChooseProvidedMods.Designer.cs +++ b/GUI/Controls/ChooseProvidedMods.Designer.cs @@ -34,7 +34,7 @@ private void InitializeComponent() this.ChooseProvidedModsListView = new ThemedListView(); this.modNameColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); this.modDescriptionColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.BottomButtonPanel = new LeftRightRowPanel(); + this.BottomButtonPanel = new CKAN.GUI.LeftRightRowPanel(); this.ChooseProvidedModsCancelButton = new System.Windows.Forms.Button(); this.ChooseProvidedModsContinueButton = new System.Windows.Forms.Button(); this.BottomButtonPanel.SuspendLayout(); @@ -136,7 +136,7 @@ private void InitializeComponent() private System.Windows.Forms.ListView ChooseProvidedModsListView; private System.Windows.Forms.ColumnHeader modNameColumnHeader; private System.Windows.Forms.ColumnHeader modDescriptionColumnHeader; - private LeftRightRowPanel BottomButtonPanel; + private CKAN.GUI.LeftRightRowPanel BottomButtonPanel; private System.Windows.Forms.Button ChooseProvidedModsCancelButton; private System.Windows.Forms.Button ChooseProvidedModsContinueButton; } diff --git a/GUI/Controls/ChooseRecommendedMods.Designer.cs b/GUI/Controls/ChooseRecommendedMods.Designer.cs index ddaec8ff7f..0395f2600f 100644 --- a/GUI/Controls/ChooseRecommendedMods.Designer.cs +++ b/GUI/Controls/ChooseRecommendedMods.Designer.cs @@ -38,7 +38,7 @@ private void InitializeComponent() this.ModNameHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); this.SourceModulesHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); this.DescriptionHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.BottomButtonPanel = new LeftRightRowPanel(); + this.BottomButtonPanel = new CKAN.GUI.LeftRightRowPanel(); this.RecommendedModsToggleCheckbox = new System.Windows.Forms.CheckBox(); this.RecommendedModsCancelButton = new System.Windows.Forms.Button(); this.RecommendedModsContinueButton = new System.Windows.Forms.Button(); @@ -180,7 +180,7 @@ private void InitializeComponent() private System.Windows.Forms.ColumnHeader ModNameHeader; private System.Windows.Forms.ColumnHeader SourceModulesHeader; private System.Windows.Forms.ColumnHeader DescriptionHeader; - private LeftRightRowPanel BottomButtonPanel; + private CKAN.GUI.LeftRightRowPanel BottomButtonPanel; private System.Windows.Forms.CheckBox RecommendedModsToggleCheckbox; private System.Windows.Forms.Button RecommendedModsCancelButton; private System.Windows.Forms.Button RecommendedModsContinueButton; diff --git a/GUI/Controls/DeleteDirectories.Designer.cs b/GUI/Controls/DeleteDirectories.Designer.cs index 6a59309080..3aa3977545 100644 --- a/GUI/Controls/DeleteDirectories.Designer.cs +++ b/GUI/Controls/DeleteDirectories.Designer.cs @@ -37,7 +37,7 @@ private void InitializeComponent() this.ContentsListView = new ThemedListView(); this.FileColumn = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); this.SelectDirPrompt = new System.Windows.Forms.ListViewItem(); - this.BottomButtonPanel = new LeftRightRowPanel(); + this.BottomButtonPanel = new CKAN.GUI.LeftRightRowPanel(); this.OpenDirectoryButton = new System.Windows.Forms.Button(); this.KeepAllButton = new System.Windows.Forms.Button(); this.DeleteButton = new System.Windows.Forms.Button(); @@ -198,7 +198,7 @@ private void InitializeComponent() private System.Windows.Forms.ListView ContentsListView; private System.Windows.Forms.ColumnHeader FileColumn; private System.Windows.Forms.ListViewItem SelectDirPrompt; - private LeftRightRowPanel BottomButtonPanel; + private CKAN.GUI.LeftRightRowPanel BottomButtonPanel; private System.Windows.Forms.Button OpenDirectoryButton; private System.Windows.Forms.Button KeepAllButton; private System.Windows.Forms.Button DeleteButton; diff --git a/GUI/Controls/EditModpack.Designer.cs b/GUI/Controls/EditModpack.Designer.cs index 717a61221c..a80dac22f0 100644 --- a/GUI/Controls/EditModpack.Designer.cs +++ b/GUI/Controls/EditModpack.Designer.cs @@ -48,7 +48,7 @@ private void InitializeComponent() this.LicenseLabel = new System.Windows.Forms.Label(); this.LicenseComboBox = new System.Windows.Forms.ComboBox(); this.IncludeVersionsCheckbox = new System.Windows.Forms.CheckBox(); - this.RelationshipsListView = new ThemedListView(); + this.RelationshipsListView = new CKAN.GUI.ThemedListView(); this.ModNameColumn = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); this.ModVersionColumn = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); this.ModAbstractColumn = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); @@ -56,7 +56,7 @@ private void InitializeComponent() this.RecommendationsGroup = new System.Windows.Forms.ListViewGroup(); this.SuggestionsGroup = new System.Windows.Forms.ListViewGroup(); this.IgnoredGroup = new System.Windows.Forms.ListViewGroup(); - this.BottomButtonPanel = new LeftRightRowPanel(); + this.BottomButtonPanel = new CKAN.GUI.LeftRightRowPanel(); this.DependsRadioButton = new System.Windows.Forms.RadioButton(); this.RecommendsRadioButton = new System.Windows.Forms.RadioButton(); this.SuggestsRadioButton = new System.Windows.Forms.RadioButton(); @@ -450,7 +450,7 @@ private void InitializeComponent() private System.Windows.Forms.ListViewGroup RecommendationsGroup; private System.Windows.Forms.ListViewGroup SuggestionsGroup; private System.Windows.Forms.ListViewGroup IgnoredGroup; - private LeftRightRowPanel BottomButtonPanel; + private CKAN.GUI.LeftRightRowPanel BottomButtonPanel; private System.Windows.Forms.RadioButton DependsRadioButton; private System.Windows.Forms.RadioButton RecommendsRadioButton; private System.Windows.Forms.RadioButton SuggestsRadioButton; diff --git a/GUI/Controls/InstallationHistory.Designer.cs b/GUI/Controls/InstallationHistory.Designer.cs index 77a177e069..48be3020a4 100644 --- a/GUI/Controls/InstallationHistory.Designer.cs +++ b/GUI/Controls/InstallationHistory.Designer.cs @@ -45,7 +45,7 @@ private void InitializeComponent() this.DescriptionColumn = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); this.SelectInstallMessage = new System.Windows.Forms.ListViewItem(); this.NoModsMessage = new System.Windows.Forms.ListViewItem(); - this.BottomButtonPanel = new LeftRightRowPanel(); + this.BottomButtonPanel = new CKAN.GUI.LeftRightRowPanel(); this.OKButton = new System.Windows.Forms.Button(); this.BottomButtonPanel.SuspendLayout(); this.SuspendLayout(); @@ -232,7 +232,7 @@ private void InitializeComponent() private System.Windows.Forms.ColumnHeader DescriptionColumn; private System.Windows.Forms.ListViewItem SelectInstallMessage; private System.Windows.Forms.ListViewItem NoModsMessage; - private LeftRightRowPanel BottomButtonPanel; + private CKAN.GUI.LeftRightRowPanel BottomButtonPanel; private System.Windows.Forms.Button OKButton; } } diff --git a/GUI/Controls/LeftRightRowPanel.cs b/GUI/Controls/LeftRightRowPanel.cs index 95c210d323..12bdfd5596 100644 --- a/GUI/Controls/LeftRightRowPanel.cs +++ b/GUI/Controls/LeftRightRowPanel.cs @@ -1,6 +1,6 @@ using System.Windows.Forms; -namespace CKAN +namespace CKAN.GUI { /// /// A panel containing two groups of controls in flow layouts, diff --git a/GUI/Controls/PlayTime.Designer.cs b/GUI/Controls/PlayTime.Designer.cs index 19828b50e3..47015dd913 100755 --- a/GUI/Controls/PlayTime.Designer.cs +++ b/GUI/Controls/PlayTime.Designer.cs @@ -2,12 +2,12 @@ namespace CKAN.GUI { partial class PlayTime { - /// + /// /// Required designer variable. /// private System.ComponentModel.IContainer components = null; - /// + /// /// Clean up any resources being used. /// /// true if managed resources should be disposed; otherwise, false. @@ -22,8 +22,8 @@ protected override void Dispose(bool disposing) #region Component Designer generated code - /// - /// Required method for Designer support - do not modify + /// + /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// private void InitializeComponent() @@ -34,7 +34,7 @@ private void InitializeComponent() this.PlayTimeGrid = new System.Windows.Forms.DataGridView(); this.NameColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.TimeColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.BottomButtonPanel = new LeftRightRowPanel(); + this.BottomButtonPanel = new CKAN.GUI.LeftRightRowPanel(); this.TotalLabel = new System.Windows.Forms.Label(); this.OKButton = new System.Windows.Forms.Button(); this.BottomButtonPanel.SuspendLayout(); @@ -149,7 +149,7 @@ private void InitializeComponent() private System.Windows.Forms.DataGridView PlayTimeGrid; private System.Windows.Forms.DataGridViewTextBoxColumn NameColumn; private System.Windows.Forms.DataGridViewTextBoxColumn TimeColumn; - private LeftRightRowPanel BottomButtonPanel; + private CKAN.GUI.LeftRightRowPanel BottomButtonPanel; private System.Windows.Forms.Label TotalLabel; private System.Windows.Forms.Button OKButton; } diff --git a/GUI/Controls/UnmanagedFiles.Designer.cs b/GUI/Controls/UnmanagedFiles.Designer.cs index ee0779b189..81b67ee212 100644 --- a/GUI/Controls/UnmanagedFiles.Designer.cs +++ b/GUI/Controls/UnmanagedFiles.Designer.cs @@ -42,7 +42,7 @@ private void InitializeComponent() this.DeleteButton = new System.Windows.Forms.ToolStripMenuItem(); this.GameFolderTree = new System.Windows.Forms.TreeView(); this.OKButton = new System.Windows.Forms.Button(); - this.BottomButtonPanel = new LeftRightRowPanel(); + this.BottomButtonPanel = new CKAN.GUI.LeftRightRowPanel(); this.BottomButtonPanel.SuspendLayout(); this.SuspendLayout(); // @@ -192,7 +192,7 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripMenuItem ShowInFolderButton; private System.Windows.Forms.ToolStripMenuItem DeleteButton; private System.Windows.Forms.TreeView GameFolderTree; - private LeftRightRowPanel BottomButtonPanel; + private CKAN.GUI.LeftRightRowPanel BottomButtonPanel; private System.Windows.Forms.Button OKButton; } } diff --git a/GUI/Controls/Wait.Designer.cs b/GUI/Controls/Wait.Designer.cs index 4baca22724..f1dc3a608d 100644 --- a/GUI/Controls/Wait.Designer.cs +++ b/GUI/Controls/Wait.Designer.cs @@ -35,7 +35,7 @@ private void InitializeComponent() this.DialogProgressBar = new System.Windows.Forms.ProgressBar(); this.ProgressBarTable = new System.Windows.Forms.TableLayoutPanel(); this.LogTextBox = new System.Windows.Forms.TextBox(); - this.BottomButtonPanel = new LeftRightRowPanel(); + this.BottomButtonPanel = new CKAN.GUI.LeftRightRowPanel(); this.CancelCurrentActionButton = new System.Windows.Forms.Button(); this.RetryCurrentActionButton = new System.Windows.Forms.Button(); this.OkButton = new System.Windows.Forms.Button(); @@ -183,7 +183,7 @@ private void InitializeComponent() private System.Windows.Forms.ProgressBar DialogProgressBar; private System.Windows.Forms.TableLayoutPanel ProgressBarTable; private System.Windows.Forms.TextBox LogTextBox; - private LeftRightRowPanel BottomButtonPanel; + private CKAN.GUI.LeftRightRowPanel BottomButtonPanel; private System.Windows.Forms.Button RetryCurrentActionButton; private System.Windows.Forms.Button CancelCurrentActionButton; private System.Windows.Forms.Button OkButton; diff --git a/GUI/Dialogs/DownloadsFailedDialog.Designer.cs b/GUI/Dialogs/DownloadsFailedDialog.Designer.cs index 02ebe26c55..dd180e2198 100644 --- a/GUI/Dialogs/DownloadsFailedDialog.Designer.cs +++ b/GUI/Dialogs/DownloadsFailedDialog.Designer.cs @@ -36,7 +36,7 @@ private void InitializeComponent() this.SkipColumn = new System.Windows.Forms.DataGridViewCheckBoxColumn(); this.ModColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.ErrorColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.BottomButtonPanel = new LeftRightRowPanel(); + this.BottomButtonPanel = new CKAN.GUI.LeftRightRowPanel(); this.RetryButton = new System.Windows.Forms.Button(); this.AbortButton = new System.Windows.Forms.Button(); ((System.ComponentModel.ISupportInitialize)(this.DownloadsGrid)).BeginInit(); @@ -186,7 +186,7 @@ private void InitializeComponent() private System.Windows.Forms.DataGridViewCheckBoxColumn SkipColumn; private System.Windows.Forms.DataGridViewTextBoxColumn ModColumn; private System.Windows.Forms.DataGridViewTextBoxColumn ErrorColumn; - private LeftRightRowPanel BottomButtonPanel; + private CKAN.GUI.LeftRightRowPanel BottomButtonPanel; private System.Windows.Forms.Button RetryButton; private System.Windows.Forms.Button AbortButton; } diff --git a/GUI/Dialogs/DownloadsFailedDialog.cs b/GUI/Dialogs/DownloadsFailedDialog.cs index f5bb21cb03..9b604f2a59 100644 --- a/GUI/Dialogs/DownloadsFailedDialog.cs +++ b/GUI/Dialogs/DownloadsFailedDialog.cs @@ -195,7 +195,6 @@ public DownloadRow(object data, Exception exc) /// /// True if Skip column has a checkmark /// - /// #pragma warning disable IDE0027 public bool Skip { get => !Retry; set { Retry = !value; } } #pragma warning restore IDE0027 diff --git a/GUI/Dialogs/SettingsDialog.Designer.cs b/GUI/Dialogs/SettingsDialog.Designer.cs index 80c4f4c412..505687706a 100644 --- a/GUI/Dialogs/SettingsDialog.Designer.cs +++ b/GUI/Dialogs/SettingsDialog.Designer.cs @@ -63,6 +63,7 @@ private void InitializeComponent() this.LatestVersionPreLabel = new System.Windows.Forms.Label(); this.LatestVersionLabel = new System.Windows.Forms.Label(); this.CheckUpdateOnLaunchCheckbox = new System.Windows.Forms.CheckBox(); + this.DevBuildsCheckbox = new System.Windows.Forms.CheckBox(); this.CheckForUpdatesButton = new System.Windows.Forms.Button(); this.InstallUpdateButton = new System.Windows.Forms.Button(); this.BehaviourGroupBox = new System.Windows.Forms.GroupBox(); @@ -100,7 +101,7 @@ private void InitializeComponent() this.RepositoryGroupBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.RepositoryGroupBox.Location = new System.Drawing.Point(12, 6); this.RepositoryGroupBox.Name = "RepositoryGroupBox"; - this.RepositoryGroupBox.Size = new System.Drawing.Size(476, 128); + this.RepositoryGroupBox.Size = new System.Drawing.Size(488, 128); this.RepositoryGroupBox.TabIndex = 0; this.RepositoryGroupBox.TabStop = false; resources.ApplyResources(this.RepositoryGroupBox, "RepositoryGroupBox"); @@ -115,7 +116,7 @@ private void InitializeComponent() this.ReposListBox.FullRowSelect = true; this.ReposListBox.MultiSelect = false; this.ReposListBox.Name = "ReposListBox"; - this.ReposListBox.Size = new System.Drawing.Size(452, 67); + this.ReposListBox.Size = new System.Drawing.Size(464, 67); this.ReposListBox.TabIndex = 0; this.ReposListBox.View = System.Windows.Forms.View.Details; this.ReposListBox.SelectedIndexChanged += new System.EventHandler(this.ReposListBox_SelectedIndexChanged); @@ -166,7 +167,7 @@ private void InitializeComponent() // this.DeleteRepoButton.Enabled = false; this.DeleteRepoButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.DeleteRepoButton.Location = new System.Drawing.Point(394, 93); + this.DeleteRepoButton.Location = new System.Drawing.Point(406, 93); this.DeleteRepoButton.Name = "DeleteRepoButton"; this.DeleteRepoButton.Size = new System.Drawing.Size(70, 25); this.DeleteRepoButton.TabIndex = 4; @@ -180,9 +181,9 @@ private void InitializeComponent() this.AuthTokensGroupBox.Controls.Add(this.DeleteAuthTokenButton); this.AuthTokensGroupBox.ForeColor = System.Drawing.SystemColors.ControlText; this.AuthTokensGroupBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.AuthTokensGroupBox.Location = new System.Drawing.Point(12, 140); + this.AuthTokensGroupBox.Location = new System.Drawing.Point(512, 6); this.AuthTokensGroupBox.Name = "AuthTokensGroupBox"; - this.AuthTokensGroupBox.Size = new System.Drawing.Size(476, 128); + this.AuthTokensGroupBox.Size = new System.Drawing.Size(244, 128); this.AuthTokensGroupBox.TabIndex = 1; this.AuthTokensGroupBox.TabStop = false; resources.ApplyResources(this.AuthTokensGroupBox, "AuthTokensGroupBox"); @@ -198,13 +199,13 @@ private void InitializeComponent() this.AuthTokensListBox.View = System.Windows.Forms.View.Details; this.AuthTokensListBox.Location = new System.Drawing.Point(12, 18); this.AuthTokensListBox.Name = "AuthTokensListBox"; - this.AuthTokensListBox.Size = new System.Drawing.Size(452, 67); + this.AuthTokensListBox.Size = new System.Drawing.Size(220, 67); this.AuthTokensListBox.TabIndex = 0; this.AuthTokensListBox.SelectedIndexChanged += new System.EventHandler(this.AuthTokensListBox_SelectedIndexChanged); // // AuthHostHeader // - this.AuthHostHeader.Width = 120; + this.AuthHostHeader.Width = 100; resources.ApplyResources(this.AuthHostHeader, "AuthHostHeader"); // // AuthTokenHeader @@ -226,13 +227,111 @@ private void InitializeComponent() // this.DeleteAuthTokenButton.Enabled = false; this.DeleteAuthTokenButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.DeleteAuthTokenButton.Location = new System.Drawing.Point(394, 93); + this.DeleteAuthTokenButton.Location = new System.Drawing.Point(164, 93); this.DeleteAuthTokenButton.Name = "DeleteAuthTokenButton"; this.DeleteAuthTokenButton.Size = new System.Drawing.Size(70, 25); this.DeleteAuthTokenButton.TabIndex = 2; this.DeleteAuthTokenButton.Click += new System.EventHandler(this.DeleteAuthTokenButton_Click); resources.ApplyResources(this.DeleteAuthTokenButton, "DeleteAuthTokenButton"); // + // AutoUpdateGroupBox + // + this.AutoUpdateGroupBox.Controls.Add(this.LocalVersionPreLabel); + this.AutoUpdateGroupBox.Controls.Add(this.LocalVersionLabel); + this.AutoUpdateGroupBox.Controls.Add(this.LatestVersionPreLabel); + this.AutoUpdateGroupBox.Controls.Add(this.LatestVersionLabel); + this.AutoUpdateGroupBox.Controls.Add(this.CheckUpdateOnLaunchCheckbox); + this.AutoUpdateGroupBox.Controls.Add(this.DevBuildsCheckbox); + this.AutoUpdateGroupBox.Controls.Add(this.CheckForUpdatesButton); + this.AutoUpdateGroupBox.Controls.Add(this.InstallUpdateButton); + this.AutoUpdateGroupBox.ForeColor = System.Drawing.SystemColors.ControlText; + this.AutoUpdateGroupBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.AutoUpdateGroupBox.Location = new System.Drawing.Point(12, 144); + this.AutoUpdateGroupBox.Name = "AutoUpdateGroupBox"; + this.AutoUpdateGroupBox.Size = new System.Drawing.Size(254, 156); + this.AutoUpdateGroupBox.TabIndex = 2; + this.AutoUpdateGroupBox.TabStop = false; + resources.ApplyResources(this.AutoUpdateGroupBox, "AutoUpdateGroupBox"); + // + // LocalVersionPreLabel + // + this.LocalVersionPreLabel.AutoSize = true; + this.LocalVersionPreLabel.Location = new System.Drawing.Point(9, 18); + this.LocalVersionPreLabel.Name = "LocalVersionPreLabel"; + this.LocalVersionPreLabel.Size = new System.Drawing.Size(73, 13); + this.LocalVersionPreLabel.TabIndex = 0; + resources.ApplyResources(this.LocalVersionPreLabel, "LocalVersionPreLabel"); + // + // LocalVersionLabel + // + this.LocalVersionLabel.AutoSize = true; + this.LocalVersionLabel.Location = new System.Drawing.Point(95, 18); + this.LocalVersionLabel.Name = "LocalVersionLabel"; + this.LocalVersionLabel.Size = new System.Drawing.Size(37, 13); + this.LocalVersionLabel.TabIndex = 1; + resources.ApplyResources(this.LocalVersionLabel, "LocalVersionLabel"); + // + // LatestVersionPreLabel + // + this.LatestVersionPreLabel.AutoSize = true; + this.LatestVersionPreLabel.Location = new System.Drawing.Point(9, 39); + this.LatestVersionPreLabel.Name = "LatestVersionPreLabel"; + this.LatestVersionPreLabel.Size = new System.Drawing.Size(76, 13); + this.LatestVersionPreLabel.TabIndex = 2; + resources.ApplyResources(this.LatestVersionPreLabel, "LatestVersionPreLabel"); + // + // LatestVersionLabel + // + this.LatestVersionLabel.AutoSize = true; + this.LatestVersionLabel.Location = new System.Drawing.Point(95, 39); + this.LatestVersionLabel.Name = "LatestVersionLabel"; + this.LatestVersionLabel.Size = new System.Drawing.Size(25, 13); + this.LatestVersionLabel.TabIndex = 3; + resources.ApplyResources(this.LatestVersionLabel, "LatestVersionLabel"); + // + // CheckUpdateOnLaunchCheckbox + // + this.CheckUpdateOnLaunchCheckbox.AutoSize = true; + this.CheckUpdateOnLaunchCheckbox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.CheckUpdateOnLaunchCheckbox.Location = new System.Drawing.Point(12, 61); + this.CheckUpdateOnLaunchCheckbox.Name = "CheckUpdateOnLaunchCheckbox"; + this.CheckUpdateOnLaunchCheckbox.Size = new System.Drawing.Size(195, 17); + this.CheckUpdateOnLaunchCheckbox.TabIndex = 4; + this.CheckUpdateOnLaunchCheckbox.CheckedChanged += new System.EventHandler(this.CheckUpdateOnLaunchCheckbox_CheckedChanged); + resources.ApplyResources(this.CheckUpdateOnLaunchCheckbox, "CheckUpdateOnLaunchCheckbox"); + // + // DevBuildsCheckbox + // + this.DevBuildsCheckbox.AutoSize = true; + this.DevBuildsCheckbox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.DevBuildsCheckbox.Location = new System.Drawing.Point(12, 85); + this.DevBuildsCheckbox.Name = "DevBuildsCheckbox"; + this.DevBuildsCheckbox.Size = new System.Drawing.Size(195, 17); + this.DevBuildsCheckbox.TabIndex = 4; + this.DevBuildsCheckbox.CheckedChanged += new System.EventHandler(this.DevBuildsCheckbox_CheckedChanged); + resources.ApplyResources(this.DevBuildsCheckbox, "DevBuildsCheckbox"); + // + // CheckForUpdatesButton + // + this.CheckForUpdatesButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.CheckForUpdatesButton.Location = new System.Drawing.Point(12, 108); + this.CheckForUpdatesButton.Name = "CheckForUpdatesButton"; + this.CheckForUpdatesButton.Size = new System.Drawing.Size(112, 38); + this.CheckForUpdatesButton.TabIndex = 5; + this.CheckForUpdatesButton.Click += new System.EventHandler(this.CheckForUpdatesButton_Click); + resources.ApplyResources(this.CheckForUpdatesButton, "CheckForUpdatesButton"); + // + // InstallUpdateButton + // + this.InstallUpdateButton.Enabled = false; + this.InstallUpdateButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.InstallUpdateButton.Location = new System.Drawing.Point(130, 108); + this.InstallUpdateButton.Name = "InstallUpdateButton"; + this.InstallUpdateButton.Size = new System.Drawing.Size(112, 38); + this.InstallUpdateButton.TabIndex = 6; + this.InstallUpdateButton.Click += new System.EventHandler(this.InstallUpdateButton_Click); + resources.ApplyResources(this.InstallUpdateButton, "InstallUpdateButton"); + // // CacheGroupBox // this.CacheGroupBox.Controls.Add(this.CachePath); @@ -246,10 +345,10 @@ private void InitializeComponent() this.CacheGroupBox.Controls.Add(this.OpenCacheButton); this.CacheGroupBox.ForeColor = System.Drawing.SystemColors.ControlText; this.CacheGroupBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.CacheGroupBox.Location = new System.Drawing.Point(12, 274); + this.CacheGroupBox.Location = new System.Drawing.Point(280, 144); this.CacheGroupBox.Name = "CacheGroupBox"; - this.CacheGroupBox.Size = new System.Drawing.Size(476, 124); - this.CacheGroupBox.TabIndex = 2; + this.CacheGroupBox.Size = new System.Drawing.Size(476, 156); + this.CacheGroupBox.TabIndex = 3; this.CacheGroupBox.TabStop = false; resources.ApplyResources(this.CacheGroupBox, "CacheGroupBox"); // @@ -367,92 +466,6 @@ private void InitializeComponent() this.OpenCacheButton.Click += new System.EventHandler(this.OpenCacheButton_Click); resources.ApplyResources(this.OpenCacheButton, "OpenCacheButton"); // - // AutoUpdateGroupBox - // - this.AutoUpdateGroupBox.Controls.Add(this.LocalVersionPreLabel); - this.AutoUpdateGroupBox.Controls.Add(this.LocalVersionLabel); - this.AutoUpdateGroupBox.Controls.Add(this.LatestVersionPreLabel); - this.AutoUpdateGroupBox.Controls.Add(this.LatestVersionLabel); - this.AutoUpdateGroupBox.Controls.Add(this.CheckUpdateOnLaunchCheckbox); - this.AutoUpdateGroupBox.Controls.Add(this.CheckForUpdatesButton); - this.AutoUpdateGroupBox.Controls.Add(this.InstallUpdateButton); - this.AutoUpdateGroupBox.ForeColor = System.Drawing.SystemColors.ControlText; - this.AutoUpdateGroupBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.AutoUpdateGroupBox.Location = new System.Drawing.Point(12, 404); - this.AutoUpdateGroupBox.Name = "AutoUpdateGroupBox"; - this.AutoUpdateGroupBox.Size = new System.Drawing.Size(254, 132); - this.AutoUpdateGroupBox.TabIndex = 3; - this.AutoUpdateGroupBox.TabStop = false; - resources.ApplyResources(this.AutoUpdateGroupBox, "AutoUpdateGroupBox"); - // - // LocalVersionPreLabel - // - this.LocalVersionPreLabel.AutoSize = true; - this.LocalVersionPreLabel.Location = new System.Drawing.Point(9, 18); - this.LocalVersionPreLabel.Name = "LocalVersionPreLabel"; - this.LocalVersionPreLabel.Size = new System.Drawing.Size(73, 13); - this.LocalVersionPreLabel.TabIndex = 0; - resources.ApplyResources(this.LocalVersionPreLabel, "LocalVersionPreLabel"); - // - // LocalVersionLabel - // - this.LocalVersionLabel.AutoSize = true; - this.LocalVersionLabel.Location = new System.Drawing.Point(95, 18); - this.LocalVersionLabel.Name = "LocalVersionLabel"; - this.LocalVersionLabel.Size = new System.Drawing.Size(37, 13); - this.LocalVersionLabel.TabIndex = 1; - resources.ApplyResources(this.LocalVersionLabel, "LocalVersionLabel"); - // - // LatestVersionPreLabel - // - this.LatestVersionPreLabel.AutoSize = true; - this.LatestVersionPreLabel.Location = new System.Drawing.Point(9, 39); - this.LatestVersionPreLabel.Name = "LatestVersionPreLabel"; - this.LatestVersionPreLabel.Size = new System.Drawing.Size(76, 13); - this.LatestVersionPreLabel.TabIndex = 2; - resources.ApplyResources(this.LatestVersionPreLabel, "LatestVersionPreLabel"); - // - // LatestVersionLabel - // - this.LatestVersionLabel.AutoSize = true; - this.LatestVersionLabel.Location = new System.Drawing.Point(95, 39); - this.LatestVersionLabel.Name = "LatestVersionLabel"; - this.LatestVersionLabel.Size = new System.Drawing.Size(25, 13); - this.LatestVersionLabel.TabIndex = 3; - resources.ApplyResources(this.LatestVersionLabel, "LatestVersionLabel"); - // - // CheckUpdateOnLaunchCheckbox - // - this.CheckUpdateOnLaunchCheckbox.AutoSize = true; - this.CheckUpdateOnLaunchCheckbox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.CheckUpdateOnLaunchCheckbox.Location = new System.Drawing.Point(12, 61); - this.CheckUpdateOnLaunchCheckbox.Name = "CheckUpdateOnLaunchCheckbox"; - this.CheckUpdateOnLaunchCheckbox.Size = new System.Drawing.Size(195, 17); - this.CheckUpdateOnLaunchCheckbox.TabIndex = 4; - this.CheckUpdateOnLaunchCheckbox.CheckedChanged += new System.EventHandler(this.CheckUpdateOnLaunchCheckbox_CheckedChanged); - resources.ApplyResources(this.CheckUpdateOnLaunchCheckbox, "CheckUpdateOnLaunchCheckbox"); - // - // CheckForUpdatesButton - // - this.CheckForUpdatesButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.CheckForUpdatesButton.Location = new System.Drawing.Point(12, 84); - this.CheckForUpdatesButton.Name = "CheckForUpdatesButton"; - this.CheckForUpdatesButton.Size = new System.Drawing.Size(112, 38); - this.CheckForUpdatesButton.TabIndex = 5; - this.CheckForUpdatesButton.Click += new System.EventHandler(this.CheckForUpdatesButton_Click); - resources.ApplyResources(this.CheckForUpdatesButton, "CheckForUpdatesButton"); - // - // InstallUpdateButton - // - this.InstallUpdateButton.Enabled = false; - this.InstallUpdateButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.InstallUpdateButton.Location = new System.Drawing.Point(130, 84); - this.InstallUpdateButton.Name = "InstallUpdateButton"; - this.InstallUpdateButton.Size = new System.Drawing.Size(112, 38); - this.InstallUpdateButton.TabIndex = 6; - this.InstallUpdateButton.Click += new System.EventHandler(this.InstallUpdateButton_Click); - resources.ApplyResources(this.InstallUpdateButton, "InstallUpdateButton"); - // // BehaviourGroupBox // this.BehaviourGroupBox.Controls.Add(this.EnableTrayIconCheckBox); @@ -463,9 +476,9 @@ private void InitializeComponent() this.BehaviourGroupBox.Controls.Add(this.PauseRefreshCheckBox); this.BehaviourGroupBox.ForeColor = System.Drawing.SystemColors.ControlText; this.BehaviourGroupBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.BehaviourGroupBox.Location = new System.Drawing.Point(278, 404); + this.BehaviourGroupBox.Location = new System.Drawing.Point(12, 310); this.BehaviourGroupBox.Name = "BehaviourGroupBox"; - this.BehaviourGroupBox.Size = new System.Drawing.Size(210, 132); + this.BehaviourGroupBox.Size = new System.Drawing.Size(254, 150); this.BehaviourGroupBox.TabIndex = 4; this.BehaviourGroupBox.TabStop = false; resources.ApplyResources(this.BehaviourGroupBox, "BehaviourGroupBox"); @@ -544,7 +557,7 @@ private void InitializeComponent() this.MoreSettingsGroupBox.Controls.Add(this.HideVCheckbox); this.MoreSettingsGroupBox.ForeColor = System.Drawing.SystemColors.ControlText; this.MoreSettingsGroupBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.MoreSettingsGroupBox.Location = new System.Drawing.Point(12, 542); + this.MoreSettingsGroupBox.Location = new System.Drawing.Point(280, 310); this.MoreSettingsGroupBox.Name = "MoreSettingsGroupBox"; this.MoreSettingsGroupBox.Size = new System.Drawing.Size(476, 150); this.MoreSettingsGroupBox.TabIndex = 5; @@ -622,7 +635,7 @@ private void InitializeComponent() // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(500, 703); + this.ClientSize = new System.Drawing.Size(768, 470); this.Controls.Add(this.RepositoryGroupBox); this.Controls.Add(this.AuthTokensGroupBox); this.Controls.Add(this.CacheGroupBox); @@ -687,6 +700,7 @@ private void InitializeComponent() private System.Windows.Forms.Label LatestVersionPreLabel; private System.Windows.Forms.Label LatestVersionLabel; private System.Windows.Forms.CheckBox CheckUpdateOnLaunchCheckbox; + private System.Windows.Forms.CheckBox DevBuildsCheckbox; private System.Windows.Forms.Button CheckForUpdatesButton; private System.Windows.Forms.Button InstallUpdateButton; private System.Windows.Forms.GroupBox BehaviourGroupBox; diff --git a/GUI/Dialogs/SettingsDialog.cs b/GUI/Dialogs/SettingsDialog.cs index d3b7f5ce4f..90f354d9a0 100644 --- a/GUI/Dialogs/SettingsDialog.cs +++ b/GUI/Dialogs/SettingsDialog.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Linq; using System.Windows.Forms; using System.Drawing; @@ -8,7 +9,6 @@ #endif using log4net; -using Autofac; using CKAN.Versioning; using CKAN.Configuration; @@ -26,23 +26,31 @@ public partial class SettingsDialog : Form public bool RepositoryRemoved { get; private set; } = false; public bool RepositoryMoved { get; private set; } = false; - private readonly IUser m_user; - private readonly IConfiguration config; - private readonly RegistryManager regMgr; + private readonly IConfiguration coreConfig; + private readonly GUIConfiguration guiConfig; + private readonly RegistryManager regMgr; + private readonly AutoUpdate updater; + private readonly IUser user; /// /// Initialize a settings window /// - public SettingsDialog(RegistryManager regMgr, IUser user) + public SettingsDialog(IConfiguration coreConfig, + GUIConfiguration guiConfig, + RegistryManager regMgr, + AutoUpdate updater, + IUser user) { InitializeComponent(); - m_user = user; - this.regMgr = regMgr; + this.coreConfig = coreConfig; + this.guiConfig = guiConfig; + this.regMgr = regMgr; + this.updater = updater; + this.user = user; if (Platform.IsMono) { ClearCacheMenu.Renderer = new FlatToolStripRenderer(); } - config = ServiceLocator.Container.Resolve(); } private void SettingsDialog_Load(object sender, EventArgs e) @@ -55,29 +63,50 @@ public void UpdateDialog() RefreshReposListBox(false); RefreshAuthTokensListBox(); UpdateLanguageSelectionComboBox(); - - LocalVersionLabel.Text = Meta.GetVersion(); - - CheckUpdateOnLaunchCheckbox.Checked = Main.Instance.configuration.CheckForUpdatesOnLaunch; - RefreshOnStartupCheckbox.Checked = Main.Instance.configuration.RefreshOnStartup; - HideEpochsCheckbox.Checked = Main.Instance.configuration.HideEpochs; - HideVCheckbox.Checked = Main.Instance.configuration.HideV; - AutoSortUpdateCheckBox.Checked = Main.Instance.configuration.AutoSortByUpdate; - EnableTrayIconCheckBox.Checked = MinimizeToTrayCheckBox.Enabled = Main.Instance.configuration.EnableTrayIcon; - MinimizeToTrayCheckBox.Checked = Main.Instance.configuration.MinimizeToTray; - PauseRefreshCheckBox.Checked = Main.Instance.configuration.RefreshPaused; + UpdateAutoUpdate(); + + CheckUpdateOnLaunchCheckbox.Checked = guiConfig.CheckForUpdatesOnLaunch; + DevBuildsCheckbox.Checked = coreConfig.DevBuilds; + RefreshOnStartupCheckbox.Checked = guiConfig.RefreshOnStartup; + HideEpochsCheckbox.Checked = guiConfig.HideEpochs; + HideVCheckbox.Checked = guiConfig.HideV; + AutoSortUpdateCheckBox.Checked = guiConfig.AutoSortByUpdate; + EnableTrayIconCheckBox.Checked = MinimizeToTrayCheckBox.Enabled = guiConfig.EnableTrayIcon; + MinimizeToTrayCheckBox.Checked = guiConfig.MinimizeToTray; + PauseRefreshCheckBox.Checked = guiConfig.RefreshPaused; UpdateRefreshRate(); - UpdateCacheInfo(config.DownloadCacheDir); + UpdateCacheInfo(coreConfig.DownloadCacheDir); + } + + private void UpdateAutoUpdate() + { + LocalVersionLabel.Text = Meta.GetVersion(); + try + { + var latestVersion = updater.GetUpdate(coreConfig.DevBuilds) + .Version; + LatestVersionLabel.Text = latestVersion.ToString(); + // Allow downgrading in case they want to stop using dev builds + InstallUpdateButton.Enabled = !latestVersion.Equals(new ModuleVersion(Meta.GetVersion())); + } + catch + { + // Can't get the version, reset the label + var resources = new SingleAssemblyComponentResourceManager(typeof(SettingsDialog)); + resources.ApplyResources(LatestVersionLabel, + LatestVersionLabel.Name); + InstallUpdateButton.Enabled = false; + } } protected override void OnFormClosing(FormClosingEventArgs e) { - if (CachePath.Text != config.DownloadCacheDir + if (CachePath.Text != coreConfig.DownloadCacheDir && !Main.Instance.Manager.TrySetupCache(CachePath.Text, out string failReason)) { - m_user.RaiseError(Properties.Resources.SettingsDialogSummaryInvalid, failReason); + user.RaiseError(Properties.Resources.SettingsDialogSummaryInvalid, failReason); e.Cancel = true; } else @@ -88,10 +117,10 @@ protected override void OnFormClosing(FormClosingEventArgs e) private void UpdateRefreshRate() { - int rate = config.RefreshRate; + int rate = coreConfig.RefreshRate; RefreshTextBox.Text = rate.ToString(); PauseRefreshCheckBox.Enabled = rate != 0; - Main.Instance.pauseToolStripMenuItem.Enabled = config.RefreshRate != 0; + Main.Instance.pauseToolStripMenuItem.Enabled = coreConfig.RefreshRate != 0; Main.Instance.UpdateRefreshTimer(); } @@ -135,7 +164,7 @@ private void UpdateLanguageSelectionComboBox() LanguageSelectionComboBox.Items.AddRange(Utilities.AvailableLanguages); // If the current language is supported by CKAN, set is as selected. // Else display a blank field. - LanguageSelectionComboBox.SelectedIndex = LanguageSelectionComboBox.FindStringExact(config.Language); + LanguageSelectionComboBox.SelectedIndex = LanguageSelectionComboBox.FindStringExact(coreConfig.Language); } private void UpdateCacheInfo(string newPath) @@ -152,10 +181,10 @@ private void UpdateCacheInfo(string newPath) Util.Invoke(this, () => { - if (config.CacheSizeLimit.HasValue) + if (coreConfig.CacheSizeLimit.HasValue) { // Show setting in MiB - CacheLimit.Text = (config.CacheSizeLimit.Value / 1024 / 1024).ToString(); + CacheLimit.Text = (coreConfig.CacheSizeLimit.Value / 1024 / 1024).ToString(); } CacheSummary.Text = string.Format( Properties.Resources.SettingsDialogSummmary, @@ -163,8 +192,8 @@ private void UpdateCacheInfo(string newPath) CacheSummary.ForeColor = SystemColors.ControlText; OpenCacheButton.Enabled = true; ClearCacheButton.Enabled = (cacheSize > 0); - PurgeToLimitMenuItem.Enabled = (config.CacheSizeLimit.HasValue - && cacheSize > config.CacheSizeLimit.Value); + PurgeToLimitMenuItem.Enabled = (coreConfig.CacheSizeLimit.HasValue + && cacheSize > coreConfig.CacheSizeLimit.Value); }); } @@ -191,12 +220,12 @@ private void CacheLimit_TextChanged(object sender, EventArgs e) { if (string.IsNullOrEmpty(CacheLimit.Text)) { - config.CacheSizeLimit = null; + coreConfig.CacheSizeLimit = null; } else { // Translate from MB to bytes - config.CacheSizeLimit = Convert.ToInt64(CacheLimit.Text) * 1024 * 1024; + coreConfig.CacheSizeLimit = Convert.ToInt64(CacheLimit.Text) * 1024 * 1024; } UpdateCacheInfo(CachePath.Text); } @@ -215,7 +244,7 @@ private void ChangeCacheButton_Click(object sender, EventArgs e) { Description = Properties.Resources.SettingsDialogCacheDescrip, RootFolder = Environment.SpecialFolder.MyComputer, - SelectedPath = config.DownloadCacheDir, + SelectedPath = coreConfig.DownloadCacheDir, ShowNewFolderButton = true }; DialogResult result = cacheChooser.ShowDialog(this); @@ -228,30 +257,30 @@ private void ChangeCacheButton_Click(object sender, EventArgs e) private void PurgeToLimitMenuItem_Click(object sender, EventArgs e) { // Purge old downloads if we're over the limit - if (config.CacheSizeLimit.HasValue) + if (coreConfig.CacheSizeLimit.HasValue) { // Switch main cache since user seems committed to this path - if (CachePath.Text != config.DownloadCacheDir + if (CachePath.Text != coreConfig.DownloadCacheDir && !Main.Instance.Manager.TrySetupCache(CachePath.Text, out string failReason)) { - m_user.RaiseError(Properties.Resources.SettingsDialogSummaryInvalid, failReason); + user.RaiseError(Properties.Resources.SettingsDialogSummaryInvalid, failReason); return; } Main.Instance.Manager.Cache.EnforceSizeLimit( - config.CacheSizeLimit.Value, + coreConfig.CacheSizeLimit.Value, regMgr.registry); - UpdateCacheInfo(config.DownloadCacheDir); + UpdateCacheInfo(coreConfig.DownloadCacheDir); } } private void PurgeAllMenuItem_Click(object sender, EventArgs e) { // Switch main cache since user seems committed to this path - if (CachePath.Text != config.DownloadCacheDir + if (CachePath.Text != coreConfig.DownloadCacheDir && !Main.Instance.Manager.TrySetupCache(CachePath.Text, out string failReason)) { - m_user.RaiseError(Properties.Resources.SettingsDialogSummaryInvalid, failReason); + user.RaiseError(Properties.Resources.SettingsDialogSummaryInvalid, failReason); return; } @@ -279,7 +308,7 @@ private void PurgeAllMenuItem_Click(object sender, EventArgs e) // finally, clear the preview contents list Main.Instance.RefreshModContentsTree(); - UpdateCacheInfo(config.DownloadCacheDir); + UpdateCacheInfo(coreConfig.DownloadCacheDir); } } @@ -291,7 +320,7 @@ private void ResetCacheButton_Click(object sender, EventArgs e) private void OpenCacheButton_Click(object sender, EventArgs e) { - Utilities.ProcessStartURL(config.DownloadCacheDir); + Utilities.ProcessStartURL(coreConfig.DownloadCacheDir); } private void ReposListBox_SelectedIndexChanged(object sender, EventArgs e) @@ -347,7 +376,7 @@ private void NewRepoButton_Click(object sender, EventArgs e) var registry = regMgr.registry; if (registry.Repositories.Values.Any(other => other.uri == repo.uri)) { - m_user.RaiseError(Properties.Resources.SettingsDialogRepoAddDuplicateURL, repo.uri); + user.RaiseError(Properties.Resources.SettingsDialogRepoAddDuplicateURL, repo.uri); return; } if (registry.Repositories.TryGetValue(repo.name, out Repository existing)) @@ -411,9 +440,9 @@ private void DownRepoButton_Click(object sender, EventArgs e) private void RefreshAuthTokensListBox() { AuthTokensListBox.Items.Clear(); - foreach (string host in config.GetAuthTokenHosts()) + foreach (string host in coreConfig.GetAuthTokenHosts()) { - if (config.TryGetAuthToken(host, out string token)) + if (coreConfig.TryGetAuthToken(host, out string token)) { AuthTokensListBox.Items.Add(new ListViewItem( new string[] { host, token }) @@ -508,7 +537,7 @@ private void NewAuthTokenButton_Click(object sender, EventArgs e) case DialogResult.OK: case DialogResult.Yes: - config.SetAuthToken(hostTextBox.Text, tokenTextBox.Text); + coreConfig.SetAuthToken(hostTextBox.Text, tokenTextBox.Text); RefreshAuthTokensListBox(); break; } @@ -518,22 +547,22 @@ private bool validNewAuthToken(string host, string token) { if (host.Length <= 0) { - m_user.RaiseError(Properties.Resources.AddAuthTokenHostRequired); + user.RaiseError(Properties.Resources.AddAuthTokenHostRequired); return false; } if (token.Length <= 0) { - m_user.RaiseError(Properties.Resources.AddAuthTokenTokenRequired); + user.RaiseError(Properties.Resources.AddAuthTokenTokenRequired); return false; } if (Uri.CheckHostName(host) == UriHostNameType.Unknown) { - m_user.RaiseError(Properties.Resources.AddAuthTokenInvalidHost, host); + user.RaiseError(Properties.Resources.AddAuthTokenInvalidHost, host); return false; } - if (ServiceLocator.Container.Resolve().TryGetAuthToken(host, out _)) + if (coreConfig.TryGetAuthToken(host, out _)) { - m_user.RaiseError(Properties.Resources.AddAuthTokenDupHost, host); + user.RaiseError(Properties.Resources.AddAuthTokenDupHost, host); return false; } @@ -547,7 +576,7 @@ private void DeleteAuthTokenButton_Click(object sender, EventArgs e) string item = AuthTokensListBox.SelectedItems[0].Tag as string; string host = item?.Split('|')[0].Trim(); - config.SetAuthToken(host, null); + coreConfig.SetAuthToken(host, null); RefreshAuthTokensListBox(); DeleteAuthTokenButton.Enabled = false; } @@ -557,16 +586,11 @@ private void CheckForUpdatesButton_Click(object sender, EventArgs e) { try { - AutoUpdate.Instance.FetchLatestReleaseInfo(); - var latestVersion = AutoUpdate.Instance.latestUpdate.Version; - InstallUpdateButton.Enabled = latestVersion.IsGreaterThan(new ModuleVersion(Meta.GetVersion(VersionFormat.Short))) - && AutoUpdate.Instance.IsFetched(); - - LatestVersionLabel.Text = latestVersion.ToString(); + UpdateAutoUpdate(); } - catch (Exception ex) + catch (Exception exc) { - log.Warn("Exception caught in CheckForUpdates:\r\n" + ex); + log.Warn("Exception caught in CheckForUpdates:\r\n" + exc); } } @@ -579,29 +603,34 @@ private void InstallUpdateButton_Click(object sender, EventArgs e) } else { - m_user.RaiseError(Properties.Resources.SettingsDialogUpdateFailed); + user.RaiseError(Properties.Resources.SettingsDialogUpdateFailed); } - } private void CheckUpdateOnLaunchCheckbox_CheckedChanged(object sender, EventArgs e) { - Main.Instance.configuration.CheckForUpdatesOnLaunch = CheckUpdateOnLaunchCheckbox.Checked; + guiConfig.CheckForUpdatesOnLaunch = CheckUpdateOnLaunchCheckbox.Checked; + } + + private void DevBuildsCheckbox_CheckedChanged(object sender, EventArgs e) + { + coreConfig.DevBuilds = DevBuildsCheckbox.Checked; + UpdateAutoUpdate(); } private void RefreshOnStartupCheckbox_CheckedChanged(object sender, EventArgs e) { - Main.Instance.configuration.RefreshOnStartup = RefreshOnStartupCheckbox.Checked; + guiConfig.RefreshOnStartup = RefreshOnStartupCheckbox.Checked; } private void HideEpochsCheckbox_CheckedChanged(object sender, EventArgs e) { - Main.Instance.configuration.HideEpochs = HideEpochsCheckbox.Checked; + guiConfig.HideEpochs = HideEpochsCheckbox.Checked; } private void HideVCheckbox_CheckedChanged(object sender, EventArgs e) { - Main.Instance.configuration.HideV = HideVCheckbox.Checked; + guiConfig.HideV = HideVCheckbox.Checked; } private void LanguageSelectionComboBox_MouseWheel(object sender, MouseEventArgs e) @@ -615,29 +644,29 @@ private void LanguageSelectionComboBox_MouseWheel(object sender, MouseEventArgs private void LanguageSelectionComboBox_SelectionChanged(object sender, EventArgs e) { - config.Language = LanguageSelectionComboBox.SelectedItem.ToString(); + coreConfig.Language = LanguageSelectionComboBox.SelectedItem.ToString(); } private void AutoSortUpdateCheckBox_CheckedChanged(object sender, EventArgs e) { - Main.Instance.configuration.AutoSortByUpdate = AutoSortUpdateCheckBox.Checked; + guiConfig.AutoSortByUpdate = AutoSortUpdateCheckBox.Checked; } private void EnableTrayIconCheckBox_CheckedChanged(object sender, EventArgs e) { - MinimizeToTrayCheckBox.Enabled = Main.Instance.configuration.EnableTrayIcon = EnableTrayIconCheckBox.Checked; + MinimizeToTrayCheckBox.Enabled = guiConfig.EnableTrayIcon = EnableTrayIconCheckBox.Checked; Main.Instance.CheckTrayState(); } private void MinimizeToTrayCheckBox_CheckedChanged(object sender, EventArgs e) { - Main.Instance.configuration.MinimizeToTray = MinimizeToTrayCheckBox.Checked; + guiConfig.MinimizeToTray = MinimizeToTrayCheckBox.Checked; Main.Instance.CheckTrayState(); } private void RefreshTextBox_TextChanged(object sender, EventArgs e) { - config.RefreshRate = string.IsNullOrEmpty(RefreshTextBox.Text) ? 0 : int.Parse(RefreshTextBox.Text); + coreConfig.RefreshRate = string.IsNullOrEmpty(RefreshTextBox.Text) ? 0 : int.Parse(RefreshTextBox.Text); UpdateRefreshRate(); } @@ -651,9 +680,9 @@ private void RefreshTextBox_KeyPress(object sender, KeyPressEventArgs e) private void PauseRefreshCheckBox_CheckedChanged(object sender, EventArgs e) { - Main.Instance.configuration.RefreshPaused = PauseRefreshCheckBox.Checked; + guiConfig.RefreshPaused = PauseRefreshCheckBox.Checked; - if (Main.Instance.configuration.RefreshPaused) + if (guiConfig.RefreshPaused) { Main.Instance.refreshTimer.Stop(); } diff --git a/GUI/Dialogs/SettingsDialog.resx b/GUI/Dialogs/SettingsDialog.resx index e57622fd9b..72e7c02d00 100644 --- a/GUI/Dialogs/SettingsDialog.resx +++ b/GUI/Dialogs/SettingsDialog.resx @@ -144,6 +144,7 @@ Latest version: ??? Check for CKAN updates on launch + Use dev builds Check for updates Install update Behaviour diff --git a/GUI/Dialogs/YesNoDialog.Designer.cs b/GUI/Dialogs/YesNoDialog.Designer.cs index 190f0470cc..6040b0d53e 100644 --- a/GUI/Dialogs/YesNoDialog.Designer.cs +++ b/GUI/Dialogs/YesNoDialog.Designer.cs @@ -31,11 +31,13 @@ private void InitializeComponent() this.components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new SingleAssemblyComponentResourceManager(typeof(YesNoDialog)); this.panel1 = new System.Windows.Forms.Panel(); - this.DescriptionLabel = new TransparentTextBox(); + this.DescriptionLabel = new CKAN.GUI.TransparentTextBox(); + this.BottomButtonPanel = new CKAN.GUI.LeftRightRowPanel(); this.SuppressCheckbox = new System.Windows.Forms.CheckBox(); this.YesButton = new System.Windows.Forms.Button(); this.NoButton = new System.Windows.Forms.Button(); this.panel1.SuspendLayout(); + this.BottomButtonPanel.SuspendLayout(); this.SuspendLayout(); // // panel1 @@ -64,6 +66,14 @@ private void InitializeComponent() this.DescriptionLabel.BorderStyle = System.Windows.Forms.BorderStyle.None; resources.ApplyResources(this.DescriptionLabel, "DescriptionLabel"); // + // BottomButtonPanel + // + this.BottomButtonPanel.LeftControls.Add(this.SuppressCheckbox); + this.BottomButtonPanel.RightControls.Add(this.NoButton); + this.BottomButtonPanel.RightControls.Add(this.YesButton); + this.BottomButtonPanel.Dock = System.Windows.Forms.DockStyle.Bottom; + this.BottomButtonPanel.Name = "BottomButtonPanel"; + // // SuppressCheckbox // this.SuppressCheckbox.AutoSize = false; @@ -80,6 +90,8 @@ private void InitializeComponent() // this.YesButton.Anchor = ((System.Windows.Forms.AnchorStyles)(System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)); + this.YesButton.AutoSize = true; + this.YesButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly; this.YesButton.DialogResult = System.Windows.Forms.DialogResult.Yes; this.YesButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.YesButton.Location = new System.Drawing.Point(250, 92); @@ -93,6 +105,8 @@ private void InitializeComponent() // this.NoButton.Anchor = ((System.Windows.Forms.AnchorStyles)(System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)); + this.NoButton.AutoSize = true; + this.NoButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly; this.NoButton.DialogResult = System.Windows.Forms.DialogResult.No; this.NoButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.NoButton.Location = new System.Drawing.Point(331, 92); @@ -107,9 +121,7 @@ private void InitializeComponent() this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(418, 127); - this.Controls.Add(this.SuppressCheckbox); - this.Controls.Add(this.NoButton); - this.Controls.Add(this.YesButton); + this.Controls.Add(this.BottomButtonPanel); this.Controls.Add(this.panel1); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow; this.Icon = EmbeddedImages.AppIcon; @@ -117,14 +129,17 @@ private void InitializeComponent() this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; resources.ApplyResources(this, "$this"); this.panel1.ResumeLayout(false); - this.ResumeLayout(false); + this.BottomButtonPanel.ResumeLayout(false); + this.BottomButtonPanel.PerformLayout(); + this.ResumeLayout(false); this.PerformLayout(); } #endregion private System.Windows.Forms.Panel panel1; - private TransparentTextBox DescriptionLabel; + private CKAN.GUI.TransparentTextBox DescriptionLabel; + private CKAN.GUI.LeftRightRowPanel BottomButtonPanel; private System.Windows.Forms.CheckBox SuppressCheckbox; private System.Windows.Forms.Button YesButton; private System.Windows.Forms.Button NoButton; diff --git a/GUI/Main/Main.cs b/GUI/Main/Main.cs index f64e5c2aad..2c427b72f8 100644 --- a/GUI/Main/Main.cs +++ b/GUI/Main/Main.cs @@ -36,6 +36,7 @@ public partial class Main : Form, IMessageFilter public readonly GameInstanceManager Manager; public GameInstance CurrentInstance => Manager.CurrentInstance; private readonly RepositoryDataManager repoData; + private readonly AutoUpdate updater = new AutoUpdate(); // Stuff we set when the game instance changes public GUIConfiguration configuration; @@ -292,14 +293,13 @@ protected override void OnShown(EventArgs e) { HideWaitDialog(); CheckTrayState(); - bool autoUpdating = CheckForCKANUpdate(); Console.CancelKeyPress += (sender2, evt2) => { // Hide tray icon on Ctrl-C minimizeNotifyIcon.Visible = false; }; InitRefreshTimer(); - CurrentInstanceUpdated(!autoUpdating); + CurrentInstanceUpdated(); } else { @@ -334,7 +334,7 @@ private void manageGameInstancesMenuItem_Click(object sender, EventArgs e) try { ManageMods.ModGrid.ClearSelection(); - CurrentInstanceUpdated(true); + CurrentInstanceUpdated(); done = true; } catch (RegistryInUseKraken kraken) @@ -351,7 +351,7 @@ private void manageGameInstancesMenuItem_Click(object sender, EventArgs e) { // Couldn't get the lock, revert to previous instance Manager.CurrentInstance = old_instance; - CurrentInstanceUpdated(false); + CurrentInstanceUpdated(); done = true; } } @@ -375,7 +375,7 @@ private void UpdateStatusBar() /// React to switching to a new game instance /// /// true if a repo update is allowed if needed (e.g. on initial load), false otherwise - private void CurrentInstanceUpdated(bool allowRepoUpdate) + private void CurrentInstanceUpdated() { // This will throw RegistryInUseKraken if locked by another process var regMgr = RegistryManager.Instance(CurrentInstance, repoData); @@ -407,16 +407,11 @@ private void CurrentInstanceUpdated(bool allowRepoUpdate) configuration?.Save(); configuration = GUIConfigForInstance(CurrentInstance); - if (!configuration.CheckForUpdatesOnLaunchNoNag && AutoUpdate.CanUpdate) - { - log.Debug("Asking user if they wish for auto-updates"); - if (new AskUserForAutoUpdatesDialog().ShowDialog(this) == DialogResult.OK) - { - configuration.CheckForUpdatesOnLaunch = true; - } + AutoUpdatePrompts(ServiceLocator.Container + .Resolve(), + configuration); - configuration.CheckForUpdatesOnLaunchNoNag = true; - } + bool autoUpdating = CheckForCKANUpdate(); var pluginsPath = Path.Combine(CurrentInstance.CkanDir(), "Plugins"); if (!Directory.Exists(pluginsPath)) @@ -429,7 +424,7 @@ private void CurrentInstanceUpdated(bool allowRepoUpdate) CurrentInstance.game.RebuildSubdirectories(CurrentInstance.GameDir()); bool repoUpdateNeeded = configuration.RefreshOnStartup; - if (allowRepoUpdate) + if (!autoUpdating) { // If not allowing, don't do anything if (repoUpdateNeeded) @@ -625,7 +620,11 @@ private void CKANSettingsToolStripMenuItem_Click(object sender, EventArgs e) { // Flipping enabled here hides the main form itself. Enabled = false; - var dialog = new SettingsDialog(RegistryManager.Instance(CurrentInstance, repoData), currentUser); + var dialog = new SettingsDialog(ServiceLocator.Container.Resolve(), + configuration, + RegistryManager.Instance(CurrentInstance, repoData), + updater, + currentUser); dialog.ShowDialog(this); Enabled = true; if (dialog.RepositoryAdded) diff --git a/GUI/Main/MainAutoUpdate.cs b/GUI/Main/MainAutoUpdate.cs index 64f6a8984d..3892c870f3 100644 --- a/GUI/Main/MainAutoUpdate.cs +++ b/GUI/Main/MainAutoUpdate.cs @@ -1,7 +1,11 @@ using System; +using System.IO; using System.ComponentModel; using System.Windows.Forms; +using Autofac; + +using CKAN.Configuration; using CKAN.Versioning; // Don't warn if we use our own obsolete properties @@ -11,6 +15,28 @@ namespace CKAN.GUI { public partial class Main { + private void AutoUpdatePrompts(IConfiguration coreConfig, + GUIConfiguration guiConfig) + { + if (!guiConfig.CheckForUpdatesOnLaunchNoNag && AutoUpdate.CanUpdate) + { + log.Debug("Asking user if they wish for auto-updates"); + if (new AskUserForAutoUpdatesDialog().ShowDialog(this) == DialogResult.OK) + { + guiConfig.CheckForUpdatesOnLaunch = true; + } + guiConfig.CheckForUpdatesOnLaunchNoNag = true; + } + + if (!guiConfig.DevBuildsNoNag && guiConfig.CheckForUpdatesOnLaunch) + { + coreConfig.DevBuilds = YesNoDialog(Properties.Resources.MainDevBuildsPrompt, + Properties.Resources.MainDevBuildsYes, + Properties.Resources.MainDevBuildsNo); + guiConfig.DevBuildsNoNag = true; + } + } + /// /// Look for a CKAN update and start installing it if found. /// Note that this will happen on a background thread! @@ -25,15 +51,16 @@ private bool CheckForCKANUpdate() try { log.Info("Making auto-update call"); - AutoUpdate.Instance.FetchLatestReleaseInfo(); - var latest_version = AutoUpdate.Instance.latestUpdate.Version; - var current_version = new ModuleVersion(Meta.GetVersion()); + var mainConfig = ServiceLocator.Container.Resolve(); + var update = updater.GetUpdate(mainConfig.DevBuilds); + var latestVersion = update.Version; + var currentVersion = new ModuleVersion(Meta.GetVersion()); - if (AutoUpdate.Instance.IsFetched() && latest_version.IsGreaterThan(current_version)) + if (latestVersion.IsGreaterThan(currentVersion)) { log.Debug("Found higher ckan version"); - var release_notes = AutoUpdate.Instance.latestUpdate.ReleaseNotes; - var dialog = new NewUpdateDialog(latest_version.ToString(), release_notes); + var releaseNotes = update.ReleaseNotes; + var dialog = new NewUpdateDialog(latestVersion.ToString(), releaseNotes); if (dialog.ShowDialog(this) == DialogResult.OK) { UpdateCKAN(); @@ -43,7 +70,8 @@ private bool CheckForCKANUpdate() } catch (Exception exception) { - currentUser.RaiseError(Properties.Resources.MainAutoUpdateFailed, exception.Message); + currentUser.RaiseError(Properties.Resources.MainAutoUpdateFailed, + exception.Message); log.Error("Error in auto-update", exception); } } @@ -59,15 +87,16 @@ public void UpdateCKAN() ShowWaitDialog(); DisableMainWindow(); tabController.RenameTab("WaitTabPage", Properties.Resources.MainUpgradingWaitTitle); - Wait.SetDescription(string.Format(Properties.Resources.MainUpgradingTo, AutoUpdate.Instance.latestUpdate.Version)); + var mainConfig = ServiceLocator.Container.Resolve(); + var update = updater.GetUpdate(mainConfig.DevBuilds); + Wait.SetDescription(string.Format(Properties.Resources.MainUpgradingTo, + update.Version)); log.Info("Start ckan update"); - Wait.StartWaiting( - (sender, args) => AutoUpdate.Instance.StartUpdateProcess(true, currentUser), - UpdateReady, - false, - null - ); + Wait.StartWaiting((sender, args) => updater.StartUpdateProcess(true, mainConfig.DevBuilds, currentUser), + UpdateReady, + false, + null); } private void UpdateReady(object sender, RunWorkerCompletedEventArgs e) diff --git a/GUI/Main/MainInstall.cs b/GUI/Main/MainInstall.cs index 796fa35d89..f239f0f5d4 100644 --- a/GUI/Main/MainInstall.cs +++ b/GUI/Main/MainInstall.cs @@ -429,7 +429,10 @@ private void PostInstallMods(object sender, RunWorkerCompletedEventArgs e) } // Now pretend they clicked the menu option for the settings Enabled = false; - new SettingsDialog(RegistryManager.Instance(CurrentInstance, repoData), + new SettingsDialog(ServiceLocator.Container.Resolve(), + configuration, + RegistryManager.Instance(CurrentInstance, repoData), + updater, currentUser) .ShowDialog(this); Enabled = true; diff --git a/GUI/Main/MainTrayIcon.cs b/GUI/Main/MainTrayIcon.cs index bfe2871b85..cc7d08b730 100644 --- a/GUI/Main/MainTrayIcon.cs +++ b/GUI/Main/MainTrayIcon.cs @@ -117,7 +117,12 @@ private void openCKANToolStripMenuItem_Click(object sender, EventArgs e) private void cKANSettingsToolStripMenuItem1_Click(object sender, EventArgs e) { OpenWindow(); - new SettingsDialog(RegistryManager.Instance(CurrentInstance, repoData), currentUser).ShowDialog(this); + new SettingsDialog(ServiceLocator.Container.Resolve(), + configuration, + RegistryManager.Instance(CurrentInstance, repoData), + updater, + currentUser) + .ShowDialog(this); } private void minimizedContextMenuStrip_Opening(object sender, CancelEventArgs e) diff --git a/GUI/Model/GUIConfiguration.cs b/GUI/Model/GUIConfiguration.cs index 1fd31c4233..394024d570 100644 --- a/GUI/Model/GUIConfiguration.cs +++ b/GUI/Model/GUIConfiguration.cs @@ -18,6 +18,7 @@ public class GUIConfiguration public bool CheckForUpdatesOnLaunch = false; public bool CheckForUpdatesOnLaunchNoNag = false; + public bool DevBuildsNoNag = false; public bool EnableTrayIcon = false; public bool MinimizeToTray = false; diff --git a/GUI/Properties/Resources.resx b/GUI/Properties/Resources.resx index 8881e2e2a7..3851527565 100644 --- a/GUI/Properties/Resources.resx +++ b/GUI/Properties/Resources.resx @@ -144,6 +144,13 @@ Try to move {2} out of {3} and restart CKAN. Unknown Method can not be called unless IsCKAN {0} (using mod version {1}) + CKAN is configured to check for updates at launch. In addition to the regular stable releases, dev builds are also available. Using dev builds will give you early access to new features and bugfixes, but with a higher chance of bugs. This option is provided so we can catch and fix such bugs before they make it into a release. + +Which build would you like to use? + +(This setting can be changed later in the CKAN settings.) + Use dev builds + Use stable releases only Error in auto-update: {0} There are conflicts. Really quit? {0} Quit diff --git a/Tests/Core/Configuration/FakeConfiguration.cs b/Tests/Core/Configuration/FakeConfiguration.cs index aab6748509..48208628f4 100644 --- a/Tests/Core/Configuration/FakeConfiguration.cs +++ b/Tests/Core/Configuration/FakeConfiguration.cs @@ -151,6 +151,8 @@ public string Language public string[] PreferredHosts { get; set; } = new string[] { }; + public bool DevBuilds { get; set; } + public void Dispose() { Directory.Delete(DownloadCacheDir, true); diff --git a/Tests/Core/Net/AutoUpdateTests.cs b/Tests/Core/Net/AutoUpdateTests.cs index faf3e00c2c..f2d3abd077 100644 --- a/Tests/Core/Net/AutoUpdateTests.cs +++ b/Tests/Core/Net/AutoUpdateTests.cs @@ -1,7 +1,16 @@ +using System; +using System.IO; +using System.Linq; using System.Net; +using System.Collections.Generic; + using NUnit.Framework; +using Newtonsoft.Json; + using CKAN; using CKAN.Versioning; +using Tests.Data; +using Tests.Core.Configuration; namespace Tests.Core.AutoUpdateTests { @@ -9,23 +18,24 @@ namespace Tests.Core.AutoUpdateTests public class AutoUpdateTests { [Test] + [TestCase(true)] + [TestCase(false)] [Category("Online")] // This could fail if run during a release, so it's marked as Flaky. [Category("FlakyNetwork")] - public void FetchLatestReleaseInfo() + public void GetUpdate_DevBuildOrStable_Works(bool devBuild) { // Force-allow TLS 1.2 for HTTPS URLs, because GitHub requires it. // This is on by default in .NET 4.6, but not in 4.5. ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; - var updater = AutoUpdate.Instance; + var updater = new AutoUpdate(); + var update = updater.GetUpdate(devBuild); // Is is a *really* basic test to just make sure we get release info // if we ask for it. - updater.FetchLatestReleaseInfo(); - Assert.IsNotNull(updater.latestUpdate.ReleaseNotes); - Assert.IsNotNull(updater.latestUpdate.Version); - Assert.IsTrue(updater.IsFetched()); + Assert.IsNotNull(update.ReleaseNotes); + Assert.IsNotNull(update.Version); } [Test] @@ -34,15 +44,13 @@ public void FetchLatestReleaseInfo() [TestCase("aaa\r\n---\r\nbbb\r\n---\r\nccc", "bbb\r\n---\r\nccc", "Multi release notes markers")] public void ExtractReleaseNotes(string body, string expected, string comment) { - Assert.AreEqual( - expected, - CkanUpdate.ExtractReleaseNotes(body), - comment - ); + Assert.AreEqual(expected, + GitHubReleaseCkanUpdate.ExtractReleaseNotes(body), + comment); } [Test] - public void CkanUpdate_NormalUpdate_ParsedCorrectly() + public void GitHubReleaseCkanUpdate_NormalUpdate_ParsedCorrectly() { // Arrange const string releaseJSON = @"{ @@ -65,15 +73,39 @@ public void CkanUpdate_NormalUpdate_ParsedCorrectly() }"; // Act - CkanUpdate cu = new CkanUpdate(releaseJSON); + var relInfo = JsonConvert.DeserializeObject(releaseJSON); + var upd = new GitHubReleaseCkanUpdate(relInfo); // Assert - Assert.AreEqual(new CkanModuleVersion("v1.25.0", "Wallops"), cu.Version); - Assert.AreEqual("https://github.com/KSP-CKAN/CKAN/releases/download/v1.25.0/ckan.exe", cu.ReleaseDownload.ToString()); - Assert.AreEqual(6651392, cu.ReleaseSize); - Assert.AreEqual("https://github.com/KSP-CKAN/CKAN/releases/download/v1.25.0/AutoUpdater.exe", cu.UpdaterDownload.ToString()); - Assert.AreEqual(414208, cu.UpdaterSize); - Assert.AreEqual("Greatest release notes of all time", cu.ReleaseNotes); + Assert.AreEqual("v1.25.0", relInfo.tag_name); + Assert.AreEqual("Wallops", relInfo.name); + Assert.AreEqual("https://github.com/KSP-CKAN/CKAN/releases/download/v1.25.0/ckan.exe", + upd.ReleaseDownload.ToString()); + Assert.AreEqual(6651392, upd.ReleaseSize); + Assert.AreEqual("https://github.com/KSP-CKAN/CKAN/releases/download/v1.25.0/AutoUpdater.exe", + upd.UpdaterDownload.ToString()); + Assert.AreEqual(414208, upd.UpdaterSize); + Assert.AreEqual("Greatest release notes of all time", upd.ReleaseNotes); } + + [Test] + public void S3BuildCkanUpdate_Constructor_ParsedCorrectly() + { + // Arrange / Act + var upd = new S3BuildCkanUpdate( + JsonConvert.DeserializeObject( + File.ReadAllText(TestData.DataDir("version.json")))); + + // Assert + Assert.AreEqual("v1.34.5.24015 aka dev", + upd.Version.ToString()); + Assert.AreEqual("### Internal\n\n- [Policy] Fix #3518 rewrite de-indexing policy (#3993 by: JonnyOThan; reviewed: HebaruSan)", + upd.ReleaseNotes); + Assert.AreEqual("https://ksp-ckan.s3-us-west-2.amazonaws.com/ckan.exe", + upd.ReleaseDownload.ToString()); + Assert.AreEqual("https://ksp-ckan.s3-us-west-2.amazonaws.com/AutoUpdater.exe", + upd.UpdaterDownload.ToString()); + } + } } diff --git a/Tests/Data/version.json b/Tests/Data/version.json new file mode 100644 index 0000000000..23892ee999 --- /dev/null +++ b/Tests/Data/version.json @@ -0,0 +1,4 @@ +{ + "version": "v1.34.5.24015", + "changelog": "### Internal\n\n- [Policy] Fix #3518 rewrite de-indexing policy (#3993 by: JonnyOThan; reviewed: HebaruSan)" +} diff --git a/bin/version_info.py b/bin/version_info.py new file mode 100644 index 0000000000..25316f9d13 --- /dev/null +++ b/bin/version_info.py @@ -0,0 +1,32 @@ +""" +Generate a JSON file containing the version and the latest changelog +""" + +from pathlib import Path +import re +import json +from git import Repo + +# Get the year and day of year from the latest commit +repo = Repo('.', search_parent_directories=True) +yyddd = repo.head.commit.committed_datetime.strftime(r'%g%j') +version = '' +changes = '' + +with open(Path(repo.working_dir) / 'CHANGELOG.md', + 'rt', encoding='utf-8') as changelog: + header_pattern = re.compile(r'^\s*\#\#\s+(v[0-9.]+)') + # First header contains the current version + for line in changelog: + match = header_pattern.match(line) + if match: + version = match.group(1) + break + # Second header marks the end of the current changelog + for line in changelog: + if header_pattern.match(line): + break + changes += line + +print(json.dumps({'version': f'{version}.{yyddd}', + 'changelog': changes.strip('\n')}, indent=4)) diff --git a/build.cake b/build.cake index 1c349f709f..68f20ebc44 100644 --- a/build.cake +++ b/build.cake @@ -1,6 +1,7 @@ #addin "nuget:?package=Cake.SemVer&version=4.0.0" #addin "nuget:?package=semver&version=2.3.0" #addin "nuget:?package=Cake.Docker&version=0.11.1" +#addin nuget:?package=Cake.Git&version=3.0.0 #tool "nuget:?package=ILRepack&version=2.0.18" #tool "nuget:?package=NUnit.ConsoleRunner&version=3.16.3" @@ -258,18 +259,17 @@ Task("Generate-GlobalAssemblyVersionInfo") .Description("Intermediate - Calculate the version strings for the assembly.") .Does(() => { - var version = GetVersion(); - var versionStr2 = string.Format("{0}.{1}", version.Major, version.Minor); - var versionStr3 = string.Format("{0}.{1}.{2}", version.Major, version.Minor, version.Patch); - var metaDirectory = buildDirectory.Combine("meta"); - CreateDirectory(metaDirectory); + var version = GetVersion(); + CreateAssemblyInfo(metaDirectory.CombineWithFilePath("GlobalAssemblyVersionInfo.cs"), new AssemblyInfoSettings { - Version = versionStr2, - FileVersion = versionStr3, + Version = string.Format("{0}.{1}", version.Major, version.Minor), + FileVersion = string.Format("{0}.{1}.{2}.{3}", + version.Major, version.Minor, version.Patch, + version.Metadata), InformationalVersion = version.ToString() }); }); @@ -531,39 +531,17 @@ private Semver.SemVersion GetVersion() if (DirectoryExists(rootDirectory.Combine(".git"))) { - var hash = GetGitCommitHash(); - - version = CreateSemVer( - version.Major, - version.Minor, - version.Patch, - version.Prerelease, - hash == null ? null : hash.Substring(0, 12) - ); + var commitDate = GitLogTip(rootDirectory).Committer.When; + version = CreateSemVer(version.Major, + version.Minor, + version.Patch, + version.Prerelease, + commitDate.ToString("yy") + commitDate.DayOfYear.ToString("000")); } return version; } -private string GetGitCommitHash() -{ - IEnumerable output; - try - { - var exitCode = StartProcess( - "git", - new ProcessSettings { Arguments = "rev-parse HEAD", RedirectStandardOutput = true }, - out output - ); - - return exitCode == 0 ? output.FirstOrDefault() : null; - } - catch(Exception) - { - return null; - } -} - private IEnumerable RunExecutable(FilePath executable, string arguments) { IEnumerable output;