diff --git a/VRCOSC.Desktop/VRCOSC.Desktop.csproj b/VRCOSC.Desktop/VRCOSC.Desktop.csproj index 9a61b70f..b126d80d 100644 --- a/VRCOSC.Desktop/VRCOSC.Desktop.csproj +++ b/VRCOSC.Desktop/VRCOSC.Desktop.csproj @@ -6,12 +6,12 @@ game.ico app.manifest 0.0.0 - 2023.1022.0 + 2023.1220.0 VRCOSC VolcanicArts VolcanicArts enable - 2023.1022.0 + 2023.1220.0 diff --git a/VRCOSC.Game/ChatBox/Clips/Clip.cs b/VRCOSC.Game/ChatBox/Clips/Clip.cs index a2ed30f9..ba9a4391 100644 --- a/VRCOSC.Game/ChatBox/Clips/Clip.cs +++ b/VRCOSC.Game/ChatBox/Clips/Clip.cs @@ -214,8 +214,6 @@ private void removeInvalidStates(List localStates) var currentStates = AssociatedModules.Where(moduleName => appManager.ModuleManager.GetModule(moduleName)!.Enabled.Value && chatBoxManager.StateValues.ContainsKey(moduleName) && chatBoxManager.StateValues[moduleName] is not null).Select(moduleName => chatBoxManager.StateValues[moduleName]).ToList(); currentStates.Sort(); - if (!currentStates.Any()) return; - foreach (var clipState in localStates.ToImmutableList()) { var clipStateStates = clipState.StateNames; diff --git a/VRCOSC.Game/OSC/VRChat/VRChatOscClient.cs b/VRCOSC.Game/OSC/VRChat/VRChatOscClient.cs index 2eeddef2..b7786ef0 100644 --- a/VRCOSC.Game/OSC/VRChat/VRChatOscClient.cs +++ b/VRCOSC.Game/OSC/VRChat/VRChatOscClient.cs @@ -6,6 +6,7 @@ using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; +using osu.Framework.IO.Network; using osu.Framework.Logging; using VRC.OSCQuery; using VRCOSC.Game.OSC.Client; @@ -72,7 +73,8 @@ public void Reset() { if (QueryPort is null) return null; - var url = $"http://127.0.0.1:{QueryPort}/avatar/parameters/{parameterName}"; + var parameterNameEncoded = UrlEncoding.UrlEncode(parameterName); + var url = $"http://127.0.0.1:{QueryPort}/avatar/parameters/{parameterNameEncoded}"; var response = await client.GetAsync(new Uri(url)); var content = await response.Content.ReadAsStringAsync(); diff --git a/VRCOSC.Game/OpenVR/OVRHelper.cs b/VRCOSC.Game/OpenVR/OVRHelper.cs index cf20ec09..6461d679 100644 --- a/VRCOSC.Game/OpenVR/OVRHelper.cs +++ b/VRCOSC.Game/OpenVR/OVRHelper.cs @@ -1,4 +1,4 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. // See the LICENSE file in the repository root for full license text. using System; @@ -108,6 +108,8 @@ internal static IEnumerable GetIndexesForTrackedDeviceClass(ETrackedDevice internal static bool GetBoolTrackedDeviceProperty(uint index, ETrackedDeviceProperty property) { + if (index == Valve.VR.OpenVR.k_unTrackedDeviceIndexInvalid) return default; + var error = new ETrackedPropertyError(); var value = Valve.VR.OpenVR.System.GetBoolTrackedDeviceProperty(index, property, ref error); @@ -119,6 +121,8 @@ internal static bool GetBoolTrackedDeviceProperty(uint index, ETrackedDeviceProp internal static int GetInt32TrackedDeviceProperty(uint index, ETrackedDeviceProperty property) { + if (index == Valve.VR.OpenVR.k_unTrackedDeviceIndexInvalid) return default; + var error = new ETrackedPropertyError(); var value = Valve.VR.OpenVR.System.GetInt32TrackedDeviceProperty(index, property, ref error); @@ -130,6 +134,8 @@ internal static int GetInt32TrackedDeviceProperty(uint index, ETrackedDeviceProp internal static float GetFloatTrackedDeviceProperty(uint index, ETrackedDeviceProperty property) { + if (index == Valve.VR.OpenVR.k_unTrackedDeviceIndexInvalid) return default; + var error = new ETrackedPropertyError(); var value = Valve.VR.OpenVR.System.GetFloatTrackedDeviceProperty(index, property, ref error); @@ -143,6 +149,8 @@ internal static float GetFloatTrackedDeviceProperty(uint index, ETrackedDevicePr internal static string GetStringTrackedDeviceProperty(uint index, ETrackedDeviceProperty property) { + if (index == Valve.VR.OpenVR.k_unTrackedDeviceIndexInvalid) return string.Empty; + var error = new ETrackedPropertyError(); sb.Clear(); Valve.VR.OpenVR.System.GetStringTrackedDeviceProperty(index, property, sb, Valve.VR.OpenVR.k_unMaxPropertyStringSize, ref error); diff --git a/VRCOSC.Game/Providers/Hardware/IComponent.cs b/VRCOSC.Game/Providers/Hardware/Components.cs similarity index 66% rename from VRCOSC.Game/Providers/Hardware/IComponent.cs rename to VRCOSC.Game/Providers/Hardware/Components.cs index cfa196ea..aec13f67 100644 --- a/VRCOSC.Game/Providers/Hardware/IComponent.cs +++ b/VRCOSC.Game/Providers/Hardware/Components.cs @@ -3,29 +3,20 @@ using System; using System.Collections.Generic; +using System.Linq; using LibreHardwareMonitor.Hardware; namespace VRCOSC.Game.Providers.Hardware; -public class SensorInfoList -{ - public readonly List InfoList = new(); - - public SensorInfoList(SensorType type, params string[] names) - { - foreach (var name in names) InfoList.Add(new SensorInfo(type, name)); - } -} +public record SensorPair(SensorType Type, string Name); public class SensorInfo { - public readonly string Name; - public readonly SensorType Type; + public readonly List Pairs = new(); - public SensorInfo(SensorType type, string name) + public SensorInfo(SensorType type, params string[] names) { - Type = type; - Name = name; + foreach (var name in names) Pairs.Add(new SensorPair(type, name)); } } @@ -33,22 +24,13 @@ public abstract class HardwareComponent { protected virtual SensorInfo LoadInfo => throw new NotImplementedException(); - public readonly int Id; - public float Usage { get; private set; } - protected HardwareComponent(int id) - { - Id = id; - } - - protected bool GetIntValue(ISensor sensor, SensorInfoList infoList, out int value) + protected static bool GetIntValue(ISensor sensor, SensorInfo info, out int value) { - foreach (var info in infoList.InfoList) + if (!GetFloatValue(sensor, info, out var floatValue)) { - if (!GetIntValue(sensor, info, out var intValue)) continue; - - value = intValue; + value = (int)MathF.Round(floatValue); return true; } @@ -56,27 +38,15 @@ protected bool GetIntValue(ISensor sensor, SensorInfoList infoList, out int valu return false; } - protected bool GetIntValue(ISensor sensor, SensorInfo info, out int value) + protected static bool GetFloatValue(ISensor sensor, SensorInfo info, out float value) { - if (GetFloatValue(sensor, info, out var valueFloat)) - { - value = (int)Math.Round(valueFloat); - return true; - } - - value = 0; - return false; - } - - protected bool GetFloatValue(ISensor sensor, SensorInfo info, out float value) - { - if (sensor.Name == info.Name && sensor.SensorType == info.Type) + if (info.Pairs.Any(pair => sensor.SensorType == pair.Type && sensor.Name == pair.Name)) { value = sensor.Value ?? 0f; return true; } - value = 0f; + value = 0; return false; } @@ -90,16 +60,11 @@ public abstract class CPU : HardwareComponent { protected override SensorInfo LoadInfo => new(SensorType.Load, "CPU Total"); protected virtual SensorInfo PowerInfo => throw new NotImplementedException(); - protected virtual SensorInfoList TemperatureInfo => throw new NotImplementedException(); + protected virtual SensorInfo TemperatureInfo => throw new NotImplementedException(); public int Power { get; private set; } public int Temperature { get; private set; } - protected CPU(int id) - : base(id) - { - } - public override void Update(ISensor sensor) { base.Update(sensor); @@ -111,24 +76,14 @@ public override void Update(ISensor sensor) public class IntelCPU : CPU { protected override SensorInfo PowerInfo => new(SensorType.Power, "CPU Package"); - protected override SensorInfoList TemperatureInfo => new(SensorType.Temperature, "CPU Package"); - - public IntelCPU(int id) - : base(id) - { - } + protected override SensorInfo TemperatureInfo => new(SensorType.Temperature, "CPU Package"); } // ReSharper disable once InconsistentNaming public class AMDCPU : CPU { protected override SensorInfo PowerInfo => new(SensorType.Power, "Package"); - protected override SensorInfoList TemperatureInfo => new(SensorType.Temperature, "Core (Tdie)", "Core (Tctl/Tdie)", "CPU Cores"); - - public AMDCPU(int id) - : base(id) - { - } + protected override SensorInfo TemperatureInfo => new(SensorType.Temperature, "Core (Tdie)", "Core (Tctl/Tdie)", "CPU Cores"); } public class GPU : HardwareComponent @@ -137,7 +92,7 @@ public class GPU : HardwareComponent private readonly SensorInfo powerInfo = new(SensorType.Power, "GPU Package"); private readonly SensorInfo temperatureInfo = new(SensorType.Temperature, "GPU Core"); private readonly SensorInfo memoryFreeInfo = new(SensorType.SmallData, "GPU Memory Free"); - private readonly SensorInfo memoryUsedINfo = new(SensorType.SmallData, "GPU Memory Used"); + private readonly SensorInfo memoryUsedInfo = new(SensorType.SmallData, "GPU Memory Used", "D3D Dedicated Memory Used"); private readonly SensorInfo memoryTotalInfo = new(SensorType.SmallData, "GPU Memory Total"); public int Power { get; private set; } @@ -147,18 +102,13 @@ public class GPU : HardwareComponent public float MemoryTotal { get; private set; } public float MemoryUsage => MemoryUsed / MemoryTotal; - public GPU(int id) - : base(id) - { - } - public override void Update(ISensor sensor) { base.Update(sensor); if (GetIntValue(sensor, powerInfo, out var powerValue)) Power = powerValue; if (GetIntValue(sensor, temperatureInfo, out var temperatureValue)) Temperature = temperatureValue; if (GetFloatValue(sensor, memoryFreeInfo, out var memoryFreeValue)) MemoryFree = memoryFreeValue; - if (GetFloatValue(sensor, memoryUsedINfo, out var memoryUsedValue)) MemoryUsed = memoryUsedValue; + if (GetFloatValue(sensor, memoryUsedInfo, out var memoryUsedValue)) MemoryUsed = memoryUsedValue; if (GetFloatValue(sensor, memoryTotalInfo, out var memoryTotalValue)) MemoryTotal = memoryTotalValue; } } @@ -173,11 +123,6 @@ public class RAM : HardwareComponent public float Available { get; private set; } public float Total => Used + Available; - public RAM() - : base(0) - { - } - public override void Update(ISensor sensor) { base.Update(sensor); diff --git a/VRCOSC.Game/Providers/Hardware/HardwareStatsProvider.cs b/VRCOSC.Game/Providers/Hardware/HardwareStatsProvider.cs index 72fb66b0..62cfc12c 100644 --- a/VRCOSC.Game/Providers/Hardware/HardwareStatsProvider.cs +++ b/VRCOSC.Game/Providers/Hardware/HardwareStatsProvider.cs @@ -3,11 +3,13 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using LibreHardwareMonitor.Hardware; using osu.Framework.Extensions.IEnumerableExtensions; +// ReSharper disable InconsistentNaming + namespace VRCOSC.Game.Providers.Hardware; public sealed class HardwareStatsProvider @@ -19,13 +21,14 @@ public sealed class HardwareStatsProvider IsMemoryEnabled = true }; - public bool CanAcceptQueries { get; private set; } + private readonly Regex hardwareIDRegex = new(".+/([0-9])"); + private readonly Regex sensorIDRegex = new(".+/([0-9])/.+"); - private readonly List components = new(); + public bool CanAcceptQueries { get; private set; } - public CPU? GetCPU(int id) => components.Where(component => component.GetType().IsSubclassOf(typeof(CPU))).Select(component => (CPU)component).SingleOrDefault(cpu => cpu.Id == id); - public GPU? GetGPU(int id) => components.Where(component => component.GetType() == typeof(GPU)).Select(component => (GPU)component).SingleOrDefault(gpu => gpu.Id == id); - public RAM? GetRam() => components.Where(component => component.GetType() == typeof(RAM)).Select(component => (RAM)component).SingleOrDefault(); + public readonly Dictionary CPUs = new(); + public readonly Dictionary GPUs = new(); + public RAM? RAM; public void Init() => Task.Run(() => { @@ -36,7 +39,10 @@ public void Init() => Task.Run(() => public void Shutdown() => Task.Run(() => { CanAcceptQueries = false; - components.Clear(); + CPUs.Clear(); + GPUs.Clear(); + RAM = null; + computer.Close(); }); @@ -46,7 +52,33 @@ public async Task Update() => await Task.Run(() => { updateHardware(hardware); auditHardware(hardware); - hardware.Sensors.ForEach(sensor => components.ForEach(component => component.Update(sensor))); + hardware.Sensors.ForEach(sensor => + { + var identifier = sensor.Identifier.ToString()!; + + if (identifier.Contains("ram", StringComparison.InvariantCultureIgnoreCase)) + { + RAM!.Update(sensor); + return; + } + + var sensorIdMatch = sensorIDRegex.Match(identifier); + if (!sensorIdMatch.Success) return; + + var sensorId = int.Parse(sensorIdMatch.Groups[1].Value); + + if (identifier.Contains("cpu", StringComparison.InvariantCultureIgnoreCase)) + { + CPUs[sensorId].Update(sensor); + return; + } + + if (identifier.Contains("gpu", StringComparison.InvariantCultureIgnoreCase)) + { + GPUs[sensorId].Update(sensor); + return; + } + }); }); }); @@ -58,51 +90,38 @@ private void updateHardware(IHardware hardware) private void auditHardware(IHardware hardware) { - var address = hardware.Identifier.ToString(); - var index = 0; + var identifier = hardware.Identifier.ToString()!; - try + if (identifier.Contains("ram", StringComparison.InvariantCultureIgnoreCase)) { - index = int.Parse(address.Split('/').Last()); + RAM ??= new RAM(); + return; } - catch (FormatException) { } - if (address.Contains("cpu", StringComparison.InvariantCultureIgnoreCase)) - { - var cpu = GetCPU(index); + var hardwareIDMatch = hardwareIDRegex.Match(identifier); + if (!hardwareIDMatch.Success) return; - if (cpu is null) - { - if (address.Contains("intel", StringComparison.InvariantCultureIgnoreCase)) - { - components.Add(new IntelCPU(index)); - } + var hardwareID = int.Parse(hardwareIDMatch.Groups[1].Value); - if (address.Contains("amd", StringComparison.InvariantCultureIgnoreCase)) - { - components.Add(new AMDCPU(index)); - } - } - } - - if (address.Contains("gpu", StringComparison.InvariantCultureIgnoreCase)) + if (identifier.Contains("cpu", StringComparison.InvariantCultureIgnoreCase)) { - var gpu = GetGPU(index); + if (identifier.Contains("intel", StringComparison.InvariantCultureIgnoreCase)) + { + CPUs.TryAdd(hardwareID, new IntelCPU()); + return; + } - if (gpu is null) + if (identifier.Contains("amd", StringComparison.InvariantCultureIgnoreCase)) { - components.Add(new GPU(index)); + CPUs.TryAdd(hardwareID, new AMDCPU()); + return; } } - if (address.Contains("ram", StringComparison.InvariantCultureIgnoreCase)) + if (identifier.Contains("gpu", StringComparison.InvariantCultureIgnoreCase)) { - var ram = GetRam(); - - if (ram is null) - { - components.Add(new RAM()); - } + GPUs.TryAdd(hardwareID, new GPU()); + return; } } } diff --git a/VRCOSC.Game/VRCOSC.Game.csproj b/VRCOSC.Game/VRCOSC.Game.csproj index a1ae79f2..eae41c1a 100644 --- a/VRCOSC.Game/VRCOSC.Game.csproj +++ b/VRCOSC.Game/VRCOSC.Game.csproj @@ -19,10 +19,10 @@ - - + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -30,7 +30,7 @@ - + diff --git a/VRCOSC.Modules/HardwareStats/HardwareStatsModule.cs b/VRCOSC.Modules/HardwareStats/HardwareStatsModule.cs index d361a0e3..025f3716 100644 --- a/VRCOSC.Modules/HardwareStats/HardwareStatsModule.cs +++ b/VRCOSC.Modules/HardwareStats/HardwareStatsModule.cs @@ -73,25 +73,23 @@ private async void updateParameters() await hardwareStatsProvider.Update(); - var cpu = hardwareStatsProvider.GetCPU(GetSetting(HardwareStatsSetting.SelectedCPU)); - var gpu = hardwareStatsProvider.GetGPU(GetSetting(HardwareStatsSetting.SelectedGPU)); - var ram = hardwareStatsProvider.GetRam(); - - if (cpu is null) + if (!hardwareStatsProvider.CPUs.TryGetValue(GetSetting(HardwareStatsSetting.SelectedCPU), out var cpu)) { - Log("Warning. Could not connect to CPU"); + Log($"CPU of id {GetSetting(HardwareStatsSetting.SelectedCPU)} isn't available. If you have multiple, try changing the index"); return; } - if (gpu is null) + if (!hardwareStatsProvider.GPUs.TryGetValue(GetSetting(HardwareStatsSetting.SelectedGPU), out var gpu)) { - Log("Warning. Could not connect to GPU"); + Log($"GPU of id {GetSetting(HardwareStatsSetting.SelectedGPU)} isn't available. If you have multiple, try changing the index"); return; } + var ram = hardwareStatsProvider.RAM; + if (ram is null) { - Log("Warning. Could not connect to RAM"); + Log("Could not connect to RAM. This is impossible, so well done!"); return; } diff --git a/VRCOSC.Modules/OpenVR/OpenVRStatisticsModule.cs b/VRCOSC.Modules/OpenVR/OpenVRStatisticsModule.cs index 3fed16f9..cd910562 100644 --- a/VRCOSC.Modules/OpenVR/OpenVRStatisticsModule.cs +++ b/VRCOSC.Modules/OpenVR/OpenVRStatisticsModule.cs @@ -37,8 +37,11 @@ protected override void CreateAttributes() } CreateVariable(OpenVrVariable.FPS, "FPS", "fps"); + CreateVariable(OpenVrVariable.HMDCharging, "HMD Charging", "hmdcharging"); CreateVariable(OpenVrVariable.HMDBattery, "HMD Battery (%)", "hmdbattery"); + CreateVariable(OpenVrVariable.LeftControllerCharging, "Left Controller Charging", "leftcontrollercharging"); CreateVariable(OpenVrVariable.LeftControllerBattery, "Left Controller Battery (%)", "leftcontrollerbattery"); + CreateVariable(OpenVrVariable.RightControllerCharging, "Right Controller Charging", "rightcontrollercharging"); CreateVariable(OpenVrVariable.RightControllerBattery, "Right Controller Battery (%)", "rightcontrollerbattery"); CreateVariable(OpenVrVariable.AverageTrackerBattery, "Average Tracker Battery (%)", "averagetrackerbattery"); @@ -71,16 +74,22 @@ private void updateVariablesAndParameters() } SetVariableValue(OpenVrVariable.FPS, OVRClient.System.FPS.ToString("##0")); + SetVariableValue(OpenVrVariable.HMDCharging, OVRClient.HMD.IsCharging ? "Charging" : "Uncharging"); SetVariableValue(OpenVrVariable.HMDBattery, ((int)(OVRClient.HMD.BatteryPercentage * 100)).ToString("##0")); + SetVariableValue(OpenVrVariable.LeftControllerCharging, OVRClient.LeftController.IsCharging ? "Charging" : "Uncharging"); SetVariableValue(OpenVrVariable.LeftControllerBattery, ((int)(OVRClient.LeftController.BatteryPercentage * 100)).ToString("##0")); + SetVariableValue(OpenVrVariable.RightControllerCharging, OVRClient.RightController.IsCharging ? "Charging" : "Uncharging"); SetVariableValue(OpenVrVariable.RightControllerBattery, ((int)(OVRClient.RightController.BatteryPercentage * 100)).ToString("##0")); SetVariableValue(OpenVrVariable.AverageTrackerBattery, ((int)(trackerBatteryAverage * 100)).ToString("##0")); } else { SetVariableValue(OpenVrVariable.FPS, "0"); + SetVariableValue(OpenVrVariable.HMDCharging, "Unknown"); SetVariableValue(OpenVrVariable.HMDBattery, "0"); + SetVariableValue(OpenVrVariable.LeftControllerCharging, "Unknown"); SetVariableValue(OpenVrVariable.LeftControllerBattery, "0"); + SetVariableValue(OpenVrVariable.RightControllerCharging, "Unknown"); SetVariableValue(OpenVrVariable.RightControllerBattery, "0"); SetVariableValue(OpenVrVariable.AverageTrackerBattery, "0"); @@ -220,6 +229,9 @@ private enum OpenVrVariable HMDBattery, LeftControllerBattery, RightControllerBattery, - AverageTrackerBattery + AverageTrackerBattery, + HMDCharging, + LeftControllerCharging, + RightControllerCharging } } diff --git a/VRCOSC.Modules/Timer/TimerModule.cs b/VRCOSC.Modules/Timer/TimerModule.cs index c25ca26c..3ae0f123 100644 --- a/VRCOSC.Modules/Timer/TimerModule.cs +++ b/VRCOSC.Modules/Timer/TimerModule.cs @@ -20,6 +20,11 @@ protected override void CreateAttributes() CreateState(TimerState.Default, "Default", GetVariableFormat(TimerVariable.Time)); } + protected override void OnModuleStart() + { + ChangeStateTo(TimerState.Default); + } + [ModuleUpdate(ModuleUpdateMode.ChatBox)] private void onChatBoxUpdate() { diff --git a/VRCOSC.Modules/VoiceRecognition/VoiceRecognitionModule.cs b/VRCOSC.Modules/VoiceRecognition/VoiceRecognitionModule.cs index dd9dc3a3..f89aa929 100644 --- a/VRCOSC.Modules/VoiceRecognition/VoiceRecognitionModule.cs +++ b/VRCOSC.Modules/VoiceRecognition/VoiceRecognitionModule.cs @@ -53,7 +53,10 @@ protected override void OnModuleStop() [ModuleUpdate(ModuleUpdateMode.Custom, false, 5000)] private void onModuleUpdate() { - speechToTextProvider?.Update(); + if (speechToTextProvider is null) return; + + speechToTextProvider.AnalysisEnabled = enabled; + speechToTextProvider.Update(); } protected override void OnRegisteredParameterReceived(AvatarParameter avatarParameter) diff --git a/VRCOSC.Modules/Weather/WeatherModule.cs b/VRCOSC.Modules/Weather/WeatherModule.cs index 2d088dbe..c3063692 100644 --- a/VRCOSC.Modules/Weather/WeatherModule.cs +++ b/VRCOSC.Modules/Weather/WeatherModule.cs @@ -56,7 +56,7 @@ private async void updateParameters() return; } - var weather = await weatherProvider.RetrieveFor(GetSetting(WeatherSetting.Postcode)); + var weather = await weatherProvider.RetrieveFor(GetSetting(WeatherSetting.Postcode), DateTime.Now); if (weather is null) { diff --git a/VRCOSC.Modules/Weather/WeatherProvider.cs b/VRCOSC.Modules/Weather/WeatherProvider.cs index 315fac0d..00bf5fb9 100644 --- a/VRCOSC.Modules/Weather/WeatherProvider.cs +++ b/VRCOSC.Modules/Weather/WeatherProvider.cs @@ -2,6 +2,7 @@ // See the LICENSE file in the repository root for full license text. using Newtonsoft.Json; +using osu.Framework.Logging; namespace VRCOSC.Modules.Weather; @@ -13,7 +14,7 @@ public class WeatherProvider private const string condition_url = "https://www.weatherapi.com/docs/weather_conditions.json"; - private static readonly TimeSpan api_call_delta = TimeSpan.FromMinutes(20); + private static readonly TimeSpan cache_expire_time = TimeSpan.FromMinutes(20); private readonly HttpClient httpClient = new(); private readonly string apiKey; @@ -29,52 +30,105 @@ public WeatherProvider(string apiKey) this.apiKey = apiKey; } - public async Task RetrieveFor(string location) + public async Task RetrieveFor(string location, DateTime locationDateTime) { - if (lastUpdate + api_call_delta > DateTimeOffset.Now && location == lastLocation) return weather; + try + { + if (lastUpdate + cache_expire_time > DateTimeOffset.Now && location == lastLocation) return weather; + + lastUpdate = DateTimeOffset.Now; + lastLocation = location; + + var currentUrl = string.Format(current_url_format, apiKey, location); + var currentResponseData = await httpClient.GetAsync(new Uri(currentUrl)); + + if (!currentResponseData.IsSuccessStatusCode) + { + Logger.Log($"{nameof(WeatherProvider)} could not retrieve {nameof(currentResponseData)}"); + return weather; + } + + var currentResponseString = await currentResponseData.Content.ReadAsStringAsync(); + var currentResponse = JsonConvert.DeserializeObject(currentResponseString)?.Current; + + if (currentResponse is null) + { + Logger.Log($"{nameof(WeatherProvider)} could not parse {nameof(currentResponse)}"); + return weather; + } + + var astronomyUrl = string.Format(astronomy_url_format, apiKey, location, DateTime.Now.ToString("yyyy-MM-dd")); + var astronomyResponseData = await httpClient.GetAsync(new Uri(astronomyUrl)); + + if (!astronomyResponseData.IsSuccessStatusCode) + { + Logger.Log($"{nameof(WeatherProvider)} could not retrieve {nameof(astronomyResponseData)}"); + return weather; + } + + var astronomyResponseString = await astronomyResponseData.Content.ReadAsStringAsync(); + var astronomyResponse = JsonConvert.DeserializeObject(astronomyResponseString)?.Astronomy.Astro; + + if (astronomyResponse is null) + { + Logger.Log($"{nameof(WeatherProvider)} could not parse {nameof(astronomyResponse)}"); + return weather; + } + + currentResponse.ConditionString = "Unknown"; + + if (!DateTime.TryParse(astronomyResponse.Sunrise, out var sunriseParsed)) return weather; + if (!DateTime.TryParse(astronomyResponse.Sunset, out var sunsetParsed)) return weather; + + await retrieveConditions(); + + if (conditions is not null) + { + var conditionResponse = conditions[currentResponse.Condition.Code]; + currentResponse.ConditionString = locationDateTime >= sunriseParsed && locationDateTime < sunsetParsed ? conditionResponse.Day : conditionResponse.Night; + } + + weather = currentResponse; + } + catch (Exception e) + { + Logger.Error(e, $"{nameof(WeatherProvider)} experienced an issue"); + weather = null; + } - lastUpdate = DateTimeOffset.Now; - lastLocation = location; - - var currentUrl = string.Format(current_url_format, apiKey, location); - var currentResponseData = await httpClient.GetAsync(new Uri(currentUrl)); - var currentResponseString = await currentResponseData.Content.ReadAsStringAsync(); - var currentResponse = JsonConvert.DeserializeObject(currentResponseString)?.Current; - - if (currentResponse is null) return null; - - var astronomyUrl = string.Format(astronomy_url_format, apiKey, location, DateTime.Now.ToString("yyyy-MM-dd")); - var astronomyResponseData = await httpClient.GetAsync(new Uri(astronomyUrl)); - var astronomyResponseString = await astronomyResponseData.Content.ReadAsStringAsync(); - var astronomyResponse = JsonConvert.DeserializeObject(astronomyResponseString)?.Astronomy.Astro; - - if (astronomyResponse is null) return null; - if (!DateTime.TryParse(astronomyResponse.Sunrise, out var sunriseParsed)) return null; - if (!DateTime.TryParse(astronomyResponse.Sunset, out var sunsetParsed)) return null; - - if (conditions is null) await retrieveConditions(); - - var conditionResponse = conditions?[currentResponse.Condition.Code]; - var dateTimeNow = DateTime.Now; - - if (dateTimeNow >= sunriseParsed && dateTimeNow < sunsetParsed) - currentResponse.ConditionString = conditionResponse?.Day ?? string.Empty; - else - currentResponse.ConditionString = conditionResponse?.Night ?? string.Empty; - - weather = currentResponse; return weather; } private async Task retrieveConditions() { - var conditionResponseData = await httpClient.GetAsync(condition_url); - var conditionResponseString = await conditionResponseData.Content.ReadAsStringAsync(); - var conditionData = JsonConvert.DeserializeObject>(conditionResponseString); - - if (conditionData is null) return; - - conditions = new Dictionary(); - conditionData.ForEach(condition => conditions.Add(condition.Code, condition)); + try + { + if (conditions is not null) return; + + var conditionResponseData = await httpClient.GetAsync(condition_url); + + if (!conditionResponseData.IsSuccessStatusCode) + { + Logger.Log($"{nameof(WeatherProvider)} could not retrieve {nameof(conditionResponseData)}"); + return; + } + + var conditionResponseString = await conditionResponseData.Content.ReadAsStringAsync(); + var conditionResponse = JsonConvert.DeserializeObject>(conditionResponseString); + + if (conditionResponse is null) + { + Logger.Log($"{nameof(WeatherProvider)} could not parse {nameof(conditionResponse)}"); + return; + } + + conditions = new Dictionary(); + conditionResponse.ForEach(condition => conditions.Add(condition.Code, condition)); + } + catch (Exception e) + { + Logger.Error(e, $"{nameof(WeatherProvider)} experienced an issue"); + conditions = null; + } } }