diff --git a/ExtLibs/AltitudeAngelWings.Plugin/AASettings.Designer.cs b/ExtLibs/AltitudeAngelWings.Plugin/AASettings.Designer.cs index df7cc6199a..841d8ab52e 100644 --- a/ExtLibs/AltitudeAngelWings.Plugin/AASettings.Designer.cs +++ b/ExtLibs/AltitudeAngelWings.Plugin/AASettings.Designer.cs @@ -1,5 +1,4 @@ using System.Windows.Forms; -using AltitudeAngelWings.Plugin.Properties; using MissionPlanner.Controls; namespace AltitudeAngelWings.Plugin @@ -34,19 +33,25 @@ private void InitializeComponent() { this.tabPages = new System.Windows.Forms.TabControl(); this.tabPageAccount = new System.Windows.Forms.TabPage(); + this.lst_FlightTelemetry = new System.Windows.Forms.ComboBox(); + this.chk_FlightsEnable = new System.Windows.Forms.CheckBox(); + this.but_Disable = new MissionPlanner.Controls.MyButton(); + this.but_Enable = new MissionPlanner.Controls.MyButton(); this.lbl_UserDetails = new System.Windows.Forms.Label(); this.lbl_OverrideClientSuffix = new System.Windows.Forms.Label(); - this.txt_OverrideUrlSuffix = new System.Windows.Forms.TextBox(); + this.txt_OverrideClientSuffix = new System.Windows.Forms.TextBox(); this.lbl_OverrideClientSecret = new System.Windows.Forms.Label(); this.txt_OverrideClientSecret = new System.Windows.Forms.TextBox(); this.lbl_OverrideClientId = new System.Windows.Forms.Label(); this.txt_OverrideClientId = new System.Windows.Forms.TextBox(); this.chk_OverrideClientSettings = new System.Windows.Forms.CheckBox(); - this.lbl_FlightReportWhat = new System.Windows.Forms.LinkLabel(); - this.chk_FlightReportEnable = new System.Windows.Forms.CheckBox(); + this.chk_FlightPlansEnable = new System.Windows.Forms.CheckBox(); this.but_SignOut = new MissionPlanner.Controls.MyButton(); this.but_SignIn = new MissionPlanner.Controls.MyButton(); this.tabPageMap = new System.Windows.Forms.TabPage(); + this.lbl_AltitudeDisplay = new System.Windows.Forms.Label(); + this.lbl_AltitudeFilter = new System.Windows.Forms.Label(); + this.trk_AltitudeFilter = new System.Windows.Forms.TrackBar(); this.chk_EnablePlanMap = new System.Windows.Forms.CheckBox(); this.chk_EnableDataMap = new System.Windows.Forms.CheckBox(); this.btn_DefaultLayers = new MissionPlanner.Controls.MyButton(); @@ -54,9 +59,8 @@ private void InitializeComponent() this.lbl_OpacityAdjust = new System.Windows.Forms.Label(); this.trk_OpacityAdjust = new System.Windows.Forms.TrackBar(); this.tabPageFlight = new System.Windows.Forms.TabPage(); - this.lbl_FlightReportDescription = new System.Windows.Forms.Label(); - this.txt_FlightReportDescription = new System.Windows.Forms.TextBox(); - this.chk_FlightReportLocalScope = new System.Windows.Forms.CheckBox(); + this.lbl_FlightPlanDescription = new System.Windows.Forms.Label(); + this.txt_FlightPlanDescription = new System.Windows.Forms.TextBox(); this.txt_SerialNumber = new System.Windows.Forms.TextBox(); this.txt_IcaoAddress = new System.Windows.Forms.TextBox(); this.txt_ContactPhone = new System.Windows.Forms.TextBox(); @@ -65,24 +69,20 @@ private void InitializeComponent() this.chk_AllowSms = new System.Windows.Forms.CheckBox(); this.chk_IcaoAddress = new System.Windows.Forms.CheckBox(); this.chk_UseExistingFlightPlanId = new System.Windows.Forms.CheckBox(); - this.chk_FlightReportCommercial = new System.Windows.Forms.CheckBox(); - this.lbl_FlightReportDuration = new System.Windows.Forms.Label(); - this.txt_FlightReportDuration = new System.Windows.Forms.TextBox(); + this.lbl_FlightPlanDuration = new System.Windows.Forms.Label(); + this.txt_FlightPlanDuration = new System.Windows.Forms.TextBox(); this.lbl_ContactPhoneNumber = new System.Windows.Forms.Label(); - this.lbl_FlightReportName = new System.Windows.Forms.Label(); - this.txt_FlightReportName = new System.Windows.Forms.TextBox(); + this.lbl_FlightPlanName = new System.Windows.Forms.Label(); + this.txt_FlightPlanName = new System.Windows.Forms.TextBox(); this.tabPageAbout = new System.Windows.Forms.TabPage(); - this.but_Disable = new MissionPlanner.Controls.MyButton(); - this.but_Enable = new MissionPlanner.Controls.MyButton(); - this.pic_AboutLogo = new System.Windows.Forms.PictureBox(); this.web_About = new System.Windows.Forms.WebBrowser(); this.tabPages.SuspendLayout(); this.tabPageAccount.SuspendLayout(); this.tabPageMap.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.trk_AltitudeFilter)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.trk_OpacityAdjust)).BeginInit(); this.tabPageFlight.SuspendLayout(); this.tabPageAbout.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.pic_AboutLogo)).BeginInit(); this.SuspendLayout(); // // tabPages @@ -94,69 +94,120 @@ private void InitializeComponent() this.tabPages.Location = new System.Drawing.Point(12, 12); this.tabPages.Name = "tabPages"; this.tabPages.SelectedIndex = 0; - this.tabPages.Size = new System.Drawing.Size(453, 284); + this.tabPages.Size = new System.Drawing.Size(452, 284); this.tabPages.TabIndex = 15; // // tabPageAccount // + this.tabPageAccount.Controls.Add(this.lst_FlightTelemetry); + this.tabPageAccount.Controls.Add(this.chk_FlightsEnable); + this.tabPageAccount.Controls.Add(this.but_Disable); + this.tabPageAccount.Controls.Add(this.but_Enable); this.tabPageAccount.Controls.Add(this.lbl_UserDetails); this.tabPageAccount.Controls.Add(this.lbl_OverrideClientSuffix); - this.tabPageAccount.Controls.Add(this.txt_OverrideUrlSuffix); + this.tabPageAccount.Controls.Add(this.txt_OverrideClientSuffix); this.tabPageAccount.Controls.Add(this.lbl_OverrideClientSecret); this.tabPageAccount.Controls.Add(this.txt_OverrideClientSecret); this.tabPageAccount.Controls.Add(this.lbl_OverrideClientId); this.tabPageAccount.Controls.Add(this.txt_OverrideClientId); this.tabPageAccount.Controls.Add(this.chk_OverrideClientSettings); - this.tabPageAccount.Controls.Add(this.lbl_FlightReportWhat); - this.tabPageAccount.Controls.Add(this.chk_FlightReportEnable); + this.tabPageAccount.Controls.Add(this.chk_FlightPlansEnable); this.tabPageAccount.Controls.Add(this.but_SignOut); this.tabPageAccount.Controls.Add(this.but_SignIn); this.tabPageAccount.Location = new System.Drawing.Point(4, 22); this.tabPageAccount.Name = "tabPageAccount"; this.tabPageAccount.Padding = new System.Windows.Forms.Padding(3); - this.tabPageAccount.Size = new System.Drawing.Size(445, 258); + this.tabPageAccount.Size = new System.Drawing.Size(444, 258); this.tabPageAccount.TabIndex = 2; - this.tabPageAccount.Text = Resources.SettingsAccountTabText; + this.tabPageAccount.Text = global::AltitudeAngelWings.Plugin.Properties.Resources.SettingsAccountTabText; this.tabPageAccount.UseVisualStyleBackColor = true; // + // lst_FlightTelemetry + // + this.lst_FlightTelemetry.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.lst_FlightTelemetry.FormattingEnabled = true; + this.lst_FlightTelemetry.Items.AddRange(new object[] { + "Do not send flight telemetry", + "Use Telemetry (UDP)", + "Use Surveillance (HTTPS)"}); + this.lst_FlightTelemetry.Location = new System.Drawing.Point(101, 59); + this.lst_FlightTelemetry.MaxDropDownItems = 3; + this.lst_FlightTelemetry.Name = "lst_FlightTelemetry"; + this.lst_FlightTelemetry.Size = new System.Drawing.Size(192, 21); + this.lst_FlightTelemetry.TabIndex = 48; + this.lst_FlightTelemetry.SelectedIndexChanged += new System.EventHandler(this.lst_FlightTelemetry_SelectedIndexChanged); + // + // chk_FlightsEnable + // + this.chk_FlightsEnable.AutoSize = true; + this.chk_FlightsEnable.Location = new System.Drawing.Point(101, 35); + this.chk_FlightsEnable.Name = "chk_FlightsEnable"; + this.chk_FlightsEnable.Size = new System.Drawing.Size(92, 17); + this.chk_FlightsEnable.TabIndex = 47; + this.chk_FlightsEnable.Text = "Enable Flights"; + this.chk_FlightsEnable.UseVisualStyleBackColor = true; + this.chk_FlightsEnable.CheckedChanged += new System.EventHandler(this.chk_FlightsEnable_CheckedChanged); + // + // but_Disable + // + this.but_Disable.Location = new System.Drawing.Point(6, 229); + this.but_Disable.Name = "but_Disable"; + this.but_Disable.Size = new System.Drawing.Size(75, 23); + this.but_Disable.TabIndex = 46; + this.but_Disable.Text = global::AltitudeAngelWings.Plugin.Properties.Resources.SettingsDisableText; + this.but_Disable.TextColorNotEnabled = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(87)))), ((int)(((byte)(4))))); + this.but_Disable.UseVisualStyleBackColor = true; + this.but_Disable.Click += new System.EventHandler(this.but_Disable_Click); + // + // but_Enable + // + this.but_Enable.Location = new System.Drawing.Point(6, 200); + this.but_Enable.Name = "but_Enable"; + this.but_Enable.Size = new System.Drawing.Size(75, 23); + this.but_Enable.TabIndex = 45; + this.but_Enable.Text = global::AltitudeAngelWings.Plugin.Properties.Resources.SettingsEnableText; + this.but_Enable.TextColorNotEnabled = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(87)))), ((int)(((byte)(4))))); + this.but_Enable.UseVisualStyleBackColor = true; + this.but_Enable.Click += new System.EventHandler(this.but_Enable_Click); + // // lbl_UserDetails // this.lbl_UserDetails.Location = new System.Drawing.Point(299, 6); this.lbl_UserDetails.Name = "lbl_UserDetails"; - this.lbl_UserDetails.Size = new System.Drawing.Size(140, 142); + this.lbl_UserDetails.Size = new System.Drawing.Size(140, 246); this.lbl_UserDetails.TabIndex = 40; // // lbl_OverrideClientSuffix // this.lbl_OverrideClientSuffix.AutoSize = true; - this.lbl_OverrideClientSuffix.Location = new System.Drawing.Point(98, 156); + this.lbl_OverrideClientSuffix.Location = new System.Drawing.Point(98, 206); this.lbl_OverrideClientSuffix.Name = "lbl_OverrideClientSuffix"; this.lbl_OverrideClientSuffix.Size = new System.Drawing.Size(97, 13); this.lbl_OverrideClientSuffix.TabIndex = 38; - this.lbl_OverrideClientSuffix.Text = Resources.SettingsURLDomainSuffixText; + this.lbl_OverrideClientSuffix.Text = "URL Domain Suffix"; // - // txt_OverrideUrlSuffix + // txt_OverrideClientSuffix // - this.txt_OverrideUrlSuffix.Location = new System.Drawing.Point(103, 172); - this.txt_OverrideUrlSuffix.Name = "txt_OverrideUrlSuffix"; - this.txt_OverrideUrlSuffix.Size = new System.Drawing.Size(172, 20); - this.txt_OverrideUrlSuffix.TabIndex = 37; - this.txt_OverrideUrlSuffix.TextChanged += new System.EventHandler(this.txt_OverrideUrlSuffix_TextChanged); + this.txt_OverrideClientSuffix.Location = new System.Drawing.Point(103, 222); + this.txt_OverrideClientSuffix.Name = "txt_OverrideClientSuffix"; + this.txt_OverrideClientSuffix.Size = new System.Drawing.Size(190, 20); + this.txt_OverrideClientSuffix.TabIndex = 37; + this.txt_OverrideClientSuffix.TextChanged += new System.EventHandler(this.txt_OverrideUrlSuffix_TextChanged); // // lbl_OverrideClientSecret // this.lbl_OverrideClientSecret.AutoSize = true; - this.lbl_OverrideClientSecret.Location = new System.Drawing.Point(98, 117); + this.lbl_OverrideClientSecret.Location = new System.Drawing.Point(98, 167); this.lbl_OverrideClientSecret.Name = "lbl_OverrideClientSecret"; this.lbl_OverrideClientSecret.Size = new System.Drawing.Size(67, 13); this.lbl_OverrideClientSecret.TabIndex = 36; - this.lbl_OverrideClientSecret.Text = Resources.SettingsClientSecretText; + this.lbl_OverrideClientSecret.Text = "Client Secret"; // // txt_OverrideClientSecret // - this.txt_OverrideClientSecret.Location = new System.Drawing.Point(103, 133); + this.txt_OverrideClientSecret.Location = new System.Drawing.Point(103, 183); this.txt_OverrideClientSecret.Name = "txt_OverrideClientSecret"; - this.txt_OverrideClientSecret.Size = new System.Drawing.Size(172, 20); + this.txt_OverrideClientSecret.Size = new System.Drawing.Size(190, 20); this.txt_OverrideClientSecret.TabIndex = 35; this.txt_OverrideClientSecret.UseSystemPasswordChar = true; this.txt_OverrideClientSecret.TextChanged += new System.EventHandler(this.txt_OverrideClientSecret_TextChanged); @@ -164,52 +215,41 @@ private void InitializeComponent() // lbl_OverrideClientId // this.lbl_OverrideClientId.AutoSize = true; - this.lbl_OverrideClientId.Location = new System.Drawing.Point(98, 78); + this.lbl_OverrideClientId.Location = new System.Drawing.Point(98, 128); this.lbl_OverrideClientId.Name = "lbl_OverrideClientId"; this.lbl_OverrideClientId.Size = new System.Drawing.Size(47, 13); this.lbl_OverrideClientId.TabIndex = 34; - this.lbl_OverrideClientId.Text = Resources.SettingsClientIDText; + this.lbl_OverrideClientId.Text = "Client ID"; // // txt_OverrideClientId // - this.txt_OverrideClientId.Location = new System.Drawing.Point(103, 94); + this.txt_OverrideClientId.Location = new System.Drawing.Point(103, 144); this.txt_OverrideClientId.Name = "txt_OverrideClientId"; - this.txt_OverrideClientId.Size = new System.Drawing.Size(172, 20); + this.txt_OverrideClientId.Size = new System.Drawing.Size(190, 20); this.txt_OverrideClientId.TabIndex = 33; this.txt_OverrideClientId.TextChanged += new System.EventHandler(this.txt_OverrideClientId_TextChanged); // // chk_OverrideClientSettings // this.chk_OverrideClientSettings.AutoSize = true; - this.chk_OverrideClientSettings.Location = new System.Drawing.Point(101, 58); + this.chk_OverrideClientSettings.Location = new System.Drawing.Point(101, 108); this.chk_OverrideClientSettings.Name = "chk_OverrideClientSettings"; this.chk_OverrideClientSettings.Size = new System.Drawing.Size(136, 17); this.chk_OverrideClientSettings.TabIndex = 32; - this.chk_OverrideClientSettings.Text = Resources.SettingsOverrideClientSettingsText; + this.chk_OverrideClientSettings.Text = global::AltitudeAngelWings.Plugin.Properties.Resources.SettingsOverrideClientSettingsText; this.chk_OverrideClientSettings.UseVisualStyleBackColor = true; this.chk_OverrideClientSettings.CheckedChanged += new System.EventHandler(this.chk_OverrideClientSettings_CheckedChanged); // - // lbl_FlightReportWhat - // - this.lbl_FlightReportWhat.AutoSize = true; - this.lbl_FlightReportWhat.Location = new System.Drawing.Point(101, 30); - this.lbl_FlightReportWhat.Name = "lbl_FlightReportWhat"; - this.lbl_FlightReportWhat.Size = new System.Drawing.Size(68, 13); - this.lbl_FlightReportWhat.TabIndex = 31; - this.lbl_FlightReportWhat.TabStop = true; - this.lbl_FlightReportWhat.Text = Resources.SettingsWhatIsThisText; - this.lbl_FlightReportWhat.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.lbl_FlightReportWhat_LinkClicked); - // - // chk_FlightReportEnable + // chk_FlightPlansEnable // - this.chk_FlightReportEnable.AutoSize = true; - this.chk_FlightReportEnable.Location = new System.Drawing.Point(101, 12); - this.chk_FlightReportEnable.Name = "chk_FlightReportEnable"; - this.chk_FlightReportEnable.Size = new System.Drawing.Size(136, 17); - this.chk_FlightReportEnable.TabIndex = 30; - this.chk_FlightReportEnable.Text = Resources.SettingsEnableFlightReportingText; - this.chk_FlightReportEnable.UseVisualStyleBackColor = true; - this.chk_FlightReportEnable.CheckedChanged += new System.EventHandler(this.chk_FlightReportEnable_CheckedChanged); + this.chk_FlightPlansEnable.AutoSize = true; + this.chk_FlightPlansEnable.Location = new System.Drawing.Point(101, 12); + this.chk_FlightPlansEnable.Name = "chk_FlightPlansEnable"; + this.chk_FlightPlansEnable.Size = new System.Drawing.Size(116, 17); + this.chk_FlightPlansEnable.TabIndex = 30; + this.chk_FlightPlansEnable.Text = "Enable Flight Plans"; + this.chk_FlightPlansEnable.UseVisualStyleBackColor = true; + this.chk_FlightPlansEnable.CheckedChanged += new System.EventHandler(this.chk_FlightPlansEnable_CheckedChanged); // // but_SignOut // @@ -217,7 +257,8 @@ private void InitializeComponent() this.but_SignOut.Name = "but_SignOut"; this.but_SignOut.Size = new System.Drawing.Size(75, 23); this.but_SignOut.TabIndex = 3; - this.but_SignOut.Text = Resources.SettingsSignOutText; + this.but_SignOut.Text = global::AltitudeAngelWings.Plugin.Properties.Resources.SettingsSignOutText; + this.but_SignOut.TextColorNotEnabled = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(87)))), ((int)(((byte)(4))))); this.but_SignOut.UseVisualStyleBackColor = true; this.but_SignOut.Click += new System.EventHandler(this.but_SignOut_Click); // @@ -227,12 +268,16 @@ private void InitializeComponent() this.but_SignIn.Name = "but_SignIn"; this.but_SignIn.Size = new System.Drawing.Size(75, 23); this.but_SignIn.TabIndex = 2; - this.but_SignIn.Text = Resources.SettingsSignInText; + this.but_SignIn.Text = global::AltitudeAngelWings.Plugin.Properties.Resources.SettingsSignInText; + this.but_SignIn.TextColorNotEnabled = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(87)))), ((int)(((byte)(4))))); this.but_SignIn.UseVisualStyleBackColor = true; this.but_SignIn.Click += new System.EventHandler(this.but_SignIn_Click); // // tabPageMap // + this.tabPageMap.Controls.Add(this.lbl_AltitudeDisplay); + this.tabPageMap.Controls.Add(this.lbl_AltitudeFilter); + this.tabPageMap.Controls.Add(this.trk_AltitudeFilter); this.tabPageMap.Controls.Add(this.chk_EnablePlanMap); this.tabPageMap.Controls.Add(this.chk_EnableDataMap); this.tabPageMap.Controls.Add(this.btn_DefaultLayers); @@ -242,40 +287,74 @@ private void InitializeComponent() this.tabPageMap.Location = new System.Drawing.Point(4, 22); this.tabPageMap.Name = "tabPageMap"; this.tabPageMap.Padding = new System.Windows.Forms.Padding(3); - this.tabPageMap.Size = new System.Drawing.Size(445, 258); + this.tabPageMap.Size = new System.Drawing.Size(444, 258); this.tabPageMap.TabIndex = 0; - this.tabPageMap.Text = Resources.SettingsMapLayersText; + this.tabPageMap.Text = global::AltitudeAngelWings.Plugin.Properties.Resources.SettingsMapLayersText; this.tabPageMap.UseVisualStyleBackColor = true; // + // lbl_AltitudeDisplay + // + this.lbl_AltitudeDisplay.Location = new System.Drawing.Point(320, 145); + this.lbl_AltitudeDisplay.Name = "lbl_AltitudeDisplay"; + this.lbl_AltitudeDisplay.Size = new System.Drawing.Size(118, 20); + this.lbl_AltitudeDisplay.TabIndex = 49; + this.lbl_AltitudeDisplay.Text = "altitude"; + this.lbl_AltitudeDisplay.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // lbl_AltitudeFilter + // + this.lbl_AltitudeFilter.AutoSize = true; + this.lbl_AltitudeFilter.Location = new System.Drawing.Point(349, 132); + this.lbl_AltitudeFilter.Name = "lbl_AltitudeFilter"; + this.lbl_AltitudeFilter.Size = new System.Drawing.Size(66, 13); + this.lbl_AltitudeFilter.TabIndex = 48; + this.lbl_AltitudeFilter.Text = "Show Below"; + // + // trk_AltitudeFilter + // + this.trk_AltitudeFilter.LargeChange = 100; + this.trk_AltitudeFilter.Location = new System.Drawing.Point(320, 100); + this.trk_AltitudeFilter.Maximum = 1000; + this.trk_AltitudeFilter.Minimum = 10; + this.trk_AltitudeFilter.Name = "trk_AltitudeFilter"; + this.trk_AltitudeFilter.Size = new System.Drawing.Size(118, 45); + this.trk_AltitudeFilter.SmallChange = 10; + this.trk_AltitudeFilter.TabIndex = 47; + this.trk_AltitudeFilter.Text = "Ground Data"; + this.trk_AltitudeFilter.TickFrequency = 100; + this.trk_AltitudeFilter.Value = 300; + this.trk_AltitudeFilter.ValueChanged += new System.EventHandler(this.trk_AltitudeFilter_ValueChanged); + // // chk_EnablePlanMap // this.chk_EnablePlanMap.AutoSize = true; - this.chk_EnablePlanMap.Location = new System.Drawing.Point(343, 134); + this.chk_EnablePlanMap.Location = new System.Drawing.Point(343, 203); this.chk_EnablePlanMap.Name = "chk_EnablePlanMap"; this.chk_EnablePlanMap.Size = new System.Drawing.Size(71, 17); this.chk_EnablePlanMap.TabIndex = 46; - this.chk_EnablePlanMap.Text = Resources.SettingsPlanMapText; + this.chk_EnablePlanMap.Text = global::AltitudeAngelWings.Plugin.Properties.Resources.SettingsPlanMapText; this.chk_EnablePlanMap.UseVisualStyleBackColor = true; this.chk_EnablePlanMap.CheckedChanged += new System.EventHandler(this.chk_EnablePlanMap_CheckedChanged); // // chk_EnableDataMap // this.chk_EnableDataMap.AutoSize = true; - this.chk_EnableDataMap.Location = new System.Drawing.Point(343, 111); + this.chk_EnableDataMap.Location = new System.Drawing.Point(343, 180); this.chk_EnableDataMap.Name = "chk_EnableDataMap"; this.chk_EnableDataMap.Size = new System.Drawing.Size(73, 17); this.chk_EnableDataMap.TabIndex = 45; - this.chk_EnableDataMap.Text = Resources.SettingsDataMapText; + this.chk_EnableDataMap.Text = global::AltitudeAngelWings.Plugin.Properties.Resources.SettingsDataMapText; this.chk_EnableDataMap.UseVisualStyleBackColor = true; this.chk_EnableDataMap.CheckedChanged += new System.EventHandler(this.chk_EnableDataMap_CheckedChanged); // // btn_DefaultLayers // - this.btn_DefaultLayers.Location = new System.Drawing.Point(348, 6); + this.btn_DefaultLayers.Location = new System.Drawing.Point(341, 6); this.btn_DefaultLayers.Name = "btn_DefaultLayers"; this.btn_DefaultLayers.Size = new System.Drawing.Size(75, 31); this.btn_DefaultLayers.TabIndex = 44; - this.btn_DefaultLayers.Text = Resources.SettingsDefaultLayersText; + this.btn_DefaultLayers.Text = global::AltitudeAngelWings.Plugin.Properties.Resources.SettingsDefaultLayersText; + this.btn_DefaultLayers.TextColorNotEnabled = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(87)))), ((int)(((byte)(4))))); this.btn_DefaultLayers.UseVisualStyleBackColor = true; this.btn_DefaultLayers.Click += new System.EventHandler(this.btn_DefaultLayers_Click); // @@ -284,39 +363,38 @@ private void InitializeComponent() this.trv_MapLayers.CheckBoxes = true; this.trv_MapLayers.Location = new System.Drawing.Point(6, 6); this.trv_MapLayers.Name = "trv_MapLayers"; - this.trv_MapLayers.Size = new System.Drawing.Size(321, 246); + this.trv_MapLayers.Size = new System.Drawing.Size(308, 246); this.trv_MapLayers.TabIndex = 18; this.trv_MapLayers.AfterCheck += new System.Windows.Forms.TreeViewEventHandler(this.trv_MapLayers_AfterCheck); // // lbl_OpacityAdjust // this.lbl_OpacityAdjust.AutoSize = true; - this.lbl_OpacityAdjust.Location = new System.Drawing.Point(349, 82); + this.lbl_OpacityAdjust.Location = new System.Drawing.Point(341, 75); this.lbl_OpacityAdjust.Name = "lbl_OpacityAdjust"; this.lbl_OpacityAdjust.Size = new System.Drawing.Size(75, 13); this.lbl_OpacityAdjust.TabIndex = 17; - this.lbl_OpacityAdjust.Text = Resources.SettingsOpacityAdjustText; + this.lbl_OpacityAdjust.Text = "Opacity Adjust"; // // trk_OpacityAdjust // this.trk_OpacityAdjust.LargeChange = 80; - this.trk_OpacityAdjust.Location = new System.Drawing.Point(343, 49); + this.trk_OpacityAdjust.Location = new System.Drawing.Point(320, 43); this.trk_OpacityAdjust.Maximum = 240; this.trk_OpacityAdjust.Minimum = 20; this.trk_OpacityAdjust.Name = "trk_OpacityAdjust"; - this.trk_OpacityAdjust.Size = new System.Drawing.Size(87, 45); + this.trk_OpacityAdjust.Size = new System.Drawing.Size(118, 45); this.trk_OpacityAdjust.SmallChange = 20; this.trk_OpacityAdjust.TabIndex = 15; - this.trk_OpacityAdjust.Text = Resources.SettingsGroundDataText; + this.trk_OpacityAdjust.Text = "Ground Data"; this.trk_OpacityAdjust.TickFrequency = 20; this.trk_OpacityAdjust.Value = 100; this.trk_OpacityAdjust.ValueChanged += new System.EventHandler(this.trk_OpacityAdjust_ValueChanged); // // tabPageFlight // - this.tabPageFlight.Controls.Add(this.lbl_FlightReportDescription); - this.tabPageFlight.Controls.Add(this.txt_FlightReportDescription); - this.tabPageFlight.Controls.Add(this.chk_FlightReportLocalScope); + this.tabPageFlight.Controls.Add(this.lbl_FlightPlanDescription); + this.tabPageFlight.Controls.Add(this.txt_FlightPlanDescription); this.tabPageFlight.Controls.Add(this.txt_SerialNumber); this.tabPageFlight.Controls.Add(this.txt_IcaoAddress); this.tabPageFlight.Controls.Add(this.txt_ContactPhone); @@ -325,47 +403,35 @@ private void InitializeComponent() this.tabPageFlight.Controls.Add(this.chk_AllowSms); this.tabPageFlight.Controls.Add(this.chk_IcaoAddress); this.tabPageFlight.Controls.Add(this.chk_UseExistingFlightPlanId); - this.tabPageFlight.Controls.Add(this.chk_FlightReportCommercial); - this.tabPageFlight.Controls.Add(this.lbl_FlightReportDuration); - this.tabPageFlight.Controls.Add(this.txt_FlightReportDuration); + this.tabPageFlight.Controls.Add(this.lbl_FlightPlanDuration); + this.tabPageFlight.Controls.Add(this.txt_FlightPlanDuration); this.tabPageFlight.Controls.Add(this.lbl_ContactPhoneNumber); - this.tabPageFlight.Controls.Add(this.lbl_FlightReportName); - this.tabPageFlight.Controls.Add(this.txt_FlightReportName); + this.tabPageFlight.Controls.Add(this.lbl_FlightPlanName); + this.tabPageFlight.Controls.Add(this.txt_FlightPlanName); this.tabPageFlight.Location = new System.Drawing.Point(4, 22); this.tabPageFlight.Name = "tabPageFlight"; this.tabPageFlight.Padding = new System.Windows.Forms.Padding(3); - this.tabPageFlight.Size = new System.Drawing.Size(445, 258); + this.tabPageFlight.Size = new System.Drawing.Size(444, 258); this.tabPageFlight.TabIndex = 1; - this.tabPageFlight.Text = Resources.SettingsFlightReportingTabText; + this.tabPageFlight.Text = "Flight Plans"; this.tabPageFlight.UseVisualStyleBackColor = true; // - // lbl_FlightReportDescription - // - this.lbl_FlightReportDescription.AutoSize = true; - this.lbl_FlightReportDescription.Location = new System.Drawing.Point(4, 91); - this.lbl_FlightReportDescription.Name = "lbl_FlightReportDescription"; - this.lbl_FlightReportDescription.Size = new System.Drawing.Size(123, 13); - this.lbl_FlightReportDescription.TabIndex = 34; - this.lbl_FlightReportDescription.Text = Resources.SettingsFlightReportDescriptionText; + // lbl_FlightPlanDescription // - // txt_FlightReportDescription + this.lbl_FlightPlanDescription.AutoSize = true; + this.lbl_FlightPlanDescription.Location = new System.Drawing.Point(4, 91); + this.lbl_FlightPlanDescription.Name = "lbl_FlightPlanDescription"; + this.lbl_FlightPlanDescription.Size = new System.Drawing.Size(112, 13); + this.lbl_FlightPlanDescription.TabIndex = 34; + this.lbl_FlightPlanDescription.Text = "Flight Plan Description"; // - this.txt_FlightReportDescription.Location = new System.Drawing.Point(9, 107); - this.txt_FlightReportDescription.Name = "txt_FlightReportDescription"; - this.txt_FlightReportDescription.Size = new System.Drawing.Size(172, 20); - this.txt_FlightReportDescription.TabIndex = 33; - this.txt_FlightReportDescription.TextChanged += new System.EventHandler(this.txt_FlightReportDescription_TextChanged); + // txt_FlightPlanDescription // - // chk_FlightReportLocalScope - // - this.chk_FlightReportLocalScope.AutoSize = true; - this.chk_FlightReportLocalScope.Location = new System.Drawing.Point(7, 193); - this.chk_FlightReportLocalScope.Name = "chk_FlightReportLocalScope"; - this.chk_FlightReportLocalScope.Size = new System.Drawing.Size(146, 17); - this.chk_FlightReportLocalScope.TabIndex = 32; - this.chk_FlightReportLocalScope.Text = Resources.SettingsUseLocalConflictScopeText; - this.chk_FlightReportLocalScope.UseVisualStyleBackColor = true; - this.chk_FlightReportLocalScope.CheckedChanged += new System.EventHandler(this.chk_FlightReportLocalScope_CheckedChanged); + this.txt_FlightPlanDescription.Location = new System.Drawing.Point(6, 110); + this.txt_FlightPlanDescription.Name = "txt_FlightPlanDescription"; + this.txt_FlightPlanDescription.Size = new System.Drawing.Size(172, 20); + this.txt_FlightPlanDescription.TabIndex = 33; + this.txt_FlightPlanDescription.TextChanged += new System.EventHandler(this.txt_FlightPlanDescription_TextChanged); // // txt_SerialNumber // @@ -402,7 +468,7 @@ private void InitializeComponent() this.chk_SerialNumber.Name = "chk_SerialNumber"; this.chk_SerialNumber.Size = new System.Drawing.Size(92, 17); this.chk_SerialNumber.TabIndex = 30; - this.chk_SerialNumber.Text = Resources.SettingsSerialNumberText; + this.chk_SerialNumber.Text = global::AltitudeAngelWings.Plugin.Properties.Resources.SettingsSerialNumberText; this.chk_SerialNumber.UseVisualStyleBackColor = true; this.chk_SerialNumber.CheckedChanged += new System.EventHandler(this.chk_SerialNumber_CheckedChanged); // @@ -423,7 +489,7 @@ private void InitializeComponent() this.chk_AllowSms.Name = "chk_AllowSms"; this.chk_AllowSms.Size = new System.Drawing.Size(117, 17); this.chk_AllowSms.TabIndex = 30; - this.chk_AllowSms.Text = Resources.SettingsAllowSMSContactText; + this.chk_AllowSms.Text = global::AltitudeAngelWings.Plugin.Properties.Resources.SettingsAllowSMSContactText; this.chk_AllowSms.UseVisualStyleBackColor = true; this.chk_AllowSms.CheckedChanged += new System.EventHandler(this.chk_AllowSms_CheckedChanged); // @@ -435,7 +501,7 @@ private void InitializeComponent() this.chk_IcaoAddress.Name = "chk_IcaoAddress"; this.chk_IcaoAddress.Size = new System.Drawing.Size(92, 17); this.chk_IcaoAddress.TabIndex = 30; - this.chk_IcaoAddress.Text = Resources.SettingsICAOAddressText; + this.chk_IcaoAddress.Text = global::AltitudeAngelWings.Plugin.Properties.Resources.SettingsICAOAddressText; this.chk_IcaoAddress.UseVisualStyleBackColor = true; this.chk_IcaoAddress.CheckedChanged += new System.EventHandler(this.chk_IcaoAddress_CheckedChanged); // @@ -443,41 +509,30 @@ private void InitializeComponent() // this.chk_UseExistingFlightPlanId.AutoSize = true; this.chk_UseExistingFlightPlanId.Enabled = false; - this.chk_UseExistingFlightPlanId.Location = new System.Drawing.Point(6, 6); + this.chk_UseExistingFlightPlanId.Location = new System.Drawing.Point(6, 10); this.chk_UseExistingFlightPlanId.Name = "chk_UseExistingFlightPlanId"; this.chk_UseExistingFlightPlanId.Size = new System.Drawing.Size(150, 17); this.chk_UseExistingFlightPlanId.TabIndex = 30; - this.chk_UseExistingFlightPlanId.Text = Resources.SettingsUseExistingFlightPlanIDText; + this.chk_UseExistingFlightPlanId.Text = global::AltitudeAngelWings.Plugin.Properties.Resources.SettingsUseExistingFlightPlanIDText; this.chk_UseExistingFlightPlanId.UseVisualStyleBackColor = true; this.chk_UseExistingFlightPlanId.CheckedChanged += new System.EventHandler(this.chk_UseExistingFlightPlanId_CheckedChanged); // - // chk_FlightReportCommercial - // - this.chk_FlightReportCommercial.AutoSize = true; - this.chk_FlightReportCommercial.Location = new System.Drawing.Point(7, 133); - this.chk_FlightReportCommercial.Name = "chk_FlightReportCommercial"; - this.chk_FlightReportCommercial.Size = new System.Drawing.Size(108, 17); - this.chk_FlightReportCommercial.TabIndex = 24; - this.chk_FlightReportCommercial.Text = Resources.SettingsCommercialFlightText; - this.chk_FlightReportCommercial.UseVisualStyleBackColor = true; - this.chk_FlightReportCommercial.CheckedChanged += new System.EventHandler(this.chk_FlightReportCommercial_CheckedChanged); - // - // lbl_FlightReportDuration + // lbl_FlightPlanDuration // - this.lbl_FlightReportDuration.AutoSize = true; - this.lbl_FlightReportDuration.Location = new System.Drawing.Point(3, 151); - this.lbl_FlightReportDuration.Name = "lbl_FlightReportDuration"; - this.lbl_FlightReportDuration.Size = new System.Drawing.Size(140, 13); - this.lbl_FlightReportDuration.TabIndex = 28; - this.lbl_FlightReportDuration.Text = Resources.SettingsFlightReportDurationText; + this.lbl_FlightPlanDuration.AutoSize = true; + this.lbl_FlightPlanDuration.Location = new System.Drawing.Point(3, 133); + this.lbl_FlightPlanDuration.Name = "lbl_FlightPlanDuration"; + this.lbl_FlightPlanDuration.Size = new System.Drawing.Size(144, 13); + this.lbl_FlightPlanDuration.TabIndex = 28; + this.lbl_FlightPlanDuration.Text = "Flight Plan Duration (minutes)"; // - // txt_FlightReportDuration + // txt_FlightPlanDuration // - this.txt_FlightReportDuration.Location = new System.Drawing.Point(8, 167); - this.txt_FlightReportDuration.Name = "txt_FlightReportDuration"; - this.txt_FlightReportDuration.Size = new System.Drawing.Size(172, 20); - this.txt_FlightReportDuration.TabIndex = 26; - this.txt_FlightReportDuration.TextChanged += new System.EventHandler(this.txt_FlightReportDuration_TextChanged); + this.txt_FlightPlanDuration.Location = new System.Drawing.Point(6, 149); + this.txt_FlightPlanDuration.Name = "txt_FlightPlanDuration"; + this.txt_FlightPlanDuration.Size = new System.Drawing.Size(172, 20); + this.txt_FlightPlanDuration.TabIndex = 26; + this.txt_FlightPlanDuration.TextChanged += new System.EventHandler(this.txt_FlightPlanDuration_TextChanged); // // lbl_ContactPhoneNumber // @@ -486,97 +541,69 @@ private void InitializeComponent() this.lbl_ContactPhoneNumber.Name = "lbl_ContactPhoneNumber"; this.lbl_ContactPhoneNumber.Size = new System.Drawing.Size(118, 13); this.lbl_ContactPhoneNumber.TabIndex = 27; - this.lbl_ContactPhoneNumber.Text = Resources.SettingsContactPhoneNumberText; + this.lbl_ContactPhoneNumber.Text = "Contact Phone Number"; // - // lbl_FlightReportName + // lbl_FlightPlanName // - this.lbl_FlightReportName.AutoSize = true; - this.lbl_FlightReportName.Location = new System.Drawing.Point(3, 52); - this.lbl_FlightReportName.Name = "lbl_FlightReportName"; - this.lbl_FlightReportName.Size = new System.Drawing.Size(98, 13); - this.lbl_FlightReportName.TabIndex = 27; - this.lbl_FlightReportName.Text = Resources.SettingsFlightReportNameText; + this.lbl_FlightPlanName.AutoSize = true; + this.lbl_FlightPlanName.Location = new System.Drawing.Point(3, 52); + this.lbl_FlightPlanName.Name = "lbl_FlightPlanName"; + this.lbl_FlightPlanName.Size = new System.Drawing.Size(87, 13); + this.lbl_FlightPlanName.TabIndex = 27; + this.lbl_FlightPlanName.Text = "Flight Plan Name"; // - // txt_FlightReportName + // txt_FlightPlanName // - this.txt_FlightReportName.Location = new System.Drawing.Point(8, 68); - this.txt_FlightReportName.Name = "txt_FlightReportName"; - this.txt_FlightReportName.Size = new System.Drawing.Size(172, 20); - this.txt_FlightReportName.TabIndex = 25; - this.txt_FlightReportName.TextChanged += new System.EventHandler(this.txt_FlightReportName_TextChanged); + this.txt_FlightPlanName.Location = new System.Drawing.Point(6, 68); + this.txt_FlightPlanName.Name = "txt_FlightPlanName"; + this.txt_FlightPlanName.Size = new System.Drawing.Size(172, 20); + this.txt_FlightPlanName.TabIndex = 25; + this.txt_FlightPlanName.TextChanged += new System.EventHandler(this.txt_FlightPlanName_TextChanged); // // tabPageAbout // this.tabPageAbout.Controls.Add(this.web_About); - this.tabPageAbout.Controls.Add(this.but_Disable); - this.tabPageAbout.Controls.Add(this.but_Enable); - this.tabPageAbout.Controls.Add(this.pic_AboutLogo); this.tabPageAbout.Location = new System.Drawing.Point(4, 22); this.tabPageAbout.Name = "tabPageAbout"; - this.tabPageAbout.Size = new System.Drawing.Size(445, 258); + this.tabPageAbout.Size = new System.Drawing.Size(444, 258); this.tabPageAbout.TabIndex = 3; - this.tabPageAbout.Text = Resources.SettingsAboutTabText; + this.tabPageAbout.Text = global::AltitudeAngelWings.Plugin.Properties.Resources.SettingsAboutTabText; this.tabPageAbout.UseVisualStyleBackColor = true; // - // but_Disable - // - this.but_Disable.Location = new System.Drawing.Point(334, 180); - this.but_Disable.Name = "but_Disable"; - this.but_Disable.Size = new System.Drawing.Size(75, 23); - this.but_Disable.TabIndex = 44; - this.but_Disable.Text = Resources.SettingsDisableText; - this.but_Disable.UseVisualStyleBackColor = true; - this.but_Disable.Click += new System.EventHandler(this.but_Disable_Click); - // - // but_Enable - // - this.but_Enable.Location = new System.Drawing.Point(334, 151); - this.but_Enable.Name = "but_Enable"; - this.but_Enable.Size = new System.Drawing.Size(75, 23); - this.but_Enable.TabIndex = 43; - this.but_Enable.Text = Resources.SettingsEnableText; - this.but_Enable.UseVisualStyleBackColor = true; - this.but_Enable.Click += new System.EventHandler(this.but_Enable_Click); - // - // pic_AboutLogo - // - this.pic_AboutLogo.Location = new System.Drawing.Point(302, 3); - this.pic_AboutLogo.Name = "pic_AboutLogo"; - this.pic_AboutLogo.Size = new System.Drawing.Size(140, 100); - this.pic_AboutLogo.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage; - this.pic_AboutLogo.TabIndex = 40; - this.pic_AboutLogo.TabStop = false; - // // web_About // - this.web_About.AllowNavigation = false; this.web_About.AllowWebBrowserDrop = false; + this.web_About.Dock = System.Windows.Forms.DockStyle.Fill; this.web_About.IsWebBrowserContextMenuEnabled = false; - this.web_About.Location = new System.Drawing.Point(3, 3); + this.web_About.Location = new System.Drawing.Point(0, 0); this.web_About.MinimumSize = new System.Drawing.Size(20, 20); this.web_About.Name = "web_About"; - this.web_About.Size = new System.Drawing.Size(293, 250); + this.web_About.ScriptErrorsSuppressed = true; + this.web_About.Size = new System.Drawing.Size(444, 258); this.web_About.TabIndex = 45; this.web_About.WebBrowserShortcutsEnabled = false; + this.web_About.NewWindow += new System.ComponentModel.CancelEventHandler(this.web_About_NewWindow); // // AASettings // this.ClientSize = new System.Drawing.Size(476, 304); this.Controls.Add(this.tabPages); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; this.Name = "AASettings"; - this.StartPosition = FormStartPosition.CenterParent; - this.Text = Resources.SettingsWindowTitleText; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Altitude Angel Settings"; this.tabPages.ResumeLayout(false); this.tabPageAccount.ResumeLayout(false); this.tabPageAccount.PerformLayout(); this.tabPageMap.ResumeLayout(false); this.tabPageMap.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.trk_AltitudeFilter)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.trk_OpacityAdjust)).EndInit(); this.tabPageFlight.ResumeLayout(false); this.tabPageFlight.PerformLayout(); this.tabPageAbout.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.pic_AboutLogo)).EndInit(); this.ResumeLayout(false); } @@ -585,23 +612,20 @@ private void InitializeComponent() private System.Windows.Forms.TabControl tabPages; private System.Windows.Forms.TabPage tabPageMap; private System.Windows.Forms.TabPage tabPageFlight; - private System.Windows.Forms.CheckBox chk_FlightReportLocalScope; private System.Windows.Forms.TextBox txt_ExistingFlightPlanId; private System.Windows.Forms.CheckBox chk_UseExistingFlightPlanId; - private System.Windows.Forms.CheckBox chk_FlightReportCommercial; - private System.Windows.Forms.Label lbl_FlightReportDuration; - private System.Windows.Forms.TextBox txt_FlightReportDuration; - private System.Windows.Forms.Label lbl_FlightReportName; - private System.Windows.Forms.TextBox txt_FlightReportName; + private System.Windows.Forms.Label lbl_FlightPlanDuration; + private System.Windows.Forms.TextBox txt_FlightPlanDuration; + private System.Windows.Forms.Label lbl_FlightPlanName; + private System.Windows.Forms.TextBox txt_FlightPlanName; private System.Windows.Forms.TabPage tabPageAccount; private MyButton but_SignOut; private MyButton but_SignIn; - private System.Windows.Forms.LinkLabel lbl_FlightReportWhat; - private System.Windows.Forms.CheckBox chk_FlightReportEnable; - private System.Windows.Forms.Label lbl_FlightReportDescription; - private System.Windows.Forms.TextBox txt_FlightReportDescription; + private System.Windows.Forms.CheckBox chk_FlightPlansEnable; + private System.Windows.Forms.Label lbl_FlightPlanDescription; + private System.Windows.Forms.TextBox txt_FlightPlanDescription; private System.Windows.Forms.Label lbl_OverrideClientSuffix; - private System.Windows.Forms.TextBox txt_OverrideUrlSuffix; + private System.Windows.Forms.TextBox txt_OverrideClientSuffix; private System.Windows.Forms.Label lbl_OverrideClientSecret; private System.Windows.Forms.TextBox txt_OverrideClientSecret; private System.Windows.Forms.Label lbl_OverrideClientId; @@ -611,9 +635,6 @@ private void InitializeComponent() private System.Windows.Forms.TrackBar trk_OpacityAdjust; private System.Windows.Forms.Label lbl_OpacityAdjust; private System.Windows.Forms.TabPage tabPageAbout; - private MyButton but_Enable; - private System.Windows.Forms.PictureBox pic_AboutLogo; - private MyButton but_Disable; private System.Windows.Forms.TreeView trv_MapLayers; private MyButton btn_DefaultLayers; private System.Windows.Forms.CheckBox chk_EnablePlanMap; @@ -626,5 +647,12 @@ private void InitializeComponent() private System.Windows.Forms.CheckBox chk_IcaoAddress; private System.Windows.Forms.Label lbl_ContactPhoneNumber; private System.Windows.Forms.WebBrowser web_About; + private Label lbl_AltitudeDisplay; + private Label lbl_AltitudeFilter; + private TrackBar trk_AltitudeFilter; + private MyButton but_Disable; + private MyButton but_Enable; + private CheckBox chk_FlightsEnable; + private ComboBox lst_FlightTelemetry; } } \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings.Plugin/AASettings.cs b/ExtLibs/AltitudeAngelWings.Plugin/AASettings.cs index 474d27c7db..ed26f977bd 100644 --- a/ExtLibs/AltitudeAngelWings.Plugin/AASettings.cs +++ b/ExtLibs/AltitudeAngelWings.Plugin/AASettings.cs @@ -1,13 +1,15 @@ using System; using System.Diagnostics; using System.Drawing; -using System.IO; using System.Linq; using System.Reactive; using System.Reactive.Linq; +using System.Threading; +using System.Threading.Tasks; using System.Windows.Forms; -using AltitudeAngelWings.ApiClient.Client; -using AltitudeAngelWings.Extra; +using AltitudeAngelWings.Clients.Api.Model; +using AltitudeAngelWings.Clients.Auth; +using AltitudeAngelWings.Clients.Auth.Model; using AltitudeAngelWings.Plugin.Properties; using AltitudeAngelWings.Service; using MissionPlanner; @@ -20,42 +22,63 @@ internal partial class AASettings : Form private readonly ISettings _settings; private readonly IAltitudeAngelService _altitudeAngelService; private readonly IMissionPlanner _missionPlanner; + private readonly IAuthClient _authClient; - public AASettings() + private static readonly object InstanceLock = new object(); + private static AASettings _instance; + + private AASettings() : this(ServiceLocator.GetService(), ServiceLocator.GetService(), - ServiceLocator.GetService()) + ServiceLocator.GetService(), + ServiceLocator.GetService()) { } - private AASettings(ISettings settings, IAltitudeAngelService altitudeAngelService, IMissionPlanner missionPlanner) + public static AASettings Instance + { + get + { + lock (InstanceLock) + { + if (_instance == null || _instance.IsDisposed) + { + _instance = new AASettings(); + } + } + return _instance; + } + } + + private AASettings(ISettings settings, IAltitudeAngelService altitudeAngelService, IMissionPlanner missionPlanner, IAuthClient authClient) { _settings = settings; _altitudeAngelService = altitudeAngelService; _missionPlanner = missionPlanner; + _authClient = authClient; InitializeComponent(); - _altitudeAngelService.IsSignedIn.ObserveOn(MainV2.instance).Subscribe(OnSignInChange); + _altitudeAngelService.IsSignedIn.ObserveOn(MainV2.instance).SubscribeWithAsync(OnSignInChange); _missionPlanner.FlightDataMap.MapChanged.ObserveOn(MainV2.instance).Subscribe(OnMapChanged); _missionPlanner.FlightPlanningMap.MapChanged.ObserveOn(MainV2.instance).Subscribe(OnMapChanged); ThemeManager.ApplyThemeTo(this); - pic_AboutLogo.Image = Image.FromStream(new MemoryStream(Resources.AALogo)); + Icon = Resources.AAIconBlack; // load settings - chk_FlightReportEnable.Checked = _settings.FlightReportEnable; + chk_FlightPlansEnable.Checked = _settings.UseFlightPlans; + chk_FlightsEnable.Checked = _settings.UseFlights; + lst_FlightTelemetry.SelectedIndex = (int)_settings.SendFlightTelemetry; chk_UseExistingFlightPlanId.Checked = _settings.UseExistingFlightPlanId; txt_ExistingFlightPlanId.Text = _settings.ExistingFlightPlanId == Guid.Empty ? "" : _settings.ExistingFlightPlanId.ToString(); - txt_FlightReportName.Text = _settings.FlightReportName; - txt_FlightReportDescription.Text = _settings.FlightReportDescription; - chk_FlightReportCommercial.Checked = _settings.FlightReportCommercial; - chk_FlightReportLocalScope.Checked = _settings.UseFlightPlanLocalScope; - txt_FlightReportDuration.Text = ((int)_settings.FlightReportTimeSpan.TotalMinutes).ToString(); - but_SignIn.Enabled = !_altitudeAngelService.IsSignedIn; - but_SignOut.Enabled = _altitudeAngelService.IsSignedIn; + txt_FlightPlanName.Text = _settings.FlightPlanName; + txt_FlightPlanDescription.Text = _settings.FlightPlanDescription; + txt_FlightPlanDuration.Text = ((int)_settings.FlightPlanTimeSpan.TotalMinutes).ToString(); + but_SignIn.Enabled = !_altitudeAngelService.IsSignedIn.Value; + but_SignOut.Enabled = _altitudeAngelService.IsSignedIn.Value; chk_OverrideClientSettings.Checked = _settings.OverrideClientUrlSettings; txt_OverrideClientId.Text = _settings.OverrideClientId; txt_OverrideClientSecret.Text = _settings.OverrideClientSecret; - txt_OverrideUrlSuffix.Text = _settings.OverrideUrlDomainSuffix; + txt_OverrideClientSuffix.Text = _settings.OverrideUrlDomainSuffix; var opacityAdjust = (int)_settings.MapOpacityAdjust * 100; if (opacityAdjust >= trk_OpacityAdjust.Minimum && opacityAdjust <= trk_OpacityAdjust.Maximum) { @@ -70,6 +93,12 @@ private AASettings(ISettings settings, IAltitudeAngelService altitudeAngelServic chk_SerialNumber.Checked = _settings.FlightIdentifierSerial; txt_SerialNumber.Text = _settings.FlightIdentifierSerialNumber; web_About.DocumentText = Resources.About; + trk_AltitudeFilter.Value = _settings.AltitudeFilter; + ((SHDocVw.WebBrowser)web_About.ActiveXInstance).NewWindow3 += + (ref object o, ref bool b, uint u, string s, string url) => + { + Process.Start(url); + }; RefreshControlStates(); } @@ -104,36 +133,37 @@ private void trv_MapLayers_AfterCheck(object sender, TreeViewEventArgs e) ProcessMapsFromCache(); } - private void txt_FlightReportName_TextChanged(object sender, EventArgs e) + private void txt_FlightPlanName_TextChanged(object sender, EventArgs e) { - _settings.FlightReportName = txt_FlightReportName.Text; + _settings.FlightPlanName = txt_FlightPlanName.Text; RefreshControlStates(); } - private void txt_FlightReportDuration_TextChanged(object sender, EventArgs e) + private void txt_FlightPlanDuration_TextChanged(object sender, EventArgs e) { - if (int.TryParse(txt_FlightReportDuration.Text, out var minutes)) + if (int.TryParse(txt_FlightPlanDuration.Text, out var minutes)) { - _settings.FlightReportTimeSpan = TimeSpan.FromMinutes(minutes); + _settings.FlightPlanTimeSpan = TimeSpan.FromMinutes(minutes); } RefreshControlStates(); } - private void chk_FlightReportCommercial_CheckedChanged(object sender, EventArgs e) + private void chk_FlightPlansEnable_CheckedChanged(object sender, EventArgs e) { - _settings.FlightReportCommercial = chk_FlightReportCommercial.Checked; + _settings.UseFlightPlans = chk_FlightPlansEnable.Checked; RefreshControlStates(); } - - private void chk_FlightReportEnable_CheckedChanged(object sender, EventArgs e) + + private void chk_FlightsEnable_CheckedChanged(object sender, EventArgs e) { - _settings.FlightReportEnable = chk_FlightReportEnable.Checked; + _settings.UseFlights = chk_FlightsEnable.Checked; RefreshControlStates(); } - private void lbl_FlightReportWhat_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + private void lst_FlightTelemetry_SelectedIndexChanged(object sender, EventArgs e) { - Process.Start("http://bit.ly/aamissionplanner1"); + _settings.SendFlightTelemetry = (FlightTelemetry)lst_FlightTelemetry.SelectedIndex; + RefreshControlStates(); } private void chk_UseExistingFlightPlanId_CheckedChanged(object sender, EventArgs e) @@ -148,44 +178,34 @@ private void txt_ExistingFlightPlanId_TextChanged(object sender, EventArgs e) RefreshControlStates(); } - private void chk_FlightReportLocalScope_CheckedChanged(object sender, EventArgs e) - { - _settings.UseFlightPlanLocalScope = chk_FlightReportLocalScope.Checked; - RefreshControlStates(); - } - - private void txt_FlightReportDescription_TextChanged(object sender, EventArgs e) + private void txt_FlightPlanDescription_TextChanged(object sender, EventArgs e) { - _settings.FlightReportDescription = txt_FlightReportDescription.Text; + _settings.FlightPlanDescription = txt_FlightPlanDescription.Text; RefreshControlStates(); } private void chk_OverrideClientSettings_CheckedChanged(object sender, EventArgs e) { - var changed = _settings.OverrideClientUrlSettings != chk_OverrideClientSettings.Checked; _settings.OverrideClientUrlSettings = chk_OverrideClientSettings.Checked; - RefreshControlStates(changed); + RefreshControlStates(); } private void txt_OverrideClientId_TextChanged(object sender, EventArgs e) { - var changed = _settings.OverrideClientId != txt_OverrideClientId.Text; _settings.OverrideClientId = txt_OverrideClientId.Text; - RefreshControlStates(changed); + RefreshControlStates(); } private void txt_OverrideClientSecret_TextChanged(object sender, EventArgs e) { - var changed = _settings.OverrideClientSecret != txt_OverrideClientSecret.Text; _settings.OverrideClientSecret = txt_OverrideClientSecret.Text; - RefreshControlStates(changed); + RefreshControlStates(); } private void txt_OverrideUrlSuffix_TextChanged(object sender, EventArgs e) { - var changed = _settings.OverrideUrlDomainSuffix != txt_OverrideUrlSuffix.Text; - _settings.OverrideUrlDomainSuffix = txt_OverrideUrlSuffix.Text; - RefreshControlStates(changed); + _settings.OverrideUrlDomainSuffix = txt_OverrideClientSuffix.Text; + RefreshControlStates(); } private void trk_OpacityAdjust_ValueChanged(object sender, EventArgs e) @@ -194,6 +214,13 @@ private void trk_OpacityAdjust_ValueChanged(object sender, EventArgs e) RefreshControlStates(processMap: true); } + + private void trk_AltitudeFilter_ValueChanged(object sender, EventArgs e) + { + _settings.AltitudeFilter = trk_AltitudeFilter.Value; + RefreshControlStates(processMap: true); + } + private void but_Enable_Click(object sender, EventArgs e) { _settings.CheckEnableAltitudeAngel = true; @@ -261,11 +288,20 @@ private void txt_SerialNumber_TextChanged(object sender, EventArgs e) RefreshControlStates(); } - private void OnSignInChange(bool signedIn) + private async Task OnSignInChange(bool signedIn, CancellationToken cancellationToken) { - var user = _altitudeAngelService.CurrentUser; - lbl_UserDetails.Text = signedIn - ? $"{user.FirstName} {user.LastName}\r\n{user.EmailAddress}\r\n{user.UserId}": string.Empty; + lbl_UserDetails.Text = string.Empty; + if (_settings.TokenResponse.IsValidForAuth()) + { + var user = await _authClient.GetUserProfile(_settings.TokenResponse.AccessToken, cancellationToken); + if (user != null) + { + lbl_UserDetails.Text = signedIn + ? $"{user.FirstName} {user.LastName}\r\n{user.EmailAddress}\r\n{user.UserId}\r\n{string.Join("\r\n", _settings.TokenResponse.AccessTokenScopes())}" + : string.Empty; + } + } + RefreshControlStates(); } @@ -280,7 +316,7 @@ private void ProcessMapsFromCache(bool resetFilters = false) _altitudeAngelService.ProcessAllFromCache(_missionPlanner.FlightPlanningMap, resetFilters); } - private void AddUpdateFilterInfoTree(TreeNode parent) + private void AddUpdateFilterInfoTree(TreeNode parent = null) { foreach (var item in _altitudeAngelService .FilterInfoDisplay @@ -314,55 +350,63 @@ private void RefreshControlStates(bool signOut = false, bool processMap = false, } trv_MapLayers.BeginUpdate(); - AddUpdateFilterInfoTree(null); + AddUpdateFilterInfoTree(); trv_MapLayers.ExpandAll(); trv_MapLayers.EndUpdate(); - but_SignIn.Enabled = !_altitudeAngelService.IsSignedIn; - but_SignOut.Enabled = _altitudeAngelService.IsSignedIn; - trv_MapLayers.Enabled = _altitudeAngelService.IsSignedIn; - trk_OpacityAdjust.Enabled = _altitudeAngelService.IsSignedIn; - chk_FlightReportEnable.Enabled = _altitudeAngelService.IsSignedIn; - chk_UseExistingFlightPlanId.Enabled = _altitudeAngelService.IsSignedIn && chk_FlightReportEnable.Checked; - txt_ExistingFlightPlanId.Enabled = _altitudeAngelService.IsSignedIn && chk_UseExistingFlightPlanId.Checked; - lbl_FlightReportName.Enabled = _altitudeAngelService.IsSignedIn && chk_FlightReportEnable.Checked && !chk_UseExistingFlightPlanId.Checked; - txt_FlightReportName.Enabled = _altitudeAngelService.IsSignedIn && chk_FlightReportEnable.Checked && !chk_UseExistingFlightPlanId.Checked; - lbl_FlightReportDescription.Enabled = _altitudeAngelService.IsSignedIn && chk_FlightReportEnable.Checked && !chk_UseExistingFlightPlanId.Checked; - txt_FlightReportDescription.Enabled = _altitudeAngelService.IsSignedIn && chk_FlightReportEnable.Checked && !chk_UseExistingFlightPlanId.Checked; - lbl_FlightReportDuration.Enabled = _altitudeAngelService.IsSignedIn && chk_FlightReportEnable.Checked && !chk_UseExistingFlightPlanId.Checked; - txt_FlightReportDuration.Enabled = _altitudeAngelService.IsSignedIn && chk_FlightReportEnable.Checked && !chk_UseExistingFlightPlanId.Checked; - chk_FlightReportCommercial.Enabled = _altitudeAngelService.IsSignedIn && chk_FlightReportEnable.Checked && !chk_UseExistingFlightPlanId.Checked; - chk_FlightReportLocalScope.Enabled = _altitudeAngelService.IsSignedIn && chk_FlightReportEnable.Checked && !chk_UseExistingFlightPlanId.Checked; - lbl_OverrideClientId.Enabled = _settings.OverrideClientUrlSettings; - txt_OverrideClientId.Enabled = _settings.OverrideClientUrlSettings; - lbl_OverrideClientSecret.Enabled = _settings.OverrideClientUrlSettings; - txt_OverrideClientSecret.Enabled = _settings.OverrideClientUrlSettings; - lbl_OverrideClientSuffix.Enabled = _settings.OverrideClientUrlSettings; - txt_OverrideUrlSuffix.Enabled = _settings.OverrideClientUrlSettings; - chk_AllowSms.Enabled = _altitudeAngelService.IsSignedIn && chk_FlightReportEnable.Checked && !chk_UseExistingFlightPlanId.Checked; - txt_ContactPhone.Enabled = _altitudeAngelService.IsSignedIn && chk_FlightReportEnable.Checked && !chk_UseExistingFlightPlanId.Checked; - chk_IcaoAddress.Enabled = _altitudeAngelService.IsSignedIn && chk_FlightReportEnable.Checked && !chk_UseExistingFlightPlanId.Checked; - txt_IcaoAddress.Enabled = _altitudeAngelService.IsSignedIn && chk_FlightReportEnable.Checked && !chk_UseExistingFlightPlanId.Checked && chk_IcaoAddress.Checked; - chk_SerialNumber.Enabled = _altitudeAngelService.IsSignedIn && chk_FlightReportEnable.Checked && !chk_UseExistingFlightPlanId.Checked; - txt_SerialNumber.Enabled = _altitudeAngelService.IsSignedIn && chk_FlightReportEnable.Checked && !chk_UseExistingFlightPlanId.Checked && chk_SerialNumber.Checked; - - SetTabVisibility(tabPageAccount, _settings.CheckEnableAltitudeAngel); - SetTabVisibility(tabPageMap, _settings.CheckEnableAltitudeAngel && _altitudeAngelService.IsSignedIn); - SetTabVisibility(tabPageFlight, _settings.CheckEnableAltitudeAngel && _altitudeAngelService.IsSignedIn && chk_FlightReportEnable.Checked); + but_SignIn.Enabled = _settings.CheckEnableAltitudeAngel && !_altitudeAngelService.IsSignedIn.Value; + but_SignOut.Enabled = _altitudeAngelService.IsSignedIn.Value; + trv_MapLayers.Enabled = _altitudeAngelService.IsSignedIn.Value; + trk_OpacityAdjust.Enabled = _altitudeAngelService.IsSignedIn.Value; + chk_FlightPlansEnable.Enabled = _altitudeAngelService.IsSignedIn.Value; + chk_FlightsEnable.Enabled = _altitudeAngelService.IsSignedIn.Value && chk_FlightPlansEnable.Checked; + lst_FlightTelemetry.Enabled = _altitudeAngelService.IsSignedIn.Value && chk_FlightPlansEnable.Checked && chk_FlightsEnable.Checked; + chk_UseExistingFlightPlanId.Enabled = _altitudeAngelService.IsSignedIn.Value && chk_FlightPlansEnable.Checked; + txt_ExistingFlightPlanId.Enabled = _altitudeAngelService.IsSignedIn.Value && chk_UseExistingFlightPlanId.Checked; + lbl_FlightPlanName.Enabled = _altitudeAngelService.IsSignedIn.Value && chk_FlightPlansEnable.Checked && !chk_UseExistingFlightPlanId.Checked; + txt_FlightPlanName.Enabled = _altitudeAngelService.IsSignedIn.Value && chk_FlightPlansEnable.Checked && !chk_UseExistingFlightPlanId.Checked; + lbl_FlightPlanDescription.Enabled = _altitudeAngelService.IsSignedIn.Value && chk_FlightPlansEnable.Checked && !chk_UseExistingFlightPlanId.Checked; + txt_FlightPlanDescription.Enabled = _altitudeAngelService.IsSignedIn.Value && chk_FlightPlansEnable.Checked && !chk_UseExistingFlightPlanId.Checked; + lbl_FlightPlanDuration.Enabled = _altitudeAngelService.IsSignedIn.Value && chk_FlightPlansEnable.Checked && !chk_UseExistingFlightPlanId.Checked; + txt_FlightPlanDuration.Enabled = _altitudeAngelService.IsSignedIn.Value && chk_FlightPlansEnable.Checked && !chk_UseExistingFlightPlanId.Checked; + chk_OverrideClientSettings.Enabled = _settings.CheckEnableAltitudeAngel && !_altitudeAngelService.IsSignedIn.Value; + lbl_OverrideClientId.Enabled = _settings.OverrideClientUrlSettings && !_altitudeAngelService.IsSignedIn.Value; + txt_OverrideClientId.Enabled = _settings.OverrideClientUrlSettings && !_altitudeAngelService.IsSignedIn.Value; + lbl_OverrideClientSecret.Enabled = _settings.OverrideClientUrlSettings && !_altitudeAngelService.IsSignedIn.Value; + txt_OverrideClientSecret.Enabled = _settings.OverrideClientUrlSettings && !_altitudeAngelService.IsSignedIn.Value; + lbl_OverrideClientSuffix.Enabled = _settings.OverrideClientUrlSettings && !_altitudeAngelService.IsSignedIn.Value; + txt_OverrideClientSuffix.Enabled = _settings.OverrideClientUrlSettings && !_altitudeAngelService.IsSignedIn.Value; + chk_AllowSms.Enabled = _altitudeAngelService.IsSignedIn.Value && chk_FlightPlansEnable.Checked && !chk_UseExistingFlightPlanId.Checked; + txt_ContactPhone.Enabled = _altitudeAngelService.IsSignedIn.Value && chk_FlightPlansEnable.Checked && !chk_UseExistingFlightPlanId.Checked; + chk_IcaoAddress.Enabled = _altitudeAngelService.IsSignedIn.Value && chk_FlightPlansEnable.Checked && !chk_UseExistingFlightPlanId.Checked; + txt_IcaoAddress.Enabled = _altitudeAngelService.IsSignedIn.Value && chk_FlightPlansEnable.Checked && !chk_UseExistingFlightPlanId.Checked && chk_IcaoAddress.Checked; + chk_SerialNumber.Enabled = _altitudeAngelService.IsSignedIn.Value && chk_FlightPlansEnable.Checked && !chk_UseExistingFlightPlanId.Checked; + txt_SerialNumber.Enabled = _altitudeAngelService.IsSignedIn.Value && chk_FlightPlansEnable.Checked && !chk_UseExistingFlightPlanId.Checked && chk_SerialNumber.Checked; + trk_AltitudeFilter.Enabled = _altitudeAngelService.IsSignedIn.Value; + lbl_AltitudeDisplay.Text = $"{_settings.AltitudeFilter}m ({_settings.AltitudeFilter*3.28084:F0}ft) AGL"; + + SetTabVisibility(tabPageMap, _settings.CheckEnableAltitudeAngel && _altitudeAngelService.IsSignedIn.Value); + SetTabVisibility(tabPageFlight, _settings.CheckEnableAltitudeAngel && _altitudeAngelService.IsSignedIn.Value && chk_FlightPlansEnable.Checked); ResumeLayout(true); } private void SetTabVisibility(TabPage tabPage, bool visible) { var exists = tabPages.TabPages.Contains(tabPage); - if (visible && !exists) - { - tabPages.TabPages.Insert(tabPages.TabPages.IndexOf(tabPageAbout), tabPage); - } - if (!visible && exists) + switch (visible) { - tabPages.TabPages.Remove(tabPage); + case true when !exists: + tabPages.TabPages.Insert(tabPages.TabPages.IndexOf(tabPageAbout), tabPage); + break; + case false when exists: + tabPages.TabPages.Remove(tabPage); + break; } } + + private void web_About_NewWindow(object sender, System.ComponentModel.CancelEventArgs e) + { + e.Cancel = true; + } } } \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Properties/Resources.resx b/ExtLibs/AltitudeAngelWings.Plugin/AASettings.resx similarity index 94% rename from ExtLibs/AltitudeAngelWings/Properties/Resources.resx rename to ExtLibs/AltitudeAngelWings.Plugin/AASettings.resx index 1dc6eee110..29dcb1b3a3 100644 --- a/ExtLibs/AltitudeAngelWings/Properties/Resources.resx +++ b/ExtLibs/AltitudeAngelWings.Plugin/AASettings.resx @@ -1,4 +1,4 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings.Plugin/WpfAuthorizeDisplay.cs b/ExtLibs/AltitudeAngelWings.Plugin/WpfAuthorizeDisplay.cs deleted file mode 100644 index a86a95df59..0000000000 --- a/ExtLibs/AltitudeAngelWings.Plugin/WpfAuthorizeDisplay.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Forms; -using AltitudeAngelWings.ApiClient.Client; - -namespace AltitudeAngelWings.Plugin -{ - /// - /// Provides auth code URIs from an auth URI. - /// - public class WpfAuthorizeDisplay : IAuthorizeCodeProvider - { - private readonly IUiThreadInvoke _invoke; - private readonly IWin32Window _owner; - private readonly int _width; - private readonly int _height; - private Uri _result; - private Action _close = () => { }; - - public WpfAuthorizeDisplay(IUiThreadInvoke invoke, IWin32Window owner = null, int width = 800, int height = 600) - { - _invoke = invoke; - _owner = owner; - _width = width; - _height = height; - } - - public async Task GetCodeUri(Uri authorizeUri, Uri redirectUri) - { - var ss = new SemaphoreSlim(0, 1); - - // BeginInvoke onto the ui thread, and await until closed - AltitudeAngelPlugin.Instance.Host.MainForm.BeginInvoke((Action)delegate () - { - var form = new Form(); - _result = redirectUri; - // ReSharper disable once AccessToDisposedClosure - _close = () => { form.Close(); }; - form.StartPosition = FormStartPosition.CenterParent; - form.Width = _width; - form.Height = _height; - var webBrowser = new WebBrowser(); - webBrowser.Navigating += WebBrowserOnNavigating; - webBrowser.Navigated += WebBrowserOnNavigated; - webBrowser.Dock = DockStyle.Fill; - form.Controls.Add(webBrowser); - webBrowser.Navigate(authorizeUri); - form.FormClosed += (s, e) => { ss.Release(); }; - - form.Show(_owner); - }); - - await ss.WaitAsync(); - - return _result; - } - - private void WebBrowserOnNavigating(object sender, WebBrowserNavigatingEventArgs navigatingEventArgs) - { - if (!IsRedirectUrl(navigatingEventArgs.Url)) return; - navigatingEventArgs.Cancel = true; - _result = navigatingEventArgs.Url; - _close(); - } - - private void WebBrowserOnNavigated(object sender, WebBrowserNavigatedEventArgs navigatedEventArgs) - { - if (!IsRedirectUrl(navigatedEventArgs.Url)) return; - _result = navigatedEventArgs.Url; - _close(); - } - - private bool IsRedirectUrl(Uri uri) - => uri.ToString().StartsWith(_result.ToString(), StringComparison.OrdinalIgnoreCase); - } -} diff --git a/ExtLibs/AltitudeAngelWings/AltitudeAngelWings.csproj b/ExtLibs/AltitudeAngelWings/AltitudeAngelWings.csproj index 9409277c3b..4bd0221eef 100644 --- a/ExtLibs/AltitudeAngelWings/AltitudeAngelWings.csproj +++ b/ExtLibs/AltitudeAngelWings/AltitudeAngelWings.csproj @@ -6,26 +6,14 @@ - + + - + + - - - - True - True - Resources.resx - - - - - PublicResXFileCodeGenerator - Resources.Designer.cs - - \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Client/AltitudeAngelClient.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Client/AltitudeAngelClient.cs deleted file mode 100644 index 6ff66d906c..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Client/AltitudeAngelClient.cs +++ /dev/null @@ -1,345 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using AltitudeAngelWings.ApiClient.Models; -using AltitudeAngelWings.ApiClient.Models.FlightV2; -using AltitudeAngelWings.ApiClient.Models.FlightV2.ServiceRequests; -using AltitudeAngelWings.ApiClient.Models.FlightV2.ServiceRequests.ProtocolConfiguration; -using AltitudeAngelWings.ApiClient.Models.Strategic; -using AltitudeAngelWings.Models; -using AltitudeAngelWings.Service; -using Flurl; -using Flurl.Http; -using Flurl.Http.Configuration; -using GeoJSON.Net.Feature; -using GeoJSON.Net.Geometry; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Newtonsoft.Json.Serialization; -using NodaTime; -using NodaTime.Serialization.JsonNet; -using Polly; - -namespace AltitudeAngelWings.ApiClient.Client -{ - public class AltitudeAngelClient : IAltitudeAngelClient - { - private FlurlClient _client; - private readonly IHttpClientFactory _clientFactory; - private readonly IAsyncPolicy _asyncPolicy; - private readonly ISettings _settings; - - public AltitudeAngelClient( - ISettings settings, - IHttpClientFactory clientFactory, - IAsyncPolicy asyncPolicy) - { - _settings = settings; - _clientFactory = clientFactory; - _asyncPolicy = asyncPolicy; - } - - protected FlurlClient Client - => _client ?? (_client = new FlurlClient - { - Settings = - { - HttpClientFactory = _clientFactory - } - }); - - /// - /// Disconnect the client from AA. Will force logon on the next request if required. - /// - public void Disconnect(bool resetAuth = false) - { - _client?.Dispose(); - _client = null; - if (resetAuth) - { - _settings.TokenResponse = null; - } - } - - public Task GetMapData(BoundingLatLong latLongBounds, CancellationToken cancellationToken) - => _asyncPolicy.ExecuteAsync(() => _settings.ApiUrl - .AppendPathSegments("v2", "mapdata", "geojson") - .SetQueryParams(new - { - n = latLongBounds.NorthEast.Latitude, - e = latLongBounds.NorthEast.Longitude, - s = latLongBounds.SouthWest.Latitude, - w = latLongBounds.SouthWest.Longitude, - isCompact = false, - useNewFilters = true, - include = "flight_report,flight_restrictions" - }) - .WithClient(Client) - .GetJsonAsync(cancellationToken)); - - /// - /// Get the weather for the specified location. Do not call this method often, typically only once per session. - /// Required scopes: talk_tower - /// - /// The location. - /// The weather info for the current conditions. - public async Task GetWeather(LatLong latLong) - { - var flightInfo = new FlightInfo - { - Position = new LatLong(latLong.Latitude, latLong.Longitude) - }; - - var aircraftInfo = new AircraftInfo - { - Id = "Id" - }; - - // Load this as a JObject as reports are extensible - var reportResponse = await _asyncPolicy.ExecuteAsync(async () => await _settings.ApiUrl - .AppendPathSegments("ops", "tower", "report") - .WithClient(Client) - .PostJsonAsync(new ReportRequest(aircraftInfo, flightInfo, "weather")) - .ReceiveJson()); - - // Grab the weather for now if there is one and process it - var currentWeather = reportResponse.SelectToken("weather.forecast.current")?.ToObject(); - - return currentWeather; - } - - /// - /// Get the user profile for the current user. Must be using user auth. Required scopes: query_userinfo - /// - /// The user profile. - public Task GetUserProfile() - =>_asyncPolicy.ExecuteAsync(() => _settings.AuthenticationUrl - .AppendPathSegment("userProfile") - .WithClient(Client) - .GetJsonAsync()); - - /// - /// Creates a flight plan via FlightService API - /// - /// - /// - /// - public Task CreateFlightPlan(FlightPlan flightPlan, UserProfileInfo currentUser) - { - var parts = CreateFlightPartsFromWaypoints(flightPlan.Waypoints, flightPlan.Duration); - return CreateFlightPlan(flightPlan, parts, currentUser); - } - - public Task StartFlight(string flightPlanId) - { - var startFlightRequest = new StartFlightRequest - { - FlightPlanId = flightPlanId, - ServiceRequests = new List - { - new TacticalDeconflictionFlightServiceRequest {Properties = new TacticalDeconflictionRequestProperties - { - Guidance = new List {"vector"}, - NotificationProtocols = new List {new {type = "Websocket"}}, - TelemetryProtocols = new List {new {type = "Udp"}}, - Scope = "global", - SurveillanceResolution = true - }} - } - }; - - return _asyncPolicy.ExecuteAsync(() => _settings.FlightServiceUrl - .AppendPathSegments("flight", "v2", "flights") - .WithClient(Client) - .ConfigureRequest(settings => - { - settings.JsonSerializer = CreateNewtonsoftJsonSerializer(); - }) - .PostJsonAsync(startFlightRequest) - .ReceiveJson()); - } - - /// - /// Completes a flight via FlightService API - /// - /// - /// - public Task CompleteFlight(string flightId) - => _asyncPolicy.ExecuteAsync(() => _settings.FlightServiceUrl - .AppendPathSegments("flight", "v2", "flights", flightId) - .WithClient(Client) - .DeleteAsync()); - - public Task CancelFlightPlan(string flightPlanId) - => _asyncPolicy.ExecuteAsync(() => _settings.FlightServiceUrl - .AppendPathSegments("v1", "conflict-resolution", "strategic", "flight-plans", flightPlanId, "cancel") - .WithClient(Client) - .PostAsync()); - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _client?.Dispose(); - _client = null; - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private static NewtonsoftJsonSerializer CreateNewtonsoftJsonSerializer() => new NewtonsoftJsonSerializer(CreateJsonSerializerSettings()); - - private static JsonSerializerSettings CreateJsonSerializerSettings() - { - var jsonSerializerSettings = new JsonSerializerSettings - { - DateParseHandling = DateParseHandling.None - }; - - jsonSerializerSettings.Converters.Add(new BaseNotificationProtocolConfigurationConverter()); - jsonSerializerSettings.Converters.Add(new BaseTelemetryProtocolConfigurationConverter()); - jsonSerializerSettings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); - jsonSerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); - return jsonSerializerSettings; - } - - private static (Instant start, Instant end) GetFlightPlanStartEndInstants(Duration duration) - { - var startInstant = SystemClock.Instance.GetCurrentInstant() + Duration.FromSeconds(30); - var endInstant = startInstant + duration; - return (startInstant, endInstant); - } - - private static CreateFlightPartRequest CreateFlightPartRequest( - int id, - int highestAltitude, - Instant start, - Instant end, - Feature geography) - => new CreateFlightPartRequest - { - Id = id.ToString(), - Geography = geography, - Start = start, - End = end, - TimeZone = DateTimeZoneProviders.Tzdb.GetSystemDefault(), - MaxAltitude = new Altitude { Meters = highestAltitude, Datum = AltitudeDatum.Agl } - }; - - /// - /// Creates a flight plan via FlightService API - /// - /// - /// - /// - /// - private async Task CreateFlightPlan( - FlightPlan flightPlan, - IEnumerable parts, - UserProfileInfo currentUser) - { - var sParts = parts.Select(p => new CreateStrategicPlanPartRequest - { - Id = p.Id, - MaxAltitude = p.MaxAltitude, - Start = p.Start, - End = p.End, - Geography = FeatureToGeometryJson(p.Geography) - }).ToList(); - - var identifiers = new Dictionary(); - if (_settings.FlightIdentifierIcao) - { - identifiers.Add("icao", _settings.FlightIdentifierIcaoAddress); - } - if (_settings.FlightIdentifierSerial) - { - identifiers.Add("serialNumber", _settings.FlightIdentifierSerialNumber); - } - if (identifiers.Count == 0) - { - identifiers = null; - } - - var req = new CreateStrategicPlanRequest - { - ConflictResolutionScope = flightPlan.UseLocalConflictScope ? StrategicConflictResolutionScope.Local : StrategicConflictResolutionScope.Global, - Summary = flightPlan.Summary, - Description = flightPlan.Description, - Parts = sParts, - PointOfContact = new StrategicContactDetails - { - FirstName = currentUser.FirstName, - LastName = currentUser.LastName, - PhoneNumber = _settings.FlightPhoneNumber, - AllowSmsContact = _settings.FlightAllowSms - }, - Identifiers = identifiers, - DroneDetails = new StrategicDroneDetails - { - AirFrame = MapToAirFrame(flightPlan.FlightCapability), - MaxWeight = 1, - Weight = 1 - } - }; - - return await _asyncPolicy.ExecuteAsync(async () => await _settings.FlightServiceUrl - .AppendPathSegments("v1", "conflict-resolution", "strategic", "flight-plans") - .WithClient(Client) - .ConfigureRequest(settings => - { - settings.JsonSerializer = CreateNewtonsoftJsonSerializer(); - }) - .PostJsonAsync(req) - .ReceiveJson()); - } - - private static StrategicAirframeType MapToAirFrame(FlightCapability flightCapability) - { - switch (flightCapability) - { - case FlightCapability.FixedWing: - return StrategicAirframeType.FixedWing; - case FlightCapability.Rotary: - return StrategicAirframeType.Rotary; - default: - throw new ArgumentOutOfRangeException(nameof(flightCapability), flightCapability, "Failed to map FlightCapability to StrategicAirframeType."); - } - } - - private static JObject FeatureToGeometryJson(Feature feature) - // Getting an IGeometry converter is a massive pain due to dependency and framework conflicts. Taking the geometry - // out of the feature that has working converters works best. - => (JObject)JObject.FromObject(feature)["geometry"]; - - private static IEnumerable CreateFlightPartsFromWaypoints(IList waypoints, Duration duration) - { - var flightPartRequests = new List(); - var highestAltitude = waypoints.Max(wp => wp.Altitude); - var (startInstant, endInstant) = GetFlightPlanStartEndInstants(duration); - - for (var pos = 1; pos < waypoints.Count; pos++) - { - if (Math.Abs(waypoints[pos - 1].Latitude - waypoints[pos].Latitude) < 0.0000001 && - Math.Abs(waypoints[pos - 1].Longitude - waypoints[pos].Longitude) < 0.0000001) continue; - var geography = new Feature(ConvertWaypointsToLineString(waypoints[pos - 1], waypoints[pos])); - var createFlightPartRequest = CreateFlightPartRequest(pos, highestAltitude, startInstant, endInstant, geography); - flightPartRequests.Add(createFlightPartRequest); - } - - return flightPartRequests; - } - - private static IGeometryObject ConvertWaypointsToLineString(FlightPlanWaypoint startWayPoint, FlightPlanWaypoint endWayPoint) - => new LineString(new List { - new Position(startWayPoint.Latitude, startWayPoint.Longitude), - new Position(endWayPoint.Latitude, endWayPoint.Longitude) - }); - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Client/AltitudeAngelHttpHandlerFactory.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Client/AltitudeAngelHttpHandlerFactory.cs deleted file mode 100644 index ae22a3e559..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Client/AltitudeAngelHttpHandlerFactory.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Net; -using System.Net.Http; -using Flurl.Http.Configuration; - -namespace AltitudeAngelWings.ApiClient.Client -{ - public class AltitudeAngelHttpHandlerFactory : DefaultHttpClientFactory - { - private readonly ITokenProvider _tokenProvider; - - public AltitudeAngelHttpHandlerFactory(ITokenProvider tokenProvider) - { - _tokenProvider = tokenProvider; - } - - public override HttpMessageHandler CreateMessageHandler() - { - return new BearerTokenHttpMessageHandler( - _tokenProvider, - new HttpClientHandler - { - AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate - }); - } - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Client/DisplayInfoExtensions.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Client/DisplayInfoExtensions.cs deleted file mode 100644 index d1516f1a34..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Client/DisplayInfoExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Text; -using MarkdownSharp; - -namespace AltitudeAngelWings.ApiClient.Client -{ - public static class DisplayInfoExtensions - { - public static string FormatAsHtml(this FeatureProperties featureProperties) - { - var builder = new StringBuilder(); - var markdown = new Markdown(new MarkdownOptions - { - AutoNewlines = false, - AutoHyperlink = false, - LinkEmails = false, - EmptyElementSuffix = "/>" - }); - builder.Append($"
"); - builder.Append($"
{featureProperties.DisplayInfo.Title}
"); - builder.Append($"
{featureProperties.DisplayInfo.Category}
"); - builder.Append($"
{featureProperties.DisplayInfo.DetailedCategory}
"); - foreach (var section in featureProperties.DisplayInfo.Sections) - { - builder.Append("
"); - builder.Append($"
{section.Title}
"); - builder.Append($"
{section.DisplayTitle}
"); - builder.Append($"
{markdown.Transform(section.Text)}
"); - builder.Append($"
{markdown.Transform(section.Disclaimer)}
"); - builder.Append("
"); - } - builder.Append("
"); - return builder.ToString(); - } - } -} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Client/FlightClient/FlightClient.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Client/FlightClient/FlightClient.cs deleted file mode 100644 index db81747aba..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Client/FlightClient/FlightClient.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Flurl; -using Flurl.Http; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Flurl.Http.Configuration; -using Polly; - -namespace AltitudeAngelWings.ApiClient.Client.FlightClient -{ - public class FlightClient : IFlightClient - { - private readonly string _flightServiceUrl; - private readonly IAsyncPolicy _asyncPolicy; - private readonly FlurlClient _client; - - public FlightClient(string flightServiceUrl, IHttpClientFactory clientFactory, IAsyncPolicy asyncPolicy) - { - _flightServiceUrl = flightServiceUrl; - _asyncPolicy = asyncPolicy; - _client = new FlurlClient - { - Settings = - { - HttpClientFactory = clientFactory - } - }; - } - - /// - /// Starts a flight via FlightService API - /// - /// - /// - /// deconfliction service required - /// - public Task StartFlight(string notificationProtocolUrl, string flightPlanId, string deconflictionService) - => _asyncPolicy.ExecuteAsync(() => _flightServiceUrl - .AppendPathSegments("flight", "start") - .WithClient(_client) - .PostJsonAsync(new - { - flightPlanId, - serviceRequired = deconflictionService, - telemetryProtocols = new List { new { type = "Udp" } }, - notificationProtocols = new List { new { type = "Webhook", properties = new { url = notificationProtocolUrl } } } - }) - .ReceiveJson()); - - /// - /// Completes a flight via FlightService API - /// - /// - /// - public Task CompleteFlight(string flightId) - => _asyncPolicy.ExecuteAsync(() => _flightServiceUrl - .AppendPathSegments("flight", "complete") - .SetQueryParam("id", flightId) - .WithClient(_client) - .DeleteAsync()); - - public Task AcceptInstruction(string instructionId) => ProcessInstruction(instructionId, true); - - public Task RejectInstruction(string instructionId) => ProcessInstruction(instructionId, false); - - private Task ProcessInstruction(string instructionId, bool accept) - => _asyncPolicy.ExecuteAsync(() => _flightServiceUrl - .AppendPathSegments("flight", "v2", "instructions", instructionId, accept ? "accept" : "reject") - .WithClient(_client) - .PutAsync()); - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _client?.Dispose(); - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Client/FlightClient/IFlightClient.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Client/FlightClient/IFlightClient.cs deleted file mode 100644 index e0d21b22a2..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Client/FlightClient/IFlightClient.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Newtonsoft.Json.Linq; -using System; -using System.Threading.Tasks; - -namespace AltitudeAngelWings.ApiClient.Client.FlightClient -{ - public interface IFlightClient : IDisposable - { - Task StartFlight(string notificationProtocolUrl, string flightPlanId, string deconflictionService); - Task CompleteFlight(string flightId); - Task AcceptInstruction(string instructionId); - Task RejectInstruction(string instructionId); - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Client/IAltitudeAngelClient.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Client/IAltitudeAngelClient.cs deleted file mode 100644 index 28262e3bdb..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Client/IAltitudeAngelClient.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using AltitudeAngelWings.ApiClient.Models; -using AltitudeAngelWings.ApiClient.Models.FlightV2; -using AltitudeAngelWings.ApiClient.Models.Strategic; -using AltitudeAngelWings.Models; - -namespace AltitudeAngelWings.ApiClient.Client -{ - public interface IAltitudeAngelClient : IDisposable - { - void Disconnect(bool resetAuth = false); - Task GetMapData(BoundingLatLong latLongBounds, CancellationToken cancellationToken); - Task GetWeather(LatLong latLong); - Task GetUserProfile(); - Task CompleteFlight(string flightId); - Task CreateFlightPlan(FlightPlan flightPlan, UserProfileInfo currentUser); - Task StartFlight(string flightPlanId); - Task CancelFlightPlan(string flightPlanId); - } -} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Client/IAuthorizeCodeProvider.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Client/IAuthorizeCodeProvider.cs deleted file mode 100644 index 99d2d4cd9c..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Client/IAuthorizeCodeProvider.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace AltitudeAngelWings.ApiClient.Client -{ - public interface IAuthorizeCodeProvider - { - Task GetCodeUri(Uri authorizeUri, Uri redirectUri); - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Client/TokenResponseExtensions.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Client/TokenResponseExtensions.cs deleted file mode 100644 index fd101cfa3b..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Client/TokenResponseExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; - -namespace AltitudeAngelWings.ApiClient.Client -{ - public static class TokenResponseExtensions - { - public static bool IsValidForAuth(this TokenResponse tokenResponse) - { - if (tokenResponse == null) - { - return false; - } - - if (string.IsNullOrEmpty(tokenResponse.AccessToken) - || tokenResponse.ExpiresIn <= 0) - { - return false; - } - - // Only valid if >= 1 minute of expiry time left - return tokenResponse.ExpiresAt >= DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(1)); - } - - public static bool CanBeRefreshed(this TokenResponse tokenResponse) - { - if (tokenResponse == null) - { - return false; - } - - return !string.IsNullOrEmpty(tokenResponse.RefreshToken); - } - - } -} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Client/UserAuthenticationTokenProvider.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Client/UserAuthenticationTokenProvider.cs deleted file mode 100644 index 505df6a4a1..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Client/UserAuthenticationTokenProvider.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using System.Web; -using AltitudeAngelWings.Service; -using AltitudeAngelWings.Service.Messaging; -using Flurl.Http.Configuration; -using Newtonsoft.Json; - -namespace AltitudeAngelWings.ApiClient.Client -{ - public class UserAuthenticationTokenProvider : ITokenProvider - { - private readonly ISettings _settings; - private readonly IHttpClientFactory _clientFactory; - private readonly IAuthorizeCodeProvider _provider; - private readonly IMessagesService _messagesService; - private readonly SemaphoreSlim _lock = new SemaphoreSlim(1); - - public UserAuthenticationTokenProvider(ISettings settings, IHttpClientFactory clientFactory, IAuthorizeCodeProvider provider, IMessagesService messagesService) - { - _settings = settings; - _clientFactory = clientFactory; - _provider = provider; - _messagesService = messagesService; - } - - public async Task GetToken(CancellationToken cancellationToken) - { - await _lock.WaitAsync(cancellationToken); - try - { - if (_settings.TokenResponse.IsValidForAuth()) - { - return _settings.TokenResponse.AccessToken; - } - - if (_settings.TokenResponse.CanBeRefreshed()) - { - try - { - await _messagesService.AddMessageAsync("Refreshing AA access token."); - return await RefreshAccessToken(cancellationToken); - } - catch (Exception) - { - // Ignore and try asking user - } - } - - await _messagesService.AddMessageAsync("Asking user for AA access token."); - return await AskUserForAccessToken(cancellationToken); - } - finally - { - _lock.Release(); - } - } - - private async Task RefreshAccessToken(CancellationToken cancellationToken) - { - _settings.TokenResponse = await MakeTokenRequest( - () => GetTokenRequestBody("refresh_token", "refresh_token", _settings.TokenResponse.RefreshToken), - cancellationToken); - return _settings.TokenResponse.AccessToken; - } - - private async Task AskUserForAccessToken(CancellationToken cancellationToken) - { - var redirectUri = new Uri(_settings.RedirectUri); - var result = await _provider.GetCodeUri( - FormatCodeAuthorizeUri( - new Uri($"{_settings.AuthenticationUrl}/oauth/v2/authorize"), - _settings.ClientId, - redirectUri, - _settings.ClientScopes), - redirectUri); - var code = GetAuthCodeFromRedirect(result); - _settings.TokenResponse = await MakeTokenRequest( - () => GetTokenRequestBody("authorization_code", "code", code), - cancellationToken); - return _settings.TokenResponse.AccessToken; - } - - private async Task MakeTokenRequest(Func postBody, CancellationToken cancellationToken) - { - var client = _clientFactory.CreateHttpClient(new HttpClientHandler()); - var request = new HttpRequestMessage(HttpMethod.Post, $"{_settings.AuthenticationUrl}/oauth/v2/token") - { - Content = postBody() - }; - using (var response = await client.SendAsync(request, cancellationToken)) - { - var content = await response.Content.ReadAsStringAsync(); - if (!response.IsSuccessStatusCode) - { - throw new InvalidOperationException( - $"Failed to get authentication token. {response.StatusCode}: {content}"); - } - - return JsonConvert.DeserializeObject(content); - } - } - - private HttpContent GetTokenRequestBody(string grantType, string tokenName, string tokenValue) - => new FormUrlEncodedContent(new Dictionary - { - ["client_id"] = _settings.ClientId, - ["client_secret"] = _settings.ClientSecret, - ["redirect_uri"] = _settings.RedirectUri, - ["grant_type"] = grantType, - [tokenName] = tokenValue, - ["token_format"] = "jwt" - }); - - private static Uri FormatCodeAuthorizeUri(Uri baseUri, string clientId, Uri redirectUri, string[] scopes) - { - var query = HttpUtility.ParseQueryString(string.Empty); - query.Add("client_id", clientId); - query.Add("redirect_uri", redirectUri.ToString()); - query.Add("scope", string.Join(" ", scopes)); - query.Add("response_type", "code"); - var builder = new UriBuilder(baseUri) - { - Query = query.ToString() - }; - return builder.Uri; - } - - private static string GetAuthCodeFromRedirect(Uri redirect) - { - var queryString = HttpUtility.ParseQueryString(redirect.Query); - var code = queryString.Get("code"); - if (string.IsNullOrEmpty(code)) - { - throw new InvalidOperationException($"Code not found in redirect URI '{redirect}'"); - } - - return code; - } - } -} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/ApprovalOption.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/ApprovalOption.cs deleted file mode 100644 index 0425569433..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/ApprovalOption.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Newtonsoft.Json; -using System.Collections.Generic; - -namespace AltitudeAngelWings.ApiClient.Models -{ - public class ApprovalOption - { - [JsonProperty("sections")] - public List Sections { get; set; } - - [JsonProperty("conditions")] - public List Conditions - { - get; - set; - } = new List(); - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/ApprovalOptionReference.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/ApprovalOptionReference.cs deleted file mode 100644 index 88c3fa88db..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/ApprovalOptionReference.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace AltitudeAngelWings.ApiClient.Models -{ - public class ApprovalOptionReference - { - [JsonProperty("id")] - public string Id - { - get; - set; - } - - [JsonProperty("approverId")] - public string ApproverId - { - get; - set; - } - - [JsonProperty("data")] - public JObject Data - { - get; - set; - } - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/ApprovalOptionalReference.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/ApprovalOptionalReference.cs deleted file mode 100644 index 6932b7cdc5..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/ApprovalOptionalReference.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Newtonsoft.Json; - -namespace AltitudeAngelWings.ApiClient.Models -{ - public class ApprovalOptionalReference - { - [JsonProperty("id")] - public string Id - { - get; - set; - } - - [JsonProperty("approverId")] - public string ApproverId - { - get; - set; - } - - [JsonProperty("data")] - public T Data - { - get; - set; - } - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/ApprovalRequired.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/ApprovalRequired.cs deleted file mode 100644 index 3aa6c95059..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/ApprovalRequired.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace AltitudeAngelWings.ApiClient.Models -{ - public enum ApprovalRequired - { - Yes, - No, - NotPossible - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/ApprovalSection.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/ApprovalSection.cs deleted file mode 100644 index 4cb1bae58d..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/ApprovalSection.cs +++ /dev/null @@ -1,23 +0,0 @@ -using GeoJSON.Net.Feature; -using Newtonsoft.Json; -using System.Collections.Generic; - -namespace AltitudeAngelWings.ApiClient.Models -{ - public class ApprovalSection - { - [JsonProperty("geog")] - public FeatureCollection Geog - { - get; - set; - } - - [JsonProperty("sectionOptions")] - public List SectionOptions - { - get; - set; - } - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/ApprovalSectionOption.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/ApprovalSectionOption.cs deleted file mode 100644 index 1e5a1a833e..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/ApprovalSectionOption.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Newtonsoft.Json; -using NodaTime; -using System.Collections.Generic; - -namespace AltitudeAngelWings.ApiClient.Models -{ - public class ApprovalSectionOption - { - [JsonProperty("approvalRequired")] - public ApprovalRequired ApprovalRequired - { - get; - set; - } - - [JsonProperty("approvalRequiredReason")] - public string ApprovalRequiredReason - { - get; - set; - } - - [JsonProperty("maxAlt")] - public Altitude MaxAlt - { - get; - set; - } - - [JsonProperty("start")] - public Instant Start - { - get; - set; - } - - [JsonProperty("end")] - public Instant End - { - get; - set; - } - - [JsonProperty("approvalType")] - public ApprovalType ApprovalType - { - get; - set; - } - - [JsonProperty("conditions")] - public List Conditions - { - get; - set; - } = new List(); - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/ApprovalType.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/ApprovalType.cs deleted file mode 100644 index b635782146..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/ApprovalType.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Newtonsoft.Json; -using System.Collections.Generic; - -namespace AltitudeAngelWings.ApiClient.Models -{ - public class ApprovalType - { - [JsonProperty("typeName")] - public string TypeName - { - get; - set; - } - - [JsonProperty("authority")] - public string Authority - { - get; - set; - } - - [JsonProperty("unit")] - public string Unit - { - get; - set; - } - - [JsonProperty("unitId")] - public string UnitId - { - get; - set; - } - - [JsonProperty("unitAirspaceClass")] - public string UnitAirspaceClass - { - get; - set; - } - - [JsonProperty("responseTime")] - public object ResponseTime => null; - - [JsonProperty("dataFields")] - public IEnumerable DataFields - { - get; - set; - } - - [JsonProperty("reference")] - public ApprovalOptionalReference Reference - { - get; - set; - } - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/Condition.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/Condition.cs deleted file mode 100644 index aef33006d0..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/Condition.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Newtonsoft.Json; -using System.Collections.Generic; - -namespace AltitudeAngelWings.ApiClient.Models -{ - public class Condition - { - [JsonProperty("id")] - public string Id - { - get; - set; - } - - [JsonProperty("text")] - public string Text - { - get; - set; - } - - [JsonProperty("data")] - public List Data - { - get; - set; - } = new List(); - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/ConditionInfo.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/ConditionInfo.cs deleted file mode 100644 index 1ae111bffa..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/ConditionInfo.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Newtonsoft.Json; -using System.Collections.Generic; - -namespace AltitudeAngelWings.ApiClient.Models -{ - public class ConditionInfo - { - [JsonProperty("label")] - public string Label - { - get; - set; - } - - [JsonProperty("data")] - public Dictionary Data - { - get; - set; - } = new Dictionary(); - - [JsonIgnore] - public string Type - { - - get => Data[WellKnownDataKeys.Type]; - set => Data[WellKnownDataKeys.Type] = value; - } - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/CreateFlightPartRequest.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/CreateFlightPartRequest.cs deleted file mode 100644 index dacf23c6ad..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/CreateFlightPartRequest.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Collections.Generic; -using GeoJSON.Net.Feature; -using Newtonsoft.Json; -using NodaTime; - -namespace AltitudeAngelWings.ApiClient.Models -{ - public class CreateFlightPartRequest - { - [JsonProperty("id")] - public string Id - { - get; set; - } - - [JsonProperty("geography")] - public Feature Geography - { - get; set; - } - - [JsonProperty("start")] - public Instant Start - { - get; set; - } - - [JsonProperty("end")] - public Instant End - { - get; set; - } - - [JsonProperty("timeZone")] - public DateTimeZone TimeZone - { - get; set; - } - - [JsonProperty("maxAltitude")] - public Altitude MaxAltitude - { - get; set; - } - - [JsonProperty("approvalOptions")] - public List ApprovalOptions - { - get; - set; - } - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/CreateFlightPlanRequest.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/CreateFlightPlanRequest.cs deleted file mode 100644 index 1d2ccf24fd..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/CreateFlightPlanRequest.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System.Collections.Generic; -using AltitudeAngelWings.Models; - -namespace AltitudeAngelWings.ApiClient.Models -{ - public class CreateFlightPlanRequest - { - [JsonProperty("summary")] - public string Summary { get; set; } - - [JsonProperty("description")] - public string Description { get; set; } - - [JsonProperty("flightOperationMode")] - public FlightOperationMode FlightOperationMode { get; set; } - - [JsonProperty("flightCapability")] - public FlightCapability FlightCapability { get; set; } - - [JsonProperty("parts")] - public List Parts { get; set; } - - [JsonProperty("dataFields")] - public JObject DataFields { get; set; } - - [JsonProperty("droneSerialNumber")] - public string DroneSerialNumber { get; set; } - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/Detail.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/Detail.cs deleted file mode 100644 index 2c8e1b8de6..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/Detail.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Newtonsoft.Json; - -namespace AltitudeAngelWings.ApiClient.Models -{ - public class Detail - { - [JsonProperty("name")] - public string Name { get; set; } - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/ExcludedData.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/ExcludedData.cs deleted file mode 100644 index 1d33e6cdea..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/ExcludedData.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Newtonsoft.Json; - -namespace AltitudeAngelWings.ApiClient.Models -{ - public class ExcludedData - { - [JsonProperty("message")] - public string Message { get; set; } - - [JsonProperty("detail")] - public Detail Detail { get; set; } - - [JsonProperty("errorReason")] - public string ErrorReason { get; set; } - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightPart.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightPart.cs deleted file mode 100644 index 2a24bbf734..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightPart.cs +++ /dev/null @@ -1,47 +0,0 @@ -using GeoJSON.Net.Feature; -using Newtonsoft.Json; -using NodaTime; -using System.Collections.Generic; - -namespace AltitudeAngelWings.ApiClient.Models -{ - public class FlightPart - { - [JsonProperty("geography")] - public Feature Geography - { - get; set; - } - - [JsonProperty("start")] - public Instant? Start - { - get; set; - } - - [JsonProperty("end")] - public Instant? End - { - get; set; - } - - [JsonProperty("timeZone")] - public DateTimeZone TimeZone - { - get; set; - } - - [JsonProperty("maxAltitude")] - public Altitude MaxAltitude - { - get; set; - } - - [JsonProperty("approvalOptions")] - public List ApprovalOptions - { - get; - set; - } - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/TelemetryProtocolConfiguration.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/TelemetryProtocolConfiguration.cs deleted file mode 100644 index b19679ae0e..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/TelemetryProtocolConfiguration.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace AltitudeAngelWings.ApiClient.Models.FlightV2.ServiceRequests -{ - class TelemetryProtocolConfiguration - { - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/StartFlightResponse.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/StartFlightResponse.cs deleted file mode 100644 index f6ef96c26b..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/StartFlightResponse.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using AltitudeAngelWings.ApiClient.Models.FlightV2.ServiceRequests; - -namespace AltitudeAngelWings.ApiClient.Models.FlightV2 -{ - public class StartFlightResponse - { - /// - ///The ID of the newly started flight. - /// - public string Id { get; set; } - - /// - /// The ID of the flight plan. - /// - public string FlightPlanId { get; set; } - - /// - /// The date and time the flight was started, if it started successfully - /// - public DateTimeOffset? Started { get; set; } - - /// - /// A list of individual responses for each issued - /// - public List ServiceResponses { get; set; } - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/LaancApprovalReferenceProperties.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/LaancApprovalReferenceProperties.cs deleted file mode 100644 index 675811fe19..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/LaancApprovalReferenceProperties.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Newtonsoft.Json; -using NodaTime; - -namespace AltitudeAngelWings.ApiClient.Models -{ - public class LaancApprovalReferenceProperties - { - [JsonProperty("expiresAt")] - public Instant ExpiresAt - { - get; - set; - } - - [JsonProperty("signature")] - public string Signature - { - get; - set; - } - - [JsonProperty("unitId")] - public string UnitId - { - get; set; - } - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/ConflictClearedNotificationProperties.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/ConflictClearedNotificationProperties.cs deleted file mode 100644 index cba7076610..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/ConflictClearedNotificationProperties.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace AltitudeAngelWings.ApiClient.Models.OutboundNotifs -{ - class ConflictClearedNotificationProperties - { - public string Message - { - get; - set; - } - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/ConflictInformationProperties.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/ConflictInformationProperties.cs deleted file mode 100644 index 2d9d29a38a..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/ConflictInformationProperties.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace AltitudeAngelWings.ApiClient.Models.OutboundNotifs -{ - class ConflictInformationProperties - { - public string Message - { - get; - set; - } - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/NotificationMessage.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/NotificationMessage.cs deleted file mode 100644 index f2a6c0f8d1..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/NotificationMessage.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Newtonsoft.Json.Linq; - -namespace AltitudeAngelWings.ApiClient.Models.OutboundNotifs -{ - /// - /// A notification message issued to an - /// - public class NotificationMessage - { - /// - /// Notification Id - /// - public string Id - { - get; - set; - } - - /// - /// Notification must be acknowledged - /// - public bool Acknowledge - { - get; - set; - } - - /// - /// Type of notification - /// - public string Type - { - get; - set; - } - - /// - /// Notification properties - /// - public JObject Properties - { - get; - set; - } - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/PermissionState.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/PermissionState.cs deleted file mode 100644 index 8b328c07ab..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/PermissionState.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace AltitudeAngelWings.ApiClient.Models.OutboundNotifs -{ - public enum PermissionState - { - PermissionRejected = 0, - PermissionPending = 1, - PermissionGranted = 2 - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/CreateStrategicPlanPartRequest.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/CreateStrategicPlanPartRequest.cs deleted file mode 100644 index 7c6e93974f..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/CreateStrategicPlanPartRequest.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Newtonsoft.Json.Linq; -using NodaTime; - -namespace AltitudeAngelWings.ApiClient.Models.Strategic -{ - public class CreateStrategicPlanPartRequest - { - /// - /// Gets or sets an identifier that uniquely identifies this part within this flight. - /// - public string Id { get; set; } - - /// - /// Gets or sets a Polygon or LineString describing the planned operating area or route. - /// - public JObject Geography { get; set; } - - /// - /// Gets or sets the instant this flight part is expected to start. - /// - public Instant? Start { get; set; } - - /// - /// Gets or sets the instant this flight part is expected to be completed by. - /// - public Instant? End { get; set; } - - /// - /// Gets or sets the maximum altitude that the drone will achieve during the flightPart. - /// - public Altitude MaxAltitude { get; set; } - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/CreateStrategicPlanRequest.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/CreateStrategicPlanRequest.cs deleted file mode 100644 index cc6d4a8b85..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/CreateStrategicPlanRequest.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Collections.Generic; -using Newtonsoft.Json.Linq; - -namespace AltitudeAngelWings.ApiClient.Models.Strategic -{ - class CreateStrategicPlanRequest - { - public string Summary { get; set; } - - public string Description { get; set; } - - public List Parts { get; set; } - - public JObject DataFields { get; set; } - - /// - /// The Id of the operator to which the flight belongs - /// Presumably unique per OrgId - /// - public string ExternalOperatorId { get; set; } - - /// - /// The regulations related to the request - /// - public IList Regulations { get; set; } - - /// - /// A collection of identifiers relating to the flightplan - /// - public IDictionary Identifiers { get; set; } - - /// - /// The point of contact for the flightplan - /// - public StrategicContactDetails PointOfContact { get; set; } - - /// - /// True if the flight plan is a draft - /// - public bool IsDraft { get; set; } - - /// - /// Gets or sets the drone details - /// - public StrategicDroneDetails DroneDetails { get; set; } - - /// - /// Gets or sets the conflict resolution scope - /// - public StrategicConflictResolutionScope ConflictResolutionScope { get; set; } - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/CreateStrategicPlanResponse.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/CreateStrategicPlanResponse.cs deleted file mode 100644 index 1d2c1e2835..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/CreateStrategicPlanResponse.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using Newtonsoft.Json; - -namespace AltitudeAngelWings.ApiClient.Models.Strategic -{ - public class CreateStrategicPlanResponse - { - /// - /// The flight plan id. - /// - [JsonProperty("flightPlanId", NullValueHandling = NullValueHandling.Ignore)] - public Guid? FlightPlanId - { - get; - set; - } - - public Guid OperationId - { - get; - set; - } - - public int OutcomeCode => (int)this.Outcome; - - public StrategicSeverity Outcome - { - get; - set; - } - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/StrategicAirframeType.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/StrategicAirframeType.cs deleted file mode 100644 index 82c656344b..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/StrategicAirframeType.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Runtime.Serialization; - -namespace AltitudeAngelWings.ApiClient.Models.Strategic -{ - public enum StrategicAirframeType - { - [EnumMember(Value = "fixedWing")] - FixedWing = 1, - [EnumMember(Value = "rotary")] - Rotary = 2 - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/StrategicConflictResolutionScope.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/StrategicConflictResolutionScope.cs deleted file mode 100644 index e335f522f3..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/StrategicConflictResolutionScope.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Runtime.Serialization; - -namespace AltitudeAngelWings.ApiClient.Models.Strategic -{ - public enum StrategicConflictResolutionScope - { - [EnumMember(Value = "local")] - Local = 1, - [EnumMember(Value = "global")] - Global = 2 - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/StrategicContactDetails.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/StrategicContactDetails.cs deleted file mode 100644 index 6a534456db..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/StrategicContactDetails.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Newtonsoft.Json; - -namespace AltitudeAngelWings.ApiClient.Models.Strategic -{ - public class StrategicContactDetails - { - [JsonProperty("firstName")] - public string FirstName { get; set; } - - [JsonProperty("lastName")] - public string LastName { get; set; } - - [JsonProperty("phoneNumber")] - public string PhoneNumber { get; set; } - - [JsonProperty("allowSmsContact")] - public bool AllowSmsContact { get; set; } - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/StrategicDroneDetails.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/StrategicDroneDetails.cs deleted file mode 100644 index 70b6f060fe..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/StrategicDroneDetails.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace AltitudeAngelWings.ApiClient.Models.Strategic -{ - public class StrategicDroneDetails - { - /// - /// Gets or sets the color of the drone - /// - public string Color { get; set; } - - /// - /// Gets or sets the markings on the drone - /// - public string Markings { get; set; } - - /// - /// Gets or sets the drone airframe - /// - public StrategicAirframeType AirFrame { get; set; } - - /// - /// Gets or sets the weight of the drone - /// - public double Weight { get; set; } - - /// - /// Gets or sets the max weight of the drone - /// - public double MaxWeight { get; set; } - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/StrategicSeverity.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/StrategicSeverity.cs deleted file mode 100644 index ebd0187b08..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/Strategic/StrategicSeverity.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace AltitudeAngelWings.ApiClient.Models.Strategic -{ - public enum StrategicSeverity - { - NoConflictingReportsFound = 1000, - ProximityWarning = 2000, - DirectConflict = 3000, - } -} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/WellKnownDataKeys.cs b/ExtLibs/AltitudeAngelWings/ApiClient/Models/WellKnownDataKeys.cs deleted file mode 100644 index 8017387641..0000000000 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/WellKnownDataKeys.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace AltitudeAngelWings.ApiClient.Models -{ - public class WellKnownDataKeys - { - public const string Type = "type"; - public const string PointOfContact = "pointOfContact"; - public const string AirspaceType = "airspaceType"; - } -} diff --git a/ExtLibs/AltitudeAngelWings/Clients/Api/ApiClient.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/ApiClient.cs new file mode 100644 index 0000000000..515c2de90d --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/ApiClient.cs @@ -0,0 +1,86 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using AltitudeAngelWings.Clients.Api.Model; +using AltitudeAngelWings.Service; +using Flurl; +using Flurl.Http; +using Flurl.Http.Configuration; +using Newtonsoft.Json.Linq; + +namespace AltitudeAngelWings.Clients.Api +{ + public class ApiClient : IApiClient + { + private readonly ISettings _settings; + private readonly IFlurlClient _client; + + public ApiClient(ISettings settings, IHttpClientFactory clientFactory, ISerializer serializer) + { + _settings = settings; + _client = new FlurlClient + { + Settings = + { + HttpClientFactory = clientFactory, + JsonSerializer = serializer, + } + }; + } + + public Task GetMapData(BoundingLatLong latLongBounds, CancellationToken cancellationToken) + => _settings.ApiUrl + .AppendPathSegments("v2", "mapdata", "geojson") + .SetQueryParams(new + { + n = latLongBounds.NorthEast.Latitude, + e = latLongBounds.NorthEast.Longitude, + s = latLongBounds.SouthWest.Latitude, + w = latLongBounds.SouthWest.Longitude, + isCompact = false, + useNewFilters = true, + include = "flight_report,flight_restrictions" + }) + .WithClient(_client) + .GetJsonAsync(cancellationToken); + + public Task GetRateCard(string rateCardId, CancellationToken cancellationToken) + => _settings.ApiUrl + .AppendPathSegments("v2", "mapdata", "rate-cards", rateCardId) + .WithClient(_client) + .GetJsonAsync(cancellationToken); + + public async Task GetWeather(LatLong latLong) + { + var reportResponse = await _settings.ApiUrl + .AppendPathSegments("ops", "tower", "report") + .WithClient(_client) + .PostJsonAsync(new ReportRequest( + new AircraftInfo + { + Id = "Id" + }, + new FlightInfo + { + Position = new LatLong(latLong.Latitude, latLong.Longitude) + }, + "weather")) + .ReceiveJson(); + return reportResponse.SelectToken("weather.forecast.current")?.ToObject(); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _client?.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Api/IApiClient.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/IApiClient.cs new file mode 100644 index 0000000000..7b237a3aa3 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/IApiClient.cs @@ -0,0 +1,14 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using AltitudeAngelWings.Clients.Api.Model; + +namespace AltitudeAngelWings.Clients.Api +{ + public interface IApiClient : IDisposable + { + Task GetMapData(BoundingLatLong latLongBounds, CancellationToken cancellationToken = default); + Task GetRateCard(string rateCardId, CancellationToken cancellationToken = default); + Task GetWeather(LatLong latLong); + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/AircraftInfo.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/AircraftInfo.cs similarity index 89% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/AircraftInfo.cs rename to ExtLibs/AltitudeAngelWings/Clients/Api/Model/AircraftInfo.cs index 535011fbcd..c8041c2897 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/AircraftInfo.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/AircraftInfo.cs @@ -1,4 +1,4 @@ -namespace AltitudeAngelWings.ApiClient.Models +namespace AltitudeAngelWings.Clients.Api.Model { public class AircraftInfo { diff --git a/ExtLibs/AltitudeAngelWings/Clients/Api/Model/Altitude.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/Altitude.cs new file mode 100644 index 0000000000..856f2970ed --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/Altitude.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace AltitudeAngelWings.Clients.Api.Model +{ + public class AltitudeProperty + { + [JsonProperty("datum")] + public string Datum { get; set; } + [JsonProperty("meters")] + public float Meters { get; set; } + // Ignore feet + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/BoundingLatLong.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/BoundingLatLong.cs similarity index 81% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/BoundingLatLong.cs rename to ExtLibs/AltitudeAngelWings/Clients/Api/Model/BoundingLatLong.cs index fd48824c8c..ecb09dd114 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/BoundingLatLong.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/BoundingLatLong.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json; -namespace AltitudeAngelWings.ApiClient.Models +namespace AltitudeAngelWings.Clients.Api.Model { public class BoundingLatLong { diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/BoundingLatLongExtensions.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/BoundingLatLongExtensions.cs similarity index 91% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/BoundingLatLongExtensions.cs rename to ExtLibs/AltitudeAngelWings/Clients/Api/Model/BoundingLatLongExtensions.cs index e65ad7b7f6..d13e59a05f 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/BoundingLatLongExtensions.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/BoundingLatLongExtensions.cs @@ -1,6 +1,6 @@ using System; -namespace AltitudeAngelWings.ApiClient.Models +namespace AltitudeAngelWings.Clients.Api.Model { public static class BoundingLatLongExtensions { diff --git a/ExtLibs/AltitudeAngelWings/Clients/Api/Model/Contact.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/Contact.cs new file mode 100644 index 0000000000..ebcdca4d75 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/Contact.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace AltitudeAngelWings.Clients.Api.Model +{ + public class Contact + { + [JsonProperty("phoneNumbers")] + public IList PhoneNumbers { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Client/DisplayInfo.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/DisplayInfo.cs similarity index 88% rename from ExtLibs/AltitudeAngelWings/ApiClient/Client/DisplayInfo.cs rename to ExtLibs/AltitudeAngelWings/Clients/Api/Model/DisplayInfo.cs index 55700dff63..5f0693fa43 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Client/DisplayInfo.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/DisplayInfo.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using Newtonsoft.Json; -namespace AltitudeAngelWings.ApiClient.Client +namespace AltitudeAngelWings.Clients.Api.Model { public class DisplayInfo { diff --git a/ExtLibs/AltitudeAngelWings/Clients/Api/Model/DisplayInfoExtensions.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/DisplayInfoExtensions.cs new file mode 100644 index 0000000000..ac89782aa3 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/DisplayInfoExtensions.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using MarkdownSharp; + +namespace AltitudeAngelWings.Clients.Api.Model +{ + public static class DisplayInfoExtensions + { + private static readonly IDictionary CurrencyLookup = CultureInfo.GetCultures(CultureTypes.AllCultures) + .Where(c => !c.IsNeutralCulture) + .Select(c => + { + try + { + return (c, new RegionInfo(c.Name) ); + } + catch (Exception) + { + return (c, null); + } + }) + .Where(r => r.Item2 != null) + .GroupBy(r => r.Item2.ISOCurrencySymbol) + .ToDictionary(g => g.Key, g => g.First().c); + + public static string FormatAsHtml(this FeatureProperties featureProperties, + IDictionary rateCardDetails) + { + var builder = new StringBuilder(); + var markdown = new Markdown(new MarkdownOptions + { + AutoNewlines = false, + AutoHyperlink = false, + LinkEmails = false, + EmptyElementSuffix = "/>" + }); + builder.Append("
"); + builder.Append($"
"); + builder.Append($"
"); + builder.Append($"
{featureProperties.DisplayInfo.Category}
"); + builder.Append($"
: {featureProperties.DisplayInfo.DetailedCategory}
"); + builder.Append($"
{featureProperties.DisplayInfo.Title.ToUpper()}
"); + builder.Append("
"); + if (featureProperties.UtmStatus?.UtmDetails != null && featureProperties.UtmStatus.Enabled) + { + builder.Append("
"); + builder.Append("
"); + + if (!string.IsNullOrWhiteSpace(featureProperties.UtmStatus.Title)) + { + builder.Append($"
{markdown.Transform(featureProperties.UtmStatus.Title)}
"); + } + + builder.Append("
FACILITY IS UTM READY
"); + + if (!string.IsNullOrWhiteSpace(featureProperties.UtmStatus.Description)) + { + builder.Append($"
{markdown.Transform(featureProperties.UtmStatus.Description)}
"); + } + + foreach (var rateType in featureProperties.UtmStatus.RateTypes.Keys) + { + var rateCard = featureProperties.UtmStatus.RateTypes[rateType] + .Where(c => + { + if (c.AppliesFrom == null) return false; + var now = DateTimeOffset.UtcNow; + if (c.AppliesFrom > now) return false; + if (c.AppliesTo == null) return true; + return c.AppliesTo >= now; + }) + .OrderBy(c => c.AppliesFrom) + .Select(c => rateCardDetails[c.Id]) + .FirstOrDefault(); + if (rateCard == null) continue; + builder.Append("
"); + builder.Append("
"); + builder.Append($"
{MapRateTypeToText(rateType)}
"); + builder.Append("
"); + builder.Append($"

{featureProperties.DisplayInfo.Title.ToUpper()}

"); + builder.Append($"

{rateCard.ExplanatoryText}

"); + builder.Append("
    "); + foreach (var rate in rateCard.Rates.OrderBy(r => r.Ordinal)) + { + builder.Append("
  • "); + builder.Append(rate.Name); + builder.Append(" ("); + var total = rate.Rate + rateCard.StandingCharge; + total += rateCard.TaxRate / 100 * total; + builder.Append(CurrencyLookup.ContainsKey(rateCard.Currency) + ? total.ToString("C", CurrencyLookup[rateCard.Currency]) + : $"{total} {rateCard.Currency}"); + + builder.Append(")
  • "); + } + builder.Append("
"); + builder.Append("

If you wish to operate here, depending on your flight plan, you may require their prior approval.

"); + builder.Append($"

Terms and conditions

"); + builder.Append("
"); + builder.Append("
"); + builder.Append("
"); + } + + if (!string.IsNullOrWhiteSpace(featureProperties.UtmStatus.UtmDetails.ExternalUrl)) + { + builder.Append($""); + } + + builder.Append("
"); + builder.Append("
"); + } + if (featureProperties.Contact?.PhoneNumbers != null && featureProperties.Contact.PhoneNumbers.Count > 0) + { + if (featureProperties.UtmStatus?.UtmDetails != null && featureProperties.UtmStatus.Enabled) + { + builder.Append("
"); + builder.Append("
"); + builder.Append("
CONTACTS
"); + } + else + { + builder.Append("
"); + builder.Append("
"); + builder.Append("
FACILITY IS NOT UTM READY
"); + builder.Append("

We can't submit a digital flight request to this facility as it isn't connected to Altitude Angel or has no compatible UTM service in operation. It may be possible to fly here, but you will have to contact the facility operator by phone to find out whether you can and what process to follow. According to our records, you can contact them on the number(s) below:

"); + } + + foreach (var phoneNumber in featureProperties.Contact.PhoneNumbers) + { + builder.Append("
"); + builder.Append($"{phoneNumber.Location}"); + builder.Append($"{phoneNumber.Number}"); + builder.Append("
"); + } + builder.Append("
"); + builder.Append("
"); + } + foreach (var section in featureProperties.DisplayInfo.Sections) + { + builder.Append("
"); + if (!string.IsNullOrWhiteSpace(section.Title)) + { + builder.Append($"
{section.Title.ToUpper()}
"); + } + + if (!string.IsNullOrWhiteSpace(section.DisplayTitle)) + { + builder.Append($"
{section.DisplayTitle.ToUpper()}
"); + } + + if (!string.IsNullOrWhiteSpace(section.Text)) + { + builder.Append($"
{markdown.Transform(section.Text)}
"); + } + + if (!string.IsNullOrWhiteSpace(section.Disclaimer)) + { + builder.Append($"
{markdown.Transform(section.Disclaimer)}
"); + } + builder.Append("
"); + } + builder.Append("
"); + return builder.ToString(); + } + + private static string MapRateTypeToText(string rateType) + { + switch (rateType) + { + case "flight-plan-approvals": + return "APPROVAL SERVICES"; + default: + return rateType; + } + } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Client/DisplaySection.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/DisplaySection.cs similarity index 87% rename from ExtLibs/AltitudeAngelWings/ApiClient/Client/DisplaySection.cs rename to ExtLibs/AltitudeAngelWings/Clients/Api/Model/DisplaySection.cs index a28d50f247..feed4b5d1b 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Client/DisplaySection.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/DisplaySection.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json; -namespace AltitudeAngelWings.ApiClient.Client +namespace AltitudeAngelWings.Clients.Api.Model { public class DisplaySection { diff --git a/ExtLibs/AltitudeAngelWings/Clients/Api/Model/ExcludedData.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/ExcludedData.cs new file mode 100644 index 0000000000..9a77ab0349 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/ExcludedData.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace AltitudeAngelWings.Clients.Api.Model +{ + public class ExcludedData + { + [JsonProperty(PropertyName = "errorReason")] + public string ErrorReason { get; set; } + [JsonProperty(PropertyName = "message")] + public string Message { get; set; } + [JsonProperty(PropertyName = "detail")] + public ExcludedDataDetail Detail { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Api/Model/ExcludedDataDetail.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/ExcludedDataDetail.cs new file mode 100644 index 0000000000..e63847008f --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/ExcludedDataDetail.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace AltitudeAngelWings.Clients.Api.Model +{ + public class ExcludedDataDetail + { + [JsonProperty(PropertyName = "name")] + public string Name { get; set; } + [JsonProperty(PropertyName = "displayName")] + public string DisplayName { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Client/FeatureExtensions.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/FeatureExtensions.cs similarity index 93% rename from ExtLibs/AltitudeAngelWings/ApiClient/Client/FeatureExtensions.cs rename to ExtLibs/AltitudeAngelWings/Clients/Api/Model/FeatureExtensions.cs index cda7ba1e8c..99c10ebdbd 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Client/FeatureExtensions.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/FeatureExtensions.cs @@ -1,10 +1,10 @@ -using GeoJSON.Net.Feature; -using Newtonsoft.Json; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using GeoJSON.Net.Feature; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace AltitudeAngelWings.ApiClient.Client +namespace AltitudeAngelWings.Clients.Api.Model { public static class FeatureExtensions { diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Client/FeatureProperties.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/FeatureProperties.cs similarity index 82% rename from ExtLibs/AltitudeAngelWings/ApiClient/Client/FeatureProperties.cs rename to ExtLibs/AltitudeAngelWings/Clients/Api/Model/FeatureProperties.cs index 87a45baf5c..5ae74a560d 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Client/FeatureProperties.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/FeatureProperties.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using Newtonsoft.Json; -namespace AltitudeAngelWings.ApiClient.Client +namespace AltitudeAngelWings.Clients.Api.Model { public class FeatureProperties { @@ -88,5 +88,17 @@ public class FeatureProperties [JsonProperty("display")] public DisplayInfo DisplayInfo { get; set; } + + [JsonProperty("altitudeFloor")] + public AltitudeProperty AltitudeFloor { get; set; } + + [JsonProperty("altitudeCeiling")] + public AltitudeProperty AltitudeCeiling { get; set; } + + [JsonProperty("utmStatus")] + public UtmStatus UtmStatus { get; set; } + + [JsonProperty("contact")] + public Contact Contact { get; set; } } } \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/FeaturePropertiesExtensions.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/FeaturePropertiesExtensions.cs similarity index 93% rename from ExtLibs/AltitudeAngelWings/FeaturePropertiesExtensions.cs rename to ExtLibs/AltitudeAngelWings/Clients/Api/Model/FeaturePropertiesExtensions.cs index 4a9ab506fd..36ae85caeb 100644 --- a/ExtLibs/AltitudeAngelWings/FeaturePropertiesExtensions.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/FeaturePropertiesExtensions.cs @@ -1,9 +1,7 @@ using System; using System.Globalization; -using AltitudeAngelWings.ApiClient.Client; -using AltitudeAngelWings.Extra; -namespace AltitudeAngelWings +namespace AltitudeAngelWings.Clients.Api.Model { public static class FeaturePropertiesExtensions { diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Client/FilterInfo.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/FilterInfo.cs similarity index 87% rename from ExtLibs/AltitudeAngelWings/ApiClient/Client/FilterInfo.cs rename to ExtLibs/AltitudeAngelWings/Clients/Api/Model/FilterInfo.cs index 888383347f..01f3fd0383 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Client/FilterInfo.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/FilterInfo.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json; -namespace AltitudeAngelWings.ApiClient.Client +namespace AltitudeAngelWings.Clients.Api.Model { public class FilterInfo { diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Client/FilterInfoDisplay.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/FilterInfoDisplay.cs similarity index 74% rename from ExtLibs/AltitudeAngelWings/ApiClient/Client/FilterInfoDisplay.cs rename to ExtLibs/AltitudeAngelWings/Clients/Api/Model/FilterInfoDisplay.cs index 55b63fce21..e0041aa408 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Client/FilterInfoDisplay.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/FilterInfoDisplay.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json; -namespace AltitudeAngelWings.ApiClient.Client +namespace AltitudeAngelWings.Clients.Api.Model { public class FilterInfoDisplay : FilterInfo { diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Client/FilterInfoEqualityComparer.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/FilterInfoEqualityComparer.cs similarity index 90% rename from ExtLibs/AltitudeAngelWings/ApiClient/Client/FilterInfoEqualityComparer.cs rename to ExtLibs/AltitudeAngelWings/Clients/Api/Model/FilterInfoEqualityComparer.cs index 7bf2401480..cfaa5cfba7 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Client/FilterInfoEqualityComparer.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/FilterInfoEqualityComparer.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace AltitudeAngelWings.ApiClient.Client +namespace AltitudeAngelWings.Clients.Api.Model { public class FilterInfoDisplayEqualityComparer : IEqualityComparer { diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightInfo.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/FlightInfo.cs similarity index 90% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightInfo.cs rename to ExtLibs/AltitudeAngelWings/Clients/Api/Model/FlightInfo.cs index e33112a177..b886d4cf7e 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightInfo.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/FlightInfo.cs @@ -1,6 +1,7 @@ +using AltitudeAngelWings.Clients.Flight.Model; using Newtonsoft.Json; -namespace AltitudeAngelWings.ApiClient.Models +namespace AltitudeAngelWings.Clients.Api.Model { public class FlightInfo { diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/LatLong.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/LatLong.cs similarity index 86% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/LatLong.cs rename to ExtLibs/AltitudeAngelWings/Clients/Api/Model/LatLong.cs index 9884cf3350..b1b68453d2 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/LatLong.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/LatLong.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json; -namespace AltitudeAngelWings.ApiClient.Models +namespace AltitudeAngelWings.Clients.Api.Model { public class LatLong { diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/AAFeatureCollection.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/MapFeatureCollection.cs similarity index 56% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/AAFeatureCollection.cs rename to ExtLibs/AltitudeAngelWings/Clients/Api/Model/MapFeatureCollection.cs index 821336b31e..917604bc36 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/AAFeatureCollection.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/MapFeatureCollection.cs @@ -1,16 +1,15 @@ using System.Collections.Generic; using GeoJSON.Net.Feature; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -namespace AltitudeAngelWings.ApiClient.Models +namespace AltitudeAngelWings.Clients.Api.Model { - public class AAFeatureCollection : FeatureCollection + public class MapFeatureCollection : FeatureCollection { [JsonProperty(PropertyName = "isCompleteData", Required = Required.Always)] public bool IsCompleteData { get; set; } [JsonProperty(PropertyName = "excludedData")] - public List ExcludedData { get; set; } = new List(); + public List ExcludedData { get; set; } = new List(); } } diff --git a/ExtLibs/AltitudeAngelWings/Clients/Api/Model/PhoneNumber.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/PhoneNumber.cs new file mode 100644 index 0000000000..dc2b3c7f1c --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/PhoneNumber.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace AltitudeAngelWings.Clients.Api.Model +{ + public class PhoneNumber + { + [JsonProperty("number")] + public string Number { get; set; } + [JsonProperty("location")] + public string Location { get; set; } + [JsonProperty("extension")] + public string Extension { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Api/Model/RateCard.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/RateCard.cs new file mode 100644 index 0000000000..283aeab0b2 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/RateCard.cs @@ -0,0 +1,17 @@ +using System; +using Newtonsoft.Json; + +namespace AltitudeAngelWings.Clients.Api.Model +{ + public class RateCard + { + [JsonProperty("id")] + public string Id { get; set; } + [JsonProperty("billableEvent")] + public string BillableEvent { get; set; } + [JsonProperty("appliesFrom")] + public DateTimeOffset? AppliesFrom { get; set; } + [JsonProperty("appliesTo")] + public DateTimeOffset? AppliesTo { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Api/Model/RateCardDetail.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/RateCardDetail.cs new file mode 100644 index 0000000000..55a17653c5 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/RateCardDetail.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace AltitudeAngelWings.Clients.Api.Model +{ + public class RateCardDetail + { + [JsonProperty("id")] + public string Id { get; set; } + [JsonProperty("version")] + public DateTimeOffset Version { get; set; } + [JsonProperty("billableEvent")] + public string BillableEvent { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("rates")] + public IList Rates { get; set; } + [JsonProperty("tenant")] + public string Tenant { get; set; } + [JsonProperty("appliesFrom")] + public DateTimeOffset AppliesFrom { get; set; } + [JsonProperty("appliesTo")] + public DateTimeOffset? AppliesTo { get; set; } + [JsonProperty("currency")] + public string Currency { get; set; } + [JsonProperty("taxRate")] + public double TaxRate { get; set; } + [JsonProperty("standingCharge")] + public double StandingCharge { get; set; } + [JsonProperty("rateCardTerms")] + public string RateCardTerms { get; set; } + [JsonProperty("rateCardTermsVersion")] + public DateTimeOffset RateCardTermsVersion { get; set; } + [JsonProperty("aaTerms")] + public string AaTerms { get; set; } + [JsonProperty("costUnitId")] + public string CostUnitId { get; set; } + [JsonProperty("costUnitVersion")] + public DateTimeOffset CostUnitVersion { get; set; } + [JsonProperty("hasCostUnit")] + public bool HasCostUnit { get; set; } + [JsonProperty("explanatoryText")] + public string ExplanatoryText { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Api/Model/RateDetail.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/RateDetail.cs new file mode 100644 index 0000000000..d2c2019a3f --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/RateDetail.cs @@ -0,0 +1,19 @@ +using System; +using Newtonsoft.Json; + +namespace AltitudeAngelWings.Clients.Api.Model +{ + public class RateDetail + { + [JsonProperty("id")] + public string Id { get; set; } + [JsonProperty("version")] + public DateTimeOffset Version { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("rate")] + public double Rate { get; set; } + [JsonProperty("ordinal")] + public int Ordinal { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/ReportRequest.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/ReportRequest.cs similarity index 90% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/ReportRequest.cs rename to ExtLibs/AltitudeAngelWings/Clients/Api/Model/ReportRequest.cs index c730316d53..f77b00a5a8 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/ReportRequest.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/ReportRequest.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using Newtonsoft.Json; -namespace AltitudeAngelWings.ApiClient.Models +namespace AltitudeAngelWings.Clients.Api.Model { public class ReportRequest { diff --git a/ExtLibs/AltitudeAngelWings/Clients/Api/Model/UtmDetails.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/UtmDetails.cs new file mode 100644 index 0000000000..edbec4a31d --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/UtmDetails.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace AltitudeAngelWings.Clients.Api.Model +{ + public class UtmDetails + { + [JsonProperty("id")] + public string Id { get; set; } + [JsonProperty("displayName")] + public string DisplayName { get; set; } + [JsonProperty("externalUrl")] + public string ExternalUrl { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Api/Model/UtmStatus.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/UtmStatus.cs new file mode 100644 index 0000000000..1c94a30d7f --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/UtmStatus.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace AltitudeAngelWings.Clients.Api.Model +{ + public class UtmStatus + { + [JsonProperty("title")] + public string Title { get; set; } + [JsonProperty("description")] + public string Description { get; set; } + [JsonProperty("enabled")] + public bool Enabled { get; set; } + [JsonProperty("utm")] + public UtmDetails UtmDetails { get; set; } + [JsonProperty("rateCards")] + public IDictionary> RateTypes { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/WeatherInfo.cs b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/WeatherInfo.cs similarity index 91% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/WeatherInfo.cs rename to ExtLibs/AltitudeAngelWings/Clients/Api/Model/WeatherInfo.cs index ae0fa281a5..faec5a5f97 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/WeatherInfo.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Api/Model/WeatherInfo.cs @@ -1,6 +1,6 @@ using System; -namespace AltitudeAngelWings.ApiClient.Models +namespace AltitudeAngelWings.Clients.Api.Model { public class WeatherInfo { diff --git a/ExtLibs/AltitudeAngelWings/Clients/Auth/AuthClient.cs b/ExtLibs/AltitudeAngelWings/Clients/Auth/AuthClient.cs new file mode 100644 index 0000000000..c31be4104d --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Auth/AuthClient.cs @@ -0,0 +1,120 @@ +using System; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using AltitudeAngelWings.Clients.Auth.Model; +using AltitudeAngelWings.Service; +using Flurl; +using Flurl.Http; +using Flurl.Http.Configuration; + +namespace AltitudeAngelWings.Clients.Auth +{ + public class AuthClient : IAuthClient + { + private readonly ISettings _settings; + private readonly IFlurlClient _client; + + public AuthClient(ISettings settings, IHttpClientFactory clientFactory, ISerializer serializer) + { + _settings = settings; + _client = new FlurlClient + { + Settings = + { + HttpClientFactory = clientFactory, + JsonSerializer = serializer + } + }; + } + + public Task GetTokenFromRefreshToken(string refreshToken, CancellationToken cancellationToken) + => _settings.AuthenticationUrl + .AppendPathSegments("oauth", "v2", "token") + .WithClient(_client) + .PostUrlEncodedAsync( + new + { + client_id = _settings.ClientId, + client_secret = _settings.ClientSecret, + redirect_uri = _settings.RedirectUri, + grant_type = "refresh_token", + refresh_token = refreshToken, + token_format = "jwt" + }, + cancellationToken) + .ReceiveJson(); + + public Task GetTokenFromAuthorizationCode(string authorizationCode, CancellationToken cancellationToken) + => _settings.AuthenticationUrl + .AppendPathSegments("oauth", "v2", "token") + .WithClient(_client) + .PostUrlEncodedAsync( + new + { + client_id = _settings.ClientId, + client_secret = _settings.ClientSecret, + redirect_uri = _settings.RedirectUri, + grant_type = "authorization_code", + code = authorizationCode, + token_format = "jwt" + }, + cancellationToken) + .ReceiveJson(); + + public Task GetTokenFromClientCredentials(CancellationToken cancellationToken) + => _settings.AuthenticationUrl + .AppendPathSegments("oauth", "v2", "token") + .WithClient(_client) + .PostUrlEncodedAsync( + new + { + client_id = _settings.ClientId, + client_secret = _settings.ClientSecret, + grant_type = "client_credentials", + token_format = "jwt" + }, + cancellationToken) + .ReceiveJson(); + + public async Task GetAuthorizationCode(string accessToken, string pollId, CancellationToken cancellationToken) + { + var response = await _settings.AuthenticationUrl + .AppendPathSegments("api", "v1", "security", "get-login") + .SetQueryParam("id", pollId) + .WithOAuthBearerToken(accessToken) + .AllowHttpStatus(HttpStatusCode.NotFound) + .WithClient(_client) + .GetAsync(cancellationToken); + + if (response.StatusCode != (int)HttpStatusCode.OK) + { + return null; + } + + var data = await response.GetJsonAsync(); + return data.code; + } + + public Task GetUserProfile(string accessToken, CancellationToken cancellationToken) + => _settings.AuthenticationUrl + .AppendPathSegment("userProfile") + .WithOAuthBearerToken(accessToken) + .WithClient(_client) + .GetJsonAsync(cancellationToken); + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _client?.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Auth/IAuthClient.cs b/ExtLibs/AltitudeAngelWings/Clients/Auth/IAuthClient.cs new file mode 100644 index 0000000000..01905ad195 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Auth/IAuthClient.cs @@ -0,0 +1,16 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using AltitudeAngelWings.Clients.Auth.Model; + +namespace AltitudeAngelWings.Clients.Auth +{ + public interface IAuthClient : IDisposable + { + Task GetTokenFromRefreshToken(string refreshToken, CancellationToken cancellationToken); + Task GetTokenFromAuthorizationCode(string authorizationCode, CancellationToken cancellationToken); + Task GetTokenFromClientCredentials(CancellationToken cancellationToken); + Task GetAuthorizationCode(string accessToken, string pollId, CancellationToken cancellationToken); + Task GetUserProfile(string accessToken, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Client/TokenResponse.cs b/ExtLibs/AltitudeAngelWings/Clients/Auth/Model/TokenResponse.cs similarity index 60% rename from ExtLibs/AltitudeAngelWings/ApiClient/Client/TokenResponse.cs rename to ExtLibs/AltitudeAngelWings/Clients/Auth/Model/TokenResponse.cs index 395f26dc6c..169cea9967 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Client/TokenResponse.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Auth/Model/TokenResponse.cs @@ -1,15 +1,12 @@ -using System; using Newtonsoft.Json; -namespace AltitudeAngelWings.ApiClient.Client +namespace AltitudeAngelWings.Clients.Auth.Model { /// /// A token response for an OAuth endpoint /// public class TokenResponse { - private int _expiresIn; - /// /// The access token /// @@ -26,26 +23,12 @@ public class TokenResponse /// the number of seconds that the Access Token expires in /// [JsonProperty("expires_in")] - public int ExpiresIn - { - get => _expiresIn; - set - { - _expiresIn = value; - ExpiresAt = DateTimeOffset.UtcNow.AddSeconds(_expiresIn); - } - } + public int ExpiresIn { get; set; } /// /// The type of access token, eg 'Bearer' or 'Basic' /// [JsonProperty("token_type")] public string TokenType { get; set; } = ""; - - /// - /// When the object was constructed - /// - [JsonProperty("expires_at")] - public DateTimeOffset ExpiresAt { get; private set; } } } \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Auth/Model/TokenResponseExtensions.cs b/ExtLibs/AltitudeAngelWings/Clients/Auth/Model/TokenResponseExtensions.cs new file mode 100644 index 0000000000..c2f0eaaaa8 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Auth/Model/TokenResponseExtensions.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; + +namespace AltitudeAngelWings.Clients.Auth.Model +{ + public static class TokenResponseExtensions + { + private static JwtSecurityToken GetJwt(string accessToken) + { + try + { + return new JwtSecurityToken(accessToken); + } + catch (Exception) + { + // Ignore + return null; + } + } + + public static string[] AccessTokenScopes(this TokenResponse tokenResponse) + { + if (!tokenResponse.HasAccessToken()) + { + return Array.Empty(); + } + + var token = GetJwt(tokenResponse.AccessToken); + if (token == null || !token.Payload.TryGetValue("urn:oauth:scope", out var value)) + { + return Array.Empty(); + } + + var scopes = new List(); + switch (value) + { + case string str: + scopes.Add(str); + break; + case IEnumerable values: + scopes.AddRange(values.Select(item => item.ToString())); + break; + default: + scopes.Add(JsonExtensions.SerializeToJson(value)); + break; + } + + scopes.Sort(); + return scopes.ToArray(); + } + + public static bool IsValidForAuth(this TokenResponse tokenResponse) + { + if (!tokenResponse.HasAccessToken()) return false; + + var token = GetJwt(tokenResponse.AccessToken); + if (token?.Payload.Exp == null) + { + return false; + } + + // Only valid if >= 1 minute of expiry time left + var expires = DateTimeOffset.FromUnixTimeSeconds(token.Payload.Exp.Value); + return expires > DateTimeOffset.UtcNow.Add(TimeSpan.FromMinutes(1)); + } + + private static bool HasAccessToken(this TokenResponse tokenResponse) + { + if (tokenResponse == null) + { + return false; + } + + return !string.IsNullOrEmpty(tokenResponse.AccessToken); + } + + public static bool CanBeRefreshed(this TokenResponse tokenResponse) + { + if (tokenResponse == null) + { + return false; + } + + return !string.IsNullOrEmpty(tokenResponse.RefreshToken); + } + + public static bool HasScopes(this TokenResponse tokenResponse, params string[] scopesToCheck) + { + if (tokenResponse == null) + { + return false; + } + + var scopes = tokenResponse.AccessTokenScopes(); + return scopesToCheck.All(c => scopes.Contains(c)); + } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/UnitOfMeasure.cs b/ExtLibs/AltitudeAngelWings/Clients/Auth/Model/UnitOfMeasure.cs similarity index 59% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/UnitOfMeasure.cs rename to ExtLibs/AltitudeAngelWings/Clients/Auth/Model/UnitOfMeasure.cs index 2e40d5b97a..652980c2f0 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/UnitOfMeasure.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Auth/Model/UnitOfMeasure.cs @@ -1,4 +1,4 @@ -namespace AltitudeAngelWings.ApiClient.Models +namespace AltitudeAngelWings.Clients.Auth.Model { public enum UnitOfMeasure { diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/UserProfileInfo.cs b/ExtLibs/AltitudeAngelWings/Clients/Auth/Model/UserProfileInfo.cs similarity index 91% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/UserProfileInfo.cs rename to ExtLibs/AltitudeAngelWings/Clients/Auth/Model/UserProfileInfo.cs index a4dc225cd8..d2f4db8bb7 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/UserProfileInfo.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Auth/Model/UserProfileInfo.cs @@ -1,7 +1,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace AltitudeAngelWings.ApiClient.Models +namespace AltitudeAngelWings.Clients.Auth.Model { public class UserProfileInfo { diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Client/BearerTokenHttpMessageHandler.cs b/ExtLibs/AltitudeAngelWings/Clients/BearerTokenHttpMessageHandler.cs similarity index 66% rename from ExtLibs/AltitudeAngelWings/ApiClient/Client/BearerTokenHttpMessageHandler.cs rename to ExtLibs/AltitudeAngelWings/Clients/BearerTokenHttpMessageHandler.cs index 1fd04cc1e4..8477095a8e 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Client/BearerTokenHttpMessageHandler.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/BearerTokenHttpMessageHandler.cs @@ -3,7 +3,7 @@ using System.Threading; using System.Threading.Tasks; -namespace AltitudeAngelWings.ApiClient.Client +namespace AltitudeAngelWings.Clients { public class BearerTokenHttpMessageHandler : DelegatingHandler { @@ -11,10 +11,7 @@ public class BearerTokenHttpMessageHandler : DelegatingHandler private readonly ITokenProvider _tokenProvider; - public BearerTokenHttpMessageHandler( - ITokenProvider tokenProvider, - HttpMessageHandler innerHandler) - : base(innerHandler) + public BearerTokenHttpMessageHandler(ITokenProvider tokenProvider) { _tokenProvider = tokenProvider; } @@ -24,7 +21,10 @@ protected override async Task SendAsync( CancellationToken cancellationToken) { var accessToken = await _tokenProvider.GetToken(cancellationToken); - request.Headers.Authorization = new AuthenticationHeaderValue(BearerScheme, accessToken); + if (!string.IsNullOrEmpty(accessToken)) + { + request.Headers.Authorization = new AuthenticationHeaderValue(BearerScheme, accessToken); + } return await base.SendAsync(request, cancellationToken); } } diff --git a/ExtLibs/AltitudeAngelWings/Clients/DelegatingHttpHandlerFactory.cs b/ExtLibs/AltitudeAngelWings/Clients/DelegatingHttpHandlerFactory.cs new file mode 100644 index 0000000000..a1f3d0db98 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/DelegatingHttpHandlerFactory.cs @@ -0,0 +1,18 @@ +using System; +using System.Net.Http; +using Flurl.Http.Configuration; + +namespace AltitudeAngelWings.Clients +{ + public class DelegatingHttpHandlerFactory : DefaultHttpClientFactory + { + private readonly Func _handlerFactory; + + public DelegatingHttpHandlerFactory(Func handlerFactory) + { + _handlerFactory = handlerFactory; + } + + public override HttpMessageHandler CreateMessageHandler() => _handlerFactory(); + } +} diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/FlightClient.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/FlightClient.cs new file mode 100644 index 0000000000..a8ae067b10 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/FlightClient.cs @@ -0,0 +1,86 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using AltitudeAngelWings.Clients.Flight.Model; +using AltitudeAngelWings.Service; +using Flurl; +using Flurl.Http; +using Flurl.Http.Configuration; + +namespace AltitudeAngelWings.Clients.Flight +{ + public class FlightClient : IFlightClient + { + private readonly ISettings _settings; + private readonly IFlurlClient _client; + + public FlightClient(ISettings settings, IHttpClientFactory clientFactory, ISerializer serializer) + { + _settings = settings; + _client = new FlurlClient + { + Settings = + { + HttpClientFactory = clientFactory, + JsonSerializer = serializer + } + }; + } + + public Task CreateFlightPlan(CreateFlightPlanRequest flightPlan, CancellationToken cancellationToken = default) + => _settings.FlightServiceUrl + .AppendPathSegments("flightapprovals") + .WithClient(_client) + .PostJsonAsync(flightPlan, cancellationToken) + .ReceiveJson(); + + public Task StartFlight(StartFlightRequest startFlightRequest, CancellationToken cancellationToken = default) + => _settings.FlightServiceUrl + .AppendPathSegments("flight", "v2", "flights") + .WithClient(_client) + .PostJsonAsync(startFlightRequest, cancellationToken) + .ReceiveJson(); + + public Task CompleteFlight(string flightId, CancellationToken cancellationToken = default) + => _settings.FlightServiceUrl + .AppendPathSegments("flight", "v2", "flights", flightId) + .WithClient(_client) + .DeleteAsync(cancellationToken); + + public Task CompleteFlightPlan(string flightPlanId, CancellationToken cancellationToken = default) + => _settings.FlightServiceUrl + .AppendPathSegments("flightapprovals", flightPlanId, "complete") + .WithClient(_client) + .PostAsync(cancellationToken: cancellationToken); + + public Task CancelFlightPlan(string flightPlanId, CancellationToken cancellationToken = default) + => _settings.FlightServiceUrl + .AppendPathSegments("flightapprovals", flightPlanId, "cancel") + .WithClient(_client) + .PostAsync(cancellationToken: cancellationToken); + + public Task AcceptInstruction(string instructionId, CancellationToken cancellationToken = default) => ProcessInstruction(instructionId, true, cancellationToken); + + public Task RejectInstruction(string instructionId, CancellationToken cancellationToken = default) => ProcessInstruction(instructionId, false, cancellationToken); + + private Task ProcessInstruction(string instructionId, bool accept, CancellationToken cancellationToken = default) + => _settings.FlightServiceUrl + .AppendPathSegments("flight", "v2", "instructions", instructionId, accept ? "accept" : "reject") + .WithClient(_client) + .PutAsync(cancellationToken: cancellationToken); + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _client?.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/IFlightClient.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/IFlightClient.cs new file mode 100644 index 0000000000..53ed4403cc --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/IFlightClient.cs @@ -0,0 +1,18 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using AltitudeAngelWings.Clients.Flight.Model; + +namespace AltitudeAngelWings.Clients.Flight +{ + public interface IFlightClient : IDisposable + { + Task CompleteFlight(string flightId, CancellationToken cancellationToken = default); + Task CreateFlightPlan(CreateFlightPlanRequest flightPlan, CancellationToken cancellationToken = default); + Task StartFlight(StartFlightRequest startFlightRequest, CancellationToken cancellationToken = default); + Task CompleteFlightPlan(string flightPlanId, CancellationToken cancellationToken = default); + Task CancelFlightPlan(string flightPlanId, CancellationToken cancellationToken = default); + Task AcceptInstruction(string instructionId, CancellationToken cancellationToken = default); + Task RejectInstruction(string instructionId, CancellationToken cancellationToken = default); + } +} diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/AirFrameType.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/AirFrameType.cs new file mode 100644 index 0000000000..5a703dc051 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/AirFrameType.cs @@ -0,0 +1,16 @@ +using System.Runtime.Serialization; + +namespace AltitudeAngelWings.Clients.Flight.Model +{ + public enum AirFrameType + { + [EnumMember(Value = "fixedWing")] + FixedWing = 1, + [EnumMember(Value = "rotary")] + Rotary = 2, + [EnumMember(Value = "vtol")] + VTOL = 3, + [EnumMember(Value = "tethered")] + Tethered = 4 + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/AirborneState.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/AirborneState.cs new file mode 100644 index 0000000000..b4288db2d2 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/AirborneState.cs @@ -0,0 +1,25 @@ +using System.Runtime.Serialization; + +namespace AltitudeAngelWings.Clients.Flight.Model +{ + public enum AirborneState + { + /// + /// The state of the aircraft is unknown + /// + [EnumMember(Value = "unknown")] + Unknown = 0, + + /// + /// The aircraft is reported as being airborne + /// + [EnumMember(Value = "airborne")] + Airborne = 1, + + /// + /// The aircraft is reported as being on the ground + /// + [EnumMember(Value = "grounded")] + Grounded = 2 + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/Altitude.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/Altitude.cs similarity index 82% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/Altitude.cs rename to ExtLibs/AltitudeAngelWings/Clients/Flight/Model/Altitude.cs index 487d15cc83..92f9eec3cf 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/Altitude.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/Altitude.cs @@ -1,7 +1,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace AltitudeAngelWings.ApiClient.Models +namespace AltitudeAngelWings.Clients.Flight.Model { public class Altitude { diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/AltitudeDatum.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/AltitudeDatum.cs similarity index 87% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/AltitudeDatum.cs rename to ExtLibs/AltitudeAngelWings/Clients/Flight/Model/AltitudeDatum.cs index ada3cfe5e0..1233b57ac5 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/AltitudeDatum.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/AltitudeDatum.cs @@ -1,4 +1,4 @@ -namespace AltitudeAngelWings.ApiClient.Models +namespace AltitudeAngelWings.Clients.Flight.Model { public enum AltitudeDatum { diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ApprovalAction.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ApprovalAction.cs new file mode 100644 index 0000000000..2f871b3f37 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ApprovalAction.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace AltitudeAngelWings.Clients.Flight.Model +{ + public class ApprovalAction + { + /// + /// The id of the action + /// + public string Id { get; set; } + + /// + /// A short description of the action to be displayed to the user. e.g. Button text + /// + public string Description { get; set; } + + /// + /// A list of required data fields which must be included when using this action. + /// It is assumed that the client knows how to send a given data item. + /// + public List RequiredData { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ApprovalOptionReference.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ApprovalOptionReference.cs new file mode 100644 index 0000000000..a0f18f19d2 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ApprovalOptionReference.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace AltitudeAngelWings.Clients.Flight.Model +{ + public class ApprovalOptionReference + { + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("approverId")] + public string ApproverId { get; set; } + + [JsonProperty("data")] + public JObject Data { get; set; } + } +} diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ApprovalState.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ApprovalState.cs new file mode 100644 index 0000000000..62138d0c14 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ApprovalState.cs @@ -0,0 +1,29 @@ +using System.Runtime.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace AltitudeAngelWings.Clients.Flight.Model +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum ApprovalState + { + [EnumMember(Value = "pending")] + Pending = 1, + [EnumMember(Value = "approved")] + Approved = 2, + [EnumMember(Value = "rejected")] + Rejected = 3, + [EnumMember(Value = "cancelled")] + Cancelled = 4, + [EnumMember(Value = "preliminaryApproved")] + PreliminaryApproved = 5, + [EnumMember(Value = "notRequired")] + NotRequired = 6, + [EnumMember(Value = "rescinded")] + Rescinded = 7, + [EnumMember(Value = "invalid")] + Invalid = 8, + [EnumMember(Value = "expired")] + Expired = 9 + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ApprovalStatus.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ApprovalStatus.cs new file mode 100644 index 0000000000..7f7a84c37e --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ApprovalStatus.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; + +namespace AltitudeAngelWings.Clients.Flight.Model +{ + public class ApprovalStatus + { + public ApprovalStatus(ApprovalState state, string approverState = null + ) + { + State = state; + ApproverState = approverState; + } + + /// + /// The canonical state of the approval + /// + public ApprovalState State { get; } + + /// + /// The local approver state. + /// + public string ApproverState { get; } + + /// + /// A human friendly message explaining the state. + /// + public string Description { get; set; } + + /// + /// Indicates that the operator must perform an action to continue with this request. + /// + public bool ActionRequired { get; set; } = false; + + /// + /// Whether this approval status should be keep workflow decisions after an edit has taken place. + /// If false, a new workflow will be generated after a edit and existing workflow decisions will be cleared. + /// If true, the workflow can continue with existing decisions after an edit. + /// + public bool ShouldKeepWorkflowDecisionsAfterEdit { get; set; } = false; + + /// + /// The workflow instance ID this approval status is related to. + /// + public string WorkflowInstanceId { get; set; } = null; + + + /// + /// I interaction is required to continue, this will contain details of actions available to the user. + /// + public List Actions { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ContactDetails.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ContactDetails.cs new file mode 100644 index 0000000000..df9810b247 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ContactDetails.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace AltitudeAngelWings.Clients.Flight.Model +{ + public class ContactDetails + { + [JsonProperty("firstName")] + public string FirstName { get; set; } + + [JsonProperty("lastName")] + public string LastName { get; set; } + + [JsonProperty("phoneNumber")] + public string PhoneNumber { get; set; } + + [JsonProperty("emailAddress")] + public string EmailAddress { get; set; } + + [JsonProperty("allowSmsContact")] + public bool AllowSmsContact { get; set; } + + [JsonProperty("registrationIds", NullValueHandling = NullValueHandling.Ignore)] + public IDictionary RegistrationIds { get; set; } + + [JsonProperty("additionalInfo", NullValueHandling = NullValueHandling.Ignore)] + public JObject AdditionalInfo { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/CreateFlightPartRequest.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/CreateFlightPartRequest.cs new file mode 100644 index 0000000000..a1aa3f1d11 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/CreateFlightPartRequest.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using GeoJSON.Net.Feature; +using Newtonsoft.Json; +using NodaTime; + +namespace AltitudeAngelWings.Clients.Flight.Model +{ + public class CreateFlightPartRequest + { + /// + /// Gets or sets an identifier that uniquely identifies this part within this flight. + /// + [JsonProperty("id")] + public string Id { get; set; } + + /// + /// Gets or sets a Polygon or LineString describing the planned operating area or route. + /// + [JsonProperty("geography")] + public Feature Geography { get; set; } + + /// + /// Gets or sets the instant this flight part is expected to start. + /// + [JsonProperty("start")] + public Instant? Start { get; set; } + + /// + /// Gets or sets the instant this flight part is expected to be completed by. + /// + [JsonProperty("end")] + public Instant? End { get; set; } + + /// + /// Gets or sets the preferred time zone for orchestrating the flight part. + /// + [JsonProperty("timeZone")] + public DateTimeZone TimeZone { get; set; } + + /// + /// Gets or sets the maximum altitude that the drone will achieve during the flightPart. + /// + [JsonProperty("maxAltitude")] + public Altitude MaxAltitude { get; set; } + + /// + /// Gets or sets the maximum altitude that the drone will achieve during the flightPart. + /// + [JsonProperty("minAltitude")] + public Altitude MinAltitude { get; set; } + + /// + /// Gets or sets the list of approval options. + /// + [JsonProperty("approvalOptions")] + public List ApprovalOptions { get; set; } + + /// + /// Gets or sets whether Take-Off and landing is at same location or at different locations. + /// + [JsonProperty("takeoffAndLandingIsAtSameLocation")] + public bool? TakeoffAndLandingIsAtSameLocation { get; set; } + } +} diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/CreateFlightPlanRequest.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/CreateFlightPlanRequest.cs new file mode 100644 index 0000000000..55bb8f57db --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/CreateFlightPlanRequest.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using Newtonsoft.Json.Linq; + +namespace AltitudeAngelWings.Clients.Flight.Model +{ + public class CreateFlightPlanRequest + { + /// + /// The summary/title of the flight + /// + public string Summary { get; set; } + + /// + /// A short description of the flight + /// + public string Description { get; set; } + + /// + /// Contact details for the pilot + /// + public ContactDetails PointOfContact { get; set; } + + /// + /// The serial number of the drone that will be flying to this flight plan, if applicable + /// + public string DroneSerialNumber { get; set; } + + /// + /// The 24-bit ICAO Address the drone is flying under, in hexadecimal format, if applicable + /// + public string IcaoAddress { get; set; } + + /// + /// Details on the drone carrying out the flight plan + /// + public CreateFlightPlanRequestDroneDetails DroneDetails { get; set; } + + /// + /// The flight's operation mode + /// + public FlightOperationMode FlightOperationMode { get; set; } + + /// + /// The flight characteristics of the drone, if applicable + /// + public FlightCapability FlightCapability { get; set; } + + /// + /// Each part that the flight is segmented into + /// + public List Parts { get; set; } + + /// + /// Extra data as required by aviation authorities in the flight area + /// + public JObject DataFields { get; set; } + + /// + /// The reason for the flight i.e. Commercial or Recreational + /// + public FlightReason FlightReason { get; set; } + + /// + /// The owners of the Flight Plan. Used if more complex ownership than "the user initiating the request owns the plan" + /// is required. + /// + public FlightPlanOwners Owners { set; get; } + + /// + /// Contact details for the Organization behind the flight + /// + public FlightPlanRequestOrganization Organization { get; set; } + + /// + /// If true (default), and providing the client is authorized to do so, approval will be requested for the provided flight plan + /// If false, no approvals will be requested and the flight plan is for information only. The request will generate a "NoService" response. + /// + public bool RequestApproval { get; set; } = true; + + /// + /// Set to false to suppress normal emails from flight - safety critical emails will still be sent. + /// + public bool SendEmails { get; set; } = true; + } +} diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/CreateFlightPlanRequestDroneDetails.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/CreateFlightPlanRequestDroneDetails.cs new file mode 100644 index 0000000000..f6b2c2ef6d --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/CreateFlightPlanRequestDroneDetails.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; + +namespace AltitudeAngelWings.Clients.Flight.Model +{ + public class CreateFlightPlanRequestDroneDetails + { + /// + /// The primary color of the drone + /// + public string Color { get; set; } + + /// + /// Any distinctive markings on the drone + /// + public string Markings { get; set; } + + /// + /// The airframe of the drone + /// + public AirFrameType AirFrame { get; set; } + + /// + /// The maximum take-off weight of the drone in kg + /// + public double MaxWeight { get; set; } + + /// + /// The manufacturer of the drone. + /// + public string Manufacturer { get; set; } + + /// + /// The model of the drone. + /// + public string Model { get; set; } + + /// + /// Registration Ids for the drone + /// + public IDictionary RegistrationIds { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/CreateFlightPlanResponse.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/CreateFlightPlanResponse.cs new file mode 100644 index 0000000000..2e30197acf --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/CreateFlightPlanResponse.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace AltitudeAngelWings.Clients.Flight.Model +{ + public class CreateFlightPlanResponse + { + [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] + public Guid? Id { get; set; } + + [JsonProperty("version", NullValueHandling = NullValueHandling.Ignore)] + public string Version { get; set; } + + [JsonProperty("status", NullValueHandling = NullValueHandling.Ignore)] + public ApprovalStatus Status { get; set; } + + [JsonProperty("flightPlanStatus", NullValueHandling = NullValueHandling.Ignore)] + public FlightPlanStatus FlightPlanStatus { get; set; } = FlightPlanStatus.Submitted; + + [JsonProperty("reasons", NullValueHandling = NullValueHandling.Ignore)] + public Dictionary> ApproverToReasons { get; set; } + + [JsonProperty("flightAlertSubscriptionId", NullValueHandling = NullValueHandling.Ignore)] + public string FlightAlertSubscriptionId { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/EmergencyState.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/EmergencyState.cs new file mode 100644 index 0000000000..a498bc2aca --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/EmergencyState.cs @@ -0,0 +1,53 @@ +using System; +using Newtonsoft.Json; + +namespace AltitudeAngelWings.Clients.Flight.Model +{ + public class EmergencyState : IEquatable + { + /// + /// The aircraft has currently declared an emergency + /// + [JsonProperty("inEmergency")] + public bool IsInEmergency { get; set; } = false; + + /// + /// Free text describing the reason for the emergency + /// + [JsonProperty("reason")] + public string Reason { get; set; } = null; + + public bool Equals(EmergencyState other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return IsInEmergency == other.IsInEmergency && Reason == other.Reason; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((EmergencyState)obj); + } + + public override int GetHashCode() + { + unchecked + { + return (IsInEmergency.GetHashCode() * 397) ^ (Reason != null ? Reason.GetHashCode() : 0); + } + } + + public static bool operator ==(EmergencyState left, EmergencyState right) + { + return Equals(left, right); + } + + public static bool operator !=(EmergencyState left, EmergencyState right) + { + return !Equals(left, right); + } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightCapability.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightCapability.cs new file mode 100644 index 0000000000..717c0a8950 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightCapability.cs @@ -0,0 +1,11 @@ +namespace AltitudeAngelWings.Clients.Flight.Model +{ + public enum FlightCapability + { + Unspecified = 0, + FixedWing = 1, + Rotary = 2, + VTOL = 3, + Tethered = 4 + } +} diff --git a/ExtLibs/AltitudeAngelWings/Models/FlightOperationMode.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightOperationMode.cs similarity index 75% rename from ExtLibs/AltitudeAngelWings/Models/FlightOperationMode.cs rename to ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightOperationMode.cs index a90ccfb62a..58acae4e4c 100644 --- a/ExtLibs/AltitudeAngelWings/Models/FlightOperationMode.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightOperationMode.cs @@ -1,4 +1,4 @@ -namespace AltitudeAngelWings.Models +namespace AltitudeAngelWings.Clients.Flight.Model { public enum FlightOperationMode { diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightPlanOwners.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightPlanOwners.cs new file mode 100644 index 0000000000..3f8f8ef78e --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightPlanOwners.cs @@ -0,0 +1,9 @@ +namespace AltitudeAngelWings.Clients.Flight.Model +{ + public class FlightPlanOwners + { + public string UserId { get; set; } + + public string OrganizationId { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightPlanRequestOrganization.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightPlanRequestOrganization.cs new file mode 100644 index 0000000000..c64a839dd7 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightPlanRequestOrganization.cs @@ -0,0 +1,20 @@ +namespace AltitudeAngelWings.Clients.Flight.Model +{ + public class FlightPlanRequestOrganization + { + /// + /// The name of the organization + /// + public string Name { get; set; } + + /// + /// The primary phone number of the organization + /// + public string PhoneNumber { get; set; } + + /// + /// The primary email address of the organization + /// + public string Email { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightPlanStatus.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightPlanStatus.cs new file mode 100644 index 0000000000..bcc016bdcd --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightPlanStatus.cs @@ -0,0 +1,19 @@ +using System.Runtime.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace AltitudeAngelWings.Clients.Flight.Model +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum FlightPlanStatus + { + [EnumMember(Value = "submitted")] + Submitted = 1, + [EnumMember(Value = "active")] + Active = 2, + [EnumMember(Value = "complete")] + Complete = 3, + [EnumMember(Value = "cancelled")] + Cancelled = 4 + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightPriorityLevel.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightPriorityLevel.cs new file mode 100644 index 0000000000..e028209ea2 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightPriorityLevel.cs @@ -0,0 +1,11 @@ +namespace AltitudeAngelWings.Clients.Flight.Model +{ + public enum FlightPriorityLevel + { + Unspecified, + Police, + Royalty, + Military, + AirAmbulance + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightReason.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightReason.cs new file mode 100644 index 0000000000..e1f03c5af8 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightReason.cs @@ -0,0 +1,9 @@ +namespace AltitudeAngelWings.Clients.Flight.Model +{ + public enum FlightReason + { + Unspecified = 0, + Recreational = 1, + Commercial = 2 + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightState.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightState.cs new file mode 100644 index 0000000000..38a70e89da --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightState.cs @@ -0,0 +1,29 @@ +using System; +using Newtonsoft.Json; + +namespace AltitudeAngelWings.Clients.Flight.Model +{ + public class FlightState + { + public FlightState() : this(default) + { + } + + public FlightState(T value) + { + Value = value; + } + + /// + /// The state value + /// + [JsonProperty("value")] + public T Value { get; set; } + + /// + /// When the value was last set in UTC. Null if never set. + /// + [JsonProperty("lastUpdatedUtc")] + public DateTime? LastUpdatedUtc { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightStates.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightStates.cs new file mode 100644 index 0000000000..f581246f7e --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/FlightStates.cs @@ -0,0 +1,25 @@ +using Newtonsoft.Json; + +namespace AltitudeAngelWings.Clients.Flight.Model +{ + public class FlightStates + { + /// + /// The airborne state of the aircraft involved in the flight. + /// + [JsonProperty("airborneState")] + public FlightState AirborneState { get; set; } + + /// + /// Whether the aircraft is in an emergency + /// + [JsonProperty("emergencyState")] + public FlightState EmergencyState { get; set; } + + /// + /// Aircraft situation response + /// + [JsonProperty("situationResponseState")] + public FlightState SituationResponseState { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/FlightApprovalServiceRequest.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/FlightApprovalServiceRequest.cs new file mode 100644 index 0000000000..a7e912a53e --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/FlightApprovalServiceRequest.cs @@ -0,0 +1,13 @@ +namespace AltitudeAngelWings.Clients.Flight.Model.ServiceRequests +{ + public class FlightApprovalServiceRequest : IFlightServiceRequest + { + public const string ServiceName = "flight_approval"; + + /// + public string Name => ServiceName; + + /// + public FlightApprovalServiceRequestProperties Properties { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/FlightApprovalServiceRequestProperties.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/FlightApprovalServiceRequestProperties.cs new file mode 100644 index 0000000000..3fedb9322a --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/FlightApprovalServiceRequestProperties.cs @@ -0,0 +1,6 @@ +namespace AltitudeAngelWings.Clients.Flight.Model.ServiceRequests +{ + public class FlightApprovalServiceRequestProperties + { + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/FlightApprovalServiceResponse.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/FlightApprovalServiceResponse.cs new file mode 100644 index 0000000000..feae7bfd54 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/FlightApprovalServiceResponse.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace AltitudeAngelWings.Clients.Flight.Model.ServiceRequests +{ + public class FlightApprovalServiceResponse : IFlightServiceResponse + { + /// + public string Name => FlightApprovalServiceRequest.ServiceName; + + /// + public FlightApprovalServiceResponseProperties Properties { get; set; } = new FlightApprovalServiceResponseProperties(); + + /// + public List Conditions { get; set; } = new List(); + + /// + public List Errors { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/FlightApprovalServiceResponseProperties.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/FlightApprovalServiceResponseProperties.cs new file mode 100644 index 0000000000..6675ea7107 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/FlightApprovalServiceResponseProperties.cs @@ -0,0 +1,10 @@ +namespace AltitudeAngelWings.Clients.Flight.Model.ServiceRequests +{ + public class FlightApprovalServiceResponseProperties + { + /// + /// Current overall approval status + /// + public ApprovalStatus FlightStatus { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/FlightServiceResponseConverter.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/FlightServiceResponseConverter.cs new file mode 100644 index 0000000000..3410089469 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/FlightServiceResponseConverter.cs @@ -0,0 +1,23 @@ +using System; +using AltitudeAngelWings.Clients.Flight.Model.ServiceRequests.ProtocolConfiguration; +using Newtonsoft.Json.Linq; + +namespace AltitudeAngelWings.Clients.Flight.Model.ServiceRequests +{ + public class FlightServiceResponseConverter : JsonCreationConverter + { + /// + protected override IFlightServiceResponse Create(Type objectType, JObject jObject) + { + switch (jObject.GetValue("name", StringComparison.InvariantCultureIgnoreCase)?.Value()) + { + case TacticalDeconflictionFlightServiceRequest.ServiceName: + return new TacticalDeconflictionFlightServiceResponse(); + case FlightApprovalServiceRequest.ServiceName: + return new FlightApprovalServiceResponse(); + default: + throw new ArgumentOutOfRangeException("wow"); + } + } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/IFlightServiceRequest.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/IFlightServiceRequest.cs similarity index 85% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/IFlightServiceRequest.cs rename to ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/IFlightServiceRequest.cs index ee1c1d5edc..9e90648344 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/IFlightServiceRequest.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/IFlightServiceRequest.cs @@ -1,4 +1,4 @@ -namespace AltitudeAngelWings.ApiClient.Models.FlightV2.ServiceRequests +namespace AltitudeAngelWings.Clients.Flight.Model.ServiceRequests { /// /// Requests an in-flight service diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/IFlightServiceResponse.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/IFlightServiceResponse.cs similarity index 90% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/IFlightServiceResponse.cs rename to ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/IFlightServiceResponse.cs index 5a7a2c9514..192e0b9545 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/IFlightServiceResponse.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/IFlightServiceResponse.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace AltitudeAngelWings.ApiClient.Models.FlightV2.ServiceRequests +namespace AltitudeAngelWings.Clients.Flight.Model.ServiceRequests { /// /// Provides a response to a flight service request diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/ProtocolConfiguration/BaseNotificationProtocolConfiguration.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/ProtocolConfiguration/BaseNotificationProtocolConfiguration.cs similarity index 93% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/ProtocolConfiguration/BaseNotificationProtocolConfiguration.cs rename to ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/ProtocolConfiguration/BaseNotificationProtocolConfiguration.cs index 26e8fa60f5..1714c71c0d 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/ProtocolConfiguration/BaseNotificationProtocolConfiguration.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/ProtocolConfiguration/BaseNotificationProtocolConfiguration.cs @@ -2,7 +2,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace AltitudeAngelWings.ApiClient.Models.FlightV2.ServiceRequests.ProtocolConfiguration +namespace AltitudeAngelWings.Clients.Flight.Model.ServiceRequests.ProtocolConfiguration { [JsonConverter(typeof(BaseNotificationProtocolConfigurationConverter))] public abstract class BaseNotificationProtocolConfiguration diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/ProtocolConfiguration/BaseTelemetryProtocolConfiguration.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/ProtocolConfiguration/BaseTelemetryProtocolConfiguration.cs similarity index 93% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/ProtocolConfiguration/BaseTelemetryProtocolConfiguration.cs rename to ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/ProtocolConfiguration/BaseTelemetryProtocolConfiguration.cs index d642ea3f73..66ad38feb8 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/ProtocolConfiguration/BaseTelemetryProtocolConfiguration.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/ProtocolConfiguration/BaseTelemetryProtocolConfiguration.cs @@ -2,7 +2,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace AltitudeAngelWings.ApiClient.Models.FlightV2.ServiceRequests.ProtocolConfiguration +namespace AltitudeAngelWings.Clients.Flight.Model.ServiceRequests.ProtocolConfiguration { [JsonConverter(typeof(BaseTelemetryProtocolConfigurationConverter))] public abstract class BaseTelemetryProtocolConfiguration diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/ProtocolConfiguration/JsonCreationConverter.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/ProtocolConfiguration/JsonCreationConverter.cs similarity index 90% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/ProtocolConfiguration/JsonCreationConverter.cs rename to ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/ProtocolConfiguration/JsonCreationConverter.cs index 216d1d8b4c..fe01f0f6d1 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/ProtocolConfiguration/JsonCreationConverter.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/ProtocolConfiguration/JsonCreationConverter.cs @@ -2,7 +2,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace AltitudeAngelWings.ApiClient.Models.FlightV2.ServiceRequests.ProtocolConfiguration +namespace AltitudeAngelWings.Clients.Flight.Model.ServiceRequests.ProtocolConfiguration { public abstract class JsonCreationConverter : JsonConverter { diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/ProtocolConfiguration/UdpTelemetryProtocolConfiguration.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/ProtocolConfiguration/UdpTelemetryProtocolConfiguration.cs similarity index 89% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/ProtocolConfiguration/UdpTelemetryProtocolConfiguration.cs rename to ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/ProtocolConfiguration/UdpTelemetryProtocolConfiguration.cs index 17005ae133..d290c850c1 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/ProtocolConfiguration/UdpTelemetryProtocolConfiguration.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/ProtocolConfiguration/UdpTelemetryProtocolConfiguration.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace AltitudeAngelWings.ApiClient.Models.FlightV2.ServiceRequests.ProtocolConfiguration +namespace AltitudeAngelWings.Clients.Flight.Model.ServiceRequests.ProtocolConfiguration { public class UdpTelemetryProtocolConfiguration : BaseTelemetryProtocolConfiguration { diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/ProtocolConfiguration/WebsocketNotificationProtocolConfiguration.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/ProtocolConfiguration/WebsocketNotificationProtocolConfiguration.cs similarity index 87% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/ProtocolConfiguration/WebsocketNotificationProtocolConfiguration.cs rename to ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/ProtocolConfiguration/WebsocketNotificationProtocolConfiguration.cs index 73a824392c..ef529d8d56 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/ProtocolConfiguration/WebsocketNotificationProtocolConfiguration.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/ProtocolConfiguration/WebsocketNotificationProtocolConfiguration.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; -namespace AltitudeAngelWings.ApiClient.Models.FlightV2.ServiceRequests.ProtocolConfiguration +namespace AltitudeAngelWings.Clients.Flight.Model.ServiceRequests.ProtocolConfiguration { public class WebsocketNotificationProtocolConfiguration : BaseNotificationProtocolConfiguration { diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/TacticalDeconflictionFlightServiceRequest.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/TacticalDeconflictionFlightServiceRequest.cs similarity index 83% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/TacticalDeconflictionFlightServiceRequest.cs rename to ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/TacticalDeconflictionFlightServiceRequest.cs index ba3a6327c8..722420a140 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/TacticalDeconflictionFlightServiceRequest.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/TacticalDeconflictionFlightServiceRequest.cs @@ -1,4 +1,4 @@ -namespace AltitudeAngelWings.ApiClient.Models.FlightV2.ServiceRequests +namespace AltitudeAngelWings.Clients.Flight.Model.ServiceRequests { public class TacticalDeconflictionFlightServiceRequest : IFlightServiceRequest { diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/TacticalDeconflictionFlightServiceResponse.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/TacticalDeconflictionFlightServiceResponse.cs similarity index 86% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/TacticalDeconflictionFlightServiceResponse.cs rename to ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/TacticalDeconflictionFlightServiceResponse.cs index 37f0243d08..6189cd66f0 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/TacticalDeconflictionFlightServiceResponse.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/TacticalDeconflictionFlightServiceResponse.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace AltitudeAngelWings.ApiClient.Models.FlightV2.ServiceRequests +namespace AltitudeAngelWings.Clients.Flight.Model.ServiceRequests { public class TacticalDeconflictionFlightServiceResponse : IFlightServiceResponse { diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/TacticalDeconflictionRequestProperties.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/TacticalDeconflictionRequestProperties.cs similarity index 92% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/TacticalDeconflictionRequestProperties.cs rename to ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/TacticalDeconflictionRequestProperties.cs index 97b822dc4d..8b9c0eaac0 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/TacticalDeconflictionRequestProperties.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/TacticalDeconflictionRequestProperties.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace AltitudeAngelWings.ApiClient.Models.FlightV2.ServiceRequests +namespace AltitudeAngelWings.Clients.Flight.Model.ServiceRequests { public class TacticalDeconflictionRequestProperties { diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/TacticalDeconflictionResponseProperties.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/TacticalDeconflictionResponseProperties.cs similarity index 85% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/TacticalDeconflictionResponseProperties.cs rename to ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/TacticalDeconflictionResponseProperties.cs index c2930ef2ee..71aa9add2c 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/ServiceRequests/TacticalDeconflictionResponseProperties.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/TacticalDeconflictionResponseProperties.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; -using AltitudeAngelWings.ApiClient.Models.FlightV2.ServiceRequests.ProtocolConfiguration; +using AltitudeAngelWings.Clients.Flight.Model.ServiceRequests.ProtocolConfiguration; -namespace AltitudeAngelWings.ApiClient.Models.FlightV2.ServiceRequests +namespace AltitudeAngelWings.Clients.Flight.Model.ServiceRequests { public class TacticalDeconflictionResponseProperties { diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/TelemetryProtocolConfiguration.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/TelemetryProtocolConfiguration.cs new file mode 100644 index 0000000000..c541d2bda0 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/ServiceRequests/TelemetryProtocolConfiguration.cs @@ -0,0 +1,6 @@ +namespace AltitudeAngelWings.Clients.Flight.Model.ServiceRequests +{ + class TelemetryProtocolConfiguration + { + } +} diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/SituationResponseState.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/SituationResponseState.cs new file mode 100644 index 0000000000..c531bd1d42 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/SituationResponseState.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace AltitudeAngelWings.Clients.Flight.Model +{ + public class SituationResponseState + { + /// + /// The aircraft priority response + /// + [JsonProperty("inResponse")] + public bool InResponse { get; set; } = false; + + /// + /// The aircraft response reason eg: emergency, no fuel etc + /// + [JsonProperty("responseReason")] + public string ResponseReason { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/StartFlightRequest.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/StartFlightRequest.cs similarity index 64% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/StartFlightRequest.cs rename to ExtLibs/AltitudeAngelWings/Clients/Flight/Model/StartFlightRequest.cs index 5b5382467e..83ef11acf9 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/FlightV2/StartFlightRequest.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/StartFlightRequest.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; -using AltitudeAngelWings.ApiClient.Models.FlightV2.ServiceRequests; +using AltitudeAngelWings.Clients.Flight.Model.ServiceRequests; -namespace AltitudeAngelWings.ApiClient.Models.FlightV2 +namespace AltitudeAngelWings.Clients.Flight.Model { public class StartFlightRequest { diff --git a/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/StartFlightResponse.cs b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/StartFlightResponse.cs new file mode 100644 index 0000000000..657766a27a --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Flight/Model/StartFlightResponse.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using AltitudeAngelWings.Clients.Flight.Model.ServiceRequests; +using Newtonsoft.Json; + +namespace AltitudeAngelWings.Clients.Flight.Model +{ + public class StartFlightResponse + { + /// + /// The ID of the newly started flight. + /// + [JsonProperty("id")] + public string Id { get; set; } + + /// + /// The ID of the flight plan. + /// + [JsonProperty("flightPlanId")] + public string FlightPlanId { get; set; } + + /// + /// The version of the flight plan that was started + /// + [JsonProperty("flightPlanVersion")] + public string FlightPlanVersion { get; set; } + + /// + /// The date and time the flight was started, if it started successfully + /// + [JsonProperty("started")] + public DateTimeOffset? Started { get; set; } + + /// + /// A list of individual responses for each issued + /// + [JsonProperty("serviceResponses")] + public List ServiceResponses { get; set; } + + /// + /// Contains various indicators of the state of the flight + /// + [JsonProperty("flightState")] + public FlightStates FlightState { get; set; } + + /// + /// The priority level of the flight + /// + [JsonProperty("priorityLevel")] + public FlightPriorityLevel PriorityLevel { get; set; } + } +} diff --git a/ExtLibs/AltitudeAngelWings/Clients/IAuthorizeCodeProvider.cs b/ExtLibs/AltitudeAngelWings/Clients/IAuthorizeCodeProvider.cs new file mode 100644 index 0000000000..ad80fd22a2 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/IAuthorizeCodeProvider.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Specialized; +using System.Threading.Tasks; + +namespace AltitudeAngelWings.Clients +{ + public interface IAuthorizeCodeProvider + { + void GetAuthorizeParameters(NameValueCollection parameters); + Task GetAuthorizeCode(Uri authorizeUri); + } +} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Client/ITokenProvider.cs b/ExtLibs/AltitudeAngelWings/Clients/ITokenProvider.cs similarity index 75% rename from ExtLibs/AltitudeAngelWings/ApiClient/Client/ITokenProvider.cs rename to ExtLibs/AltitudeAngelWings/Clients/ITokenProvider.cs index e87b36b29a..b5b864d604 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Client/ITokenProvider.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/ITokenProvider.cs @@ -1,7 +1,7 @@ using System.Threading; using System.Threading.Tasks; -namespace AltitudeAngelWings.ApiClient.Client +namespace AltitudeAngelWings.Clients { public interface ITokenProvider { diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/CommandAcknowledgement.cs b/ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/CommandAcknowledgement.cs similarity index 77% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/CommandAcknowledgement.cs rename to ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/CommandAcknowledgement.cs index 9ff53f651a..38810da5c0 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/CommandAcknowledgement.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/CommandAcknowledgement.cs @@ -1,4 +1,4 @@ -namespace AltitudeAngelWings.ApiClient.Models.OutboundNotifs +namespace AltitudeAngelWings.Clients.OutboundNotifications.Model { /// /// Acknowledges a command diff --git a/ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/ConflictClearedNotificationProperties.cs b/ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/ConflictClearedNotificationProperties.cs new file mode 100644 index 0000000000..0208b2a9bf --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/ConflictClearedNotificationProperties.cs @@ -0,0 +1,11 @@ +namespace AltitudeAngelWings.Clients.OutboundNotifications.Model +{ + public class ConflictClearedNotificationProperties + { + public string Message + { + get; + set; + } + } +} diff --git a/ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/ConflictInformationProperties.cs b/ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/ConflictInformationProperties.cs new file mode 100644 index 0000000000..fb263d8e7b --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/ConflictInformationProperties.cs @@ -0,0 +1,11 @@ +namespace AltitudeAngelWings.Clients.OutboundNotifications.Model +{ + public class ConflictInformationProperties + { + public string Message + { + get; + set; + } + } +} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/InstructionNotificationProperties.cs b/ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/InstructionNotificationProperties.cs similarity index 78% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/InstructionNotificationProperties.cs rename to ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/InstructionNotificationProperties.cs index c8df122b05..82b57c1cc4 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/InstructionNotificationProperties.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/InstructionNotificationProperties.cs @@ -1,4 +1,4 @@ -namespace AltitudeAngelWings.ApiClient.Models.OutboundNotifs +namespace AltitudeAngelWings.Clients.OutboundNotifications.Model { public class InstructionNotificationProperties { diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/LandNotificationProperties.cs b/ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/LandNotificationProperties.cs similarity index 59% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/LandNotificationProperties.cs rename to ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/LandNotificationProperties.cs index e4fc64b563..64c9c93f38 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/LandNotificationProperties.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/LandNotificationProperties.cs @@ -1,6 +1,6 @@ -namespace AltitudeAngelWings.ApiClient.Models.OutboundNotifs +namespace AltitudeAngelWings.Clients.OutboundNotifications.Model { - class LandNotificationProperties + public class LandNotificationProperties { public double Latitude { diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/LoiterNotificationProperties.cs b/ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/LoiterNotificationProperties.cs similarity index 59% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/LoiterNotificationProperties.cs rename to ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/LoiterNotificationProperties.cs index 50c687a55f..750c7475a2 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/LoiterNotificationProperties.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/LoiterNotificationProperties.cs @@ -1,6 +1,8 @@ -namespace AltitudeAngelWings.ApiClient.Models.OutboundNotifs +using AltitudeAngelWings.Clients.Flight.Model; + +namespace AltitudeAngelWings.Clients.OutboundNotifications.Model { - class LoiterNotificationProperties + public class LoiterNotificationProperties { public double Latitude { diff --git a/ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/NotificationMessage.cs b/ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/NotificationMessage.cs new file mode 100644 index 0000000000..834771f523 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/NotificationMessage.cs @@ -0,0 +1,40 @@ +using Newtonsoft.Json.Linq; + +namespace AltitudeAngelWings.Clients.OutboundNotifications.Model +{ + /// + /// A notification message issued to an + /// + public class NotificationMessage + { + /// + /// Notification Id + /// + public string Id { get; set; } + + /// + /// Notification must be acknowledged + /// + public bool Acknowledge { get; set; } + + /// + /// Type of notification + /// + public string Type { get; set; } + + /// + /// Notification properties + /// + public JObject Properties { get; set; } + + /// + /// Human-readable version of the message + /// + public string FriendlyText { get; set; } + + /// + /// The ID of the flight this message targets. + /// + public string FlightId { get; set; } + } +} diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/PermissionNotificationProperties.cs b/ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/PermissionNotificationProperties.cs similarity index 62% rename from ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/PermissionNotificationProperties.cs rename to ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/PermissionNotificationProperties.cs index 7422f6275a..3b2af80508 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Models/OutboundNotifs/PermissionNotificationProperties.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/PermissionNotificationProperties.cs @@ -1,4 +1,4 @@ -namespace AltitudeAngelWings.ApiClient.Models.OutboundNotifs +namespace AltitudeAngelWings.Clients.OutboundNotifications.Model { public class PermissionNotificationProperties { diff --git a/ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/PermissionState.cs b/ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/PermissionState.cs new file mode 100644 index 0000000000..f322ced067 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/OutboundNotifications/Model/PermissionState.cs @@ -0,0 +1,11 @@ +namespace AltitudeAngelWings.Clients.OutboundNotifications.Model +{ + public enum PermissionState + { + PermissionRejected = 0, + PermissionPending = 1, + PermissionGranted = 2, + PermissionWithdrawn =3, + NoService = 4 + } +} diff --git a/ExtLibs/AltitudeAngelWings/Clients/PolicyHandler.cs b/ExtLibs/AltitudeAngelWings/Clients/PolicyHandler.cs new file mode 100644 index 0000000000..32a2d7ec9f --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/PolicyHandler.cs @@ -0,0 +1,22 @@ +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Polly; + +namespace AltitudeAngelWings.Clients +{ + public class PolicyHandler : DelegatingHandler + { + private readonly IAsyncPolicy _asyncPolicy; + + public PolicyHandler(IAsyncPolicy asyncPolicy) + { + _asyncPolicy = asyncPolicy; + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + return _asyncPolicy.ExecuteAsync(ct => base.SendAsync(request, ct), cancellationToken); + } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Scopes.cs b/ExtLibs/AltitudeAngelWings/Clients/Scopes.cs new file mode 100644 index 0000000000..8a3c9181a7 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Scopes.cs @@ -0,0 +1,15 @@ +namespace AltitudeAngelWings.Clients +{ + public static class Scopes + { + public const string QueryMapData = "query_mapdata"; + public const string QueryMapAirData = "query_mapairdata"; + public const string TalkTower = "talk_tower"; + public const string QueryUserInfo = "query_userinfo"; + public const string ManageFlightReports = "manage_flightreports"; + public const string RequestFlightApprovals = "request_flightapprovals"; + public const string StrategicCrs = "strategic_crs"; + public const string TacticalCrs = "tactical_crs"; + public const string SurveillanceApi = "surveillance_api"; + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Surveillance/ISurveillanceClient.cs b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/ISurveillanceClient.cs new file mode 100644 index 0000000000..bd816b08fb --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/ISurveillanceClient.cs @@ -0,0 +1,12 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using AltitudeAngelWings.Clients.Surveillance.Model; + +namespace AltitudeAngelWings.Clients.Surveillance +{ + public interface ISurveillanceClient : IDisposable + { + Task SendReport(SurveillanceReport surveillanceReport, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/BearingRange.cs b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/BearingRange.cs new file mode 100644 index 0000000000..72ee7e8a28 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/BearingRange.cs @@ -0,0 +1,25 @@ +using System.Text.Json.Serialization; + +namespace AltitudeAngelWings.Clients.Surveillance.Model +{ + public class BearingRange + { + /// + /// The minimum number of decimal degrees horizontal bearing where the detection was seen. 0 is North. + /// + [JsonPropertyName("min")] + public double Min { get; set; } + + /// + /// The maximum number of decimal degrees horizontal bearing where the detection was seen. 0 is North. + /// + [JsonPropertyName("max")] + public double Max { get; set; } + + /// + /// The rate at which the detection was seen moving horizontally, in decimal degrees per second. Clockwise is positive, anti-clockwise is negative. + /// + [JsonPropertyName("rate")] + public double Rate { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/DetectionData.cs b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/DetectionData.cs new file mode 100644 index 0000000000..190f929c5b --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/DetectionData.cs @@ -0,0 +1,50 @@ +using System; +using System.Text.Json.Serialization; + +namespace AltitudeAngelWings.Clients.Surveillance.Model +{ + public class DetectionData + { + /// + /// The ID of this detection report. + /// + [JsonPropertyName("id")] + public string Id { get; set; } + + /// + /// The target ID of this detection report. + /// + [JsonPropertyName("targetId")] + public string TargetId { get; set; } + + /// + /// When the detection was made, in UTC. + /// + [JsonPropertyName("sourceTimeStamp")] + public DateTime SourceTimestamp { get; set; } + + /// + /// The bearing of the detection, relative to the sensor. + /// + [JsonPropertyName("bearing")] + public BearingRange Bearing { get; set; } + + /// + /// The distance between the detection and the sensor. + /// + [JsonPropertyName("distance")] + public MetersRange Distance { get; set; } + + /// + /// The elevation of the detection, relative to the sensor. + /// + [JsonPropertyName("elevation")] + public ElevationRange Elevation { get; set; } + + /// + /// Additional information about the detection. + /// + [JsonPropertyName("additionalInfo")] + public object AdditionalInfo { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/ElevationRange.cs b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/ElevationRange.cs new file mode 100644 index 0000000000..6ed9c0a11d --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/ElevationRange.cs @@ -0,0 +1,25 @@ +using System.Text.Json.Serialization; + +namespace AltitudeAngelWings.Clients.Surveillance.Model +{ + public class ElevationRange + { + /// + /// The minimum number of decimal degrees veritcal azimuth where the detection was seen. 0 is the horizontal tangent to the WGS84 ellipsoid at the sensor location. + /// + [JsonPropertyName("min")] + public double Min { get; set; } + + /// + /// The maximum number of decimal degrees veritcal azimuth where the detection was seen. Zero is the horizontal tangent to the WGS84 ellipsoid at the sensor location. + /// + [JsonPropertyName("max")] + public double Max { get; set; } + + /// + /// The rate at which the detection was seen moving vertically in decimal degrees per second. Upward is positive, downward is negative. + /// + [JsonPropertyName("rate")] + public double Rate { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/GeographicPosition.cs b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/GeographicPosition.cs new file mode 100644 index 0000000000..ff96ed1d5b --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/GeographicPosition.cs @@ -0,0 +1,37 @@ +using System.Text.Json.Serialization; + +namespace AltitudeAngelWings.Clients.Surveillance.Model +{ + public class GeographicPosition + { + /// + /// The Latitude. + /// + [JsonPropertyName("lat")] + public double? Lat { get; set; } + + /// + /// The Longitude. + /// + [JsonPropertyName("lon")] + public double? Lon { get; set; } + + /// + /// Accuracy of the position in meters. If not specified, accuracy is assumed to be the accuracy of the lat/long provided. + /// + [JsonPropertyName("accuracy")] + public double? Accuracy { get; set; } + + /// + /// Indicates the source of the position data (e.g GPS). + /// + [JsonPropertyName("source")] + public string Source { get; set; } + + /// + /// The age in seconds since this data was acquired. + /// + [JsonPropertyName("age")] + public double? Age { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/GeographicVector.cs b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/GeographicVector.cs new file mode 100644 index 0000000000..6a6f7b2948 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/GeographicVector.cs @@ -0,0 +1,49 @@ +using System.Text.Json.Serialization; + +namespace AltitudeAngelWings.Clients.Surveillance.Model +{ + public class GeographicVector + { + /// + /// East-West part of the vector. + /// + [JsonPropertyName("x")] + public double? X { get; set; } + + /// + /// North-South part of the vector. + /// + [JsonPropertyName("y")] + public double? Y { get; set; } + + /// + /// Up-Down part of the vector. + /// + [JsonPropertyName("z")] + public double? Z { get; set; } + + /// + /// Accuracy of x/y part. + /// + [JsonPropertyName("horizontalAccuracy")] + public double? HorizontalAccuracy { get; set; } + + /// + /// Accuracy of z part. + /// + [JsonPropertyName("verticalAccuracy")] + public double? VerticalAccuracy { get; set; } + + /// + /// The source of the vector information. + /// + [JsonPropertyName("source")] + public string Source { get; set; } + + /// + /// The age in seconds since this data was acquired. + /// + [JsonPropertyName("age")] + public double? Age { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/MetersRange.cs b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/MetersRange.cs new file mode 100644 index 0000000000..3b8bea00ab --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/MetersRange.cs @@ -0,0 +1,25 @@ +using System.Text.Json.Serialization; + +namespace AltitudeAngelWings.Clients.Surveillance.Model +{ + public class MetersRange + { + /// + /// The minimum number of metres where the detection was seen. + /// + [JsonPropertyName("min")] + public double Min { get; set; } + + /// + /// The maximum number of metres where the detection was seen. + /// + [JsonPropertyName("max")] + public double Max { get; set; } + + /// + /// The rate at which the detection was seen moving in metres per second. Towards the sensor is negative, away is postive. + /// + [JsonPropertyName("rate")] + public double Rate { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/PositionData.cs b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/PositionData.cs new file mode 100644 index 0000000000..78322d8083 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/PositionData.cs @@ -0,0 +1,75 @@ +using System; +using System.Text.Json.Serialization; +using AltitudeAngelWings.Clients.Flight.Model; + +namespace AltitudeAngelWings.Clients.Surveillance.Model +{ + public class PositionData + { + /// + /// Uniquely identifies this specific position report by the sender. + /// + [JsonPropertyName("id")] + public string Id { get; set; } + + /// + /// Indicates when the position report was sent, according to the sensor clock, in UTC. (IAT should be accounted for by the sensor) + /// + [JsonPropertyName("sourceTimeStamp")] + public DateTime? SourceTimeStamp { get; set; } + + /// + /// Contains identification details of the detected object. + /// + [JsonPropertyName("target")] + public Target Target { get; set; } + + /// + /// Contains the position of the detected object. + /// + [JsonPropertyName("position")] + public GeographicPosition Position { get; set; } + + /// + /// Contains one or more detected altitudes for the object. + /// + [JsonPropertyName("altitudes")] + public Altitude[] Altitudes { get; set; } + + /// + /// True if the sensor considers the object to be on the ground. + /// + [JsonPropertyName("onGround")] + public bool OnGround { get; set; } + + /// + /// Contains data for the speed and track of the object. + /// + [JsonPropertyName("groundVelocity")] + public GeographicVector GroundVelocity { get; set; } + + /// + /// Contains information about the true airspeed (TAS) of the object. + /// + [JsonPropertyName("trueAirspeed")] + public GeographicVector TrueAirSpeed { get; set; } + + /// + /// Contains acceleration information for the object. + /// + [JsonPropertyName("acceleration")] + public GeographicVector Acceleration { get; set; } + + /// + /// Aircraft heading in degrees. + /// + [JsonPropertyName("heading")] + public double Heading { get; set; } + + /// + /// Provides any additional sensor-specific information. + /// + [JsonPropertyName("additionalInfo")] + public object AdditionalInfo { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/SensorLocation.cs b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/SensorLocation.cs new file mode 100644 index 0000000000..f30fa63e0f --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/SensorLocation.cs @@ -0,0 +1,33 @@ +using System.Text.Json.Serialization; +using AltitudeAngelWings.Clients.Flight.Model; + +namespace AltitudeAngelWings.Clients.Surveillance.Model +{ + public class SensorLocation + { + /// + /// The position of the sensor providing the positions. + /// + [JsonPropertyName("position")] + public GeographicPosition Position { get; set; } + + /// + /// The altitude of the sensor. For ground-based sensors, altitude datum should be MSL. + /// + [JsonPropertyName("altitude")] + public Altitude Altitude { get; set; } + + /// + /// For directed sensors, the direction it is facing in degrees from north. + /// + [JsonPropertyName("heading")] + public double Heading { get; set; } + + /// + /// For directed sensors, the angle of the sensor in degrees from horizontal. + /// + [JsonPropertyName("angle")] + public double Angle { get; set; } + + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/SensorState.cs b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/SensorState.cs new file mode 100644 index 0000000000..971fd9052e --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/SensorState.cs @@ -0,0 +1,31 @@ +using System.Text.Json.Serialization; + +namespace AltitudeAngelWings.Clients.Surveillance.Model +{ + public class SensorState + { + /// + /// The identifier of the sensor providing the positions. + /// + [JsonPropertyName("id")] + public string Id { get; set; } + + /// + /// Current pressure in mb at the sensor location. + /// + [JsonPropertyName("pressure")] + public double? Pressure { get; set; } + + /// + /// Provides up to date sensor position information if the sensor can move/rotate/etc. + /// + [JsonPropertyName("location")] + public SensorLocation Location { get; set; } + + /// + /// Provides any additional information about the current state of the sensor. + /// + [JsonPropertyName("additionalInfo")] + public object AdditionalInfo { get; set; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/SurveillanceIdentifier.cs b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/SurveillanceIdentifier.cs new file mode 100644 index 0000000000..04f7af512c --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/SurveillanceIdentifier.cs @@ -0,0 +1,16 @@ +namespace AltitudeAngelWings.Clients.Surveillance.Model +{ + public class SurveillanceIdentifier + { + /// + /// Type of the identifier + /// + public string Type { get; set; } + + /// + /// Identifier Value + /// + public string Id { get; set; } + + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/SurveillanceReport.cs b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/SurveillanceReport.cs new file mode 100644 index 0000000000..559e0c9766 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/SurveillanceReport.cs @@ -0,0 +1,31 @@ +using System.Text.Json.Serialization; + +namespace AltitudeAngelWings.Clients.Surveillance.Model +{ + public class SurveillanceReport + { + /// + /// Provides information about the sensor’s current state. + /// + [JsonPropertyName("sensor")] + public SensorState Sensor { get; set; } + + /// + /// List of all aircraft positions. + /// + [JsonPropertyName("positions")] + public PositionData[] Positions { get; set; } + + /// + /// List of all aircraft detections. + /// + [JsonPropertyName("detections")] + public DetectionData[] Detections { get; set; } + + /// + /// Tags that apply to this position report. + /// + [JsonPropertyName("tags")] + public string[] Tags { get; set; } + } +} diff --git a/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/Target.cs b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/Target.cs new file mode 100644 index 0000000000..d6afa04821 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/Model/Target.cs @@ -0,0 +1,52 @@ +using System.Linq; + +namespace AltitudeAngelWings.Clients.Surveillance.Model +{ + public class Target + { + /// + /// Construct a new with defined properties + /// + public Target(string name, SurveillanceIdentifier[] ids, string type, int confidence, object additionalInfo) + { + Name = name; + Ids = ids; + Id = ids?.FirstOrDefault()?.Id ?? string.Empty; + Type = type; + Confidence = confidence; + AdditionalInfo = additionalInfo; + } + + + /// + /// Human readable name of the flying object + /// + public string Name { get; } + + /// + /// Unique Id that represents the flying object (highest preference in Ids) + /// + public string Id { get; } + + /// + /// URN encoding of the flying object + /// + public string Type { get; } + + /// + /// percentage confidence of classification of flying object + /// + public int Confidence { get; } + + /// + /// Additional info regarding the flying object. + /// + public object AdditionalInfo { get; } + + + /// + /// Ids that represent the flying object in order of preference + /// + public SurveillanceIdentifier[] Ids { get; } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/Surveillance/SurveillanceClient.cs b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/SurveillanceClient.cs new file mode 100644 index 0000000000..1177386e57 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/Surveillance/SurveillanceClient.cs @@ -0,0 +1,50 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using AltitudeAngelWings.Clients.Surveillance.Model; +using AltitudeAngelWings.Service; +using Flurl; +using Flurl.Http; +using Flurl.Http.Configuration; + +namespace AltitudeAngelWings.Clients.Surveillance +{ + public class SurveillanceClient : ISurveillanceClient + { + private readonly ISettings _settings; + private readonly IFlurlClient _client; + + public SurveillanceClient(ISettings settings, IHttpClientFactory clientFactory, ISerializer serializer) + { + _settings = settings; + _client = new FlurlClient + { + Settings = new ClientFlurlHttpSettings + { + HttpClientFactory = clientFactory, + JsonSerializer = serializer + } + }; + } + + public Task SendReport(SurveillanceReport surveillanceReport, CancellationToken cancellationToken = default) + => _settings.SurveillanceUrl + .AppendPathSegments("v2", "reports") + .WithClient(_client) + .PostJsonAsync(surveillanceReport, cancellationToken); + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _client?.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Client/TelemetryClient/ITelemetryClient.cs b/ExtLibs/AltitudeAngelWings/Clients/Telemetry/ITelemetryClient.cs similarity index 77% rename from ExtLibs/AltitudeAngelWings/ApiClient/Client/TelemetryClient/ITelemetryClient.cs rename to ExtLibs/AltitudeAngelWings/Clients/Telemetry/ITelemetryClient.cs index 336c9b02d8..f80707dd38 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Client/TelemetryClient/ITelemetryClient.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Telemetry/ITelemetryClient.cs @@ -1,6 +1,6 @@ using AltitudeAngelWings.Service.AltitudeAngelTelemetry.TelemetryEvents; -namespace AltitudeAngelWings.ApiClient.Client.TelemetryClient +namespace AltitudeAngelWings.Clients.Telemetry { public interface ITelemetryClient { diff --git a/ExtLibs/AltitudeAngelWings/ApiClient/Client/TelemetryClient/TelemetryClient.cs b/ExtLibs/AltitudeAngelWings/Clients/Telemetry/TelemetryClient.cs similarity index 81% rename from ExtLibs/AltitudeAngelWings/ApiClient/Client/TelemetryClient/TelemetryClient.cs rename to ExtLibs/AltitudeAngelWings/Clients/Telemetry/TelemetryClient.cs index 5d6900379c..abdd8622e2 100644 --- a/ExtLibs/AltitudeAngelWings/ApiClient/Client/TelemetryClient/TelemetryClient.cs +++ b/ExtLibs/AltitudeAngelWings/Clients/Telemetry/TelemetryClient.cs @@ -1,9 +1,9 @@ -using AltitudeAngelWings.Service.AltitudeAngelTelemetry.Encryption; +using System; +using AltitudeAngelWings.Service.AltitudeAngelTelemetry.Encryption; using AltitudeAngelWings.Service.AltitudeAngelTelemetry.Services; using AltitudeAngelWings.Service.AltitudeAngelTelemetry.TelemetryEvents; -using System; -namespace AltitudeAngelWings.ApiClient.Client.TelemetryClient +namespace AltitudeAngelWings.Clients.Telemetry { public class TelemetryClient: ITelemetryClient { diff --git a/ExtLibs/AltitudeAngelWings/Clients/UserAgentHandler.cs b/ExtLibs/AltitudeAngelWings/Clients/UserAgentHandler.cs new file mode 100644 index 0000000000..1aefe2ee3a --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/UserAgentHandler.cs @@ -0,0 +1,23 @@ +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; + +namespace AltitudeAngelWings.Clients +{ + public class UserAgentHandler : DelegatingHandler + { + private readonly ProductInfoHeaderValue _version; + + public UserAgentHandler(ProductInfoHeaderValue version) + { + _version = version; + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + request.Headers.UserAgent.Add(_version); + return base.SendAsync(request, cancellationToken); + } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Clients/UserAuthenticationTokenProvider.cs b/ExtLibs/AltitudeAngelWings/Clients/UserAuthenticationTokenProvider.cs new file mode 100644 index 0000000000..633bdfc087 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Clients/UserAuthenticationTokenProvider.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Specialized; +using System.Threading; +using System.Threading.Tasks; +using System.Web; +using AltitudeAngelWings.Clients.Auth; +using AltitudeAngelWings.Clients.Auth.Model; +using AltitudeAngelWings.Model; +using AltitudeAngelWings.Service; +using AltitudeAngelWings.Service.Messaging; + +namespace AltitudeAngelWings.Clients +{ + public class UserAuthenticationTokenProvider : ITokenProvider + { + private readonly ISettings _settings; + private readonly IAuthClient _authClient; + private readonly Lazy _service; + private readonly IAuthorizeCodeProvider _provider; + private readonly IMessagesService _messagesService; + private readonly SemaphoreSlim _lock = new SemaphoreSlim(1); + + public UserAuthenticationTokenProvider(ISettings settings, IAuthClient authClient, Lazy service, IAuthorizeCodeProvider provider, IMessagesService messagesService) + { + _settings = settings; + _authClient = authClient; + _service = service; + _provider = provider; + _messagesService = messagesService; + } + + public async Task GetToken(CancellationToken cancellationToken) + { + await _lock.WaitAsync(cancellationToken); + try + { + if (_settings.TokenResponse.IsValidForAuth()) + { + return _settings.TokenResponse.AccessToken; + } + + if (_settings.TokenResponse.CanBeRefreshed()) + { + try + { + await _messagesService.AddMessageAsync(Message.ForInfo("Refreshing Altitude Angel access token.")); + _settings.TokenResponse = await _authClient.GetTokenFromRefreshToken(_settings.TokenResponse.RefreshToken, cancellationToken); + return _settings.TokenResponse.AccessToken; + } + catch (Exception) + { + // Ignore and try asking user + } + } + + if (_service.Value.SigningIn) + { + return await AskUserForAccessToken(cancellationToken); + } + else + { + await _messagesService.AddMessageAsync(Message.ForAction( + "AskToSignIn", + "You need to sign into Altitude Angel. Click here to sign in.", + () => Task.Factory.StartNew(() => AskUserForAccessToken(CancellationToken.None), cancellationToken), + () => _settings.TokenResponse.IsValidForAuth())); + return null; + } + } + finally + { + _lock.Release(); + } + } + + private async Task AskUserForAccessToken(CancellationToken cancellationToken) + { + var redirectUri = new Uri(_settings.RedirectUri); + + var parameters = HttpUtility.ParseQueryString(string.Empty); + parameters.Add("client_id", _settings.ClientId); + parameters.Add("redirect_uri", redirectUri.ToString()); + parameters.Add("scope", string.Join(" ", _settings.ClientScopes)); + parameters.Add("response_type", "code"); + _provider.GetAuthorizeParameters(parameters); + + var authCode = await _provider.GetAuthorizeCode( + FormatCodeAuthorizeUri( + new Uri($"{_settings.AuthenticationUrl}/oauth/v2/authorize"), + parameters)); + + _settings.TokenResponse = await _authClient.GetTokenFromAuthorizationCode(authCode, cancellationToken); + _service.Value.IsSignedIn.Value = _settings.TokenResponse.IsValidForAuth(); + return _settings.TokenResponse.AccessToken; + } + + private static Uri FormatCodeAuthorizeUri(Uri baseUri, NameValueCollection parameters) + { + var builder = new UriBuilder(baseUri) + { + Query = parameters.ToString() + }; + return builder.Uri; + } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/CollectionExtensions.cs b/ExtLibs/AltitudeAngelWings/CollectionExtensions.cs index e82edf2b8b..1c1c00c68f 100644 --- a/ExtLibs/AltitudeAngelWings/CollectionExtensions.cs +++ b/ExtLibs/AltitudeAngelWings/CollectionExtensions.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Linq; +using System.Text; namespace AltitudeAngelWings { @@ -12,5 +14,34 @@ public static TValue Get(this IDictionary dict, TKey return val; } + + public static string AsReadableList(this IEnumerable items, string separator = ", ", string lastSeparator = " and ") + { + var list = items?.ToList(); + if (list == null || list.Count == 0) + { + return string.Empty; + } + + if (list.Count == 1) + { + return list[0]; + } + + var builder = new StringBuilder(); + for (var index = 0; index < list.Count - 1; index++) + { + builder.Append(list[index]); + if (index < list.Count - 2) + { + builder.Append(separator); + } + } + + builder.Append(lastSeparator); + builder.Append(list[list.Count - 1]); + + return builder.ToString(); + } } } diff --git a/ExtLibs/AltitudeAngelWings/Extra/ColorInfo.cs b/ExtLibs/AltitudeAngelWings/ColorInfo.cs similarity index 83% rename from ExtLibs/AltitudeAngelWings/Extra/ColorInfo.cs rename to ExtLibs/AltitudeAngelWings/ColorInfo.cs index d66e961e6d..7d96be592a 100644 --- a/ExtLibs/AltitudeAngelWings/Extra/ColorInfo.cs +++ b/ExtLibs/AltitudeAngelWings/ColorInfo.cs @@ -1,6 +1,6 @@ using System; -namespace AltitudeAngelWings.Extra +namespace AltitudeAngelWings { [Serializable] public class ColorInfo diff --git a/ExtLibs/AltitudeAngelWings/ExceptionExtensions.cs b/ExtLibs/AltitudeAngelWings/ExceptionExtensions.cs new file mode 100644 index 0000000000..d07d3a4443 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/ExceptionExtensions.cs @@ -0,0 +1,48 @@ +using System; +using System.Text; +using Flurl.Http; + +namespace AltitudeAngelWings +{ + public static class ExceptionExtensions + { + public static string ToDisplayedException(this Exception exception) + { + var builder = new StringBuilder(); + if (exception is AggregateException aggregate) + { + foreach (var inner in aggregate.InnerExceptions) + { + FormatException(builder, inner); + } + } + else + { + FormatException(builder, exception); + } + return builder.ToString(); + } + + private static void FormatException(StringBuilder builder, Exception ex) + { + var message = $"{ex.GetType().Name}: {ex.Message}"; + switch (ex) + { + case FlurlHttpException exception: + var response = exception.GetResponseStringAsync().GetAwaiter().GetResult(); + builder.AppendLine($"{message}: {response}"); + break; + + default: + builder.AppendLine(message); + break; + } + + if (ex.InnerException != null) + { + builder.AppendLine(); + FormatException(builder, ex.InnerException); + } + } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/FlightTelemetry.cs b/ExtLibs/AltitudeAngelWings/FlightTelemetry.cs new file mode 100644 index 0000000000..96978e2b5c --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/FlightTelemetry.cs @@ -0,0 +1,9 @@ +namespace AltitudeAngelWings +{ + public enum FlightTelemetry + { + None = 0, + Telemetry = 1, + Surveillance = 2 + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Extra/IMap.cs b/ExtLibs/AltitudeAngelWings/IMap.cs similarity index 83% rename from ExtLibs/AltitudeAngelWings/Extra/IMap.cs rename to ExtLibs/AltitudeAngelWings/IMap.cs index 6145bce3fd..2a9fb05bf7 100644 --- a/ExtLibs/AltitudeAngelWings/Extra/IMap.cs +++ b/ExtLibs/AltitudeAngelWings/IMap.cs @@ -1,9 +1,9 @@ using System; using System.Reactive; -using AltitudeAngelWings.ApiClient.Models; +using AltitudeAngelWings.Clients.Api.Model; using GeoJSON.Net.Feature; -namespace AltitudeAngelWings.Extra +namespace AltitudeAngelWings { public interface IMap { diff --git a/ExtLibs/AltitudeAngelWings/Service/IMapInfoDockPanel.cs b/ExtLibs/AltitudeAngelWings/IMapInfoDockPanel.cs similarity index 60% rename from ExtLibs/AltitudeAngelWings/Service/IMapInfoDockPanel.cs rename to ExtLibs/AltitudeAngelWings/IMapInfoDockPanel.cs index 2b0fd7a5cb..c677392564 100644 --- a/ExtLibs/AltitudeAngelWings/Service/IMapInfoDockPanel.cs +++ b/ExtLibs/AltitudeAngelWings/IMapInfoDockPanel.cs @@ -1,6 +1,6 @@ -using AltitudeAngelWings.ApiClient.Client; +using AltitudeAngelWings.Clients.Api.Model; -namespace AltitudeAngelWings.Service +namespace AltitudeAngelWings { public interface IMapInfoDockPanel { diff --git a/ExtLibs/AltitudeAngelWings/Extra/IMissionPlanner.cs b/ExtLibs/AltitudeAngelWings/IMissionPlanner.cs similarity index 83% rename from ExtLibs/AltitudeAngelWings/Extra/IMissionPlanner.cs rename to ExtLibs/AltitudeAngelWings/IMissionPlanner.cs index 97d03d984b..a9602b5aec 100644 --- a/ExtLibs/AltitudeAngelWings/Extra/IMissionPlanner.cs +++ b/ExtLibs/AltitudeAngelWings/IMissionPlanner.cs @@ -1,13 +1,12 @@ +using System.Net.Http.Headers; using System.Threading.Tasks; -using AltitudeAngelWings.Models; -namespace AltitudeAngelWings.Extra +namespace AltitudeAngelWings { public interface IMissionPlanner { IMap FlightPlanningMap { get; } IMap FlightDataMap { get; } - Task GetFlightPlan(); Task CommandDroneToReturnToBase(); Task CommandDroneToLoiter( float latitude, @@ -22,5 +21,6 @@ Task CommandDroneToLand( Task Disarm(); Task ShowMessageBox(string message, string caption = "Message"); Task ShowYesNoMessageBox(string message, string caption = "Message"); + ProductInfoHeaderValue VersionHeader { get; } } } \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Extra/IMissionPlannerState.cs b/ExtLibs/AltitudeAngelWings/IMissionPlannerState.cs similarity index 50% rename from ExtLibs/AltitudeAngelWings/Extra/IMissionPlannerState.cs rename to ExtLibs/AltitudeAngelWings/IMissionPlannerState.cs index 3151763e85..0cdaf38844 100644 --- a/ExtLibs/AltitudeAngelWings/Extra/IMissionPlannerState.cs +++ b/ExtLibs/AltitudeAngelWings/IMissionPlannerState.cs @@ -1,4 +1,8 @@ -namespace AltitudeAngelWings.Extra +using System.Collections.Generic; +using AltitudeAngelWings.Clients.Flight.Model; +using AltitudeAngelWings.Model; + +namespace AltitudeAngelWings { public interface IMissionPlannerState { @@ -10,5 +14,8 @@ public interface IMissionPlannerState float GroundCourse { get; } bool IsConnected { get; } float VerticalSpeed { get; } + FlightCapability FlightCapability { get; } + IList Waypoints { get; } + FlightPlanWaypoint HomeLocation { get; } } } diff --git a/ExtLibs/AltitudeAngelWings/Extra/IOverlay.cs b/ExtLibs/AltitudeAngelWings/IOverlay.cs similarity index 85% rename from ExtLibs/AltitudeAngelWings/Extra/IOverlay.cs rename to ExtLibs/AltitudeAngelWings/IOverlay.cs index 88e11d3881..d7244d6732 100644 --- a/ExtLibs/AltitudeAngelWings/Extra/IOverlay.cs +++ b/ExtLibs/AltitudeAngelWings/IOverlay.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; -using AltitudeAngelWings.ApiClient.Models; +using AltitudeAngelWings.Clients.Api.Model; using GeoJSON.Net.Feature; -namespace AltitudeAngelWings.Extra +namespace AltitudeAngelWings { public interface IOverlay { diff --git a/ExtLibs/AltitudeAngelWings/IServiceLocator.cs b/ExtLibs/AltitudeAngelWings/IServiceLocator.cs index 5115d91d31..c1aac7ac7f 100644 --- a/ExtLibs/AltitudeAngelWings/IServiceLocator.cs +++ b/ExtLibs/AltitudeAngelWings/IServiceLocator.cs @@ -2,6 +2,6 @@ namespace AltitudeAngelWings { public interface IServiceLocator { - T Resolve(); + T Resolve(string key = null); } } \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Service/ISettings.cs b/ExtLibs/AltitudeAngelWings/ISettings.cs similarity index 74% rename from ExtLibs/AltitudeAngelWings/Service/ISettings.cs rename to ExtLibs/AltitudeAngelWings/ISettings.cs index b21b61feb9..69b698db12 100644 --- a/ExtLibs/AltitudeAngelWings/Service/ISettings.cs +++ b/ExtLibs/AltitudeAngelWings/ISettings.cs @@ -1,9 +1,10 @@ using System; using System.Collections.Generic; -using AltitudeAngelWings.ApiClient.Client; +using AltitudeAngelWings.Clients.Api.Model; +using AltitudeAngelWings.Clients.Auth.Model; using AltitudeAngelWings.Service.AltitudeAngelTelemetry.Encryption; -namespace AltitudeAngelWings.Service +namespace AltitudeAngelWings { public interface ISettings { @@ -13,19 +14,17 @@ public interface ISettings string FlightServiceUrl { get; } string ClientId { get; } string ClientSecret { get; } - bool SurveillanceMode { get; } + bool UseFlightPlans { get; set; } + bool UseFlights { get; set; } TokenResponse TokenResponse { get; set; } IList MapFilters { get; set; } - bool FlightReportEnable { get; set; } bool UseExistingFlightPlanId { get; set; } - bool UseFlightPlanLocalScope { get; set; } Guid ExistingFlightPlanId { get; set; } - string CurrentFlightReportId { get; set; } + string CurrentFlightPlanId { get; set; } string CurrentFlightId { get; set; } - string FlightReportName { get; set; } - string FlightReportDescription { get; set; } - bool FlightReportCommercial { get; set; } - TimeSpan FlightReportTimeSpan { get; set; } + string FlightPlanName { get; set; } + string FlightPlanDescription { get; set; } + TimeSpan FlightPlanTimeSpan { get; set; } HashType EncryptionHashType { get; } string EncryptionKeySecret { get; } SymmetricEncryptionType EncryptionType { get; } @@ -35,8 +34,8 @@ public interface ISettings int TelemetryPortNumber { get; set; } int TransmissionRateInMilliseconds { get; set; } TimeSpan MinimumPollInterval { get; set; } - string OutboundNotifsEndpointUrl { get; set; } - bool DisableTelemetrySending { get; } + string OutboundNotificationsUrl { get; set; } + FlightTelemetry SendFlightTelemetry { get; set; } string UrlDomainSuffix { get; } bool OverrideClientUrlSettings { get; set; } string OverrideClientId { get; set; } @@ -55,6 +54,7 @@ public interface ISettings string FlightIdentifierIcaoAddress { get; set; } bool FlightIdentifierSerial { get; set; } string FlightIdentifierSerialNumber { get; set; } + int AltitudeFilter { get; set; } + string SurveillanceUrl { get; } } - } \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/IUiThreadInvoke.cs b/ExtLibs/AltitudeAngelWings/IUiThreadInvoke.cs index db97699b73..55385afeba 100644 --- a/ExtLibs/AltitudeAngelWings/IUiThreadInvoke.cs +++ b/ExtLibs/AltitudeAngelWings/IUiThreadInvoke.cs @@ -5,7 +5,7 @@ namespace AltitudeAngelWings { public interface IUiThreadInvoke { - void FireAndForget(Action action); + void Invoke(Action action); Task Invoke(Func action); } } \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Models/FlightData.cs b/ExtLibs/AltitudeAngelWings/Model/FlightData.cs similarity index 91% rename from ExtLibs/AltitudeAngelWings/Models/FlightData.cs rename to ExtLibs/AltitudeAngelWings/Model/FlightData.cs index bac8f0737c..57eeab6a3e 100644 --- a/ExtLibs/AltitudeAngelWings/Models/FlightData.cs +++ b/ExtLibs/AltitudeAngelWings/Model/FlightData.cs @@ -1,6 +1,6 @@ using System; -namespace AltitudeAngelWings.Models +namespace AltitudeAngelWings.Model { public class FlightData { diff --git a/ExtLibs/AltitudeAngelWings/Models/FlightDataPosition.cs b/ExtLibs/AltitudeAngelWings/Model/FlightDataPosition.cs similarity index 75% rename from ExtLibs/AltitudeAngelWings/Models/FlightDataPosition.cs rename to ExtLibs/AltitudeAngelWings/Model/FlightDataPosition.cs index 42709c2540..b3835b0bfb 100644 --- a/ExtLibs/AltitudeAngelWings/Models/FlightDataPosition.cs +++ b/ExtLibs/AltitudeAngelWings/Model/FlightDataPosition.cs @@ -1,11 +1,11 @@ -namespace AltitudeAngelWings.Models +namespace AltitudeAngelWings.Model { public class FlightDataPosition { public double Longitude { get; set; } public double Latitude { get; set; } public double Speed { get; set; } - public int Altitude { get; set; } + public double Altitude { get; set; } public float Course { get; set; } public float VerticalSpeed { get; set; } } diff --git a/ExtLibs/AltitudeAngelWings/Models/FlightPlan.cs b/ExtLibs/AltitudeAngelWings/Model/FlightPlan.cs similarity index 90% rename from ExtLibs/AltitudeAngelWings/Models/FlightPlan.cs rename to ExtLibs/AltitudeAngelWings/Model/FlightPlan.cs index a0e7ab3f12..c0e8233c41 100644 --- a/ExtLibs/AltitudeAngelWings/Models/FlightPlan.cs +++ b/ExtLibs/AltitudeAngelWings/Model/FlightPlan.cs @@ -1,7 +1,8 @@ using System.Collections.Generic; +using AltitudeAngelWings.Clients.Flight.Model; using NodaTime; -namespace AltitudeAngelWings.Models +namespace AltitudeAngelWings.Model { public class FlightPlan { diff --git a/ExtLibs/AltitudeAngelWings/Models/FlightPlanWaypoint.cs b/ExtLibs/AltitudeAngelWings/Model/FlightPlanWaypoint.cs similarity index 61% rename from ExtLibs/AltitudeAngelWings/Models/FlightPlanWaypoint.cs rename to ExtLibs/AltitudeAngelWings/Model/FlightPlanWaypoint.cs index 18b741e890..7facecf55f 100644 --- a/ExtLibs/AltitudeAngelWings/Models/FlightPlanWaypoint.cs +++ b/ExtLibs/AltitudeAngelWings/Model/FlightPlanWaypoint.cs @@ -1,9 +1,9 @@ -namespace AltitudeAngelWings.Models +namespace AltitudeAngelWings.Model { public class FlightPlanWaypoint { public double Longitude { get; set; } public double Latitude { get; set; } - public int Altitude { get; set; } + public double Altitude { get; set; } } } \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Model/Message.cs b/ExtLibs/AltitudeAngelWings/Model/Message.cs new file mode 100644 index 0000000000..74ec2bf422 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Model/Message.cs @@ -0,0 +1,114 @@ +using System; + +namespace AltitudeAngelWings.Model +{ + public class Message + { + private Action _onClick; + private const int DefaultExpirySeconds = 3; + private const int ClickExpirySeconds = 60; + + public static Message ForInfo(string content, TimeSpan timeToLive = default) => ForInfo(null, content, timeToLive); + + public static Message ForInfo(string key, string content, TimeSpan timeToLive = default) + { + var message = new Message(content) + { + Key = key + }; + if (timeToLive != default) + { + message.HasExpired = () => message.Clicked || DateTimeOffset.UtcNow.Subtract(message.Created) >= timeToLive; + } + return message; + } + + public static Message ForError(string content, Exception exception) => ForError(null, content, exception); + + public static Message ForError(string key, string content, Exception exception) + => new Message(content) + { + Key = key, + Type = MessageType.Error, + OnClick = () => ServiceLocator.GetService().ShowMessageBox(exception.ToDisplayedException(), "Exception") + }; + + public static Message ForAction(string content, Action action, Func condition = null) => ForAction(null, content, action, condition); + + public static Message ForAction(string key, string content, Action action, Func condition = null) + { + var message = new Message(content) + { + Key = key, + OnClick = action + }; + if (condition == null) + { + message.HasExpired = () => message.Clicked; + } + else + { + message.HasExpired = () => message.Clicked || condition(); + } + return message; + } + + private Message(string content) + { + Content = content; + Created = DateTimeOffset.UtcNow; + HasExpired = () => Clicked || DateTimeOffset.UtcNow.Subtract(Created) >= TimeSpan.FromSeconds(DefaultExpirySeconds); + } + + /// + /// When the message was created. + /// + public DateTimeOffset Created { get; } + + /// + /// Sets a key associated with the message. If another message with the same key + /// already exists on a message display then it will be removed. A null or empty + /// key denotes an ad-hoc message. + /// + public string Key { get; set; } = ""; + + /// + /// The type of message which affects how it is displayed. + /// + public MessageType Type { get; set; } = MessageType.Information; + + /// + /// The content of the message. + /// + public string Content { get; set; } + + /// + /// Sets the function to see if the message has expired or not. + /// + public Func HasExpired { get; set; } + + /// + /// Sets an action to be performed when the message is clicked. + /// + public Action OnClick + { + get => _onClick; + set + { + _onClick = () => + { + value(); + Clicked = true; + }; + HasExpired = () => Clicked || DateTimeOffset.UtcNow.Subtract(Created) >= TimeSpan.FromSeconds(ClickExpirySeconds); + } + } + + public bool Clicked { get; private set; } + + public override string ToString() + { + return Content; + } + } +} diff --git a/ExtLibs/AltitudeAngelWings/Model/MessageType.cs b/ExtLibs/AltitudeAngelWings/Model/MessageType.cs new file mode 100644 index 0000000000..7128ae5d1e --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Model/MessageType.cs @@ -0,0 +1,8 @@ +namespace AltitudeAngelWings.Model +{ + public enum MessageType + { + Information, + Error + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Models/FlightCapability.cs b/ExtLibs/AltitudeAngelWings/Models/FlightCapability.cs deleted file mode 100644 index 349e958c96..0000000000 --- a/ExtLibs/AltitudeAngelWings/Models/FlightCapability.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace AltitudeAngelWings.Models -{ - public enum FlightCapability - { - Unspecified = 0, - FixedWing = 1, - Rotary = 2 - } -} diff --git a/ExtLibs/AltitudeAngelWings/Models/Message.cs b/ExtLibs/AltitudeAngelWings/Models/Message.cs deleted file mode 100644 index 4bd8cd7501..0000000000 --- a/ExtLibs/AltitudeAngelWings/Models/Message.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; - -namespace AltitudeAngelWings.Models -{ - public class Message - { - public Message(string content) - { - Content = content; - } - - public Message(string content, params object[] data) - { - Content = string.Format(content, data); - } - - public static implicit operator Message(string content) - { - return new Message(content); - } - - public string Content { get; set; } - public TimeSpan TimeToLive { get; set; } = TimeSpan.FromSeconds(1); - } -} diff --git a/ExtLibs/AltitudeAngelWings/Properties/Resources.Designer.cs b/ExtLibs/AltitudeAngelWings/Properties/Resources.Designer.cs deleted file mode 100644 index 1b40e4676f..0000000000 --- a/ExtLibs/AltitudeAngelWings/Properties/Resources.Designer.cs +++ /dev/null @@ -1,63 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace AltitudeAngelWings.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - public class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AltitudeAngelWings.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - } -} diff --git a/ExtLibs/AltitudeAngelWings/Service/AltitudeAngelService.cs b/ExtLibs/AltitudeAngelWings/Service/AltitudeAngelService.cs index e65deb8aa6..78fa488fd7 100644 --- a/ExtLibs/AltitudeAngelWings/Service/AltitudeAngelService.cs +++ b/ExtLibs/AltitudeAngelWings/Service/AltitudeAngelService.cs @@ -1,334 +1,390 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Threading; -using System.Threading.Tasks; -using AltitudeAngelWings.ApiClient.Client; -using AltitudeAngelWings.ApiClient.Models; -using AltitudeAngelWings.Extra; -using AltitudeAngelWings.Models; -using AltitudeAngelWings.Service.AltitudeAngelTelemetry; -using AltitudeAngelWings.Service.FlightService; -using AltitudeAngelWings.Service.Messaging; -using Flurl.Http; -using GeoJSON.Net; -using GeoJSON.Net.Feature; -using GeoJSON.Net.Geometry; - -namespace AltitudeAngelWings.Service -{ - public class AltitudeAngelService : IAltitudeAngelService - { - private const string MapOverlayName = "AAMapData"; - private static readonly Dictionary MapFeatureCache = new Dictionary(); - - private readonly IMessagesService _messagesService; - private readonly IMissionPlanner _missionPlanner; - private readonly CompositeDisposable _disposer = new CompositeDisposable(); - private readonly IAltitudeAngelClient _client; - private readonly ITelemetryService _telemetryService; - private readonly IFlightService _flightService; - private readonly ISettings _settings; - private readonly SemaphoreSlim _processLock = new SemaphoreSlim(1); - - public ObservableProperty IsSignedIn { get; } - public ObservableProperty WeatherReport { get; } - public UserProfileInfo CurrentUser { get; private set; } - public IList FilterInfoDisplay { get; } - - public AltitudeAngelService( - IMessagesService messagesService, - IMissionPlanner missionPlanner, - ISettings settings, - IAltitudeAngelClient client, - ITelemetryService telemetryService, - IFlightService flightService) - { - _messagesService = messagesService; - _missionPlanner = missionPlanner; - _settings = settings; - _client = client; - _telemetryService = telemetryService; - _flightService = flightService; - - IsSignedIn = new ObservableProperty(false); - _disposer.Add(IsSignedIn); - WeatherReport = new ObservableProperty(); - _disposer.Add(WeatherReport); - FilterInfoDisplay = _settings.MapFilters; - - _disposer.Add(_missionPlanner.FlightDataMap - .MapChanged - .SubscribeWithAsync((i, ct) => UpdateMapData(_missionPlanner.FlightDataMap, ct))); - _disposer.Add(_missionPlanner.FlightPlanningMap - .MapChanged - .SubscribeWithAsync((i, ct) => UpdateMapData(_missionPlanner.FlightPlanningMap, ct))); - - _disposer.Add(_missionPlanner.FlightDataMap - .FeatureClicked - .Select(f => new { Feature = f, Properties = f.GetFeatureProperties()}) - .Where(i => i.Properties.DetailedCategory == "user:flight_plan_report" && i.Properties.IsOwner) - .SubscribeWithAsync((i, ct) => OnFlightReportClicked(i.Feature))); - _disposer.Add(_missionPlanner.FlightPlanningMap - .FeatureClicked - .Select(f => new { Feature = f, Properties = f.GetFeatureProperties()}) - .Where(i => i.Properties.DetailedCategory == "user:flight_plan_report" && i.Properties.IsOwner) - .SubscribeWithAsync((i, ct) => OnFlightReportClicked(i.Feature))); - } - - public async Task SignInAsync() - { - if (!_settings.CheckEnableAltitudeAngel) - { - return; - } - try - { - // Load the user's profile, will trigger auth - CurrentUser = await _client.GetUserProfile(); - IsSignedIn.Value = true; - await UpdateMapData(_missionPlanner.FlightDataMap, CancellationToken.None); - await UpdateMapData(_missionPlanner.FlightPlanningMap, CancellationToken.None); - } - catch (Exception) - { - await _messagesService.AddMessageAsync("There was a problem signing you in."); - } - } - - public Task DisconnectAsync() - { - _client.Disconnect(true); - IsSignedIn.Value = false; - ProcessAllFromCache(_missionPlanner.FlightDataMap); - ProcessAllFromCache(_missionPlanner.FlightPlanningMap); - return Task.CompletedTask; - } - - private async Task UpdateMapData(IMap map, CancellationToken cancellationToken) - { - if (!IsSignedIn) - { - MapFeatureCache.Clear(); - await SignInAsync(); - } - - if (!map.Enabled) - { - map.DeleteOverlay(MapOverlayName); - map.Invalidate(); - return; - } - - try - { - var area = map.GetViewArea().RoundExpand(4); - var sw = new Stopwatch(); - sw.Start(); - var mapData = await _client.GetMapData(area, cancellationToken); - sw.Stop(); - - mapData.Features.UpdateFilterInfo(FilterInfoDisplay); - _settings.MapFilters = FilterInfoDisplay; - - await _messagesService.AddMessageAsync( - $"Map area loaded {area.NorthEast.Latitude:F4}, {area.SouthWest.Latitude:F4}, {area.SouthWest.Longitude:F4}, {area.NorthEast.Longitude:F4} in {sw.Elapsed.TotalMilliseconds:N2}ms"); - - // add all items to cache - MapFeatureCache.Clear(); - mapData.Features.ForEach(feature => MapFeatureCache[feature.Id] = feature); - - // Only get the features that are enabled by default, and have not been filtered out - ProcessFeatures(map, mapData.Features); - } - catch (Exception ex) when (!(ex is FlurlHttpException) && !(ex.InnerException is TaskCanceledException)) - { - await _messagesService.AddMessageAsync("Failed to update map data."); - } - } - - public void ProcessAllFromCache(IMap map, bool resetFilters = false) - { - map.DeleteOverlay(MapOverlayName); - if (!IsSignedIn) - { - MapFeatureCache.Clear(); - } - - if (!map.Enabled) - { - map.DeleteOverlay(MapOverlayName); - map.Invalidate(); - return; - } - - if (resetFilters) - { - MapFeatureCache.Values.UpdateFilterInfo(FilterInfoDisplay, resetFilters); - } - - ProcessFeatures(map, MapFeatureCache.Values); - map.Invalidate(); - } - - private void ProcessFeatures(IMap map, IEnumerable features) - { - try - { - if (!_processLock.Wait(TimeSpan.FromSeconds(1))) return; - var overlay = map.GetOverlay(MapOverlayName, true); - var polygons = new List(); - var lines = new List(); - - foreach (var feature in features) - { - if (!FilterInfoDisplay - .Intersect(feature.GetFilterInfo(), new FilterInfoDisplayEqualityComparer()) - .Any(i => i.Visible)) - { - continue; - } - - var properties = feature.GetFeatureProperties(); - - switch (feature.Geometry.Type) - { - case GeoJSONObjectType.Point: - { - var pnt = (Point)feature.Geometry; - - var coordinates = new List(); - - if (!string.IsNullOrEmpty(properties.Radius)) - { - var rad = double.Parse(properties.Radius); - for (var i = 0; i <= 360; i += 10) - { - coordinates.Add( - PositionFromBearingAndDistance(new LatLong(((Position)pnt.Coordinates).Latitude, - ((Position)pnt.Coordinates).Longitude), i, rad)); - } - } - - var colorInfo = properties.ToColorInfo(_settings.MapOpacityAdjust); - overlay.AddOrUpdatePolygon(feature.Id, coordinates, colorInfo, feature); - polygons.Add(feature.Id); - } - break; - case GeoJSONObjectType.MultiPoint: - break; - case GeoJSONObjectType.LineString: - { - var line = (LineString)feature.Geometry; - var coordinates = line.Coordinates.OfType() - .Select(c => new LatLong(c.Latitude, c.Longitude)) - .ToList(); - var colorInfo = properties.ToColorInfo(_settings.MapOpacityAdjust); - overlay.AddOrUpdateLine(feature.Id, coordinates, colorInfo, feature); - lines.Add(feature.Id); - } - break; - - case GeoJSONObjectType.MultiLineString: - break; - case GeoJSONObjectType.Polygon: - { - var poly = (Polygon)feature.Geometry; - var coordinates = - poly.Coordinates[0].Coordinates.OfType() - .Select(c => new LatLong(c.Latitude, c.Longitude)) - .ToList(); - - var colorInfo = properties.ToColorInfo(_settings.MapOpacityAdjust); - overlay.AddOrUpdatePolygon(feature.Id, coordinates, colorInfo, feature); - polygons.Add(feature.Id); - } - break; - case GeoJSONObjectType.MultiPolygon: - foreach (var poly in ((MultiPolygon)feature.Geometry).Coordinates) - { - var coordinates = - poly.Coordinates[0].Coordinates.OfType() - .Select(c => new LatLong(c.Latitude, c.Longitude)) - .ToList(); - - var colorInfo = properties.ToColorInfo(_settings.MapOpacityAdjust); - overlay.AddOrUpdatePolygon(feature.Id, coordinates, colorInfo, feature); - polygons.Add(feature.Id); - } - - break; - case GeoJSONObjectType.GeometryCollection: - break; - case GeoJSONObjectType.Feature: - break; - case GeoJSONObjectType.FeatureCollection: - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - overlay.RemovePolygonsExcept(polygons); - overlay.RemoveLinesExcept(lines); - } - finally - { - _processLock.Release(); - } - } - - private static LatLong PositionFromBearingAndDistance(LatLong input, double bearing, double distance) - { - const double rad2deg = 180 / Math.PI; - const double deg2rad = 1.0 / rad2deg; - - // '''extrapolate latitude/longitude given a heading and distance - // thanks to http://www.movable-type.co.uk/scripts/latlong.html - // ''' - // from math import sin, asin, cos, atan2, radians, degrees - var radiusOfEarth = 6378100.0;//# in meters - - var lat1 = deg2rad * input.Latitude; - var lon1 = deg2rad * input.Longitude; - var brng = deg2rad * bearing; - var dr = distance / radiusOfEarth; - - var lat2 = Math.Asin(Math.Sin(lat1) * Math.Cos(dr) + - Math.Cos(lat1) * Math.Sin(dr) * Math.Cos(brng)); - var lon2 = lon1 + Math.Atan2(Math.Sin(brng) * Math.Sin(dr) * Math.Cos(lat1), - Math.Cos(dr) - Math.Sin(lat1) * Math.Sin(lat2)); - - return new LatLong(rad2deg * lat2, rad2deg * lon2); - } - - private async Task OnFlightReportClicked(Feature feature) - { - if (!await _missionPlanner.ShowYesNoMessageBox( - $"You have clicked your flight plan '{feature.GetDisplayInfo().Title}'.{Environment.NewLine}Would you like to set the current flight plan to this one?", - "Flight Plan")) return; - _settings.ExistingFlightPlanId = Guid.Parse(feature.Id); - _settings.UseExistingFlightPlanId = true; - await _messagesService.AddMessageAsync(new Message($"Current flight plan ID set to {feature.Id}") - { TimeToLive = TimeSpan.FromSeconds(10) }); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool isDisposing) - { - if (!isDisposing) return; - _telemetryService.Dispose(); - _flightService.Dispose(); - _client.Dispose(); - _disposer?.Dispose(); - } - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Threading; +using System.Threading.Tasks; +using AltitudeAngelWings.Clients; +using AltitudeAngelWings.Clients.Api; +using AltitudeAngelWings.Clients.Api.Model; +using AltitudeAngelWings.Clients.Auth.Model; +using AltitudeAngelWings.Model; +using AltitudeAngelWings.Service.AltitudeAngelTelemetry; +using AltitudeAngelWings.Service.FlightService; +using AltitudeAngelWings.Service.Messaging; +using Flurl.Http; +using GeoJSON.Net; +using GeoJSON.Net.Feature; +using GeoJSON.Net.Geometry; + +namespace AltitudeAngelWings.Service +{ + public class AltitudeAngelService : IAltitudeAngelService + { + private const string MapOverlayName = "AAMapData"; + private static readonly Dictionary MapFeatureCache = new Dictionary(); + + private readonly IMessagesService _messagesService; + private readonly IMissionPlanner _missionPlanner; + private readonly CompositeDisposable _disposer = new CompositeDisposable(); + private readonly IApiClient _apiClient; + private readonly ITelemetryService _telemetryService; + private readonly IFlightService _flightService; + private readonly ISettings _settings; + private readonly ITokenProvider _tokenProvider; + private readonly SemaphoreSlim _signInLock = new SemaphoreSlim(1); + private readonly SemaphoreSlim _processLock = new SemaphoreSlim(1); + + public ObservableProperty IsSignedIn { get; } + public ObservableProperty WeatherReport { get; } + public IList FilterInfoDisplay { get; } + + public bool SigningIn => _signInLock.CurrentCount == 0; + + public AltitudeAngelService( + IMessagesService messagesService, + IMissionPlanner missionPlanner, + ISettings settings, + ITokenProvider tokenProvider, + IApiClient apiClient, + ITelemetryService telemetryService, + IFlightService flightService) + { + _messagesService = messagesService; + _missionPlanner = missionPlanner; + _settings = settings; + _tokenProvider = tokenProvider; + _apiClient = apiClient; + _telemetryService = telemetryService; + _flightService = flightService; + + IsSignedIn = new ObservableProperty(_settings.TokenResponse.IsValidForAuth()); + _disposer.Add(IsSignedIn); + WeatherReport = new ObservableProperty(); + _disposer.Add(WeatherReport); + FilterInfoDisplay = _settings.MapFilters; + + _disposer.Add(_missionPlanner.FlightDataMap + .MapChanged + .SubscribeWithAsync((i, ct) => UpdateMapData(_missionPlanner.FlightDataMap, ct))); + _disposer.Add(_missionPlanner.FlightPlanningMap + .MapChanged + .SubscribeWithAsync((i, ct) => UpdateMapData(_missionPlanner.FlightPlanningMap, ct))); + _disposer.Add(_missionPlanner.FlightDataMap + .FeatureClicked + .Select(f => new { Feature = f, Properties = f.GetFeatureProperties() }) + .Where(i => i.Properties.DetailedCategory == "user:flight_plan_report" && i.Properties.IsOwner) + .SubscribeWithAsync((i, ct) => OnFlightReportClicked(i.Feature))); + _disposer.Add(_missionPlanner.FlightPlanningMap + .FeatureClicked + .Select(f => new { Feature = f, Properties = f.GetFeatureProperties() }) + .Where(i => i.Properties.DetailedCategory == "user:flight_plan_report" && i.Properties.IsOwner) + .SubscribeWithAsync((i, ct) => OnFlightReportClicked(i.Feature))); + } + + public async Task SignInAsync(CancellationToken cancellationToken = default) + { + if (!_settings.CheckEnableAltitudeAngel) + { + return; + } + + try + { + await _signInLock.WaitAsync(cancellationToken); + + // Attempt to get a token + var token = await _tokenProvider.GetToken(cancellationToken); + } + catch (FlurlHttpException ex) when (ex.StatusCode == 401) + { + // Ignore these as they'll be messaged by the sign in components + } + catch (Exception ex) + { + await _messagesService.AddMessageAsync( + Message.ForError("There was a problem signing you in to Altitude Angel.", ex)); + _settings.TokenResponse = null; + } + finally + { + _signInLock.Release(); + } + + if (_settings.TokenResponse.IsValidForAuth()) + { + IsSignedIn.Value = true; + await UpdateMapData(_missionPlanner.FlightDataMap, CancellationToken.None); + await UpdateMapData(_missionPlanner.FlightPlanningMap, CancellationToken.None); + } + } + + public Task DisconnectAsync() + { + _settings.TokenResponse = null; + IsSignedIn.Value = false; + ProcessAllFromCache(_missionPlanner.FlightDataMap); + ProcessAllFromCache(_missionPlanner.FlightPlanningMap); + return Task.CompletedTask; + } + + private async Task UpdateMapData(IMap map, CancellationToken cancellationToken) + { + if (!(IsSignedIn.Value || _settings.CheckEnableAltitudeAngel)) + { + MapFeatureCache.Clear(); + } + + if (!map.Enabled) + { + map.DeleteOverlay(MapOverlayName); + map.Invalidate(); + return; + } + + try + { + var area = map.GetViewArea().RoundExpand(4); + var sw = new Stopwatch(); + sw.Start(); + var mapData = await _apiClient.GetMapData(area, cancellationToken); + sw.Stop(); + + foreach (var errorReason in mapData.ExcludedData.Select(e => e.ErrorReason).Distinct()) + { + var reasons = mapData.ExcludedData.Where(e => e.ErrorReason == errorReason).ToList(); + if (reasons.Count <= 0) continue; + string message; + switch (errorReason) + { + case "QueryAreaTooLarge": + message = $"Zoom in to see {reasons.Select(d => d.Detail.DisplayName).AsReadableList()} from Altitude Angel."; + break; + + default: + message = $"Warning: {reasons.Select(d => d.Detail.DisplayName).AsReadableList()} have been excluded from the Altitude Angel data."; + break; + } + await _messagesService.AddMessageAsync(Message.ForInfo(errorReason, message, TimeSpan.FromSeconds(_settings.MapUpdateRefresh))); + } + + mapData.Features.UpdateFilterInfo(FilterInfoDisplay); + _settings.MapFilters = FilterInfoDisplay; + + await _messagesService.AddMessageAsync(Message.ForInfo( + "UpdateMapData", + $"Map area loaded {area.NorthEast.Latitude:F4}, {area.SouthWest.Latitude:F4}, {area.SouthWest.Longitude:F4}, {area.NorthEast.Longitude:F4} in {sw.Elapsed.TotalMilliseconds:N2}ms", + TimeSpan.FromSeconds(1))); + + // add all items to cache + MapFeatureCache.Clear(); + mapData.Features.ForEach(feature => MapFeatureCache[feature.Id] = feature); + + // Only get the features that are enabled by default, and have not been filtered out + ProcessFeatures(map, mapData.Features); + } + catch (Exception ex) when (!(ex is FlurlHttpException) && !(ex.InnerException is TaskCanceledException)) + { + await _messagesService.AddMessageAsync(Message.ForError("UpdateMapData", "Failed to update map data.", ex)); + } + } + + public void ProcessAllFromCache(IMap map, bool resetFilters = false) + { + map.DeleteOverlay(MapOverlayName); + if (!(IsSignedIn || _settings.CheckEnableAltitudeAngel)) + { + MapFeatureCache.Clear(); + } + + if (!map.Enabled) + { + map.DeleteOverlay(MapOverlayName); + map.Invalidate(); + return; + } + + if (resetFilters) + { + MapFeatureCache.Values.UpdateFilterInfo(FilterInfoDisplay, true); + } + + ProcessFeatures(map, MapFeatureCache.Values); + map.Invalidate(); + } + + private void ProcessFeatures(IMap map, IEnumerable features) + { + try + { + if (!_processLock.Wait(TimeSpan.FromSeconds(1))) return; + var overlay = map.GetOverlay(MapOverlayName, true); + var polygons = new List(); + var lines = new List(); + + foreach (var feature in features) + { + if (!FilterInfoDisplay + .Intersect(feature.GetFilterInfo(), new FilterInfoDisplayEqualityComparer()) + .Any(i => i.Visible)) + { + continue; + } + + var properties = feature.GetFeatureProperties(); + if (properties.AltitudeFloor != null) + { + // TODO: Ignoring datum for now + if (properties.AltitudeFloor.Meters > _settings.AltitudeFilter) + { + continue; + } + } + + switch (feature.Geometry.Type) + { + case GeoJSONObjectType.Point: + { + var pnt = (Point)feature.Geometry; + + var coordinates = new List(); + + if (!string.IsNullOrEmpty(properties.Radius)) + { + var rad = double.Parse(properties.Radius); + for (var i = 0; i <= 360; i += 10) + { + coordinates.Add( + PositionFromBearingAndDistance(new LatLong(((Position)pnt.Coordinates).Latitude, + ((Position)pnt.Coordinates).Longitude), i, rad)); + } + } + + var colorInfo = properties.ToColorInfo(_settings.MapOpacityAdjust); + overlay.AddOrUpdatePolygon(feature.Id, coordinates, colorInfo, feature); + polygons.Add(feature.Id); + } + break; + case GeoJSONObjectType.MultiPoint: + break; + case GeoJSONObjectType.LineString: + { + var line = (LineString)feature.Geometry; + var coordinates = line.Coordinates.OfType() + .Select(c => new LatLong(c.Latitude, c.Longitude)) + .ToList(); + var colorInfo = properties.ToColorInfo(_settings.MapOpacityAdjust); + overlay.AddOrUpdateLine(feature.Id, coordinates, colorInfo, feature); + lines.Add(feature.Id); + } + break; + + case GeoJSONObjectType.MultiLineString: + break; + case GeoJSONObjectType.Polygon: + { + var poly = (Polygon)feature.Geometry; + var coordinates = + poly.Coordinates[0].Coordinates.OfType() + .Select(c => new LatLong(c.Latitude, c.Longitude)) + .ToList(); + + var colorInfo = properties.ToColorInfo(_settings.MapOpacityAdjust); + overlay.AddOrUpdatePolygon(feature.Id, coordinates, colorInfo, feature); + polygons.Add(feature.Id); + } + break; + case GeoJSONObjectType.MultiPolygon: + foreach (var poly in ((MultiPolygon)feature.Geometry).Coordinates) + { + var coordinates = + poly.Coordinates[0].Coordinates.OfType() + .Select(c => new LatLong(c.Latitude, c.Longitude)) + .ToList(); + + var colorInfo = properties.ToColorInfo(_settings.MapOpacityAdjust); + overlay.AddOrUpdatePolygon(feature.Id, coordinates, colorInfo, feature); + polygons.Add(feature.Id); + } + + break; + case GeoJSONObjectType.GeometryCollection: + break; + case GeoJSONObjectType.Feature: + break; + case GeoJSONObjectType.FeatureCollection: + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + overlay.RemovePolygonsExcept(polygons); + overlay.RemoveLinesExcept(lines); + } + finally + { + _processLock.Release(); + } + } + + private static LatLong PositionFromBearingAndDistance(LatLong input, double bearing, double distance) + { + const double rad2deg = 180 / Math.PI; + const double deg2rad = 1.0 / rad2deg; + + // '''extrapolate latitude/longitude given a heading and distance + // thanks to http://www.movable-type.co.uk/scripts/latlong.html + // ''' + // from math import sin, asin, cos, atan2, radians, degrees + var radiusOfEarth = 6378100.0;//# in meters + + var lat1 = deg2rad * input.Latitude; + var lon1 = deg2rad * input.Longitude; + var brng = deg2rad * bearing; + var dr = distance / radiusOfEarth; + + var lat2 = Math.Asin(Math.Sin(lat1) * Math.Cos(dr) + + Math.Cos(lat1) * Math.Sin(dr) * Math.Cos(brng)); + var lon2 = lon1 + Math.Atan2(Math.Sin(brng) * Math.Sin(dr) * Math.Cos(lat1), + Math.Cos(dr) - Math.Sin(lat1) * Math.Sin(lat2)); + + return new LatLong(rad2deg * lat2, rad2deg * lon2); + } + + private async Task OnFlightReportClicked(Feature feature) + { + if (!(_settings.UseFlightPlans && _settings.TokenResponse.HasScopes(Scopes.ManageFlightReports))) + { + return; + } + + if (_settings.CurrentFlightPlanId == null && _settings.ExistingFlightPlanId != Guid.Parse(feature.Id)) + { + if (!await _missionPlanner.ShowYesNoMessageBox( + $"You have clicked your flight plan '{feature.GetDisplayInfo().Title}'.{Environment.NewLine}Would you like to use this flight plan when you arm your drone?", + "Flight Plan")) return; + _settings.ExistingFlightPlanId = Guid.Parse(feature.Id); + _settings.UseExistingFlightPlanId = true; + await _messagesService.AddMessageAsync( + Message.ForInfo($"Use existing flight plan ID set to {feature.Id}", TimeSpan.FromSeconds(10))); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool isDisposing) + { + if (!isDisposing) return; + _telemetryService.Dispose(); + _flightService.Dispose(); + _disposer?.Dispose(); + } + } +} diff --git a/ExtLibs/AltitudeAngelWings/Service/AltitudeAngelTelemetry/TelemetryService.cs b/ExtLibs/AltitudeAngelWings/Service/AltitudeAngelTelemetry/TelemetryService.cs index e72d3ca6d1..929d18cc48 100644 --- a/ExtLibs/AltitudeAngelWings/Service/AltitudeAngelTelemetry/TelemetryService.cs +++ b/ExtLibs/AltitudeAngelWings/Service/AltitudeAngelTelemetry/TelemetryService.cs @@ -1,15 +1,17 @@ -using AltitudeAngelWings.ApiClient.Client.TelemetryClient; -using AltitudeAngelWings.Models; -using AltitudeAngelWings.Service.AltitudeAngelTelemetry.TelemetryEvents; +using AltitudeAngelWings.Service.AltitudeAngelTelemetry.TelemetryEvents; using AltitudeAngelWings.Service.FlightData; using AltitudeAngelWings.Service.Messaging; using System; using System.Reactive.Disposables; using System.Threading.Tasks; +using AltitudeAngelWings.Clients; +using AltitudeAngelWings.Clients.Auth.Model; +using AltitudeAngelWings.Clients.Telemetry; +using AltitudeAngelWings.Model; namespace AltitudeAngelWings.Service.AltitudeAngelTelemetry { - public class TelemetryService: ITelemetryService + public class TelemetryService : ITelemetryService { private readonly IMessagesService _messagesService; private readonly CompositeDisposable _disposer = new CompositeDisposable(); @@ -28,7 +30,6 @@ public TelemetryService( _settings = settings; _client = client; - if (_settings.DisableTelemetrySending) return; _disposer.Add(flightDataService.ArmedFlightData .SubscribeWithAsync((i, ct) => SendTelemetry(i))); @@ -36,18 +37,22 @@ public TelemetryService( .SubscribeWithAsync((i, ct) => FlightDisarmed(i))); } - private Task FlightDisarmed(Models.FlightData flightData) + private Task FlightDisarmed(Model.FlightData flightData) { _sequenceNumber = 0; return Task.CompletedTask; } - private async Task SendTelemetry(Models.FlightData flightData) + private async Task SendTelemetry(Model.FlightData flightData) { + if (!(_settings.UseFlightPlans && _settings.UseFlights && _settings.SendFlightTelemetry > FlightTelemetry.None && _settings.TokenResponse.HasScopes(Scopes.TacticalCrs))) + { + return; + } + if (_settings.CurrentFlightId == null) { - await _messagesService.AddMessageAsync( - new Message("Not sending telemetry as no current flight ID is set.")); + await _messagesService.AddMessageAsync(Message.ForInfo("Telemetry", "Not sending telemetry as no current flight ID is set.")); return; } try @@ -76,14 +81,14 @@ await _messagesService.AddMessageAsync( var telemetry = new TelemetryEvent(telemetryId, udpMessage) { SequenceNumber = _sequenceNumber }; var message = string.Format($"Sending Telemetry {flightData.CurrentPosition.Latitude}, {flightData.CurrentPosition.Longitude}, {flightData.CurrentPosition.Altitude}"); - await _messagesService.AddMessageAsync(new Message(message)); + await _messagesService.AddMessageAsync(Message.ForInfo("Telemetry", message)); _client.SendTelemetry(telemetry, _settings.TelemetryHostName, _settings.TelemetryPortNumber, _settings.EncryptionKey); _sequenceNumber += 1; } - catch (Exception) + catch (Exception ex) { - await _messagesService.AddMessageAsync(new Message("ERROR: Sending telemetry failed.")); + await _messagesService.AddMessageAsync(Message.ForError("Sending telemetry failed.", ex)); } } diff --git a/ExtLibs/AltitudeAngelWings/Service/FlightData/FlightDataService.cs b/ExtLibs/AltitudeAngelWings/Service/FlightData/FlightDataService.cs index 446ed1720d..1952f3c5dc 100644 --- a/ExtLibs/AltitudeAngelWings/Service/FlightData/FlightDataService.cs +++ b/ExtLibs/AltitudeAngelWings/Service/FlightData/FlightDataService.cs @@ -1,17 +1,17 @@ using System; using System.Reactive.Linq; using System.Reactive.Subjects; -using AltitudeAngelWings.Models; +using AltitudeAngelWings.Model; using AltitudeAngelWings.Service.FlightData.Providers; namespace AltitudeAngelWings.Service.FlightData { public class FlightDataService : IFlightDataService { - public IObservable FlightArmed { get; } - public IObservable FlightDisarmed { get; } - public IObservable ArmedFlightData { get; } - public IObservable RawFlightData => _rawFlightData; + public IObservable FlightArmed { get; } + public IObservable FlightDisarmed { get; } + public IObservable ArmedFlightData { get; } + public IObservable RawFlightData => _rawFlightData; public FlightDataService( TimeSpan pollInterval, @@ -24,7 +24,7 @@ public FlightDataService( FlightArmed = _rawFlightData .DistinctUntilChanged(i => i.Armed) .Where(i => i.Armed) - .Select(flightData => new Models.FlightData(flightData) {HomePosition = flightData.CurrentPosition}); + .Select(flightData => new Model.FlightData(flightData) {HomePosition = flightData.CurrentPosition}); FlightDisarmed = _rawFlightData .DistinctUntilChanged(i => i.Armed) @@ -32,7 +32,7 @@ public FlightDataService( ArmedFlightData = _rawFlightData .Where(i => i.Armed) - .Select(flightData => new Models.FlightData(flightData) {HomePosition = _homePosition}); + .Select(flightData => new Model.FlightData(flightData) {HomePosition = _homePosition}); FlightArmed .Subscribe(i => _homePosition = i.CurrentPosition); @@ -55,7 +55,7 @@ protected virtual void Dispose(bool isDisposing) _rawFlightDataSubscription = null; } - private readonly IConnectableObservable _rawFlightData; + private readonly IConnectableObservable _rawFlightData; private FlightDataPosition _homePosition; private IDisposable _rawFlightDataSubscription; } diff --git a/ExtLibs/AltitudeAngelWings/Service/FlightData/IFlightDataService.cs b/ExtLibs/AltitudeAngelWings/Service/FlightData/IFlightDataService.cs index fbd7fd6e82..0d14e68368 100644 --- a/ExtLibs/AltitudeAngelWings/Service/FlightData/IFlightDataService.cs +++ b/ExtLibs/AltitudeAngelWings/Service/FlightData/IFlightDataService.cs @@ -4,9 +4,9 @@ namespace AltitudeAngelWings.Service.FlightData { public interface IFlightDataService : IDisposable { - IObservable FlightArmed { get; } - IObservable FlightDisarmed { get; } - IObservable ArmedFlightData { get; } - IObservable RawFlightData { get; } + IObservable FlightArmed { get; } + IObservable FlightDisarmed { get; } + IObservable ArmedFlightData { get; } + IObservable RawFlightData { get; } } } \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Service/FlightData/Providers/IFlightDataProvider.cs b/ExtLibs/AltitudeAngelWings/Service/FlightData/Providers/IFlightDataProvider.cs index 5ec9697887..6cfc1788eb 100644 --- a/ExtLibs/AltitudeAngelWings/Service/FlightData/Providers/IFlightDataProvider.cs +++ b/ExtLibs/AltitudeAngelWings/Service/FlightData/Providers/IFlightDataProvider.cs @@ -2,6 +2,6 @@ namespace AltitudeAngelWings.Service.FlightData.Providers { public interface IFlightDataProvider { - Models.FlightData GetCurrentFlightData(); + Model.FlightData GetCurrentFlightData(); } } diff --git a/ExtLibs/AltitudeAngelWings/Service/FlightData/Providers/MissionPlannerFlightDataProvider.cs b/ExtLibs/AltitudeAngelWings/Service/FlightData/Providers/MissionPlannerFlightDataProvider.cs index 44e82845eb..5aca7da445 100644 --- a/ExtLibs/AltitudeAngelWings/Service/FlightData/Providers/MissionPlannerFlightDataProvider.cs +++ b/ExtLibs/AltitudeAngelWings/Service/FlightData/Providers/MissionPlannerFlightDataProvider.cs @@ -1,25 +1,28 @@ -using AltitudeAngelWings.Extra; -using AltitudeAngelWings.Models; +using System; +using AltitudeAngelWings.Model; namespace AltitudeAngelWings.Service.FlightData.Providers { public class MissionPlannerFlightDataProvider : IFlightDataProvider { + private const int GeographicPrecision = 7; + private const int AltitudePrecision = 2; + public MissionPlannerFlightDataProvider(IMissionPlannerState missionPlannerState) { _missionPlannerState = missionPlannerState; } - public Models.FlightData GetCurrentFlightData() + public Model.FlightData GetCurrentFlightData() { - return new Models.FlightData + return new Model.FlightData { Armed = _missionPlannerState.IsArmed, CurrentPosition = new FlightDataPosition { - Longitude = _missionPlannerState.Longitude, - Latitude = _missionPlannerState.Latitude, - Altitude = (int) _missionPlannerState.Altitude, + Longitude = Math.Round(_missionPlannerState.Longitude, GeographicPrecision, MidpointRounding.AwayFromZero), + Latitude = Math.Round(_missionPlannerState.Latitude, GeographicPrecision, MidpointRounding.AwayFromZero), + Altitude = Math.Round(_missionPlannerState.Altitude, AltitudePrecision, MidpointRounding.AwayFromZero), Course = _missionPlannerState.GroundCourse, Speed = _missionPlannerState.GroundSpeed, VerticalSpeed = _missionPlannerState.VerticalSpeed diff --git a/ExtLibs/AltitudeAngelWings/Service/FlightService/FlightService.cs b/ExtLibs/AltitudeAngelWings/Service/FlightService/FlightService.cs index 08cad57374..2186f5db32 100644 --- a/ExtLibs/AltitudeAngelWings/Service/FlightService/FlightService.cs +++ b/ExtLibs/AltitudeAngelWings/Service/FlightService/FlightService.cs @@ -1,62 +1,69 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reactive.Disposables; +using System.Threading; using System.Threading.Tasks; -using AltitudeAngelWings.ApiClient.Client; -using AltitudeAngelWings.ApiClient.Models; -using AltitudeAngelWings.ApiClient.Models.FlightV2.ServiceRequests.ProtocolConfiguration; -using AltitudeAngelWings.ApiClient.Models.Strategic; -using AltitudeAngelWings.Extra; -using AltitudeAngelWings.Models; +using AltitudeAngelWings.Clients; +using AltitudeAngelWings.Clients.Auth; +using AltitudeAngelWings.Clients.Auth.Model; +using AltitudeAngelWings.Clients.Flight; +using AltitudeAngelWings.Clients.Flight.Model; +using AltitudeAngelWings.Clients.Flight.Model.ServiceRequests; +using AltitudeAngelWings.Clients.Flight.Model.ServiceRequests.ProtocolConfiguration; +using AltitudeAngelWings.Model; using AltitudeAngelWings.Service.FlightData; using AltitudeAngelWings.Service.Messaging; -using AltitudeAngelWings.Service.OutboundNotifs; +using AltitudeAngelWings.Service.OutboundNotifications; +using NodaTime; +using GeoJSON.Net.Feature; +using GeoJSON.Net.Geometry; +using NetTopologySuite.Algorithm; namespace AltitudeAngelWings.Service.FlightService { public class FlightService : IFlightService { - public UserProfileInfo CurrentUser { get; private set; } - private readonly IMessagesService _messagesService; - private readonly IMissionPlanner _missionPlanner; + private readonly IMissionPlannerState _missionPlannerState; private readonly CompositeDisposable _disposer = new CompositeDisposable(); - private readonly IAltitudeAngelClient _client; + private readonly IFlightClient _flightClient; + private readonly IAuthClient _authClient; private readonly ISettings _settings; - private readonly IOutboundNotifsService _notificationsService; + private readonly IOutboundNotificationsService _notificationsService; public FlightService( IMessagesService messagesService, - IMissionPlanner missionPlanner, + IMissionPlannerState missionPlannerState, ISettings settings, IFlightDataService flightDataService, - IAltitudeAngelClient client, - IOutboundNotifsService notificationsService) + IFlightClient flightClient, + IAuthClient authClient, + IOutboundNotificationsService notificationsService) { _messagesService = messagesService; - _missionPlanner = missionPlanner; + _missionPlannerState = missionPlannerState; _settings = settings; - _client = client; + _flightClient = flightClient; + _authClient = authClient; _notificationsService = notificationsService; - _settings.CurrentFlightReportId = null; + _settings.CurrentFlightPlanId = null; _settings.CurrentFlightId = null; - if (_settings.SurveillanceMode) - { - _disposer.Add(flightDataService.FlightArmed - .SubscribeWithAsync(async (i, ct) => await StartSurveillanceFlight(await _missionPlanner.GetFlightPlan()))); - } - else - { - _disposer.Add(flightDataService.FlightArmed - .SubscribeWithAsync(async (i, ct) => await StartTelemetryFlight(await _missionPlanner.GetFlightPlan()))); - _disposer.Add(flightDataService.FlightDisarmed - .SubscribeWithAsync((i, ct) => CompleteFlight())); - } + _disposer.Add(flightDataService.FlightArmed + .SubscribeWithAsync(async (i, ct) => await StartTelemetryFlight(ct))); + _disposer.Add(flightDataService.FlightDisarmed + .SubscribeWithAsync((i, ct) => CompleteFlight(ct))); } - private async Task StartTelemetryFlight(FlightPlan flightPlan) + private async Task StartTelemetryFlight(CancellationToken cancellationToken) { + if (!(_settings.UseFlightPlans && _settings.UseFlights && _settings.TokenResponse.HasScopes(Scopes.ManageFlightReports))) + { + return; + } + + var flightPlan = GetFlightPlan(); if (flightPlan == null) { return; @@ -64,115 +71,135 @@ private async Task StartTelemetryFlight(FlightPlan flightPlan) if (_settings.CurrentFlightId != null) { // Complete flight if starting one before the previous ends. - await CompleteFlight(); + await CompleteFlight(cancellationToken); } - // TODO somehow prevent Arming of UAV until the following try statement has been completed, so telemetry isnt sent late. PBI 8490 + Guid? flightPlanId = null; try { - CurrentUser = await _client.GetUserProfile(); - - Guid? flightPlanId; if (_settings.UseExistingFlightPlanId) { flightPlanId = _settings.ExistingFlightPlanId; } else { - await _messagesService.AddMessageAsync(new Message("Creating flight plan...") - { TimeToLive = TimeSpan.FromSeconds(10) }); - var createPlanResponse = await _client.CreateFlightPlan(flightPlan, CurrentUser); - if (createPlanResponse.Outcome == StrategicSeverity.DirectConflict) - { - await _messagesService.AddMessageAsync(new Message("Conflict detected; flight cancelled.") - { TimeToLive = TimeSpan.FromSeconds(10) }); - await _missionPlanner.Disarm(); - return; - } - - flightPlanId = createPlanResponse.FlightPlanId; + await _messagesService.AddMessageAsync(Message.ForInfo("FlightPlan", "Creating flight plan.")); + var profile = await _authClient.GetUserProfile(_settings.TokenResponse.AccessToken, cancellationToken); + var flightPlanRequest = ConstructFlightPlanRequest(flightPlan, profile); + var createPlanResponse = await _flightClient.CreateFlightPlan(flightPlanRequest, cancellationToken); + await _messagesService.AddMessageAsync(Message.ForInfo("FlightPlan", $"Flight plan created. Approval status is {createPlanResponse.Status.State}.", TimeSpan.FromSeconds(30))); + flightPlanId = createPlanResponse.Id; } + } + catch (Exception ex) + { + await _messagesService.AddMessageAsync(Message.ForError("FlightPlan", "Flight plan create failed.", ex)); + } + try + { // Check flight plan id is valid if (flightPlanId == null || flightPlanId == Guid.Empty) { - await _messagesService.AddMessageAsync(new Message("Flight plan not available; flight cancelled.") - { TimeToLive = TimeSpan.FromSeconds(10) }); - await _missionPlanner.Disarm(); + await _messagesService.AddMessageAsync(Message.ForInfo("Flight", "Flight plan not available.", TimeSpan.FromSeconds(10))); return; } - _settings.CurrentFlightReportId = flightPlanId.ToString(); - await _messagesService.AddMessageAsync(new Message($"Flight plan {flightPlanId} in use.") - { TimeToLive = TimeSpan.FromSeconds(10) }); + _settings.CurrentFlightPlanId = flightPlanId.ToString(); // Flight being rejected will throw, and cause a disarm - await _messagesService.AddMessageAsync(new Message("Starting flight...") - { TimeToLive = TimeSpan.FromSeconds(10) }); - var startFlightResponse = await _client.StartFlight(flightPlanId.Value.ToString("D")); - - _settings.CurrentFlightId = startFlightResponse.Id; - var tacticalSettings = startFlightResponse.ServiceResponses.First(); + await _messagesService.AddMessageAsync(Message.ForInfo("Flight", $"Starting flight on flight plan {flightPlanId}.", TimeSpan.FromSeconds(10))); + var startFlightRequest = new StartFlightRequest + { + FlightPlanId = flightPlanId.ToString() + }; + if (_settings.TokenResponse.HasScopes(Scopes.TacticalCrs)) + { + startFlightRequest.ServiceRequests.Add(new TacticalDeconflictionFlightServiceRequest + { + Properties = new TacticalDeconflictionRequestProperties + { + Guidance = new List {"vector"}, + NotificationProtocols = new List {new {type = "Websocket"}}, + TelemetryProtocols = new List {new {type = "Udp"}}, + Scope = "global", + SurveillanceResolution = true + } + }); + } - var notificationSettings = (WebsocketNotificationProtocolConfiguration)tacticalSettings.Properties.NotificationProtocols.First(); - _settings.OutboundNotifsEndpointUrl = notificationSettings.Properties.Endpoints.First(); + var startFlightResponse = await _flightClient.StartFlight(startFlightRequest, cancellationToken); - var telemetrySettings = (UdpTelemetryProtocolConfiguration)tacticalSettings.Properties.TelemetryProtocols.First(); - _settings.CurrentTelemetryId = telemetrySettings.Id; - _settings.EncryptionKey = telemetrySettings.Properties.EncryptionKey; + _settings.CurrentFlightId = startFlightResponse.Id; + var tacticalSettings = startFlightResponse.ServiceResponses.OfType().FirstOrDefault(); + if (tacticalSettings != null) + { + var notificationSettings = + (WebsocketNotificationProtocolConfiguration)tacticalSettings.Properties.NotificationProtocols + .First(); + _settings.OutboundNotificationsUrl = notificationSettings.Properties.Endpoints.First(); - var telemetryEndPoint = telemetrySettings.Properties.Endpoints.First(); - _settings.TelemetryHostName = telemetryEndPoint.Split(':')[0]; - _settings.TelemetryPortNumber = int.Parse(telemetryEndPoint.Split(':')[1]); - _settings.TransmissionRateInMilliseconds = telemetrySettings.Properties.TransmissionRateInMilliseconds; + var telemetrySettings = + (UdpTelemetryProtocolConfiguration)tacticalSettings.Properties.TelemetryProtocols.First(); + _settings.CurrentTelemetryId = telemetrySettings.Id; + _settings.EncryptionKey = telemetrySettings.Properties.EncryptionKey; - var task = _notificationsService.StartWebSocket(); + var telemetryEndPoint = telemetrySettings.Properties.Endpoints.First(); + _settings.TelemetryHostName = telemetryEndPoint.Split(':')[0]; + _settings.TelemetryPortNumber = int.Parse(telemetryEndPoint.Split(':')[1]); + _settings.TransmissionRateInMilliseconds = + telemetrySettings.Properties.TransmissionRateInMilliseconds; - await _messagesService.AddMessageAsync(new Message($"Flight {startFlightResponse.Id} approved and underway.") - { TimeToLive = TimeSpan.FromSeconds(10) }); + await _notificationsService.StartWebSocket(cancellationToken); + } - await task; + await _messagesService.AddMessageAsync(Message.ForInfo("Flight", $"Flight {startFlightResponse.Id} underway.", TimeSpan.FromSeconds(10))); } - catch (Exception) + catch (Exception ex) { - await _missionPlanner.Disarm(); - await _messagesService.AddMessageAsync(new Message($"Flight create failed.")); + await _messagesService.AddMessageAsync(Message.ForError("Flight", "Flight create failed.", ex)); } } - private async Task CompleteFlight() + private async Task CompleteFlight(CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(_settings.CurrentFlightId) || string.IsNullOrEmpty(_settings.CurrentFlightReportId)) + if (!(_settings.UseFlightPlans && _settings.UseFlights && _settings.TokenResponse.HasScopes(Scopes.ManageFlightReports))) { return; } - try + if (string.IsNullOrEmpty(_settings.CurrentFlightId) || string.IsNullOrEmpty(_settings.CurrentFlightPlanId)) { - await _client.CompleteFlight(_settings.CurrentFlightId); - await _messagesService.AddMessageAsync(new Message($"Flight {_settings.CurrentFlightId} completed.") - { TimeToLive = TimeSpan.FromSeconds(10) }); - - //await _client.CancelFlightPlan(_settings.CurrentFlightReportId); - await _messagesService.AddMessageAsync(new Message($"Flight plan {_settings.CurrentFlightReportId} completed.") - { TimeToLive = TimeSpan.FromSeconds(10) }); + return; } - catch (Exception ex) + try { - await _messagesService.AddMessageAsync(new Message($"ERROR: Failed to complete flight {_settings.CurrentFlightId} and plan {_settings.CurrentFlightReportId}:, {ex}")); + try + { + await _flightClient.CompleteFlight(_settings.CurrentFlightId, cancellationToken); + await _messagesService.AddMessageAsync(Message.ForInfo("FlightComplete", $"Flight {_settings.CurrentFlightId} completed.", TimeSpan.FromSeconds(10))); + } + catch (Exception ex) + { + await _messagesService.AddMessageAsync(Message.ForError("FlightComplete", $"Failed to complete flight {_settings.CurrentFlightId}.", ex)); + } + + try + { + await _flightClient.CompleteFlightPlan(_settings.CurrentFlightPlanId, cancellationToken); + await _messagesService.AddMessageAsync(Message.ForInfo("FlightPlanComplete", $"Flight plan {_settings.CurrentFlightPlanId} completed.", TimeSpan.FromSeconds(10))); + } + catch (Exception ex) + { + await _messagesService.AddMessageAsync(Message.ForError("FlightPlanComplete", $"Failed to complete flight plan {_settings.CurrentFlightPlanId}.", ex)); + } } finally { - await _notificationsService.StopWebSocket(); + await _notificationsService.StopWebSocket(cancellationToken); _settings.CurrentFlightId = null; - _settings.CurrentFlightReportId = null; + _settings.CurrentFlightPlanId = null; } } - private Task StartSurveillanceFlight(FlightPlan flightPlan) - { - // TODO : Implement 8962 - return Task.Run(() => new NotImplementedException("Please implement the 8962")); - } - public void Dispose() { Dispose(true); @@ -186,5 +213,145 @@ protected void Dispose(bool isDisposing) _disposer?.Dispose(); } } + + public FlightPlan GetFlightPlan() + { + var waypoints = _missionPlannerState.Waypoints; + if (waypoints.Count == 0) + { + return null; + } + + waypoints.Insert(0, _missionPlannerState.HomeLocation); + var envelope = NetTopologySuite.Geometries.GeometryFactory.Default.CreateMultiPoint( + waypoints + .Select(l => new NetTopologySuite.Geometries.Point(l.Longitude, l.Latitude)) + .ToArray()) + .Envelope; + var center = envelope.Centroid; + var minimumBoundingCircle = new MinimumBoundingCircle(envelope); + return new FlightPlan(waypoints) + { + CenterLongitude = center.X, + CenterLatitude = center.Y, + BoundingRadius = (int)Math.Ceiling(minimumBoundingCircle.GetRadius()), + FlightCapability = _missionPlannerState.FlightCapability, + Summary = _settings.FlightPlanName, + Description = _settings.FlightPlanDescription, + Duration = Duration.FromTimeSpan(_settings.FlightPlanTimeSpan), + UseLocalConflictScope = false, + AllowSmsContact = false, + SmsPhoneNumber = "", + DroneSerialNumber = "", + FlightOperationMode = FlightOperationMode.BVLOS + }; + } + + private CreateFlightPlanRequest ConstructFlightPlanRequest(FlightPlan flightPlan, UserProfileInfo profile) + { + var parts = CreateFlightPartsFromWaypoints(flightPlan.Waypoints, flightPlan.Duration); + var sParts = parts.Select(p => new CreateFlightPartRequest + { + Id = p.Id, + MaxAltitude = p.MaxAltitude, + Start = p.Start, + End = p.End, + TimeZone = p.TimeZone, + Geography = p.Geography + }).ToList(); + + var flightPlanRequest = new CreateFlightPlanRequest + { + Summary = flightPlan.Summary, + Description = flightPlan.Description, + Parts = sParts, + PointOfContact = new ContactDetails + { + FirstName = profile.FirstName, + LastName = profile.LastName, + PhoneNumber = _settings.FlightPhoneNumber, + AllowSmsContact = _settings.FlightAllowSms + }, + DroneDetails = new CreateFlightPlanRequestDroneDetails + { + AirFrame = MapToAirFrame(flightPlan.FlightCapability), + MaxWeight = 1 + } + }; + if (_settings.FlightIdentifierIcao) + { + flightPlanRequest.IcaoAddress = _settings.FlightIdentifierIcaoAddress; + } + if (_settings.FlightIdentifierSerial) + { + flightPlanRequest.DroneSerialNumber = _settings.FlightIdentifierSerialNumber; + } + return flightPlanRequest; + } + + private static AirFrameType MapToAirFrame(FlightCapability flightCapability) + { + switch (flightCapability) + { + case FlightCapability.FixedWing: + return AirFrameType.FixedWing; + case FlightCapability.Rotary: + return AirFrameType.Rotary; + case FlightCapability.VTOL: + return AirFrameType.VTOL; + case FlightCapability.Tethered: + return AirFrameType.Tethered; + case FlightCapability.Unspecified: + default: + throw new ArgumentOutOfRangeException(nameof(flightCapability), flightCapability, "Failed to map FlightCapability to AirFrameType."); + } + } + + private static IEnumerable CreateFlightPartsFromWaypoints(IList waypoints, Duration duration) + { + var flightPartRequests = new List(); + var highestAltitude = waypoints.Max(wp => wp.Altitude); + var (startInstant, endInstant) = GetFlightPlanStartEndInstants(duration); + + for (var pos = 1; pos < waypoints.Count; pos++) + { + if (Math.Abs(waypoints[pos - 1].Latitude - waypoints[pos].Latitude) < 0.0000001 && + Math.Abs(waypoints[pos - 1].Longitude - waypoints[pos].Longitude) < 0.0000001) continue; + var geography = new Feature(ConvertWaypointsToLineString(waypoints[pos - 1], waypoints[pos])); + var createFlightPartRequest = CreateFlightPartRequest(pos, highestAltitude, startInstant, endInstant, geography); + flightPartRequests.Add(createFlightPartRequest); + } + + return flightPartRequests; + } + + private static CreateFlightPartRequest CreateFlightPartRequest( + int id, + double highestAltitude, + Instant start, + Instant end, + Feature geography) + => new CreateFlightPartRequest + { + Id = id.ToString(), + Geography = geography, + Start = start, + End = end, + TimeZone = DateTimeZoneProviders.Tzdb.GetSystemDefault(), + MaxAltitude = new Altitude { Meters = highestAltitude, Datum = AltitudeDatum.Agl } + }; + + private static (Instant start, Instant end) GetFlightPlanStartEndInstants(Duration duration) + { + var startInstant = SystemClock.Instance.GetCurrentInstant() + Duration.FromSeconds(30); + var endInstant = startInstant + duration; + return (startInstant, endInstant); + } + + private static IGeometryObject ConvertWaypointsToLineString(FlightPlanWaypoint startWayPoint, FlightPlanWaypoint endWayPoint) + => new LineString(new List { + new Position(startWayPoint.Latitude, startWayPoint.Longitude), + new Position(endWayPoint.Latitude, endWayPoint.Longitude) + }); } } diff --git a/ExtLibs/AltitudeAngelWings/Service/FlightService/IFlightService.cs b/ExtLibs/AltitudeAngelWings/Service/FlightService/IFlightService.cs index b1bd91b1ef..3bc0a6a50f 100644 --- a/ExtLibs/AltitudeAngelWings/Service/FlightService/IFlightService.cs +++ b/ExtLibs/AltitudeAngelWings/Service/FlightService/IFlightService.cs @@ -1,10 +1,8 @@ -using AltitudeAngelWings.ApiClient.Models; -using System; +using System; namespace AltitudeAngelWings.Service.FlightService { public interface IFlightService: IDisposable { - UserProfileInfo CurrentUser { get; } } } diff --git a/ExtLibs/AltitudeAngelWings/Service/IAltitudeAngelService.cs b/ExtLibs/AltitudeAngelWings/Service/IAltitudeAngelService.cs index 795592d897..104fd34dc2 100644 --- a/ExtLibs/AltitudeAngelWings/Service/IAltitudeAngelService.cs +++ b/ExtLibs/AltitudeAngelWings/Service/IAltitudeAngelService.cs @@ -1,19 +1,18 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; -using AltitudeAngelWings.ApiClient.Client; -using AltitudeAngelWings.ApiClient.Models; -using AltitudeAngelWings.Extra; +using AltitudeAngelWings.Clients.Api.Model; namespace AltitudeAngelWings.Service { public interface IAltitudeAngelService : IDisposable { ObservableProperty IsSignedIn { get; } + bool SigningIn { get; } ObservableProperty WeatherReport { get; } - UserProfileInfo CurrentUser { get; } IList FilterInfoDisplay { get; } - Task SignInAsync(); + Task SignInAsync(CancellationToken cancellationToken = default); Task DisconnectAsync(); void ProcessAllFromCache(IMap map, bool resetFilters = false); } diff --git a/ExtLibs/AltitudeAngelWings/Service/Messaging/IMessageDisplay.cs b/ExtLibs/AltitudeAngelWings/Service/Messaging/IMessageDisplay.cs index 0e26f691be..bf8c9053a8 100644 --- a/ExtLibs/AltitudeAngelWings/Service/Messaging/IMessageDisplay.cs +++ b/ExtLibs/AltitudeAngelWings/Service/Messaging/IMessageDisplay.cs @@ -1,4 +1,4 @@ -using AltitudeAngelWings.Models; +using AltitudeAngelWings.Model; namespace AltitudeAngelWings.Service.Messaging { diff --git a/ExtLibs/AltitudeAngelWings/Service/Messaging/IMessagesService.cs b/ExtLibs/AltitudeAngelWings/Service/Messaging/IMessagesService.cs index eb81aa736b..d4631abd60 100644 --- a/ExtLibs/AltitudeAngelWings/Service/Messaging/IMessagesService.cs +++ b/ExtLibs/AltitudeAngelWings/Service/Messaging/IMessagesService.cs @@ -1,5 +1,5 @@ using System.Threading.Tasks; -using AltitudeAngelWings.Models; +using AltitudeAngelWings.Model; namespace AltitudeAngelWings.Service.Messaging { diff --git a/ExtLibs/AltitudeAngelWings/Service/Messaging/MessagesService.cs b/ExtLibs/AltitudeAngelWings/Service/Messaging/MessagesService.cs index 4ac74c10d9..52b1298721 100644 --- a/ExtLibs/AltitudeAngelWings/Service/Messaging/MessagesService.cs +++ b/ExtLibs/AltitudeAngelWings/Service/Messaging/MessagesService.cs @@ -1,32 +1,30 @@ using System; -using System.Diagnostics; +using System.Reactive.Disposables; using System.Reactive.Linq; using System.Threading.Tasks; -using AltitudeAngelWings.Models; +using AltitudeAngelWings.Model; namespace AltitudeAngelWings.Service.Messaging { public class MessagesService : IMessagesService, IDisposable { + private readonly CompositeDisposable _disposer = new CompositeDisposable(); + public MessagesService(IMessageDisplay messageDisplay) { Messages = new ObservableProperty(0); - Messages + _disposer.Add(Messages); + _disposer.Add(Messages .Do(messageDisplay.AddMessage) - .Delay(m => Observable.Timer(m.TimeToLive)) - .Subscribe(messageDisplay.RemoveMessage); + .SelectMany(m => Observable.Interval(TimeSpan.FromMilliseconds(100)) + .SkipWhile(i => !m.HasExpired()) + .Select(i => m)) + .Subscribe(messageDisplay.RemoveMessage)); } public ObservableProperty Messages { get; } - public Task AddMessageAsync(Message message) - { - Console.WriteLine(message.Content); -#if DEBUG - Debug.WriteLine(message.Content); -#endif - return Task.Factory.StartNew(() => Messages.Value = message); - } + public Task AddMessageAsync(Message message) => Task.Factory.StartNew(() => Messages.Value = message); public void Dispose() { @@ -38,7 +36,7 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - Messages?.Dispose(); + _disposer?.Dispose(); } } } diff --git a/ExtLibs/AltitudeAngelWings/Service/Messaging/MultipleMessageDisplay.cs b/ExtLibs/AltitudeAngelWings/Service/Messaging/MultipleMessageDisplay.cs new file mode 100644 index 0000000000..b298a0fbfb --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Service/Messaging/MultipleMessageDisplay.cs @@ -0,0 +1,30 @@ +using AltitudeAngelWings.Model; + +namespace AltitudeAngelWings.Service.Messaging +{ + public class MultipleMessageDisplay : IMessageDisplay + { + private readonly IMessageDisplay[] _displays; + + public MultipleMessageDisplay(params IMessageDisplay[] displays) + { + _displays = displays; + } + + public void AddMessage(Message message) + { + foreach (var display in _displays) + { + display.AddMessage(message); + } + } + + public void RemoveMessage(Message message) + { + foreach (var display in _displays) + { + display.RemoveMessage(message); + } + } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Service/Messaging/TextWriterMessageDisplay.cs b/ExtLibs/AltitudeAngelWings/Service/Messaging/TextWriterMessageDisplay.cs new file mode 100644 index 0000000000..0086af5f38 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Service/Messaging/TextWriterMessageDisplay.cs @@ -0,0 +1,30 @@ +using System.IO; +using AltitudeAngelWings.Model; + +namespace AltitudeAngelWings.Service.Messaging +{ + public class TextWriterMessageDisplay : IMessageDisplay + { + private const string DefaultPrefix = "[AA] "; + + private readonly TextWriter _writer; + private readonly string _prefix; + + public TextWriterMessageDisplay(TextWriter writer, string prefix = DefaultPrefix) + { + _writer = writer; + _prefix = prefix; + } + + public void AddMessage(Message message) + { + _writer.Write(_prefix); + _writer.WriteLine(message.Content); + } + + public void RemoveMessage(Message message) + { + // Do nothing + } + } +} \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Extra/OutboundNotifsWebSocket/AaClientWebSocket.cs b/ExtLibs/AltitudeAngelWings/Service/OutboundNotifications/ClientWebSocket.cs similarity index 59% rename from ExtLibs/AltitudeAngelWings/Extra/OutboundNotifsWebSocket/AaClientWebSocket.cs rename to ExtLibs/AltitudeAngelWings/Service/OutboundNotifications/ClientWebSocket.cs index 9f9361764d..8058849eb4 100644 --- a/ExtLibs/AltitudeAngelWings/Extra/OutboundNotifsWebSocket/AaClientWebSocket.cs +++ b/ExtLibs/AltitudeAngelWings/Service/OutboundNotifications/ClientWebSocket.cs @@ -4,24 +4,26 @@ using System.Threading; using System.Threading.Tasks; -namespace AltitudeAngelWings.Extra.OutboundNotifsWebSocket +namespace AltitudeAngelWings.Service.OutboundNotifications { - public class AaClientWebSocket : AaWebSocket + public class ClientWebSocket : WebSocket { private Task _listening; - public AaClientWebSocket() + public ClientWebSocket() { - Socket = new ClientWebSocket(); + Socket = new System.Net.WebSockets.ClientWebSocket(); } /// /// Open a socket connection /// /// + /// /// Optional headers to send with the request /// - public async Task OpenAsync(Uri uri, Dictionary requestHeaders = null) + public async Task OpenAsync(Uri uri, CancellationToken cancellationToken, + Dictionary requestHeaders = null) { await ClientSocket.ConnectAsync(uri, CancellationToken.None); @@ -30,23 +32,24 @@ public async Task OpenAsync(Uri uri, Dictionary requestHeaders = throw new Exception("Failed to open connection"); } - await OnConnected(); + await OnConnected(cancellationToken); - _listening = Task.Run(Listen); + _listening = Task.Run(() => Listen(cancellationToken), cancellationToken); } /// /// Closes the connection and disposes the underlying socket /// + /// /// - public async Task CloseAsync() + public async Task CloseAsync(CancellationToken cancellationToken) { if (ClientSocket == null) { throw new InvalidOperationException("Socket not open"); } - await ClientSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); + await ClientSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken); await _listening; var socket = Socket; @@ -67,9 +70,9 @@ public void SetSocketHeaders(Dictionary headers) } } - public Func OnConnected { get; set; } + public Func OnConnected { get; set; } - protected ClientWebSocket ClientSocket => - Socket as ClientWebSocket ?? throw new InvalidOperationException("Invalid socket type"); + protected System.Net.WebSockets.ClientWebSocket ClientSocket => + Socket as System.Net.WebSockets.ClientWebSocket ?? throw new InvalidOperationException("Invalid socket type"); } } diff --git a/ExtLibs/AltitudeAngelWings/Service/OutboundNotifications/IOutboundNotificationsService.cs b/ExtLibs/AltitudeAngelWings/Service/OutboundNotifications/IOutboundNotificationsService.cs new file mode 100644 index 0000000000..db399bb692 --- /dev/null +++ b/ExtLibs/AltitudeAngelWings/Service/OutboundNotifications/IOutboundNotificationsService.cs @@ -0,0 +1,12 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace AltitudeAngelWings.Service.OutboundNotifications +{ + public interface IOutboundNotificationsService + { + Task StartWebSocket(CancellationToken cancellationToken = default); + + Task StopWebSocket(CancellationToken cancellationToken = default); + } +} diff --git a/ExtLibs/AltitudeAngelWings/Service/OutboundNotifs/OutboundNotifsCommands.cs b/ExtLibs/AltitudeAngelWings/Service/OutboundNotifications/OutboundNotificationsCommands.cs similarity index 87% rename from ExtLibs/AltitudeAngelWings/Service/OutboundNotifs/OutboundNotifsCommands.cs rename to ExtLibs/AltitudeAngelWings/Service/OutboundNotifications/OutboundNotificationsCommands.cs index 65888d2e22..9e2f8a1ea9 100644 --- a/ExtLibs/AltitudeAngelWings/Service/OutboundNotifs/OutboundNotifsCommands.cs +++ b/ExtLibs/AltitudeAngelWings/Service/OutboundNotifications/OutboundNotificationsCommands.cs @@ -1,9 +1,9 @@ -namespace AltitudeAngelWings.Service.OutboundNotifs +namespace AltitudeAngelWings.Service.OutboundNotifications { /// /// List of valid Outbound Notifications commands /// - public static class OutboundNotifsCommands + public static class OutboundNotificationsCommands { /// /// UAV should land immediately. diff --git a/ExtLibs/AltitudeAngelWings/Service/OutboundNotifs/OutboundNotifsService.cs b/ExtLibs/AltitudeAngelWings/Service/OutboundNotifications/OutboundNotificationsService.cs similarity index 59% rename from ExtLibs/AltitudeAngelWings/Service/OutboundNotifs/OutboundNotifsService.cs rename to ExtLibs/AltitudeAngelWings/Service/OutboundNotifications/OutboundNotificationsService.cs index 165810bafc..f29e69cc8e 100644 --- a/ExtLibs/AltitudeAngelWings/Service/OutboundNotifs/OutboundNotifsService.cs +++ b/ExtLibs/AltitudeAngelWings/Service/OutboundNotifications/OutboundNotificationsService.cs @@ -1,27 +1,26 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading; using System.Threading.Tasks; -using AltitudeAngelWings.ApiClient.Client.FlightClient; -using AltitudeAngelWings.ApiClient.Models.OutboundNotifs; -using AltitudeAngelWings.Extra; -using AltitudeAngelWings.Extra.OutboundNotifsWebSocket; -using AltitudeAngelWings.Models; +using AltitudeAngelWings.Clients.Flight; +using AltitudeAngelWings.Clients.OutboundNotifications.Model; +using AltitudeAngelWings.Model; using AltitudeAngelWings.Service.Messaging; using Newtonsoft.Json; -namespace AltitudeAngelWings.Service.OutboundNotifs +namespace AltitudeAngelWings.Service.OutboundNotifications { - public class OutboundNotifsService : IOutboundNotifsService + public class OutboundNotificationsService : IOutboundNotificationsService { private readonly ISettings _settings; private readonly IMissionPlanner _missionPlanner; private readonly IMessagesService _messagesService; private readonly IFlightClient _flightServiceClient; private readonly IMissionPlannerState _missionPlannerState; - private AaClientWebSocket _aaClientWebSocket; + private ClientWebSocket _clientWebSocket; - public OutboundNotifsService( + public OutboundNotificationsService( IMissionPlanner missionPlanner, ISettings settings, IMessagesService messagesService, @@ -35,25 +34,25 @@ public OutboundNotifsService( _missionPlannerState = missionPlannerState; } - public async Task StartWebSocket() + public async Task StartWebSocket(CancellationToken cancellationToken = default) { - if (null == _aaClientWebSocket) + if (null == _clientWebSocket) { - await SetupAaClientWebSocket(); + await SetupAaClientWebSocket(cancellationToken); } } - public async Task StopWebSocket() + public async Task StopWebSocket(CancellationToken cancellationToken = default) { - if (null != _aaClientWebSocket) + if (null != _clientWebSocket) { - await TearDownAaClientWebSocket(); + await TearDownAaClientWebSocket(cancellationToken); } } - private Task SetupAaClientWebSocket() + private Task SetupAaClientWebSocket(CancellationToken cancellationToken) { - _aaClientWebSocket = new AaClientWebSocket + _clientWebSocket = new ClientWebSocket { OnError = OnError, OnDisconnected = OnDisconnected, // TODO: Always attempt to reconnect on disconnect @@ -61,17 +60,16 @@ private Task SetupAaClientWebSocket() OnMessage = OnMessage }; - _aaClientWebSocket.SetSocketHeaders(new Dictionary + _clientWebSocket.SetSocketHeaders(new Dictionary { {"Authorization", $"Bearer {_settings.TokenResponse.AccessToken}" } }); - return _aaClientWebSocket.OpenAsync(new Uri(_settings.OutboundNotifsEndpointUrl)); + return _clientWebSocket.OpenAsync(new Uri(_settings.OutboundNotificationsUrl), cancellationToken); } - private async Task OnMessage(byte[] bytes) + private async Task OnMessage(byte[] bytes, CancellationToken cancellationToken) { - await _messagesService.AddMessageAsync(new Message($"INFO: Received notification message of {bytes.Length} bytes.")); var notification = new NotificationMessage(); try { @@ -79,48 +77,45 @@ private async Task OnMessage(byte[] bytes) notification = JsonConvert.DeserializeObject(msg); if (notification.Acknowledge) { - await SendAck(_aaClientWebSocket, notification.Id); + await SendAck(_clientWebSocket, notification.Id); } } catch (Exception e) { - await _messagesService.AddMessageAsync(new Message($"ERROR: Failed to deserialize and acknowledge notification message. {e}")); + await _messagesService.AddMessageAsync(Message.ForError("Failed to deserialize and acknowledge notification message.", e)); } - await _messagesService.AddMessageAsync(new Message($"INFO: Processing {notification.Type} notification message.")); - try { switch (notification.Type) { - case OutboundNotifsCommands.Land: - LandNotificationProperties landProps = notification.Properties.ToObject(); + case OutboundNotificationsCommands.Land: + var landProps = notification.Properties.ToObject(); await _missionPlanner.CommandDroneToLand((float)landProps.Latitude, (float)landProps.Longitude); break; - case OutboundNotifsCommands.Loiter: - LoiterNotificationProperties loiterProps = notification.Properties.ToObject(); + case OutboundNotificationsCommands.Loiter: + var loiterProps = notification.Properties.ToObject(); await _missionPlanner.CommandDroneToLoiter((float)loiterProps.Latitude, (float)loiterProps.Longitude, (float)loiterProps.Altitude.Meters); break; - case OutboundNotifsCommands.AllClear: + case OutboundNotificationsCommands.AllClear: await _missionPlanner.CommandDroneAllClear(); break; - case OutboundNotifsCommands.ReturnToBase: + case OutboundNotificationsCommands.ReturnToBase: await _missionPlanner.CommandDroneToReturnToBase(); break; - case OutboundNotifsCommands.PermissionUpdate: + case OutboundNotificationsCommands.PermissionUpdate: var permissionProperties = notification.Properties.ToObject(); - await _messagesService.AddMessageAsync(new Message($"Flight permissions updated: {permissionProperties.PermissionState}") - { TimeToLive = TimeSpan.FromSeconds(10) }); + await _messagesService.AddMessageAsync(Message.ForInfo($"Flight permissions updated: {permissionProperties.PermissionState}", TimeSpan.FromSeconds(10))); break; - case OutboundNotifsCommands.ConflictInformation: + case OutboundNotificationsCommands.ConflictInformation: var conflictProperties = notification.Properties.ToObject(); await _missionPlanner.NotifyConflict(conflictProperties.Message); break; - case OutboundNotifsCommands.ConflictClearedInformation: + case OutboundNotificationsCommands.ConflictClearedInformation: var conflictClearedProperties = notification.Properties.ToObject(); await _missionPlanner.NotifyConflictResolved(conflictClearedProperties.Message); break; - case OutboundNotifsCommands.Instruction: + case OutboundNotificationsCommands.Instruction: var instructionProperties = notification.Properties.ToObject(); if (await _missionPlanner.ShowYesNoMessageBox( $"You have been sent the following instruction:\r\n\r\n\"{instructionProperties.Instruction}\"\r\n\r\nDo you wish to accept and follow the instruction?", @@ -156,41 +151,38 @@ await _messagesService.AddMessageAsync(new Message($"Flight permissions updated: } break; default: - await _messagesService.AddMessageAsync(new Message($"WARN: Unknown notification message type '{notification.Type}'.")); + await _messagesService.AddMessageAsync(Message.ForInfo($"Unknown notification message type {notification.Type}.")); break; } } catch (Exception e) { - await _messagesService.AddMessageAsync(new Message($"ERROR: Failed to process {notification.Type} notification message. {e}")); + await _messagesService.AddMessageAsync(Message.ForError($"Failed to process {notification.Type} notification message.", e)); } } - private Task OnConnected() - => _messagesService.AddMessageAsync(new Message($"INFO: Notifications web socket connected to {_settings.OutboundNotifsEndpointUrl}.")); + private Task OnConnected(CancellationToken cancellationToken = default) + => _messagesService.AddMessageAsync(Message.ForInfo("WebSocket", "Notifications web socket connected.")); - private Task OnDisconnected() - => _messagesService.AddMessageAsync(new Message("WARNING: Notifications web socket disconnected.")); + private Task OnDisconnected(CancellationToken cancellationToken = default) + => _messagesService.AddMessageAsync(Message.ForInfo("WebSocket", "Notifications web socket disconnected.")); - private Task OnError(Exception e) - => _messagesService.AddMessageAsync(new Message($"ERROR: Notifications web socket error. {e}")); + private Task OnError(Exception e, CancellationToken cancellationToken = default) + => _messagesService.AddMessageAsync(Message.ForError("WebSocket", "Notifications web socket error.", e)); - private async Task TearDownAaClientWebSocket() + private async Task TearDownAaClientWebSocket(CancellationToken cancellationToken) { - await _aaClientWebSocket.CloseAsync(); - _aaClientWebSocket = null; + await _clientWebSocket.CloseAsync(cancellationToken); + _clientWebSocket = null; } - private async Task SendAck(AaWebSocket socket, string id) - { - await _messagesService.AddMessageAsync(new Message($"INFO: Sending notification acknowledgement: {JsonConvert.SerializeObject(new CommandAcknowledgement { Id = id })}")); - await socket.SendMessageAsync( + private static async Task SendAck(WebSocket socket, string id) + => await socket.SendMessageAsync( Encoding.UTF8.GetBytes( JsonConvert.SerializeObject( new CommandAcknowledgement { Id = id }))); - } } } diff --git a/ExtLibs/AltitudeAngelWings/Extra/OutboundNotifsWebSocket/AaWebSocket.cs b/ExtLibs/AltitudeAngelWings/Service/OutboundNotifications/WebSocket.cs similarity index 61% rename from ExtLibs/AltitudeAngelWings/Extra/OutboundNotifsWebSocket/AaWebSocket.cs rename to ExtLibs/AltitudeAngelWings/Service/OutboundNotifications/WebSocket.cs index be6a8fecf3..ab38421980 100644 --- a/ExtLibs/AltitudeAngelWings/Extra/OutboundNotifsWebSocket/AaWebSocket.cs +++ b/ExtLibs/AltitudeAngelWings/Service/OutboundNotifications/WebSocket.cs @@ -4,14 +4,14 @@ using System.Threading; using System.Threading.Tasks; -namespace AltitudeAngelWings.Extra.OutboundNotifsWebSocket +namespace AltitudeAngelWings.Service.OutboundNotifications { - public class AaWebSocket + public class WebSocket { private const int ReceiveBlockSize = 1024; private const int SendBlockSize = 1024; - public async Task SendMessageAsync(byte[] message, WebSocketMessageType type = WebSocketMessageType.Text) + public async Task SendMessageAsync(byte[] message, CancellationToken cancellationToken = default, WebSocketMessageType type = WebSocketMessageType.Text) { var messageCount = (int)Math.Ceiling((double)message.Length / SendBlockSize); @@ -23,11 +23,11 @@ public async Task SendMessageAsync(byte[] message, WebSocketMessageType type = W var count = bytesRemaining < SendBlockSize ? bytesRemaining : SendBlockSize; - await Socket.SendAsync(new ArraySegment(message, offset, count), type, lastMessage, CancellationToken.None); + await Socket.SendAsync(new ArraySegment(message, offset, count), type, lastMessage, cancellationToken); } } - protected async Task Listen() + protected async Task Listen(CancellationToken cancellationToken = default) { try { @@ -39,37 +39,37 @@ protected async Task Listen() WebSocketReceiveResult result; do { - result = await Socket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + result = await Socket.ReceiveAsync(new ArraySegment(buffer), cancellationToken); if (result.MessageType == WebSocketMessageType.Close) { await Socket.CloseAsync( WebSocketCloseStatus.NormalClosure, string.Empty, - CancellationToken.None); - await OnDisconnected(); + cancellationToken); + await OnDisconnected(cancellationToken); return; } - await message.WriteAsync(buffer, 0, result.Count); + await message.WriteAsync(buffer, 0, result.Count, cancellationToken); } while (!result.EndOfMessage); - await OnMessage(message.GetBuffer()); + await OnMessage(message.GetBuffer(), cancellationToken); } } } catch (Exception e) { - await OnError(e); + await OnError(e, cancellationToken); } } - public Func OnMessage { get; set; } + public Func OnMessage { get; set; } - public Func OnDisconnected { get; set; } + public Func OnDisconnected { get; set; } - public Func OnError { get; set; } + public Func OnError { get; set; } - protected WebSocket Socket { get; set; } + protected System.Net.WebSockets.WebSocket Socket { get; set; } } } diff --git a/ExtLibs/AltitudeAngelWings/Service/OutboundNotifs/IOutboundNotifsService.cs b/ExtLibs/AltitudeAngelWings/Service/OutboundNotifs/IOutboundNotifsService.cs deleted file mode 100644 index 765e83ae14..0000000000 --- a/ExtLibs/AltitudeAngelWings/Service/OutboundNotifs/IOutboundNotifsService.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Threading.Tasks; - -namespace AltitudeAngelWings.Service.OutboundNotifs -{ - public interface IOutboundNotifsService - { - Task StartWebSocket(); - - Task StopWebSocket(); - - } -} diff --git a/ExtLibs/AltitudeAngelWings/ServiceLocator.cs b/ExtLibs/AltitudeAngelWings/ServiceLocator.cs index 0a6356b5a8..1656640b33 100644 --- a/ExtLibs/AltitudeAngelWings/ServiceLocator.cs +++ b/ExtLibs/AltitudeAngelWings/ServiceLocator.cs @@ -9,43 +9,91 @@ public static class ServiceLocator { private class Resolver : IServiceLocator { - public T Resolve() + public T Resolve(string key = null) { - return GetService(); + return GetService(key); + } + } + + private class TypeKey : IEquatable + { + public TypeKey(Type type, string key) + { + Type = type; + Key = string.IsNullOrEmpty(key) ? null : key; + } + + private Type Type { get; } + private string Key { get; } + + public bool Equals(TypeKey other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Type == other.Type && Key == other.Key; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return obj.GetType() == this.GetType() && Equals((TypeKey)obj); + } + + public override int GetHashCode() + { + unchecked + { + return ((Type != null ? Type.GetHashCode() : 0) * 397) ^ (Key != null ? Key.GetHashCode() : 0); + } + } + + public static bool operator ==(TypeKey left, TypeKey right) + { + return Equals(left, right); + } + + public static bool operator !=(TypeKey left, TypeKey right) + { + return !Equals(left, right); } } private static readonly object Lock = new object(); - private static readonly IDictionary ServiceInstances = new Dictionary(); - private static readonly IDictionary> ServiceRegistry = new Dictionary>(); + private static readonly IDictionary ServiceInstances = new Dictionary(); + private static readonly IDictionary> ServiceRegistry = new Dictionary>(); - public static T GetService() + public static T GetService(string key = null) { - if (ServiceInstances.ContainsKey(typeof(T))) + var typeKey = new TypeKey(typeof(T), key); + if (ServiceInstances.ContainsKey(typeKey)) { - return (T)ServiceInstances[typeof(T)]; + return (T)ServiceInstances[typeKey]; } lock (Lock) { - if (!ServiceRegistry.ContainsKey(typeof(T))) + if (!ServiceRegistry.ContainsKey(typeKey)) { throw new InvalidOperationException($"The type {typeof(T)} is not registered."); } - var resolve = ServiceRegistry[typeof(T)](new Resolver()); - ServiceInstances[typeof(T)] = resolve; - return (T) resolve; + var resolve = ServiceRegistry[typeKey](new Resolver()); + ServiceInstances[typeKey] = resolve; + return (T)resolve; } } - public static void Register(Func constructor) + public static void Register(Func constructor) => Register(null, constructor); + + public static void Register(string key, Func constructor) { + var typeKey = new TypeKey(typeof(T), key); lock (Lock) { - if (ServiceRegistry.ContainsKey(typeof(T))) + if (ServiceRegistry.ContainsKey(typeKey)) { throw new InvalidOperationException($"The type {typeof(T)} is already registered."); } - ServiceRegistry.Add(typeof(T), l => constructor(l)); + ServiceRegistry.Add(typeKey, l => constructor(l)); } } @@ -80,18 +128,5 @@ public static void ConfigureFromAssembly(Assembly assembly) configuration.Configure(); } } - - public static void Configure(AppDomain appDomain = null) - { - if (appDomain == null) - { - appDomain = AppDomain.CurrentDomain; - } - - foreach (var assembly in appDomain.GetAssemblies()) - { - ConfigureFromAssembly(assembly); - } - } } } \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/ServiceLocatorConfiguration.cs b/ExtLibs/AltitudeAngelWings/ServiceLocatorConfiguration.cs index 2ae0b226dd..585297867a 100644 --- a/ExtLibs/AltitudeAngelWings/ServiceLocatorConfiguration.cs +++ b/ExtLibs/AltitudeAngelWings/ServiceLocatorConfiguration.cs @@ -1,8 +1,15 @@ using System; -using AltitudeAngelWings.ApiClient.Client; -using AltitudeAngelWings.ApiClient.Client.FlightClient; -using AltitudeAngelWings.ApiClient.Client.TelemetryClient; -using AltitudeAngelWings.Extra; +using System.Net; +using System.Net.Http; +using AltitudeAngelWings.Clients; +using AltitudeAngelWings.Clients.Api; +using AltitudeAngelWings.Clients.Auth; +using AltitudeAngelWings.Clients.Auth.Model; +using AltitudeAngelWings.Clients.Flight; +using AltitudeAngelWings.Clients.Flight.Model.ServiceRequests; +using AltitudeAngelWings.Clients.Flight.Model.ServiceRequests.ProtocolConfiguration; +using AltitudeAngelWings.Clients.Surveillance; +using AltitudeAngelWings.Clients.Telemetry; using AltitudeAngelWings.Service; using AltitudeAngelWings.Service.AltitudeAngelTelemetry; using AltitudeAngelWings.Service.AltitudeAngelTelemetry.Encryption; @@ -10,9 +17,13 @@ using AltitudeAngelWings.Service.FlightData.Providers; using AltitudeAngelWings.Service.FlightService; using AltitudeAngelWings.Service.Messaging; -using AltitudeAngelWings.Service.OutboundNotifs; +using AltitudeAngelWings.Service.OutboundNotifications; using Flurl.Http; using Flurl.Http.Configuration; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using NodaTime; +using NodaTime.Serialization.JsonNet; using Polly; namespace AltitudeAngelWings @@ -23,15 +34,13 @@ public void Configure() { ServiceLocator.Register(l => Policy.WrapAsync( Policy - .TimeoutAsync(TimeSpan.FromSeconds(30)), - Policy - .Handle(e => e.StatusCode == 401) - .RetryAsync(2, (exception, i) => l.Resolve().TokenResponse.AccessToken = ""), + .Handle(e => e.StatusCode == 401 && !l.Resolve().SigningIn && l.Resolve().TokenResponse.CanBeRefreshed()) + .RetryAsync((e, i) => ResetAccessToken(l.Resolve())), Policy .Handle(e => e.StatusCode >= 500) - .WaitAndRetryAsync(5, i => TimeSpan.FromSeconds(Math.Pow(2, i) / 10)), + .WaitAndRetryAsync(5, i => TimeSpan.FromSeconds(Math.Pow(2, i) / 2)), Policy - .TimeoutAsync(TimeSpan.FromSeconds(5)))); + .TimeoutAsync(TimeSpan.FromSeconds(30)))); ServiceLocator.Register(l => new HmacKeyGenerator( l.Resolve().EncryptionHashType, l.Resolve().EncryptionKeySecret)); @@ -48,22 +57,67 @@ public void Configure() l.Resolve().MinimumPollInterval, l.Resolve())); ServiceLocator.Register(l => new UserAuthenticationTokenProvider( - l.Resolve(), - new DefaultHttpClientFactory(), + l.Resolve(), + l.Resolve(), + new Lazy(() => l.Resolve()), l.Resolve(), l.Resolve())); - ServiceLocator.Register(l => new AltitudeAngelHttpHandlerFactory( - l.Resolve())); + ServiceLocator.Register("Auth", + l => new DelegatingHttpHandlerFactory(() => new PolicyHandler(l.Resolve()) + { + InnerHandler = new UserAgentHandler(l.Resolve().VersionHeader) + { + InnerHandler = new HttpClientHandler + { + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate + } + } + })); + ServiceLocator.Register( + l => new DelegatingHttpHandlerFactory(() => new PolicyHandler(l.Resolve()) + { + InnerHandler = new BearerTokenHttpMessageHandler(l.Resolve()) + { + InnerHandler = new UserAgentHandler(l.Resolve().VersionHeader) + { + InnerHandler = new HttpClientHandler + { + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate + } + } + } + })); ServiceLocator.Register(l => new TelemetryClient(l.Resolve())); + ServiceLocator.Register(l => + { + var settings = new JsonSerializerSettings + { + DateParseHandling = DateParseHandling.None + }; + settings.Converters.Add(new BaseNotificationProtocolConfigurationConverter()); + settings.Converters.Add(new BaseTelemetryProtocolConfigurationConverter()); + settings.Converters.Add(new FlightServiceResponseConverter()); + settings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); + settings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + return new NewtonsoftJsonSerializer(settings); + }); ServiceLocator.Register(l => new FlightClient( - l.Resolve().FlightServiceUrl, + l.Resolve(), l.Resolve(), - l.Resolve())); - ServiceLocator.Register(l => new AltitudeAngelClient( + l.Resolve())); + ServiceLocator.Register(l => new SurveillanceClient( l.Resolve(), l.Resolve(), - l.Resolve())); - ServiceLocator.Register(l => new OutboundNotifsService( + l.Resolve())); + ServiceLocator.Register(l => new ApiClient( + l.Resolve(), + l.Resolve(), + l.Resolve())); + ServiceLocator.Register(l => new AuthClient( + l.Resolve(), + l.Resolve("Auth"), + l.Resolve())); + ServiceLocator.Register(l => new OutboundNotificationsService( l.Resolve(), l.Resolve(), l.Resolve(), @@ -71,11 +125,12 @@ public void Configure() l.Resolve())); ServiceLocator.Register(l => new FlightService( l.Resolve(), - l.Resolve(), + l.Resolve(), l.Resolve(), l.Resolve(), - l.Resolve(), - l.Resolve())); + l.Resolve(), + l.Resolve(), + l.Resolve())); ServiceLocator.Register(l => new TelemetryService( l.Resolve(), l.Resolve(), @@ -85,9 +140,18 @@ public void Configure() l.Resolve(), l.Resolve(), l.Resolve(), - l.Resolve(), + l.Resolve(), + l.Resolve(), l.Resolve(), l.Resolve())); } + + private static void ResetAccessToken(ISettings settings) + { + if (settings.TokenResponse == null) return; + var token = settings.TokenResponse; + token.AccessToken = ""; + settings.TokenResponse = token; + } } } \ No newline at end of file diff --git a/ExtLibs/AltitudeAngelWings/Service/Settings.cs b/ExtLibs/AltitudeAngelWings/Settings.cs similarity index 69% rename from ExtLibs/AltitudeAngelWings/Service/Settings.cs rename to ExtLibs/AltitudeAngelWings/Settings.cs index 37c565dfdd..282a964961 100644 --- a/ExtLibs/AltitudeAngelWings/Service/Settings.cs +++ b/ExtLibs/AltitudeAngelWings/Settings.cs @@ -1,18 +1,21 @@ using System; using System.Collections.Generic; -using System.Configuration; using System.Linq; -using AltitudeAngelWings.ApiClient.Client; +using System.Threading; +using AltitudeAngelWings.Clients; +using AltitudeAngelWings.Clients.Api.Model; +using AltitudeAngelWings.Clients.Auth.Model; using AltitudeAngelWings.Service.AltitudeAngelTelemetry.Encryption; using Newtonsoft.Json; -namespace AltitudeAngelWings.Service +namespace AltitudeAngelWings { public class Settings : ISettings { private readonly Func _loadSetting; private readonly Action _clearSetting; private readonly Action _saveSetting; + private readonly SemaphoreSlim _saveLock = new SemaphoreSlim(1); public Settings(Func loadSetting, Action clearSetting, Action saveSetting) { @@ -27,40 +30,46 @@ public bool CheckEnableAltitudeAngel set => Set(nameof(CheckEnableAltitudeAngel), value); } - public bool SurveillanceMode + public bool UseFlightPlans { - get - { - var value = ConfigurationManager.AppSettings["SurveillanceMode"]; - bool.TryParse(value, out var result); - return result; - } + get => Get(nameof(UseFlightPlans), true, bool.Parse); + set => Set(nameof(UseFlightPlans), value); + } + + public bool UseFlights + { + get => Get(nameof(UseFlights), true, bool.Parse); + set => Set(nameof(UseFlights), value); } public string AuthenticationUrl => $"https://auth.{UrlDomainSuffix}"; public string ApiUrl => $"https://api.{UrlDomainSuffix}"; - public string ClientId => OverrideClientUrlSettings ? OverrideClientId : ConfigurationManager.AppSettings["ClientId"]; + public string ClientId => OverrideClientUrlSettings ? OverrideClientId : "zHTnuEq0RAWoLy5thcvTtMdwX7r6et2L3MAhxv8a0"; - public string ClientSecret => OverrideClientUrlSettings ? OverrideClientSecret : ConfigurationManager.AppSettings["ClientSecret"]; + public string ClientSecret => OverrideClientUrlSettings ? OverrideClientSecret : "1ylYlXV4GuWJHIUywFg+XxE6hxsd3P/Dq5+J1PCUGxulC05/GC4Xpg=="; public string[] ClientScopes => new[] { - "query_mapdata", - "query_mapairdata", - "talk_tower", - "query_userinfo", - "manage_flightreports", - "strategic_crs", - "tactical_crs" + Scopes.QueryMapData, + Scopes.QueryMapAirData, + Scopes.TalkTower, + Scopes.QueryUserInfo, + Scopes.ManageFlightReports, + Scopes.RequestFlightApprovals, + Scopes.StrategicCrs, + Scopes.TacticalCrs, + Scopes.SurveillanceApi }; - public string RedirectUri => $"https://auth{UrlDomainSuffix}/authorization/poll_complete"; + public string RedirectUri => $"https://auth.{UrlDomainSuffix}/authorization/poll_complete"; public string FlightServiceUrl => $"https://flight.{UrlDomainSuffix}"; - public string UrlDomainSuffix => OverrideClientUrlSettings ? OverrideUrlDomainSuffix : ConfigurationManager.AppSettings["UrlDomainSuffix"]; + public string SurveillanceUrl => $"https://surveillance-api.{UrlDomainSuffix}"; + + public string UrlDomainSuffix => OverrideClientUrlSettings ? OverrideUrlDomainSuffix : "altitudeangel.com"; public bool OverrideClientUrlSettings { @@ -85,14 +94,17 @@ public string OverrideUrlDomainSuffix get => Get(nameof(OverrideUrlDomainSuffix), "altitudeangel.com", s => s); set => Set(nameof(OverrideUrlDomainSuffix), value); } - public string OutboundNotifsEndpointUrl + public string OutboundNotificationsUrl { - get => Get(nameof(OutboundNotifsEndpointUrl), null, s => s); - set => Set(nameof(OutboundNotifsEndpointUrl), value); + get => Get(nameof(OutboundNotificationsUrl), null, s => s); + set => Set(nameof(OutboundNotificationsUrl), value); } - /// - public bool DisableTelemetrySending => Convert.ToBoolean(ConfigurationManager.AppSettings["DisableTelemetrySending"]); + public FlightTelemetry SendFlightTelemetry + { + get => Get(nameof(SendFlightTelemetry), FlightTelemetry.Surveillance, s => (FlightTelemetry)Enum.Parse(typeof(FlightTelemetry), s)); + set => Set(nameof(SendFlightTelemetry), value, t => Enum.GetName(typeof(FlightTelemetry), t)); + } public TokenResponse TokenResponse { @@ -100,11 +112,11 @@ public TokenResponse TokenResponse set => Set(nameof(TokenResponse), value, JsonConvert.SerializeObject); } - public HashType EncryptionHashType => HashType.Hmac256; //TODO add to config file to hide from mission planner + public HashType EncryptionHashType => HashType.Hmac256; - public SymmetricEncryptionType EncryptionType => SymmetricEncryptionType.Aes128; //TODO add to config file to hide from mission planner + public SymmetricEncryptionType EncryptionType => SymmetricEncryptionType.Aes128; - public string EncryptionKeySecret => ConfigurationManager.AppSettings["AA.Telemetry.EncryptionKeySecret"]; + public string EncryptionKeySecret => "e05b7b90-3866-4eea-9b2a-73b02f46423a"; public TimeSpan MinimumPollInterval { @@ -118,34 +130,22 @@ public IList MapFilters set => Set(nameof(MapFilters), value, JsonConvert.SerializeObject); } - public bool FlightReportEnable - { - get => Get(nameof(FlightReportEnable), true, bool.Parse); - set => Set(nameof(FlightReportEnable), value); - } - public bool UseExistingFlightPlanId { get => Get(nameof(UseExistingFlightPlanId), false, bool.Parse); set => Set(nameof(UseExistingFlightPlanId), value); } - public bool UseFlightPlanLocalScope - { - get => Get(nameof(UseFlightPlanLocalScope), false, bool.Parse); - set => Set(nameof(UseFlightPlanLocalScope), value); - } - public Guid ExistingFlightPlanId { get => Get(nameof(ExistingFlightPlanId), Guid.Empty, Guid.Parse); set => Set(nameof(ExistingFlightPlanId), value); } - public string CurrentFlightReportId + public string CurrentFlightPlanId { - get => Get(nameof(CurrentFlightReportId), null, s => s); - set => Set(nameof(CurrentFlightReportId), value); + get => Get(nameof(CurrentFlightPlanId), null, s => s); + set => Set(nameof(CurrentFlightPlanId), value); } public string CurrentFlightId @@ -154,28 +154,22 @@ public string CurrentFlightId set => Set(nameof(CurrentFlightId), value); } - public string FlightReportName + public string FlightPlanName { - get => Get(nameof(FlightReportName), "Mission Planner", s => s); - set => Set(nameof(FlightReportName), value); + get => Get(nameof(FlightPlanName), "Mission Planner", s => s); + set => Set(nameof(FlightPlanName), value); } - public string FlightReportDescription + public string FlightPlanDescription { - get => Get(nameof(FlightReportDescription), "Mission Planner flight report", s => s); - set => Set(nameof(FlightReportDescription), value); + get => Get(nameof(FlightPlanDescription), "Mission Planner flight plan", s => s); + set => Set(nameof(FlightPlanDescription), value); } - public bool FlightReportCommercial + public TimeSpan FlightPlanTimeSpan { - get => Get(nameof(FlightReportCommercial), false, bool.Parse); - set => Set(nameof(FlightReportCommercial), value); - } - - public TimeSpan FlightReportTimeSpan - { - get => Get(nameof(FlightReportTimeSpan), TimeSpan.FromMinutes(60), TimeSpan.Parse); - set => Set(nameof(FlightReportTimeSpan), value); + get => Get(nameof(FlightPlanTimeSpan), TimeSpan.FromMinutes(60), TimeSpan.Parse); + set => Set(nameof(FlightPlanTimeSpan), value); } public string CurrentTelemetryId @@ -214,6 +208,12 @@ public float MapOpacityAdjust set => Set(nameof(MapOpacityAdjust), value); } + public int AltitudeFilter + { + get => Get(nameof(AltitudeFilter), 300, int.Parse); + set => Set(nameof(AltitudeFilter), value); + } + public bool EnableDataMap { get => Get(nameof(EnableDataMap), true, bool.Parse); @@ -228,7 +228,7 @@ public bool EnablePlanMap public double MapUpdateThrottle { - get => Get(nameof(MapUpdateThrottle), 0.25, double.Parse); + get => Get(nameof(MapUpdateThrottle), 1, double.Parse); set => Set(nameof(MapUpdateThrottle), value); } @@ -285,16 +285,25 @@ private T Get(string settingName, T defaultValue, Func getSetting) private void Set(string settingName, T value, Func setSetting = null) { settingName = CheckAndPrefixSettingName(settingName); - if (value == null) - { - _clearSetting(settingName); - return; - } if (setSetting == null) { setSetting = v => v.ToString(); } - _saveSetting(settingName, setSetting(value)); + + try + { + _saveLock.Wait(); + if (value == null) + { + _clearSetting(settingName); + return; + } + _saveSetting(settingName, setSetting(value)); + } + finally + { + _saveLock.Release(); + } } private static string CheckAndPrefixSettingName(string settingName) diff --git a/ExtLibs/AltitudeAngelWings/UiThreadInvoke.cs b/ExtLibs/AltitudeAngelWings/UiThreadInvoke.cs index ed943fd7a7..6a08bf7494 100644 --- a/ExtLibs/AltitudeAngelWings/UiThreadInvoke.cs +++ b/ExtLibs/AltitudeAngelWings/UiThreadInvoke.cs @@ -12,7 +12,7 @@ public UiThreadInvoke(Func, Task> invokeOnUiThread) _invokeOnUiThread = invokeOnUiThread; } - public void FireAndForget(Action action) + public void Invoke(Action action) { Invoke(() => { diff --git a/ExtLibs/AltitudeAngelWings/mykey.snk b/ExtLibs/AltitudeAngelWings/mykey.snk deleted file mode 100644 index fa40666d7d..0000000000 Binary files a/ExtLibs/AltitudeAngelWings/mykey.snk and /dev/null differ diff --git a/app.config b/app.config index 67b65e13f4..5573fb9709 100644 --- a/app.config +++ b/app.config @@ -20,16 +20,6 @@ - - - - - - - - - -