diff --git a/Giants.DataContract/Contracts/V1/VersionInfo.cs b/Giants.DataContract/Contracts/V1/VersionInfo.cs index 983eca6..4e606eb 100644 --- a/Giants.DataContract/Contracts/V1/VersionInfo.cs +++ b/Giants.DataContract/Contracts/V1/VersionInfo.cs @@ -13,5 +13,8 @@ [Required] public Uri InstallerUri { get; set; } + + [Required] + public string BranchName { get; set;} } } diff --git a/Giants.DataContract/Contracts/V1/VersionInfoUpdate.cs b/Giants.DataContract/Contracts/V1/VersionInfoUpdate.cs index 2a6c504..d35a831 100644 --- a/Giants.DataContract/Contracts/V1/VersionInfoUpdate.cs +++ b/Giants.DataContract/Contracts/V1/VersionInfoUpdate.cs @@ -2,5 +2,5 @@ namespace Giants.DataContract.Contracts.V1 { - public record VersionInfoUpdate(string AppName, AppVersion AppVersion, string FileName); + public record VersionInfoUpdate(string AppName, AppVersion AppVersion, string FileName, string BranchName, bool ForceUpdate); } diff --git a/Giants.Launcher/Config.cs b/Giants.Launcher/Config.cs index 4a28690..4185b2f 100644 --- a/Giants.Launcher/Config.cs +++ b/Giants.Launcher/Config.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using System.Globalization; using System.IO; using System.Windows.Forms; using Newtonsoft.Json; @@ -34,14 +35,17 @@ } } - public string GetString(string section, string key) + public bool TryGetObject(string section, string key, object defaultValue, out object value) { + value = defaultValue; + if (this.userConfig.ContainsKey(section)) { dynamic sectionObject = this.userConfig[section]; if (sectionObject != null && sectionObject.ContainsKey(key)) { - return (string)sectionObject[key]; + value = sectionObject[key]; + return true; } } @@ -50,14 +54,38 @@ dynamic sectionObject = this.defaultConfig[section]; if (sectionObject != null && sectionObject.ContainsKey(key)) { - return (string)sectionObject[key]; + value = sectionObject[key]; + return true; } } - return string.Empty; + return false; } - // TODO: other accessors unimplemented as we only need master server host name for now + public bool TryGetString(string section, string key, string defaultValue, out string value) + { + value = defaultValue; + + if (this.TryGetObject(section, key, defaultValue, out object objValue)) + { + value = objValue.ToString(); + return true; + } + + return false; + } + + public bool TryGetBool(string section, string key, bool defaultValue, out bool value) + { + value = defaultValue; + + if (this.TryGetObject(section, key, defaultValue, out object objValue)) + { + return bool.TryParse(objValue.ToString(), out value); + } + + return false; + } private static IDictionary ReadConfig(string filePath) { diff --git a/Giants.Launcher/ConfigDefaults.cs b/Giants.Launcher/ConfigDefaults.cs new file mode 100644 index 0000000..0144b3e --- /dev/null +++ b/Giants.Launcher/ConfigDefaults.cs @@ -0,0 +1,8 @@ +namespace Giants.Launcher +{ + public static class ConfigDefaults + { + public const string BranchNameDefault = "Release"; + public const string MasterServerHostNameDefault = "https://giants.azurewebsites.net/"; + } +} diff --git a/Giants.Launcher/ConfigKeys.cs b/Giants.Launcher/ConfigKeys.cs index 576403f..ff7bf59 100644 --- a/Giants.Launcher/ConfigKeys.cs +++ b/Giants.Launcher/ConfigKeys.cs @@ -2,6 +2,11 @@ { public static class ConfigKeys { + // Update + public const string BranchName = "branchName"; + public const string EnableBranchSelection = "enableBranchSelection"; + + // Network public const string MasterServerHostName = "masterServerHostName"; public const string BannedPlayers = "bannedPlayers"; } diff --git a/Giants.Launcher/ConfigSections.cs b/Giants.Launcher/ConfigSections.cs index 9092c41..f9a793c 100644 --- a/Giants.Launcher/ConfigSections.cs +++ b/Giants.Launcher/ConfigSections.cs @@ -2,6 +2,7 @@ { public static class ConfigSections { + public const string Update = "update"; public const string Network = "network"; } } diff --git a/Giants.Launcher/Forms/LauncherForm.cs b/Giants.Launcher/Forms/LauncherForm.cs index 95057dd..b4fcbcd 100644 --- a/Giants.Launcher/Forms/LauncherForm.cs +++ b/Giants.Launcher/Forms/LauncherForm.cs @@ -21,13 +21,15 @@ namespace Giants.Launcher private const string RegistryValue = "DestDir"; private readonly HttpClient httpClient; - private readonly VersionClient versionHttpClient; + private readonly BranchesClient branchHttpClient; + private readonly VersionClient versionHttpClient; private readonly CommunityClient communityHttpClient; private string commandLine; private string gamePath = null; private Updater updater; - private Config config; + private readonly Config config; + private string branchName; private string communityAppUri; public LauncherForm() @@ -43,13 +45,21 @@ namespace Giants.Launcher this.config = new Config(); this.config.Read(); - string baseUrl = this.config.GetString(ConfigSections.Network, ConfigKeys.MasterServerHostName); + this.config.TryGetString(ConfigSections.Network, ConfigKeys.MasterServerHostName, ConfigDefaults.MasterServerHostNameDefault, out string baseUrl); + this.config.TryGetString(ConfigSections.Update, ConfigKeys.BranchName, defaultValue: ConfigDefaults.BranchNameDefault, out string branchName); - this.httpClient = new HttpClient( + this.branchName = branchName; + + this.httpClient = new HttpClient( new HttpClientHandler() { UseProxy = false }); + this.branchHttpClient = new BranchesClient(this.httpClient) + { + BaseUrl = baseUrl, + + }; this.versionHttpClient = new VersionClient(this.httpClient) { BaseUrl = baseUrl @@ -96,7 +106,13 @@ namespace Giants.Launcher private void btnOptions_Click(object sender, EventArgs e) { - OptionsForm form = new OptionsForm(Resources.AppName + " Options", this.gamePath); + OptionsForm form = new OptionsForm( + title: Resources.AppName + " Options", + gamePath: this.gamePath, + appName: ApplicationNames.Giants, + branchName: this.branchName, + config: this.config, + branchesClient: this.branchHttpClient); form.StartPosition = FormStartPosition.CenterParent; form.ShowDialog(); @@ -172,23 +188,13 @@ namespace Giants.Launcher updateCompletedCallback: this.LauncherForm_DownloadCompletedCallback, updateProgressCallback: this.LauncherForm_DownloadProgressCallback); - Task gameVersionInfo = this.GetVersionInfo( + VersionInfo gameVersionInfo = await this.GetVersionInfo( GetApplicationName(ApplicationType.Game)); - Task launcherVersionInfo = this.GetVersionInfo( - GetApplicationName(ApplicationType.Launcher)); - await Task.WhenAll(gameVersionInfo, launcherVersionInfo); - - if (this.updater.IsUpdateRequired(ApplicationType.Game, gameVersionInfo.Result)) + if (this.updater.IsUpdateRequired(ApplicationType.Game, gameVersionInfo)) { this.btnPlay.Enabled = false; - await this.updater.UpdateApplication(ApplicationType.Game, gameVersionInfo.Result); - } - - if (this.updater.IsUpdateRequired(ApplicationType.Launcher, launcherVersionInfo.Result)) - { - this.btnPlay.Enabled = false; - await this.updater.UpdateApplication(ApplicationType.Launcher, launcherVersionInfo.Result); + await this.updater.UpdateApplication(ApplicationType.Game, gameVersionInfo); } } @@ -197,17 +203,7 @@ namespace Giants.Launcher switch (applicationType) { case ApplicationType.Game: -#if BETA - return ApplicationNames.GiantsBeta; -#else return ApplicationNames.Giants; -#endif - case ApplicationType.Launcher: -#if BETA - return ApplicationNames.GiantsLauncherBeta; -#else - return ApplicationNames.GiantsLauncher; -#endif } throw new ArgumentOutOfRangeException(); @@ -218,7 +214,7 @@ namespace Giants.Launcher VersionInfo versionInfo; try { - versionInfo = await this.versionHttpClient.GetVersionInfoAsync(appName); + versionInfo = await this.versionHttpClient.GetVersionInfoAsync(appName, this.branchName); return versionInfo; } catch (ApiException ex) @@ -233,7 +229,27 @@ namespace Giants.Launcher } } - private void LauncherForm_MouseDown(object sender, MouseEventArgs e) + private async Task GetBranches(string appName) + { + VersionInfo versionInfo; + try + { + versionInfo = await this.versionHttpClient.GetVersionInfoAsync(appName, this.branchName); + return versionInfo; + } + catch (ApiException ex) + { + MessageBox.Show($"Exception retrieving branch information: {ex.StatusCode}"); + return null; + } + catch (Exception ex) + { + MessageBox.Show($"Exception retrieving branch information: {ex.Message}"); + return null; + } + } + + private void LauncherForm_MouseDown(object sender, MouseEventArgs e) { // Force window to be draggable even though we have no menu bar if (e.Button == MouseButtons.Left) diff --git a/Giants.Launcher/Forms/OptionsForm.Designer.cs b/Giants.Launcher/Forms/OptionsForm.Designer.cs index f330152..207cf83 100644 --- a/Giants.Launcher/Forms/OptionsForm.Designer.cs +++ b/Giants.Launcher/Forms/OptionsForm.Designer.cs @@ -44,18 +44,22 @@ this.btnResetDefaults = new System.Windows.Forms.Button(); this.groupBox3 = new System.Windows.Forms.GroupBox(); this.chkUpdates = new System.Windows.Forms.CheckBox(); + this.cmbBranch = new System.Windows.Forms.ComboBox(); + this.BranchGroupBox = new System.Windows.Forms.GroupBox(); this.groupBox1.SuspendLayout(); this.groupBox2.SuspendLayout(); this.groupBox3.SuspendLayout(); + this.BranchGroupBox.SuspendLayout(); this.SuspendLayout(); // // cmbRenderer // this.cmbRenderer.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.cmbRenderer.FormattingEnabled = true; - this.cmbRenderer.Location = new System.Drawing.Point(124, 19); + this.cmbRenderer.Location = new System.Drawing.Point(165, 23); + this.cmbRenderer.Margin = new System.Windows.Forms.Padding(4); this.cmbRenderer.Name = "cmbRenderer"; - this.cmbRenderer.Size = new System.Drawing.Size(252, 21); + this.cmbRenderer.Size = new System.Drawing.Size(335, 24); this.cmbRenderer.TabIndex = 0; this.cmbRenderer.SelectedIndexChanged += new System.EventHandler(this.cmbRenderer_SelectedIndexChanged); // @@ -63,18 +67,20 @@ // this.cmbResolution.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.cmbResolution.FormattingEnabled = true; - this.cmbResolution.Location = new System.Drawing.Point(124, 50); + this.cmbResolution.Location = new System.Drawing.Point(165, 62); + this.cmbResolution.Margin = new System.Windows.Forms.Padding(4); this.cmbResolution.Name = "cmbResolution"; - this.cmbResolution.Size = new System.Drawing.Size(252, 21); + this.cmbResolution.Size = new System.Drawing.Size(335, 24); this.cmbResolution.TabIndex = 1; // // cmbAntialiasing // this.cmbAntialiasing.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.cmbAntialiasing.FormattingEnabled = true; - this.cmbAntialiasing.Location = new System.Drawing.Point(124, 81); + this.cmbAntialiasing.Location = new System.Drawing.Point(165, 100); + this.cmbAntialiasing.Margin = new System.Windows.Forms.Padding(4); this.cmbAntialiasing.Name = "cmbAntialiasing"; - this.cmbAntialiasing.Size = new System.Drawing.Size(252, 21); + this.cmbAntialiasing.Size = new System.Drawing.Size(335, 24); this.cmbAntialiasing.TabIndex = 2; // // groupBox1 @@ -85,9 +91,11 @@ this.groupBox1.Controls.Add(this.cmbRenderer); this.groupBox1.Controls.Add(this.cmbResolution); this.groupBox1.Controls.Add(this.cmbAntialiasing); - this.groupBox1.Location = new System.Drawing.Point(12, 12); + this.groupBox1.Location = new System.Drawing.Point(16, 15); + this.groupBox1.Margin = new System.Windows.Forms.Padding(4); this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(382, 116); + this.groupBox1.Padding = new System.Windows.Forms.Padding(4); + this.groupBox1.Size = new System.Drawing.Size(509, 143); this.groupBox1.TabIndex = 4; this.groupBox1.TabStop = false; this.groupBox1.Text = "Graphics Settings"; @@ -95,27 +103,30 @@ // label3 // this.label3.AutoSize = true; - this.label3.Location = new System.Drawing.Point(52, 84); + this.label3.Location = new System.Drawing.Point(69, 103); + this.label3.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); this.label3.Name = "label3"; - this.label3.Size = new System.Drawing.Size(66, 13); + this.label3.Size = new System.Drawing.Size(83, 16); this.label3.TabIndex = 6; this.label3.Text = "Anti-aliasing:"; // // label2 // this.label2.AutoSize = true; - this.label2.Location = new System.Drawing.Point(58, 53); + this.label2.Location = new System.Drawing.Point(77, 65); + this.label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(60, 13); + this.label2.Size = new System.Drawing.Size(74, 16); this.label2.TabIndex = 5; this.label2.Text = "Resolution:"; // // label1 // this.label1.AutoSize = true; - this.label1.Location = new System.Drawing.Point(64, 22); + this.label1.Location = new System.Drawing.Point(85, 27); + this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(54, 13); + this.label1.Size = new System.Drawing.Size(67, 16); this.label1.TabIndex = 4; this.label1.Text = "Renderer:"; // @@ -124,9 +135,11 @@ this.groupBox2.Controls.Add(this.cmbMode); this.groupBox2.Controls.Add(this.chkTripleBuffering); this.groupBox2.Controls.Add(this.chkVSync); - this.groupBox2.Location = new System.Drawing.Point(12, 138); + this.groupBox2.Location = new System.Drawing.Point(16, 170); + this.groupBox2.Margin = new System.Windows.Forms.Padding(4); this.groupBox2.Name = "groupBox2"; - this.groupBox2.Size = new System.Drawing.Size(118, 93); + this.groupBox2.Padding = new System.Windows.Forms.Padding(4); + this.groupBox2.Size = new System.Drawing.Size(157, 114); this.groupBox2.TabIndex = 5; this.groupBox2.TabStop = false; this.groupBox2.Text = "Mode"; @@ -139,17 +152,19 @@ "Fullscreen", "Windowed", "Borderless"}); - this.cmbMode.Location = new System.Drawing.Point(12, 19); + this.cmbMode.Location = new System.Drawing.Point(16, 23); + this.cmbMode.Margin = new System.Windows.Forms.Padding(4); this.cmbMode.Name = "cmbMode"; - this.cmbMode.Size = new System.Drawing.Size(88, 21); + this.cmbMode.Size = new System.Drawing.Size(116, 24); this.cmbMode.TabIndex = 3; // // chkTripleBuffering // this.chkTripleBuffering.AutoSize = true; - this.chkTripleBuffering.Location = new System.Drawing.Point(12, 64); + this.chkTripleBuffering.Location = new System.Drawing.Point(16, 79); + this.chkTripleBuffering.Margin = new System.Windows.Forms.Padding(4); this.chkTripleBuffering.Name = "chkTripleBuffering"; - this.chkTripleBuffering.Size = new System.Drawing.Size(97, 17); + this.chkTripleBuffering.Size = new System.Drawing.Size(119, 20); this.chkTripleBuffering.TabIndex = 2; this.chkTripleBuffering.Text = "Triple Buffering"; this.chkTripleBuffering.UseVisualStyleBackColor = true; @@ -157,18 +172,20 @@ // chkVSync // this.chkVSync.AutoSize = true; - this.chkVSync.Location = new System.Drawing.Point(12, 43); + this.chkVSync.Location = new System.Drawing.Point(16, 53); + this.chkVSync.Margin = new System.Windows.Forms.Padding(4); this.chkVSync.Name = "chkVSync"; - this.chkVSync.Size = new System.Drawing.Size(88, 17); + this.chkVSync.Size = new System.Drawing.Size(107, 20); this.chkVSync.TabIndex = 1; this.chkVSync.Text = "Vertical Sync"; this.chkVSync.UseVisualStyleBackColor = true; // // btnOK // - this.btnOK.Location = new System.Drawing.Point(238, 208); + this.btnOK.Location = new System.Drawing.Point(317, 272); + this.btnOK.Margin = new System.Windows.Forms.Padding(4); this.btnOK.Name = "btnOK"; - this.btnOK.Size = new System.Drawing.Size(75, 23); + this.btnOK.Size = new System.Drawing.Size(100, 28); this.btnOK.TabIndex = 6; this.btnOK.Text = "OK"; this.btnOK.UseVisualStyleBackColor = true; @@ -177,9 +194,10 @@ // btnCancel // this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.btnCancel.Location = new System.Drawing.Point(319, 208); + this.btnCancel.Location = new System.Drawing.Point(425, 272); + this.btnCancel.Margin = new System.Windows.Forms.Padding(4); this.btnCancel.Name = "btnCancel"; - this.btnCancel.Size = new System.Drawing.Size(75, 23); + this.btnCancel.Size = new System.Drawing.Size(100, 28); this.btnCancel.TabIndex = 7; this.btnCancel.Text = "Cancel"; this.btnCancel.UseVisualStyleBackColor = true; @@ -187,9 +205,10 @@ // // btnResetDefaults // - this.btnResetDefaults.Location = new System.Drawing.Point(300, 138); + this.btnResetDefaults.Location = new System.Drawing.Point(400, 170); + this.btnResetDefaults.Margin = new System.Windows.Forms.Padding(4); this.btnResetDefaults.Name = "btnResetDefaults"; - this.btnResetDefaults.Size = new System.Drawing.Size(94, 23); + this.btnResetDefaults.Size = new System.Drawing.Size(125, 28); this.btnResetDefaults.TabIndex = 8; this.btnResetDefaults.Text = "Reset Defaults"; this.btnResetDefaults.UseVisualStyleBackColor = true; @@ -198,9 +217,11 @@ // groupBox3 // this.groupBox3.Controls.Add(this.chkUpdates); - this.groupBox3.Location = new System.Drawing.Point(136, 138); + this.groupBox3.Location = new System.Drawing.Point(181, 167); + this.groupBox3.Margin = new System.Windows.Forms.Padding(4); this.groupBox3.Name = "groupBox3"; - this.groupBox3.Size = new System.Drawing.Size(127, 49); + this.groupBox3.Padding = new System.Windows.Forms.Padding(4); + this.groupBox3.Size = new System.Drawing.Size(169, 54); this.groupBox3.TabIndex = 6; this.groupBox3.TabStop = false; this.groupBox3.Text = "Other"; @@ -208,22 +229,43 @@ // chkUpdates // this.chkUpdates.AutoSize = true; - this.chkUpdates.Location = new System.Drawing.Point(8, 22); + this.chkUpdates.Location = new System.Drawing.Point(11, 24); + this.chkUpdates.Margin = new System.Windows.Forms.Padding(4); this.chkUpdates.Name = "chkUpdates"; - this.chkUpdates.Size = new System.Drawing.Size(115, 17); + this.chkUpdates.Size = new System.Drawing.Size(140, 20); this.chkUpdates.TabIndex = 1; this.chkUpdates.Text = "Check for Updates"; this.chkUpdates.UseVisualStyleBackColor = true; // + // cmbBranch + // + this.cmbBranch.FormattingEnabled = true; + this.cmbBranch.Location = new System.Drawing.Point(27, 17); + this.cmbBranch.Name = "cmbBranch"; + this.cmbBranch.Size = new System.Drawing.Size(124, 24); + this.cmbBranch.TabIndex = 9; + // + // BranchGroupBox + // + this.BranchGroupBox.Controls.Add(this.cmbBranch); + this.BranchGroupBox.Location = new System.Drawing.Point(181, 222); + this.BranchGroupBox.Name = "BranchGroupBox"; + this.BranchGroupBox.Size = new System.Drawing.Size(169, 49); + this.BranchGroupBox.TabIndex = 10; + this.BranchGroupBox.TabStop = false; + this.BranchGroupBox.Text = "Branch"; + this.BranchGroupBox.Visible = false; + // // OptionsForm // this.AcceptButton = this.btnOK; - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoSize = true; this.CancelButton = this.btnCancel; - this.ClientSize = new System.Drawing.Size(406, 239); + this.ClientSize = new System.Drawing.Size(541, 316); this.ControlBox = false; + this.Controls.Add(this.BranchGroupBox); this.Controls.Add(this.groupBox3); this.Controls.Add(this.btnResetDefaults); this.Controls.Add(this.btnCancel); @@ -231,6 +273,7 @@ this.Controls.Add(this.groupBox2); this.Controls.Add(this.groupBox1); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + this.Margin = new System.Windows.Forms.Padding(4); this.Name = "OptionsForm"; this.ShowIcon = false; this.ShowInTaskbar = false; @@ -245,6 +288,7 @@ this.groupBox2.PerformLayout(); this.groupBox3.ResumeLayout(false); this.groupBox3.PerformLayout(); + this.BranchGroupBox.ResumeLayout(false); this.ResumeLayout(false); } @@ -267,5 +311,7 @@ private System.Windows.Forms.GroupBox groupBox3; private System.Windows.Forms.CheckBox chkUpdates; private System.Windows.Forms.ComboBox cmbMode; + private System.Windows.Forms.ComboBox cmbBranch; + private System.Windows.Forms.GroupBox BranchGroupBox; } } \ No newline at end of file diff --git a/Giants.Launcher/Forms/OptionsForm.cs b/Giants.Launcher/Forms/OptionsForm.cs index 37a1bb7..07cf63a 100644 --- a/Giants.Launcher/Forms/OptionsForm.cs +++ b/Giants.Launcher/Forms/OptionsForm.cs @@ -1,7 +1,9 @@ -using System; +using Giants.WebApi.Clients; +using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using System.Windows.Forms; namespace Giants.Launcher @@ -9,16 +11,34 @@ namespace Giants.Launcher public partial class OptionsForm : Form { private readonly string gamePath = null; + private readonly string appName; + private readonly Config config; + private readonly string branchName; + private readonly bool enableBranchSelection; + private readonly BranchesClient branchesClient; - public OptionsForm(string title, string gamePath) + public OptionsForm( + string title, + string gamePath, + string appName, + Config config, + string branchName, + BranchesClient branchesClient) { this.InitializeComponent(); this.Text = title; this.gamePath = gamePath; - } + this.appName = appName; + this.config = config; + this.branchName = branchName; + this.branchesClient = branchesClient; - private void OptionsForm_Load(object sender, EventArgs e) + this.config.TryGetBool(ConfigSections.Update, ConfigKeys.EnableBranchSelection, defaultValue: false, out bool enableBranchSelection); + this.enableBranchSelection = enableBranchSelection; + } + + private async void OptionsForm_Load(object sender, EventArgs e) { // Must come first as other options depend on it this.PopulateRenderers(); @@ -28,9 +48,24 @@ namespace Giants.Launcher this.PopulateAnisotropy(); this.PopulateAntialiasing(); + await this.PopulateBranches(); + this.SetOptions(); } + private async Task PopulateBranches() + { + if (this.enableBranchSelection) + { + var branches = await this.branchesClient.GetBranchesAsync(this.appName); + + cmbBranch.Items.AddRange(branches.ToArray()); + + BranchGroupBox.Visible = true; + cmbBranch.Visible = true; + } + } + private void PopulateRenderers() { this.cmbRenderer.Items.Clear(); @@ -72,6 +107,11 @@ namespace Giants.Launcher this.cmbAntialiasing.SelectedIndex = 0; this.chkUpdates.Checked = GameSettings.Get(RegistryKeys.NoAutoUpdate) != 1; + + if (this.enableBranchSelection) + { + this.cmbBranch.SelectedItem = this.branchName; + } } private void PopulateAntialiasing() diff --git a/Giants.Launcher/Giants.Launcher.csproj b/Giants.Launcher/Giants.Launcher.csproj index 247c5d9..47f031e 100644 --- a/Giants.Launcher/Giants.Launcher.csproj +++ b/Giants.Launcher/Giants.Launcher.csproj @@ -76,6 +76,7 @@ + @@ -181,7 +182,6 @@ - \ No newline at end of file diff --git a/Giants.Launcher/Updater/ApplicationType.cs b/Giants.Launcher/Updater/ApplicationType.cs index cb08a1b..1ecce7d 100644 --- a/Giants.Launcher/Updater/ApplicationType.cs +++ b/Giants.Launcher/Updater/ApplicationType.cs @@ -2,6 +2,8 @@ { public enum ApplicationType { + None = 0, + Launcher, Game, } diff --git a/Giants.Services/BranchConstants.cs b/Giants.Services/BranchConstants.cs new file mode 100644 index 0000000..33b6ce8 --- /dev/null +++ b/Giants.Services/BranchConstants.cs @@ -0,0 +1,7 @@ +namespace Giants.Services +{ + public static class BranchConstants + { + public const string DefaultBranchName = "Release"; + } +} diff --git a/Giants.Services/Core/CacheKeys.cs b/Giants.Services/Core/CacheKeys.cs index 1b7832b..0e6af9b 100644 --- a/Giants.Services/Core/CacheKeys.cs +++ b/Giants.Services/Core/CacheKeys.cs @@ -1,9 +1,5 @@ namespace Giants.Services { - using System; - using System.Collections.Generic; - using System.Text; - public static class CacheKeys { public const string ServerInfo = nameof(ServerInfo); diff --git a/Giants.Services/Core/Entities/VersionInfo.cs b/Giants.Services/Core/Entities/VersionInfo.cs index a8d24e7..0cc0ce1 100644 --- a/Giants.Services/Core/Entities/VersionInfo.cs +++ b/Giants.Services/Core/Entities/VersionInfo.cs @@ -4,10 +4,10 @@ namespace Giants.Services { public class VersionInfo : DataContract.V1.VersionInfo, IIdentifiable { - public string id => GenerateId(this.AppName); + public string id => GenerateId(this.AppName, this.BranchName ?? BranchConstants.DefaultBranchName); public string DocumentType => nameof(VersionInfo); - public static string GenerateId(string gameName) => $"{nameof(VersionInfo)}-{gameName}"; + public static string GenerateId(string appName, string branchName) => $"{nameof(VersionInfo)}-{appName}-{branchName}"; } } diff --git a/Giants.Services/Core/ServicesModule.cs b/Giants.Services/Core/ServicesModule.cs index 8077bb8..f327c4a 100644 --- a/Giants.Services/Core/ServicesModule.cs +++ b/Giants.Services/Core/ServicesModule.cs @@ -1,37 +1,75 @@ namespace Giants.Services { - using System; - using System.Net.Http.Headers; + using Autofac; using Giants.Services.Core; using Giants.Services.Services; using Giants.Services.Store; + using Giants.Services.Utility; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; - using Microsoft.Extensions.DependencyInjection; + using System; - public static class ServicesModule + public class ServicesModule : Module { - public static void RegisterServices(IServiceCollection services, IConfiguration configuration) + private readonly IConfiguration configuration; + + public ServicesModule(IConfiguration configuration) { - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + this.configuration = configuration; + } - services.AddHostedService(); - services.AddHostedService(); + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType() + .As() + .SingleInstance(); + builder.RegisterType() + .As() + .SingleInstance(); + builder.RegisterType() + .As() + .SingleInstance(); + builder.RegisterType() + .As() + .SingleInstance(); + builder.RegisterType() + .As() + .SingleInstance(); + builder.RegisterType() + .As() + .SingleInstance(); + builder.RegisterType() + .As() + .SingleInstance(); + builder.RegisterType() + .As() + .SingleInstance(); - services.AddHttpClient("Sentry", c => + var cosmosClient = new CosmosDbClient( + connectionString: this.configuration["CosmosDbEndpoint"], + authKeyOrResourceToken: this.configuration["CosmosDbKey"], + databaseId: this.configuration["DatabaseId"], + containerId: this.configuration["ContainerId"]); + + cosmosClient.Initialize().GetAwaiter().GetResult(); + + builder.RegisterInstance(cosmosClient).SingleInstance(); + + builder.Register>(icc => { - c.BaseAddress = new Uri(configuration["SentryBaseUri"]); + var versionStore = icc.Resolve(); - string sentryAuthenticationToken = configuration["SentryAuthenticationToken"]; - c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", sentryAuthenticationToken); - }); + return new SimpleMemoryCache( + expirationPeriod: TimeSpan.FromMinutes(5), + memoryCache: icc.Resolve(), + cacheKey: CacheKeys.VersionInfo, + getAllItems: async (cacheEntry) => + { + return await versionStore.GetVersions(); + }); + }) + .AsSelf() + .SingleInstance(); } } } diff --git a/Giants.Services/Giants.Services.csproj b/Giants.Services/Giants.Services.csproj index d20ea5c..71db794 100644 --- a/Giants.Services/Giants.Services.csproj +++ b/Giants.Services/Giants.Services.csproj @@ -5,6 +5,7 @@ + diff --git a/Giants.Services/Services/IVersioningService.cs b/Giants.Services/Services/IVersioningService.cs index 60276d5..3b3c3be 100644 --- a/Giants.Services/Services/IVersioningService.cs +++ b/Giants.Services/Services/IVersioningService.cs @@ -1,12 +1,15 @@ namespace Giants.Services { using Giants.DataContract.V1; + using System.Collections.Generic; using System.Threading.Tasks; public interface IVersioningService { - Task GetVersionInfo(string appName); + Task> GetBranches(string appName); - Task UpdateVersionInfo(string appName, AppVersion appVersion, string fileName); + Task GetVersionInfo(string appName, string branchName); + + Task UpdateVersionInfo(string appName, AppVersion appVersion, string fileName, string branchName, bool force); } } diff --git a/Giants.Services/Services/InitializerService.cs b/Giants.Services/Services/InitializerService.cs deleted file mode 100644 index 7b1d15b..0000000 --- a/Giants.Services/Services/InitializerService.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace Giants.Services -{ - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Extensions.Hosting; - - public class InitializerService : IHostedService - { - private readonly IVersioningStore updaterStore; - private readonly IServerRegistryStore serverRegistryStore; - - public InitializerService( - IVersioningStore updaterStore, - IServerRegistryStore serverRegistryStore) - { - // TODO: Pick these up from reflection and auto initialize - this.updaterStore = updaterStore; - this.serverRegistryStore = serverRegistryStore; - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - await this.serverRegistryStore.Initialize(); - await this.updaterStore.Initialize(); - } - - public Task StopAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - } -} diff --git a/Giants.Services/Services/VersioningService.cs b/Giants.Services/Services/VersioningService.cs index 45554b1..9f16cb5 100644 --- a/Giants.Services/Services/VersioningService.cs +++ b/Giants.Services/Services/VersioningService.cs @@ -1,8 +1,10 @@ using Giants.DataContract.V1; +using Giants.Services.Utility; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using System; -using System.IO; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; namespace Giants.Services @@ -11,41 +13,52 @@ namespace Giants.Services { private readonly IVersioningStore versioningStore; private readonly IConfiguration configuration; + private readonly ISimpleMemoryCache versionCache; private readonly ILogger logger; + private const string InstallerContainerName = "public"; public VersioningService( + ILogger logger, IVersioningStore updaterStore, IConfiguration configuration, - ILogger logger) + ISimpleMemoryCache versionCache) { + this.logger = logger; this.versioningStore = updaterStore; this.configuration = configuration; - this.logger = logger; + this.versionCache = versionCache; } - public Task GetVersionInfo(string appName) + public async Task GetVersionInfo(string appName, string branchName) { ArgumentUtility.CheckStringForNullOrEmpty(appName); - return this.versioningStore.GetVersionInfo(appName); + branchName ??= BranchConstants.DefaultBranchName; + + var versions = await this.versionCache.GetItems(); + + return versions + .Where(x => x.AppName.Equals(appName, StringComparison.Ordinal) && x.BranchName.Equals(branchName, StringComparison.OrdinalIgnoreCase)) + .FirstOrDefault(); } - public async Task UpdateVersionInfo(string appName, AppVersion appVersion, string fileName) + public async Task UpdateVersionInfo(string appName, AppVersion appVersion, string fileName, string branchName, bool force) { ArgumentUtility.CheckStringForNullOrEmpty(appName); ArgumentUtility.CheckForNull(appVersion); ArgumentUtility.CheckStringForNullOrEmpty(fileName); + ArgumentUtility.CheckStringForNullOrEmpty(branchName); - Uri storageAccountUri = new Uri(this.configuration["StorageAccountUri"]); + var storageAccountUri = new Uri(this.configuration["StorageAccountUri"]); - VersionInfo versionInfo = await this.GetVersionInfo(appName); + VersionInfo versionInfo = await this.GetVersionInfo(appName, branchName); if (versionInfo == null) { - throw new ArgumentException($"No version information for {appName} found.", nameof(appName)); + throw new ArgumentException($"No version information for {appName} ({branchName}) found."); } - if (appVersion < versionInfo.Version) + if (!force && (appVersion < versionInfo.Version)) { throw new ArgumentException($"Version {appVersion.SerializeToJson()} is less than current version {versionInfo.Version.SerializeToJson()}", nameof(appVersion)); } @@ -61,11 +74,22 @@ namespace Giants.Services AppName = appName, Version = appVersion, InstallerUri = installerUri, + BranchName = branchName, }; this.logger.LogInformation("Updating version info for {appName}: {versionInfo}", appName, newVersionInfo.SerializeToJson()); await this.versioningStore.UpdateVersionInfo(newVersionInfo); + this.versionCache.Invalidate(); + } + + public async Task> GetBranches(string appName) + { + var allVersions = await this.versionCache.GetItems(); + + return allVersions + .Where(x => x.AppName.Equals(appName, StringComparison.OrdinalIgnoreCase)) + .Select(x => x.BranchName).ToList(); } } } diff --git a/Giants.Services/Store/CosmosDbServerRegistryStore.cs b/Giants.Services/Store/CosmosDbServerRegistryStore.cs index 9a0c746..8782380 100644 --- a/Giants.Services/Store/CosmosDbServerRegistryStore.cs +++ b/Giants.Services/Store/CosmosDbServerRegistryStore.cs @@ -18,8 +18,8 @@ private readonly IConfiguration configuration; private readonly IMemoryCache memoryCache; private readonly IDateTimeProvider dateTimeProvider; + private readonly CosmosDbClient client; private readonly TimeSpan timeoutPeriod; - private CosmosDbClient client; private const int ServerRefreshIntervalInMinutes = 1; @@ -27,12 +27,14 @@ ILogger logger, IConfiguration configuration, IMemoryCache memoryCache, - IDateTimeProvider dateTimeProvider) + IDateTimeProvider dateTimeProvider, + CosmosDbClient client) { this.logger = logger; this.configuration = configuration; this.memoryCache = memoryCache; this.dateTimeProvider = dateTimeProvider; + this.client = client; this.timeoutPeriod = TimeSpan.FromMinutes(Convert.ToDouble(this.configuration["ServerTimeoutPeriodInMinutes"])); } @@ -176,17 +178,6 @@ } } - public async Task Initialize() - { - this.client = new CosmosDbClient( - connectionString: this.configuration["CosmosDbEndpoint"], - authKeyOrResourceToken: this.configuration["CosmosDbKey"], - databaseId: this.configuration["DatabaseId"], - containerId: this.configuration["ContainerId"]); - - await this.client.Initialize(); - } - private async Task>> PopulateCache(ICacheEntry entry) { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1); diff --git a/Giants.Services/Store/CosmosDbVersioningStore.cs b/Giants.Services/Store/CosmosDbVersioningStore.cs index af549c5..e6d3d57 100644 --- a/Giants.Services/Store/CosmosDbVersioningStore.cs +++ b/Giants.Services/Store/CosmosDbVersioningStore.cs @@ -1,63 +1,26 @@ namespace Giants.Services.Store { - using System; + using System.Collections.Generic; using System.Threading.Tasks; - using Microsoft.Extensions.Caching.Memory; - using Microsoft.Extensions.Configuration; - using Microsoft.Extensions.Logging; public class CosmosDbVersioningStore : IVersioningStore { - private readonly ILogger logger; - private readonly IMemoryCache memoryCache; - private readonly IConfiguration configuration; - private CosmosDbClient client; + private readonly CosmosDbClient client; public CosmosDbVersioningStore( - ILogger logger, - IMemoryCache memoryCache, - IConfiguration configuration) + CosmosDbClient cosmosDbClient) { - this.logger = logger; - this.memoryCache = memoryCache; - this.configuration = configuration; + this.client = cosmosDbClient; } - public async Task GetVersionInfo(string appName) + public Task> GetVersions() { - VersionInfo versionInfo = await this.memoryCache.GetOrCreateAsync( - key: GetCacheKey(appName), - factory: async (entry) => - { - entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5); - - return await this.client.GetItemById( - VersionInfo.GenerateId(appName), - nameof(VersionInfo)); - }); - - return versionInfo; + return this.client.GetItems(); } public async Task UpdateVersionInfo(VersionInfo versionInfo) { await this.client.UpsertItem(versionInfo); } - - public async Task Initialize() - { - this.client = new CosmosDbClient( - connectionString: this.configuration["CosmosDbEndpoint"], - authKeyOrResourceToken: this.configuration["CosmosDbKey"], - databaseId: this.configuration["DatabaseId"], - containerId: this.configuration["ContainerId"]); - - await this.client.Initialize(); - } - - private static string GetCacheKey(string gameName) - { - return $"{CacheKeys.VersionInfo}-{gameName}"; - } } } diff --git a/Giants.Services/Store/FileVersioningStore.cs b/Giants.Services/Store/FileVersioningStore.cs deleted file mode 100644 index b644f8c..0000000 --- a/Giants.Services/Store/FileVersioningStore.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Giants.Services -{ - using System; - using System.Threading.Tasks; - - public class FileVersioningStore : IVersioningStore - { - public Task GetVersionInfo(string appName) - { - throw new NotImplementedException(); - } - - public Task UpdateVersionInfo(VersionInfo versionInfo) - { - throw new NotImplementedException(); - } - - public Task Initialize() - { - throw new NotImplementedException(); - } - } -} diff --git a/Giants.Services/Store/IServerRegistryStore.cs b/Giants.Services/Store/IServerRegistryStore.cs index a2a699a..2769111 100644 --- a/Giants.Services/Store/IServerRegistryStore.cs +++ b/Giants.Services/Store/IServerRegistryStore.cs @@ -7,8 +7,6 @@ public interface IServerRegistryStore { - Task Initialize(); - Task DeleteServer(string id, string partitionKey = null); Task DeleteServers(IEnumerable ids, string partitionKey = null); diff --git a/Giants.Services/Store/IVersioningStore.cs b/Giants.Services/Store/IVersioningStore.cs index f1fccdb..3ee384e 100644 --- a/Giants.Services/Store/IVersioningStore.cs +++ b/Giants.Services/Store/IVersioningStore.cs @@ -1,13 +1,12 @@ namespace Giants.Services { + using System.Collections.Generic; using System.Threading.Tasks; public interface IVersioningStore { - Task GetVersionInfo(string appName); + Task> GetVersions(); Task UpdateVersionInfo(VersionInfo versionInfo); - - Task Initialize(); } } diff --git a/Giants.Services/Utility/ISimpleMemoryCache.cs b/Giants.Services/Utility/ISimpleMemoryCache.cs new file mode 100644 index 0000000..307f4ac --- /dev/null +++ b/Giants.Services/Utility/ISimpleMemoryCache.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Giants.Services.Utility +{ + public interface ISimpleMemoryCache + { + Task> GetItems(); + + void Invalidate(); + } +} diff --git a/Giants.Services/Utility/SimpleMemoryCache.cs b/Giants.Services/Utility/SimpleMemoryCache.cs new file mode 100644 index 0000000..c9a59f2 --- /dev/null +++ b/Giants.Services/Utility/SimpleMemoryCache.cs @@ -0,0 +1,82 @@ +using Microsoft.Extensions.Azure; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Primitives; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Giants.Services.Utility +{ + /// + /// Wrapper around that caches all items of the specified type. + /// + /// + public class SimpleMemoryCache : ISimpleMemoryCache, IDisposable + { + private readonly TimeSpan? expirationPeriod; + private IMemoryCache memoryCache; + private readonly object cacheKey; + private readonly Func>> getAllItems; + private CancellationTokenSource resetCacheToken; + private bool disposedValue; + + public SimpleMemoryCache( + TimeSpan? expirationPeriod, + IMemoryCache memoryCache, + object cacheKey, + Func>> getAllItems) + { + this.expirationPeriod = expirationPeriod; + this.memoryCache = memoryCache; + this.cacheKey = cacheKey; + this.getAllItems = getAllItems; + this.resetCacheToken = new CancellationTokenSource(); + } + + public async Task> GetItems() + { + IEnumerable items = await this.memoryCache.GetOrCreateAsync(cacheKey, this.PopulateCache); + return items; + } + + public void Invalidate() + { + this.resetCacheToken.Cancel(); + this.resetCacheToken.Dispose(); + this.resetCacheToken = new CancellationTokenSource(); + } + + private async Task> PopulateCache(ICacheEntry cacheEntry) + { + if (this.expirationPeriod.HasValue) + { + cacheEntry.AbsoluteExpirationRelativeToNow = this.expirationPeriod; + } + + cacheEntry.AddExpirationToken(new CancellationChangeToken(this.resetCacheToken.Token)); + + return await this.getAllItems(cacheEntry); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + this.memoryCache?.Dispose(); + this.resetCacheToken?.Dispose(); + } + + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/Giants.Services/packages.lock.json b/Giants.Services/packages.lock.json index 1f3b717..f6cad57 100644 --- a/Giants.Services/packages.lock.json +++ b/Giants.Services/packages.lock.json @@ -2,6 +2,15 @@ "version": 1, "dependencies": { "net6.0": { + "Autofac": { + "type": "Direct", + "requested": "[6.4.0, )", + "resolved": "6.4.0", + "contentHash": "tkFxl6wAPuwVhrlN8wuNADnd+k2tv4ReP7ZZSL0vjfcN0RcfC9v25ogxK6b03HC7D4NwWjSLf1G/zTG8Bw43wQ==", + "dependencies": { + "System.Diagnostics.DiagnosticSource": "4.7.1" + } + }, "AutoMapper": { "type": "Direct", "requested": "[11.0.1, )", diff --git a/Giants.WebApi.Clients/Clients.cs b/Giants.WebApi.Clients/Clients.cs index 7efdcb0..92a5bc4 100644 --- a/Giants.WebApi.Clients/Clients.cs +++ b/Giants.WebApi.Clients/Clients.cs @@ -52,17 +52,26 @@ namespace Giants.WebApi.Clients partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); /// A server side error occurred. - public virtual System.Threading.Tasks.Task DeleteServerAsync() + public virtual System.Threading.Tasks.Task DeleteServerAsync(string gameName, int? port) { - return DeleteServerAsync(System.Threading.CancellationToken.None); + return DeleteServerAsync(gameName, port, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// A server side error occurred. - public virtual async System.Threading.Tasks.Task DeleteServerAsync(System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task DeleteServerAsync(string gameName, int? port, System.Threading.CancellationToken cancellationToken) { var urlBuilder_ = new System.Text.StringBuilder(); - urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/Servers"); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/Servers?"); + if (gameName != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("gameName") + "=").Append(System.Uri.EscapeDataString(ConvertToString(gameName, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + if (port != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("port") + "=").Append(System.Uri.EscapeDataString(ConvertToString(port, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + urlBuilder_.Length--; var client_ = _httpClient; var disposeClient_ = false; @@ -364,6 +373,220 @@ namespace Giants.WebApi.Clients } } + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.16.1.0 (NJsonSchema v10.7.2.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class BranchesClient + { + private string _baseUrl = "https://localhost:44304"; + private System.Net.Http.HttpClient _httpClient; + private System.Lazy _settings; + + public BranchesClient(System.Net.Http.HttpClient httpClient) + { + _httpClient = httpClient; + _settings = new System.Lazy(CreateSerializerSettings); + } + + private Newtonsoft.Json.JsonSerializerSettings CreateSerializerSettings() + { + var settings = new Newtonsoft.Json.JsonSerializerSettings(); + UpdateJsonSerializerSettings(settings); + return settings; + } + + public string BaseUrl + { + get { return _baseUrl; } + set { _baseUrl = value; } + } + + protected Newtonsoft.Json.JsonSerializerSettings JsonSerializerSettings { get { return _settings.Value; } } + + partial void UpdateJsonSerializerSettings(Newtonsoft.Json.JsonSerializerSettings settings); + + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url); + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder); + partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); + + /// A server side error occurred. + public virtual System.Threading.Tasks.Task> GetBranchesAsync(string appName) + { + return GetBranchesAsync(appName, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task> GetBranchesAsync(string appName, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/Branches?"); + if (appName != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("appName") + "=").Append(System.Uri.EscapeDataString(ConvertToString(appName, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + urlBuilder_.Length--; + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + protected struct ObjectResponseResult + { + public ObjectResponseResult(T responseObject, string responseText) + { + this.Object = responseObject; + this.Text = responseText; + } + + public T Object { get; } + + public string Text { get; } + } + + public bool ReadResponseAsString { get; set; } + + protected virtual async System.Threading.Tasks.Task> ReadObjectResponseAsync(System.Net.Http.HttpResponseMessage response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Threading.CancellationToken cancellationToken) + { + if (response == null || response.Content == null) + { + return new ObjectResponseResult(default(T), string.Empty); + } + + if (ReadResponseAsString) + { + var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + try + { + var typedBody = Newtonsoft.Json.JsonConvert.DeserializeObject(responseText, JsonSerializerSettings); + return new ObjectResponseResult(typedBody, responseText); + } + catch (Newtonsoft.Json.JsonException exception) + { + var message = "Could not deserialize the response body string as " + typeof(T).FullName + "."; + throw new ApiException(message, (int)response.StatusCode, responseText, headers, exception); + } + } + else + { + try + { + using (var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) + using (var streamReader = new System.IO.StreamReader(responseStream)) + using (var jsonTextReader = new Newtonsoft.Json.JsonTextReader(streamReader)) + { + var serializer = Newtonsoft.Json.JsonSerializer.Create(JsonSerializerSettings); + var typedBody = serializer.Deserialize(jsonTextReader); + return new ObjectResponseResult(typedBody, string.Empty); + } + } + catch (Newtonsoft.Json.JsonException exception) + { + var message = "Could not deserialize the response body stream as " + typeof(T).FullName + "."; + throw new ApiException(message, (int)response.StatusCode, string.Empty, headers, exception); + } + } + } + + private string ConvertToString(object value, System.Globalization.CultureInfo cultureInfo) + { + if (value == null) + { + return ""; + } + + if (value is System.Enum) + { + var name = System.Enum.GetName(value.GetType(), value); + if (name != null) + { + var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name); + if (field != null) + { + var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute)) + as System.Runtime.Serialization.EnumMemberAttribute; + if (attribute != null) + { + return attribute.Value != null ? attribute.Value : name; + } + } + + var converted = System.Convert.ToString(System.Convert.ChangeType(value, System.Enum.GetUnderlyingType(value.GetType()), cultureInfo)); + return converted == null ? string.Empty : converted; + } + } + else if (value is bool) + { + return System.Convert.ToString((bool)value, cultureInfo).ToLowerInvariant(); + } + else if (value is byte[]) + { + return System.Convert.ToBase64String((byte[]) value); + } + else if (value.GetType().IsArray) + { + var array = System.Linq.Enumerable.OfType((System.Array) value); + return string.Join(",", System.Linq.Enumerable.Select(array, o => ConvertToString(o, cultureInfo))); + } + + var result = System.Convert.ToString(value, cultureInfo); + return result == null ? "" : result; + } + } + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.16.1.0 (NJsonSchema v10.7.2.0 (Newtonsoft.Json v11.0.0.0))")] public partial class CommunityClient { @@ -573,6 +796,210 @@ namespace Giants.WebApi.Clients } } + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.16.1.0 (NJsonSchema v10.7.2.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class CrashReportsClient + { + private string _baseUrl = "https://localhost:44304"; + private System.Net.Http.HttpClient _httpClient; + private System.Lazy _settings; + + public CrashReportsClient(System.Net.Http.HttpClient httpClient) + { + _httpClient = httpClient; + _settings = new System.Lazy(CreateSerializerSettings); + } + + private Newtonsoft.Json.JsonSerializerSettings CreateSerializerSettings() + { + var settings = new Newtonsoft.Json.JsonSerializerSettings(); + UpdateJsonSerializerSettings(settings); + return settings; + } + + public string BaseUrl + { + get { return _baseUrl; } + set { _baseUrl = value; } + } + + protected Newtonsoft.Json.JsonSerializerSettings JsonSerializerSettings { get { return _settings.Value; } } + + partial void UpdateJsonSerializerSettings(Newtonsoft.Json.JsonSerializerSettings settings); + + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url); + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder); + partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); + + /// A server side error occurred. + public virtual System.Threading.Tasks.Task UploadAsync() + { + return UploadAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task UploadAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/CrashReports"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Content = new System.Net.Http.StringContent(string.Empty, System.Text.Encoding.UTF8, "application/json"); + request_.Method = new System.Net.Http.HttpMethod("POST"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + return; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + protected struct ObjectResponseResult + { + public ObjectResponseResult(T responseObject, string responseText) + { + this.Object = responseObject; + this.Text = responseText; + } + + public T Object { get; } + + public string Text { get; } + } + + public bool ReadResponseAsString { get; set; } + + protected virtual async System.Threading.Tasks.Task> ReadObjectResponseAsync(System.Net.Http.HttpResponseMessage response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Threading.CancellationToken cancellationToken) + { + if (response == null || response.Content == null) + { + return new ObjectResponseResult(default(T), string.Empty); + } + + if (ReadResponseAsString) + { + var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + try + { + var typedBody = Newtonsoft.Json.JsonConvert.DeserializeObject(responseText, JsonSerializerSettings); + return new ObjectResponseResult(typedBody, responseText); + } + catch (Newtonsoft.Json.JsonException exception) + { + var message = "Could not deserialize the response body string as " + typeof(T).FullName + "."; + throw new ApiException(message, (int)response.StatusCode, responseText, headers, exception); + } + } + else + { + try + { + using (var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) + using (var streamReader = new System.IO.StreamReader(responseStream)) + using (var jsonTextReader = new Newtonsoft.Json.JsonTextReader(streamReader)) + { + var serializer = Newtonsoft.Json.JsonSerializer.Create(JsonSerializerSettings); + var typedBody = serializer.Deserialize(jsonTextReader); + return new ObjectResponseResult(typedBody, string.Empty); + } + } + catch (Newtonsoft.Json.JsonException exception) + { + var message = "Could not deserialize the response body stream as " + typeof(T).FullName + "."; + throw new ApiException(message, (int)response.StatusCode, string.Empty, headers, exception); + } + } + } + + private string ConvertToString(object value, System.Globalization.CultureInfo cultureInfo) + { + if (value == null) + { + return ""; + } + + if (value is System.Enum) + { + var name = System.Enum.GetName(value.GetType(), value); + if (name != null) + { + var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name); + if (field != null) + { + var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute)) + as System.Runtime.Serialization.EnumMemberAttribute; + if (attribute != null) + { + return attribute.Value != null ? attribute.Value : name; + } + } + + var converted = System.Convert.ToString(System.Convert.ChangeType(value, System.Enum.GetUnderlyingType(value.GetType()), cultureInfo)); + return converted == null ? string.Empty : converted; + } + } + else if (value is bool) + { + return System.Convert.ToString((bool)value, cultureInfo).ToLowerInvariant(); + } + else if (value is byte[]) + { + return System.Convert.ToBase64String((byte[]) value); + } + else if (value.GetType().IsArray) + { + var array = System.Linq.Enumerable.OfType((System.Array) value); + return string.Join(",", System.Linq.Enumerable.Select(array, o => ConvertToString(o, cultureInfo))); + } + + var result = System.Convert.ToString(value, cultureInfo); + return result == null ? "" : result; + } + } + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.16.1.0 (NJsonSchema v10.7.2.0 (Newtonsoft.Json v11.0.0.0))")] public partial class VersionClient { @@ -608,14 +1035,14 @@ namespace Giants.WebApi.Clients partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); /// A server side error occurred. - public virtual System.Threading.Tasks.Task GetVersionInfoAsync(string appName) + public virtual System.Threading.Tasks.Task GetVersionInfoAsync(string appName, string branchName) { - return GetVersionInfoAsync(appName, System.Threading.CancellationToken.None); + return GetVersionInfoAsync(appName, branchName, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// A server side error occurred. - public virtual async System.Threading.Tasks.Task GetVersionInfoAsync(string appName, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task GetVersionInfoAsync(string appName, string branchName, System.Threading.CancellationToken cancellationToken) { var urlBuilder_ = new System.Text.StringBuilder(); urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/Version?"); @@ -623,6 +1050,10 @@ namespace Giants.WebApi.Clients { urlBuilder_.Append(System.Uri.EscapeDataString("appName") + "=").Append(System.Uri.EscapeDataString(ConvertToString(appName, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); } + if (branchName != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("branchName") + "=").Append(System.Uri.EscapeDataString(ConvertToString(branchName, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } urlBuilder_.Length--; var client_ = _httpClient; @@ -684,6 +1115,78 @@ namespace Giants.WebApi.Clients } } + /// A server side error occurred. + public virtual System.Threading.Tasks.Task UpdateVersionInfoAsync(VersionInfoUpdate versionInfoUpdate) + { + return UpdateVersionInfoAsync(versionInfoUpdate, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task UpdateVersionInfoAsync(VersionInfoUpdate versionInfoUpdate, System.Threading.CancellationToken cancellationToken) + { + if (versionInfoUpdate == null) + throw new System.ArgumentNullException("versionInfoUpdate"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/Version"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(versionInfoUpdate, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + return; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + protected struct ObjectResponseResult { public ObjectResponseResult(T responseObject, string responseText) @@ -829,7 +1332,7 @@ namespace Giants.WebApi.Clients [Newtonsoft.Json.JsonProperty("numPlayers", Required = Newtonsoft.Json.Required.Always)] public int NumPlayers { get; set; } - [Newtonsoft.Json.JsonProperty("maxPlayers", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [Newtonsoft.Json.JsonProperty("maxPlayers", Required = Newtonsoft.Json.Required.Always)] public int MaxPlayers { get; set; } [Newtonsoft.Json.JsonProperty("gameState", Required = Newtonsoft.Json.Required.Always)] @@ -920,6 +1423,30 @@ namespace Giants.WebApi.Clients [System.ComponentModel.DataAnnotations.Required] public System.Uri InstallerUri { get; set; } + [Newtonsoft.Json.JsonProperty("branchName", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public string BranchName { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.16.1.0 (NJsonSchema v10.7.2.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class VersionInfoUpdate + { + [Newtonsoft.Json.JsonProperty("appName", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string AppName { get; set; } + + [Newtonsoft.Json.JsonProperty("appVersion", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public AppVersion AppVersion { get; set; } + + [Newtonsoft.Json.JsonProperty("fileName", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string FileName { get; set; } + + [Newtonsoft.Json.JsonProperty("branchName", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string BranchName { get; set; } + + [Newtonsoft.Json.JsonProperty("forceUpdate", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool ForceUpdate { get; set; } + } diff --git a/Giants.WebApi.Clients/swagger.json b/Giants.WebApi.Clients/swagger.json index 0a59deb..fdd2a3b 100644 --- a/Giants.WebApi.Clients/swagger.json +++ b/Giants.WebApi.Clients/swagger.json @@ -1,5 +1,5 @@ { - "x-generator": "NSwag v13.7.0.0 (NJsonSchema v10.1.24.0 (Newtonsoft.Json v10.0.0.0))", + "x-generator": "NSwag v13.16.1.0 (NJsonSchema v10.7.2.0 (Newtonsoft.Json v10.0.0.0))", "openapi": "3.0.0", "info": { "title": "My Title", @@ -17,6 +17,26 @@ "Servers" ], "operationId": "Servers_DeleteServer", + "parameters": [ + { + "name": "gameName", + "in": "query", + "schema": { + "type": "string", + "nullable": true + }, + "x-position": 1 + }, + { + "name": "port", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + }, + "x-position": 2 + } + ], "responses": { "200": { "description": "" @@ -68,6 +88,40 @@ } } }, + "/api/Branches": { + "get": { + "tags": [ + "Branches" + ], + "operationId": "Branches_GetBranches", + "parameters": [ + { + "name": "appName", + "in": "query", + "schema": { + "type": "string", + "nullable": true + }, + "x-position": 1 + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, "/api/Community": { "get": { "tags": [ @@ -88,6 +142,19 @@ } } }, + "/api/CrashReports": { + "post": { + "tags": [ + "CrashReports" + ], + "operationId": "CrashReports_Upload", + "responses": { + "200": { + "description": "" + } + } + } + }, "/api/Version": { "get": { "tags": [ @@ -103,6 +170,15 @@ "nullable": true }, "x-position": 1 + }, + { + "name": "branchName", + "in": "query", + "schema": { + "type": "string", + "nullable": true + }, + "x-position": 2 } ], "responses": { @@ -117,6 +193,29 @@ } } } + }, + "post": { + "tags": [ + "Version" + ], + "operationId": "Version_UpdateVersionInfo", + "requestBody": { + "x-name": "versionInfoUpdate", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VersionInfoUpdate" + } + } + }, + "required": true, + "x-position": 1 + }, + "responses": { + "200": { + "description": "" + } + } } } }, @@ -153,6 +252,7 @@ "mapName", "gameType", "numPlayers", + "maxPlayers", "gameState", "timeLimit", "fragLimit", @@ -305,7 +405,8 @@ "required": [ "appName", "version", - "installerUri" + "installerUri", + "branchName" ], "properties": { "appName": { @@ -319,9 +420,42 @@ "type": "string", "format": "uri", "minLength": 1 + }, + "branchName": { + "type": "string", + "minLength": 1 + } + } + }, + "VersionInfoUpdate": { + "type": "object", + "additionalProperties": false, + "properties": { + "appName": { + "type": "string", + "nullable": true + }, + "appVersion": { + "nullable": true, + "oneOf": [ + { + "$ref": "#/components/schemas/AppVersion" + } + ] + }, + "fileName": { + "type": "string", + "nullable": true + }, + "branchName": { + "type": "string", + "nullable": true + }, + "forceUpdate": { + "type": "boolean" } } } } } -} +} \ No newline at end of file diff --git a/Giants.WebApi/Controllers/BranchesController.cs b/Giants.WebApi/Controllers/BranchesController.cs new file mode 100644 index 0000000..a8bd8d2 --- /dev/null +++ b/Giants.WebApi/Controllers/BranchesController.cs @@ -0,0 +1,30 @@ +using Giants.DataContract.V1; +using Giants.Services; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Giants.WebApi.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [ApiVersion("1.1")] + [Route("api/[controller]")] + public class BranchesController : ControllerBase + { + private readonly IVersioningService versioningService; + + public BranchesController(IVersioningService branchService) + { + this.versioningService = branchService; + } + + [HttpGet] + public async Task> GetBranches([FromQuery]string appName) + { + ArgumentUtility.CheckStringForNullOrEmpty(appName); + + return await this.versioningService.GetBranches(appName); + } + } +} diff --git a/Giants.WebApi/Controllers/VersionController.cs b/Giants.WebApi/Controllers/VersionController.cs index bda0451..d5edb64 100644 --- a/Giants.WebApi/Controllers/VersionController.cs +++ b/Giants.WebApi/Controllers/VersionController.cs @@ -4,6 +4,7 @@ using Giants.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Identity.Web.Resource; +using System; using System.Threading.Tasks; namespace Giants.WebApi.Controllers @@ -31,7 +32,29 @@ namespace Giants.WebApi.Controllers { ArgumentUtility.CheckStringForNullOrEmpty(appName); - Services.VersionInfo versionInfo = await this.versioningService.GetVersionInfo(appName); + VersionInfo versionInfo = await this.versioningService.GetVersionInfo(appName, BranchConstants.DefaultBranchName); + + if (versionInfo == null) + { + throw new ArgumentException($"No version information for {appName} found.", appName); + } + + return this.mapper.Map(versionInfo); + } + + [HttpGet] + [MapToApiVersion("1.1")] + public async Task GetVersionInfo(string appName, string branchName) + { + ArgumentUtility.CheckStringForNullOrEmpty(appName); + ArgumentUtility.CheckStringForNullOrEmpty(branchName); + + VersionInfo versionInfo = await this.versioningService.GetVersionInfo(appName, branchName); + + if (versionInfo == null) + { + throw new ArgumentException($"No version information for {appName} ({branchName}) found.", appName); + } return this.mapper.Map(versionInfo); } @@ -44,7 +67,12 @@ namespace Giants.WebApi.Controllers { ArgumentUtility.CheckForNull(versionInfoUpdate); - await this.versioningService.UpdateVersionInfo(versionInfoUpdate.AppName, versionInfoUpdate.AppVersion, versionInfoUpdate.FileName); + await this.versioningService.UpdateVersionInfo( + appName: versionInfoUpdate.AppName, + appVersion: versionInfoUpdate.AppVersion, + fileName: versionInfoUpdate.FileName, + branchName: versionInfoUpdate.BranchName, + force: versionInfoUpdate.ForceUpdate); } } } diff --git a/Giants.WebApi/Giants.WebApi.csproj b/Giants.WebApi/Giants.WebApi.csproj index 115c928..882bdd4 100644 --- a/Giants.WebApi/Giants.WebApi.csproj +++ b/Giants.WebApi/Giants.WebApi.csproj @@ -10,6 +10,8 @@ + + diff --git a/Giants.WebApi/Program.cs b/Giants.WebApi/Program.cs index 561e0d2..36fd81a 100644 --- a/Giants.WebApi/Program.cs +++ b/Giants.WebApi/Program.cs @@ -1,5 +1,8 @@ namespace Giants.Web { + using Autofac; + using Autofac.Core; + using Autofac.Extensions.DependencyInjection; using AutoMapper; using Giants.Services; using Microsoft.AspNetCore.Authentication.JwtBearer; @@ -14,8 +17,10 @@ namespace Giants.Web using Microsoft.Extensions.Logging; using Microsoft.Identity.Web; using Microsoft.IdentityModel.Logging; + using NSwag.Generation.Processors; using System; using System.Linq; + using System.Net.Http.Headers; using System.Threading.Tasks; public class Program @@ -26,7 +31,11 @@ namespace Giants.Web ConfigureServices(builder); - var app = builder.Build(); + builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); + builder.Host.ConfigureContainer((containerBuilder) => ConfigureAutofacServices(containerBuilder, builder.Configuration)); + + var app = builder + .Build(); ConfigureApplication(app, app.Environment); app.Run(); @@ -87,17 +96,40 @@ namespace Giants.Web services.AddHttpContextAccessor(); services.TryAddSingleton(); - ServicesModule.RegisterServices(services, builder.Configuration); - IMapper mapper = Services.Mapper.GetMapper(); services.AddSingleton(mapper); services.AddHealthChecks(); + RegisterHttpClients(services, builder.Configuration); + + RegisterHostedServices(services); + builder.Logging.AddEventSourceLogger(); builder.Logging.AddApplicationInsights(); } + private static void ConfigureAutofacServices(ContainerBuilder containerBuilder, IConfiguration configuration) + { + containerBuilder.RegisterModule(new ServicesModule(configuration)); + } + + private static void RegisterHttpClients(IServiceCollection services, IConfiguration configuration) + { + services.AddHttpClient("Sentry", c => + { + c.BaseAddress = new Uri(configuration["SentryBaseUri"]); + + string sentryAuthenticationToken = configuration["SentryAuthenticationToken"]; + c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", sentryAuthenticationToken); + }); + } + + private static void RegisterHostedServices(IServiceCollection services) + { + services.AddHostedService(); + } + private static void ConfigureApplication(WebApplication app, IWebHostEnvironment env) { if (env.IsDevelopment()) diff --git a/Giants.WebApi/packages.lock.json b/Giants.WebApi/packages.lock.json index d66d69b..7f1c7ce 100644 --- a/Giants.WebApi/packages.lock.json +++ b/Giants.WebApi/packages.lock.json @@ -2,6 +2,25 @@ "version": 1, "dependencies": { "net6.0": { + "Autofac": { + "type": "Direct", + "requested": "[6.4.0, )", + "resolved": "6.4.0", + "contentHash": "tkFxl6wAPuwVhrlN8wuNADnd+k2tv4ReP7ZZSL0vjfcN0RcfC9v25ogxK6b03HC7D4NwWjSLf1G/zTG8Bw43wQ==", + "dependencies": { + "System.Diagnostics.DiagnosticSource": "4.7.1" + } + }, + "Autofac.Extensions.DependencyInjection": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "nGrXNpQX2FiZpIBydK9cxZnnoqP/cUd3k/53uRERYEqLtWzKtE15R6L+j5q5ax5Rv/+3wAIkOaPePkahfqrwjg==", + "dependencies": { + "Autofac": "6.4.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0" + } + }, "AutoMapper": { "type": "Direct", "requested": "[11.0.1, )", @@ -1908,6 +1927,7 @@ "type": "Project", "dependencies": { "AutoMapper": "[11.0.1, )", + "Autofac": "[6.4.0, )", "Azure.Storage.Blobs": "[12.13.1, )", "Giants.DataContract": "[1.0.0, )", "Microsoft.Azure.Cosmos": "[3.30.1, )", diff --git a/GiantsTools.sln b/GiantsTools.sln index c7bc549..b1c2972 100644 --- a/GiantsTools.sln +++ b/GiantsTools.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30320.27 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32811.315 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Giants.Services", "Giants.Services\Giants.Services.csproj", "{0CD61424-4E74-4A48-A726-729FEA32C50C}" EndProject @@ -25,8 +25,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{0801 EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "imp_gbs", "Plugins\imp_gbs\imp_gbs.vcxproj", "{448F061E-AE05-4E06-84A1-C95660FD048C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Giants.Services.Tests", "Giants.Services.Tests\Giants.Services.Tests.csproj", "{2AFB71CA-8313-472E-B242-0517343764B4}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ServerConsole", "ServerConsoleExample\ServerConsole.vcxproj", "{8AEE9CFF-0E24-498F-B60A-627A7F4A727D}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NavMeshGenerator", "NavMeshGenerator\NavMeshGenerator.vcxproj", "{CA9C0938-3ADA-4C73-A89A-E9447ABCE101}" @@ -34,327 +32,107 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 - DebugBeta|Any CPU = DebugBeta|Any CPU - DebugBeta|x64 = DebugBeta|x64 - DebugBeta|x86 = DebugBeta|x86 Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 Release|x86 = Release|x86 - ReleaseBeta|Any CPU = ReleaseBeta|Any CPU - ReleaseBeta|x64 = ReleaseBeta|x64 - ReleaseBeta|x86 = ReleaseBeta|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {0CD61424-4E74-4A48-A726-729FEA32C50C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0CD61424-4E74-4A48-A726-729FEA32C50C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0CD61424-4E74-4A48-A726-729FEA32C50C}.Debug|x64.ActiveCfg = Debug|Any CPU - {0CD61424-4E74-4A48-A726-729FEA32C50C}.Debug|x64.Build.0 = Debug|Any CPU {0CD61424-4E74-4A48-A726-729FEA32C50C}.Debug|x86.ActiveCfg = Debug|Any CPU {0CD61424-4E74-4A48-A726-729FEA32C50C}.Debug|x86.Build.0 = Debug|Any CPU - {0CD61424-4E74-4A48-A726-729FEA32C50C}.DebugBeta|Any CPU.ActiveCfg = Debug|Any CPU - {0CD61424-4E74-4A48-A726-729FEA32C50C}.DebugBeta|Any CPU.Build.0 = Debug|Any CPU - {0CD61424-4E74-4A48-A726-729FEA32C50C}.DebugBeta|x64.ActiveCfg = Debug|Any CPU - {0CD61424-4E74-4A48-A726-729FEA32C50C}.DebugBeta|x64.Build.0 = Debug|Any CPU - {0CD61424-4E74-4A48-A726-729FEA32C50C}.DebugBeta|x86.ActiveCfg = Debug|Any CPU - {0CD61424-4E74-4A48-A726-729FEA32C50C}.DebugBeta|x86.Build.0 = Debug|Any CPU {0CD61424-4E74-4A48-A726-729FEA32C50C}.Release|Any CPU.ActiveCfg = Release|Any CPU {0CD61424-4E74-4A48-A726-729FEA32C50C}.Release|Any CPU.Build.0 = Release|Any CPU - {0CD61424-4E74-4A48-A726-729FEA32C50C}.Release|x64.ActiveCfg = Release|Any CPU - {0CD61424-4E74-4A48-A726-729FEA32C50C}.Release|x64.Build.0 = Release|Any CPU {0CD61424-4E74-4A48-A726-729FEA32C50C}.Release|x86.ActiveCfg = Release|Any CPU {0CD61424-4E74-4A48-A726-729FEA32C50C}.Release|x86.Build.0 = Release|Any CPU - {0CD61424-4E74-4A48-A726-729FEA32C50C}.ReleaseBeta|Any CPU.ActiveCfg = Release|Any CPU - {0CD61424-4E74-4A48-A726-729FEA32C50C}.ReleaseBeta|Any CPU.Build.0 = Release|Any CPU - {0CD61424-4E74-4A48-A726-729FEA32C50C}.ReleaseBeta|x64.ActiveCfg = Release|Any CPU - {0CD61424-4E74-4A48-A726-729FEA32C50C}.ReleaseBeta|x64.Build.0 = Release|Any CPU - {0CD61424-4E74-4A48-A726-729FEA32C50C}.ReleaseBeta|x86.ActiveCfg = Release|Any CPU - {0CD61424-4E74-4A48-A726-729FEA32C50C}.ReleaseBeta|x86.Build.0 = Release|Any CPU {D426FE47-231B-41F0-AC78-293D12EF66A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D426FE47-231B-41F0-AC78-293D12EF66A0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D426FE47-231B-41F0-AC78-293D12EF66A0}.Debug|x64.ActiveCfg = Debug|Any CPU - {D426FE47-231B-41F0-AC78-293D12EF66A0}.Debug|x64.Build.0 = Debug|Any CPU {D426FE47-231B-41F0-AC78-293D12EF66A0}.Debug|x86.ActiveCfg = Debug|Any CPU {D426FE47-231B-41F0-AC78-293D12EF66A0}.Debug|x86.Build.0 = Debug|Any CPU - {D426FE47-231B-41F0-AC78-293D12EF66A0}.DebugBeta|Any CPU.ActiveCfg = Debug|Any CPU - {D426FE47-231B-41F0-AC78-293D12EF66A0}.DebugBeta|Any CPU.Build.0 = Debug|Any CPU - {D426FE47-231B-41F0-AC78-293D12EF66A0}.DebugBeta|x64.ActiveCfg = Debug|Any CPU - {D426FE47-231B-41F0-AC78-293D12EF66A0}.DebugBeta|x64.Build.0 = Debug|Any CPU - {D426FE47-231B-41F0-AC78-293D12EF66A0}.DebugBeta|x86.ActiveCfg = Debug|Any CPU - {D426FE47-231B-41F0-AC78-293D12EF66A0}.DebugBeta|x86.Build.0 = Debug|Any CPU {D426FE47-231B-41F0-AC78-293D12EF66A0}.Release|Any CPU.ActiveCfg = Release|Any CPU {D426FE47-231B-41F0-AC78-293D12EF66A0}.Release|Any CPU.Build.0 = Release|Any CPU - {D426FE47-231B-41F0-AC78-293D12EF66A0}.Release|x64.ActiveCfg = Release|Any CPU - {D426FE47-231B-41F0-AC78-293D12EF66A0}.Release|x64.Build.0 = Release|Any CPU {D426FE47-231B-41F0-AC78-293D12EF66A0}.Release|x86.ActiveCfg = Release|Any CPU {D426FE47-231B-41F0-AC78-293D12EF66A0}.Release|x86.Build.0 = Release|Any CPU - {D426FE47-231B-41F0-AC78-293D12EF66A0}.ReleaseBeta|Any CPU.ActiveCfg = Release|Any CPU - {D426FE47-231B-41F0-AC78-293D12EF66A0}.ReleaseBeta|Any CPU.Build.0 = Release|Any CPU - {D426FE47-231B-41F0-AC78-293D12EF66A0}.ReleaseBeta|x64.ActiveCfg = Release|Any CPU - {D426FE47-231B-41F0-AC78-293D12EF66A0}.ReleaseBeta|x64.Build.0 = Release|Any CPU - {D426FE47-231B-41F0-AC78-293D12EF66A0}.ReleaseBeta|x86.ActiveCfg = Release|Any CPU - {D426FE47-231B-41F0-AC78-293D12EF66A0}.ReleaseBeta|x86.Build.0 = Release|Any CPU {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.Debug|x64.ActiveCfg = Debug|Any CPU - {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.Debug|x64.Build.0 = Debug|Any CPU {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.Debug|x86.ActiveCfg = Debug|Any CPU {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.Debug|x86.Build.0 = Debug|Any CPU - {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.DebugBeta|Any CPU.ActiveCfg = Debug|Any CPU - {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.DebugBeta|Any CPU.Build.0 = Debug|Any CPU - {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.DebugBeta|x64.ActiveCfg = Debug|Any CPU - {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.DebugBeta|x64.Build.0 = Debug|Any CPU - {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.DebugBeta|x86.ActiveCfg = Debug|Any CPU - {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.DebugBeta|x86.Build.0 = Debug|Any CPU {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.Release|Any CPU.ActiveCfg = Release|Any CPU {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.Release|Any CPU.Build.0 = Release|Any CPU - {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.Release|x64.ActiveCfg = Release|Any CPU - {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.Release|x64.Build.0 = Release|Any CPU {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.Release|x86.ActiveCfg = Release|Any CPU {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.Release|x86.Build.0 = Release|Any CPU - {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.ReleaseBeta|Any CPU.ActiveCfg = Release|Any CPU - {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.ReleaseBeta|Any CPU.Build.0 = Release|Any CPU - {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.ReleaseBeta|x64.ActiveCfg = Release|Any CPU - {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.ReleaseBeta|x64.Build.0 = Release|Any CPU - {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.ReleaseBeta|x86.ActiveCfg = Release|Any CPU - {9A284C34-8F4B-4E20-B74B-1A0AF04EDBD1}.ReleaseBeta|x86.Build.0 = Release|Any CPU {612FD606-F072-4A04-9054-65BC592E9D3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {612FD606-F072-4A04-9054-65BC592E9D3E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {612FD606-F072-4A04-9054-65BC592E9D3E}.Debug|x64.ActiveCfg = Debug|Any CPU - {612FD606-F072-4A04-9054-65BC592E9D3E}.Debug|x64.Build.0 = Debug|Any CPU {612FD606-F072-4A04-9054-65BC592E9D3E}.Debug|x86.ActiveCfg = Debug|Any CPU {612FD606-F072-4A04-9054-65BC592E9D3E}.Debug|x86.Build.0 = Debug|Any CPU - {612FD606-F072-4A04-9054-65BC592E9D3E}.DebugBeta|Any CPU.ActiveCfg = DebugBeta|Any CPU - {612FD606-F072-4A04-9054-65BC592E9D3E}.DebugBeta|Any CPU.Build.0 = DebugBeta|Any CPU - {612FD606-F072-4A04-9054-65BC592E9D3E}.DebugBeta|x64.ActiveCfg = Debug|Any CPU - {612FD606-F072-4A04-9054-65BC592E9D3E}.DebugBeta|x64.Build.0 = Debug|Any CPU - {612FD606-F072-4A04-9054-65BC592E9D3E}.DebugBeta|x86.ActiveCfg = Debug|Any CPU - {612FD606-F072-4A04-9054-65BC592E9D3E}.DebugBeta|x86.Build.0 = Debug|Any CPU {612FD606-F072-4A04-9054-65BC592E9D3E}.Release|Any CPU.ActiveCfg = Release|Any CPU {612FD606-F072-4A04-9054-65BC592E9D3E}.Release|Any CPU.Build.0 = Release|Any CPU - {612FD606-F072-4A04-9054-65BC592E9D3E}.Release|x64.ActiveCfg = Release|Any CPU - {612FD606-F072-4A04-9054-65BC592E9D3E}.Release|x64.Build.0 = Release|Any CPU {612FD606-F072-4A04-9054-65BC592E9D3E}.Release|x86.ActiveCfg = Release|Any CPU {612FD606-F072-4A04-9054-65BC592E9D3E}.Release|x86.Build.0 = Release|Any CPU - {612FD606-F072-4A04-9054-65BC592E9D3E}.ReleaseBeta|Any CPU.ActiveCfg = ReleaseBeta|Any CPU - {612FD606-F072-4A04-9054-65BC592E9D3E}.ReleaseBeta|Any CPU.Build.0 = ReleaseBeta|Any CPU - {612FD606-F072-4A04-9054-65BC592E9D3E}.ReleaseBeta|x64.ActiveCfg = Release|Any CPU - {612FD606-F072-4A04-9054-65BC592E9D3E}.ReleaseBeta|x64.Build.0 = Release|Any CPU - {612FD606-F072-4A04-9054-65BC592E9D3E}.ReleaseBeta|x86.ActiveCfg = Release|Any CPU - {612FD606-F072-4A04-9054-65BC592E9D3E}.ReleaseBeta|x86.Build.0 = Release|Any CPU {D4C21170-82D4-4D1F-81EC-036835AC1238}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D4C21170-82D4-4D1F-81EC-036835AC1238}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D4C21170-82D4-4D1F-81EC-036835AC1238}.Debug|x64.ActiveCfg = Debug|Any CPU - {D4C21170-82D4-4D1F-81EC-036835AC1238}.Debug|x64.Build.0 = Debug|Any CPU {D4C21170-82D4-4D1F-81EC-036835AC1238}.Debug|x86.ActiveCfg = Debug|Any CPU {D4C21170-82D4-4D1F-81EC-036835AC1238}.Debug|x86.Build.0 = Debug|Any CPU - {D4C21170-82D4-4D1F-81EC-036835AC1238}.DebugBeta|Any CPU.ActiveCfg = Debug|Any CPU - {D4C21170-82D4-4D1F-81EC-036835AC1238}.DebugBeta|Any CPU.Build.0 = Debug|Any CPU - {D4C21170-82D4-4D1F-81EC-036835AC1238}.DebugBeta|x64.ActiveCfg = Debug|Any CPU - {D4C21170-82D4-4D1F-81EC-036835AC1238}.DebugBeta|x64.Build.0 = Debug|Any CPU - {D4C21170-82D4-4D1F-81EC-036835AC1238}.DebugBeta|x86.ActiveCfg = Debug|Any CPU - {D4C21170-82D4-4D1F-81EC-036835AC1238}.DebugBeta|x86.Build.0 = Debug|Any CPU {D4C21170-82D4-4D1F-81EC-036835AC1238}.Release|Any CPU.ActiveCfg = Release|Any CPU {D4C21170-82D4-4D1F-81EC-036835AC1238}.Release|Any CPU.Build.0 = Release|Any CPU - {D4C21170-82D4-4D1F-81EC-036835AC1238}.Release|x64.ActiveCfg = Release|Any CPU - {D4C21170-82D4-4D1F-81EC-036835AC1238}.Release|x64.Build.0 = Release|Any CPU {D4C21170-82D4-4D1F-81EC-036835AC1238}.Release|x86.ActiveCfg = Release|Any CPU {D4C21170-82D4-4D1F-81EC-036835AC1238}.Release|x86.Build.0 = Release|Any CPU - {D4C21170-82D4-4D1F-81EC-036835AC1238}.ReleaseBeta|Any CPU.ActiveCfg = Release|Any CPU - {D4C21170-82D4-4D1F-81EC-036835AC1238}.ReleaseBeta|Any CPU.Build.0 = Release|Any CPU - {D4C21170-82D4-4D1F-81EC-036835AC1238}.ReleaseBeta|x64.ActiveCfg = Release|Any CPU - {D4C21170-82D4-4D1F-81EC-036835AC1238}.ReleaseBeta|x64.Build.0 = Release|Any CPU - {D4C21170-82D4-4D1F-81EC-036835AC1238}.ReleaseBeta|x86.ActiveCfg = Release|Any CPU - {D4C21170-82D4-4D1F-81EC-036835AC1238}.ReleaseBeta|x86.Build.0 = Release|Any CPU {9A0AF60B-3C3B-45B7-B5E1-4C9997A68EBB}.Debug|Any CPU.ActiveCfg = Debug|Win32 {9A0AF60B-3C3B-45B7-B5E1-4C9997A68EBB}.Debug|Any CPU.Build.0 = Debug|Win32 - {9A0AF60B-3C3B-45B7-B5E1-4C9997A68EBB}.Debug|x64.ActiveCfg = Debug|Win32 {9A0AF60B-3C3B-45B7-B5E1-4C9997A68EBB}.Debug|x86.ActiveCfg = Debug|Win32 {9A0AF60B-3C3B-45B7-B5E1-4C9997A68EBB}.Debug|x86.Build.0 = Debug|Win32 - {9A0AF60B-3C3B-45B7-B5E1-4C9997A68EBB}.DebugBeta|Any CPU.ActiveCfg = Debug|Win32 - {9A0AF60B-3C3B-45B7-B5E1-4C9997A68EBB}.DebugBeta|Any CPU.Build.0 = Debug|Win32 - {9A0AF60B-3C3B-45B7-B5E1-4C9997A68EBB}.DebugBeta|x64.ActiveCfg = Debug|Win32 - {9A0AF60B-3C3B-45B7-B5E1-4C9997A68EBB}.DebugBeta|x86.ActiveCfg = Debug|Win32 - {9A0AF60B-3C3B-45B7-B5E1-4C9997A68EBB}.DebugBeta|x86.Build.0 = Debug|Win32 {9A0AF60B-3C3B-45B7-B5E1-4C9997A68EBB}.Release|Any CPU.ActiveCfg = Release|Win32 {9A0AF60B-3C3B-45B7-B5E1-4C9997A68EBB}.Release|Any CPU.Build.0 = Release|Win32 - {9A0AF60B-3C3B-45B7-B5E1-4C9997A68EBB}.Release|x64.ActiveCfg = Release|Win32 {9A0AF60B-3C3B-45B7-B5E1-4C9997A68EBB}.Release|x86.ActiveCfg = Release|Win32 {9A0AF60B-3C3B-45B7-B5E1-4C9997A68EBB}.Release|x86.Build.0 = Release|Win32 - {9A0AF60B-3C3B-45B7-B5E1-4C9997A68EBB}.ReleaseBeta|Any CPU.ActiveCfg = Release|Win32 - {9A0AF60B-3C3B-45B7-B5E1-4C9997A68EBB}.ReleaseBeta|Any CPU.Build.0 = Release|Win32 - {9A0AF60B-3C3B-45B7-B5E1-4C9997A68EBB}.ReleaseBeta|x64.ActiveCfg = Release|Win32 - {9A0AF60B-3C3B-45B7-B5E1-4C9997A68EBB}.ReleaseBeta|x86.ActiveCfg = Release|Win32 - {9A0AF60B-3C3B-45B7-B5E1-4C9997A68EBB}.ReleaseBeta|x86.Build.0 = Release|Win32 {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.Debug|x64.ActiveCfg = Debug|Any CPU - {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.Debug|x64.Build.0 = Debug|Any CPU {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.Debug|x86.ActiveCfg = Debug|Any CPU {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.Debug|x86.Build.0 = Debug|Any CPU - {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.DebugBeta|Any CPU.ActiveCfg = Debug|Any CPU - {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.DebugBeta|Any CPU.Build.0 = Debug|Any CPU - {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.DebugBeta|x64.ActiveCfg = Debug|Any CPU - {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.DebugBeta|x64.Build.0 = Debug|Any CPU - {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.DebugBeta|x86.ActiveCfg = Debug|Any CPU - {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.DebugBeta|x86.Build.0 = Debug|Any CPU {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.Release|Any CPU.ActiveCfg = Release|Any CPU {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.Release|Any CPU.Build.0 = Release|Any CPU - {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.Release|x64.ActiveCfg = Release|Any CPU - {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.Release|x64.Build.0 = Release|Any CPU {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.Release|x86.ActiveCfg = Release|Any CPU {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.Release|x86.Build.0 = Release|Any CPU - {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.ReleaseBeta|Any CPU.ActiveCfg = Release|Any CPU - {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.ReleaseBeta|Any CPU.Build.0 = Release|Any CPU - {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.ReleaseBeta|x64.ActiveCfg = Release|Any CPU - {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.ReleaseBeta|x64.Build.0 = Release|Any CPU - {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.ReleaseBeta|x86.ActiveCfg = Release|Any CPU - {6286A2C7-15F0-4D87-B928-4B012F9E0FFE}.ReleaseBeta|x86.Build.0 = Release|Any CPU {F5F3D216-9787-4CFF-88DF-8259CF667F88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F5F3D216-9787-4CFF-88DF-8259CF667F88}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F5F3D216-9787-4CFF-88DF-8259CF667F88}.Debug|x64.ActiveCfg = Debug|Any CPU - {F5F3D216-9787-4CFF-88DF-8259CF667F88}.Debug|x64.Build.0 = Debug|Any CPU {F5F3D216-9787-4CFF-88DF-8259CF667F88}.Debug|x86.ActiveCfg = Debug|Any CPU {F5F3D216-9787-4CFF-88DF-8259CF667F88}.Debug|x86.Build.0 = Debug|Any CPU - {F5F3D216-9787-4CFF-88DF-8259CF667F88}.DebugBeta|Any CPU.ActiveCfg = Debug|Any CPU - {F5F3D216-9787-4CFF-88DF-8259CF667F88}.DebugBeta|Any CPU.Build.0 = Debug|Any CPU - {F5F3D216-9787-4CFF-88DF-8259CF667F88}.DebugBeta|x64.ActiveCfg = Debug|Any CPU - {F5F3D216-9787-4CFF-88DF-8259CF667F88}.DebugBeta|x64.Build.0 = Debug|Any CPU - {F5F3D216-9787-4CFF-88DF-8259CF667F88}.DebugBeta|x86.ActiveCfg = Debug|Any CPU - {F5F3D216-9787-4CFF-88DF-8259CF667F88}.DebugBeta|x86.Build.0 = Debug|Any CPU {F5F3D216-9787-4CFF-88DF-8259CF667F88}.Release|Any CPU.ActiveCfg = Release|Any CPU {F5F3D216-9787-4CFF-88DF-8259CF667F88}.Release|Any CPU.Build.0 = Release|Any CPU - {F5F3D216-9787-4CFF-88DF-8259CF667F88}.Release|x64.ActiveCfg = Release|Any CPU - {F5F3D216-9787-4CFF-88DF-8259CF667F88}.Release|x64.Build.0 = Release|Any CPU {F5F3D216-9787-4CFF-88DF-8259CF667F88}.Release|x86.ActiveCfg = Release|Any CPU {F5F3D216-9787-4CFF-88DF-8259CF667F88}.Release|x86.Build.0 = Release|Any CPU - {F5F3D216-9787-4CFF-88DF-8259CF667F88}.ReleaseBeta|Any CPU.ActiveCfg = Release|Any CPU - {F5F3D216-9787-4CFF-88DF-8259CF667F88}.ReleaseBeta|Any CPU.Build.0 = Release|Any CPU - {F5F3D216-9787-4CFF-88DF-8259CF667F88}.ReleaseBeta|x64.ActiveCfg = Release|Any CPU - {F5F3D216-9787-4CFF-88DF-8259CF667F88}.ReleaseBeta|x64.Build.0 = Release|Any CPU - {F5F3D216-9787-4CFF-88DF-8259CF667F88}.ReleaseBeta|x86.ActiveCfg = Release|Any CPU - {F5F3D216-9787-4CFF-88DF-8259CF667F88}.ReleaseBeta|x86.Build.0 = Release|Any CPU {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.Debug|x64.ActiveCfg = Debug|Any CPU - {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.Debug|x64.Build.0 = Debug|Any CPU {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.Debug|x86.ActiveCfg = Debug|Any CPU {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.Debug|x86.Build.0 = Debug|Any CPU - {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.DebugBeta|Any CPU.ActiveCfg = Debug|Any CPU - {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.DebugBeta|Any CPU.Build.0 = Debug|Any CPU - {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.DebugBeta|x64.ActiveCfg = Debug|Any CPU - {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.DebugBeta|x64.Build.0 = Debug|Any CPU - {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.DebugBeta|x86.ActiveCfg = Debug|Any CPU - {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.DebugBeta|x86.Build.0 = Debug|Any CPU {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.Release|Any CPU.ActiveCfg = Release|Any CPU {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.Release|Any CPU.Build.0 = Release|Any CPU - {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.Release|x64.ActiveCfg = Release|Any CPU - {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.Release|x64.Build.0 = Release|Any CPU {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.Release|x86.ActiveCfg = Release|Any CPU {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.Release|x86.Build.0 = Release|Any CPU - {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.ReleaseBeta|Any CPU.ActiveCfg = Release|Any CPU - {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.ReleaseBeta|Any CPU.Build.0 = Release|Any CPU - {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.ReleaseBeta|x64.ActiveCfg = Release|Any CPU - {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.ReleaseBeta|x64.Build.0 = Release|Any CPU - {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.ReleaseBeta|x86.ActiveCfg = Release|Any CPU - {49423BA5-4A9F-47A3-9D2D-E83936272DD0}.ReleaseBeta|x86.Build.0 = Release|Any CPU {448F061E-AE05-4E06-84A1-C95660FD048C}.Debug|Any CPU.ActiveCfg = Debug|x64 {448F061E-AE05-4E06-84A1-C95660FD048C}.Debug|Any CPU.Build.0 = Debug|x64 - {448F061E-AE05-4E06-84A1-C95660FD048C}.Debug|x64.ActiveCfg = Debug|x64 - {448F061E-AE05-4E06-84A1-C95660FD048C}.Debug|x64.Build.0 = Debug|x64 - {448F061E-AE05-4E06-84A1-C95660FD048C}.Debug|x86.ActiveCfg = Debug|Win32 - {448F061E-AE05-4E06-84A1-C95660FD048C}.Debug|x86.Build.0 = Debug|Win32 - {448F061E-AE05-4E06-84A1-C95660FD048C}.DebugBeta|Any CPU.ActiveCfg = Debug|x64 - {448F061E-AE05-4E06-84A1-C95660FD048C}.DebugBeta|Any CPU.Build.0 = Debug|x64 - {448F061E-AE05-4E06-84A1-C95660FD048C}.DebugBeta|x64.ActiveCfg = Debug|x64 - {448F061E-AE05-4E06-84A1-C95660FD048C}.DebugBeta|x64.Build.0 = Debug|x64 - {448F061E-AE05-4E06-84A1-C95660FD048C}.DebugBeta|x86.ActiveCfg = Debug|Win32 - {448F061E-AE05-4E06-84A1-C95660FD048C}.DebugBeta|x86.Build.0 = Debug|Win32 + {448F061E-AE05-4E06-84A1-C95660FD048C}.Debug|x86.ActiveCfg = Debug|x64 + {448F061E-AE05-4E06-84A1-C95660FD048C}.Debug|x86.Build.0 = Debug|x64 {448F061E-AE05-4E06-84A1-C95660FD048C}.Release|Any CPU.ActiveCfg = Release|x64 {448F061E-AE05-4E06-84A1-C95660FD048C}.Release|Any CPU.Build.0 = Release|x64 - {448F061E-AE05-4E06-84A1-C95660FD048C}.Release|x64.ActiveCfg = Release|x64 - {448F061E-AE05-4E06-84A1-C95660FD048C}.Release|x64.Build.0 = Release|x64 {448F061E-AE05-4E06-84A1-C95660FD048C}.Release|x86.ActiveCfg = Release|Win32 {448F061E-AE05-4E06-84A1-C95660FD048C}.Release|x86.Build.0 = Release|Win32 - {448F061E-AE05-4E06-84A1-C95660FD048C}.ReleaseBeta|Any CPU.ActiveCfg = Release|x64 - {448F061E-AE05-4E06-84A1-C95660FD048C}.ReleaseBeta|Any CPU.Build.0 = Release|x64 - {448F061E-AE05-4E06-84A1-C95660FD048C}.ReleaseBeta|x64.ActiveCfg = Release|x64 - {448F061E-AE05-4E06-84A1-C95660FD048C}.ReleaseBeta|x64.Build.0 = Release|x64 - {448F061E-AE05-4E06-84A1-C95660FD048C}.ReleaseBeta|x86.ActiveCfg = Release|Win32 - {448F061E-AE05-4E06-84A1-C95660FD048C}.ReleaseBeta|x86.Build.0 = Release|Win32 - {2AFB71CA-8313-472E-B242-0517343764B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.Debug|x64.ActiveCfg = Debug|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.Debug|x64.Build.0 = Debug|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.Debug|x86.ActiveCfg = Debug|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.Debug|x86.Build.0 = Debug|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.DebugBeta|Any CPU.ActiveCfg = Debug|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.DebugBeta|Any CPU.Build.0 = Debug|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.DebugBeta|x64.ActiveCfg = Debug|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.DebugBeta|x64.Build.0 = Debug|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.DebugBeta|x86.ActiveCfg = Debug|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.DebugBeta|x86.Build.0 = Debug|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.Release|Any CPU.Build.0 = Release|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.Release|x64.ActiveCfg = Release|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.Release|x64.Build.0 = Release|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.Release|x86.ActiveCfg = Release|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.Release|x86.Build.0 = Release|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.ReleaseBeta|Any CPU.ActiveCfg = Release|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.ReleaseBeta|Any CPU.Build.0 = Release|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.ReleaseBeta|x64.ActiveCfg = Release|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.ReleaseBeta|x64.Build.0 = Release|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.ReleaseBeta|x86.ActiveCfg = Release|Any CPU - {2AFB71CA-8313-472E-B242-0517343764B4}.ReleaseBeta|x86.Build.0 = Release|Any CPU {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.Debug|Any CPU.ActiveCfg = Debug|Win32 {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.Debug|Any CPU.Build.0 = Debug|Win32 - {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.Debug|x64.ActiveCfg = Debug|x64 - {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.Debug|x64.Build.0 = Debug|x64 {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.Debug|x86.ActiveCfg = Debug|Win32 {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.Debug|x86.Build.0 = Debug|Win32 - {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.DebugBeta|Any CPU.ActiveCfg = Debug|Win32 - {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.DebugBeta|Any CPU.Build.0 = Debug|Win32 - {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.DebugBeta|x64.ActiveCfg = Debug|x64 - {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.DebugBeta|x64.Build.0 = Debug|x64 - {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.DebugBeta|x86.ActiveCfg = Debug|Win32 - {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.DebugBeta|x86.Build.0 = Debug|Win32 {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.Release|Any CPU.ActiveCfg = Release|Win32 {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.Release|Any CPU.Build.0 = Release|Win32 - {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.Release|x64.ActiveCfg = Release|x64 - {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.Release|x64.Build.0 = Release|x64 {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.Release|x86.ActiveCfg = Release|Win32 {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.Release|x86.Build.0 = Release|Win32 - {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.ReleaseBeta|Any CPU.ActiveCfg = Debug|Win32 - {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.ReleaseBeta|Any CPU.Build.0 = Debug|Win32 - {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.ReleaseBeta|x64.ActiveCfg = Release|x64 - {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.ReleaseBeta|x64.Build.0 = Release|x64 - {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.ReleaseBeta|x86.ActiveCfg = Release|Win32 - {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.ReleaseBeta|x86.Build.0 = Release|Win32 {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Debug|Any CPU.ActiveCfg = Debug|Win32 {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Debug|Any CPU.Build.0 = Debug|Win32 - {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Debug|x64.ActiveCfg = Debug|x64 - {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Debug|x64.Build.0 = Debug|x64 {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Debug|x86.ActiveCfg = Debug|Win32 {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Debug|x86.Build.0 = Debug|Win32 - {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.DebugBeta|Any CPU.ActiveCfg = Debug|Win32 - {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.DebugBeta|Any CPU.Build.0 = Debug|Win32 - {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.DebugBeta|x64.ActiveCfg = Debug|x64 - {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.DebugBeta|x64.Build.0 = Debug|x64 - {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.DebugBeta|x86.ActiveCfg = Debug|Win32 - {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.DebugBeta|x86.Build.0 = Debug|Win32 {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Release|Any CPU.ActiveCfg = Release|Win32 {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Release|Any CPU.Build.0 = Release|Win32 - {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Release|x64.ActiveCfg = Release|x64 - {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Release|x64.Build.0 = Release|x64 {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Release|x86.ActiveCfg = Release|Win32 {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Release|x86.Build.0 = Release|Win32 - {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.ReleaseBeta|Any CPU.ActiveCfg = Debug|Win32 - {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.ReleaseBeta|Any CPU.Build.0 = Debug|Win32 - {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.ReleaseBeta|x64.ActiveCfg = Release|x64 - {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.ReleaseBeta|x64.Build.0 = Release|x64 - {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.ReleaseBeta|x86.ActiveCfg = Release|Win32 - {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.ReleaseBeta|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE