11namespace Loupedeck . WeatherPlugin . Commands
22{
33 using System ;
4+ using System . Collections . Concurrent ;
45 using System . Collections . Generic ;
6+ using System . Data ;
7+ using System . IO ;
58 using System . Linq ;
69 using System . Text ;
710 using System . Threading ;
811 using System . Timers ;
912
1013 using Timer = System . Timers . Timer ;
1114
12- public class WeatherCommand : PluginDynamicCommand
15+ public sealed class WeatherCommand : PluginDynamicCommand
1316 {
14- private readonly Services . WeatherApiService weatherApiService = new Services . WeatherApiService ( ) ;
17+ private CancellationToken _instanceCancellation = new CancellationToken ( ) ;
18+
19+ private readonly Services . WeatherApiService _weatherApiService ;
1520 private readonly static WeatherPlugin Parent = WeatherPlugin . Instance ;
16- private System . Timers . Timer Timer = new Timer ( TimeSpan . FromMinutes ( 5 ) . TotalMilliseconds ) { Enabled = false , AutoReset = true } ;
21+ private readonly Timer Timer = new Timer ( TimeSpan . FromMinutes ( 15 ) . TotalMilliseconds ) { Enabled = false , AutoReset = true } ;
22+
23+ static internal ConcurrentDictionary < string , Data > DataCache = new ConcurrentDictionary < string , Data > ( ) ;
1724
18- internal IDictionary < string , Data > DataCache = new Dictionary < string , Data > ( ) ;
1925
2026 internal class Data
2127 {
@@ -30,9 +36,11 @@ internal class Data
3036
3137 public WeatherCommand ( ) : base ( "Locations" , "Weather Locations" , "Weather Locations" )
3238 {
33- this . MakeProfileAction ( "text;Use Zip, City Name, Postal Code, or even IP address to help get location. The input must be 'Location :API Key[:HideName]' Example: Paris :6dc3123b6e844f9e9580b205552a8c" ) ;
39+ this . MakeProfileAction ( "text;Use zip then 2-letter ISO Country Code. \n \t The input must look like 'Zip,ISOCountryCode :API Key[:HideName]'\n \t Example: 14220,US :6dc3123b6e844f9e9580b205552a8c" ) ;
3440 this . Timer . Elapsed += this . OnTimerElapse ;
3541 this . Timer . Enabled = true ;
42+
43+ this . _weatherApiService = new Services . WeatherApiService ( ) ;
3644 }
3745
3846 protected override Boolean OnLoad ( )
@@ -42,29 +50,38 @@ protected override Boolean OnLoad()
4250
4351 private void OnTimerElapse ( object sender , ElapsedEventArgs e )
4452 {
45- foreach ( var ap in this . DataCache . Keys )
53+ foreach ( var ap in DataCache . Keys )
4654 {
47- this . RetrieveData ( ap ) ;
55+ this . RetrieveData ( ap , this . _instanceCancellation ) ;
4856 this . ActionImageChanged ( ap ) ;
57+ DataCache . TryRemove ( ap , out var _ ) ;
4958 }
59+
5060 this . Timer . AutoReset = true ;
5161 this . Timer . Enabled = true ;
62+ this . _instanceCancellation = new CancellationToken ( false ) ;
5263 }
5364
5465 protected override BitmapImage GetCommandImage ( String actionParameter , PluginImageSize imageSize )
5566 {
5667 if ( string . IsNullOrEmpty ( actionParameter ) )
68+ {
5769 return null ;
70+ }
5871
5972 Data data = this . GetData ( actionParameter ) ;
6073 if ( ! data . IsValid )
74+ {
6175 return null ;
76+ }
6277
6378 var iconBuilder = new BitmapBuilder ( imageSize ) ;
6479 iconBuilder . Clear ( BitmapColor . Black ) ;
6580
6681 if ( data . Icon != null )
82+ {
6783 iconBuilder . DrawImage ( data . Icon ) ;
84+ }
6885
6986 iconBuilder . FillRectangle ( 0 , 0 , iconBuilder . Width , iconBuilder . Height , new BitmapColor ( 0 , 0 , 0 , 128 ) ) ;
7087
@@ -74,8 +91,16 @@ protected override BitmapImage GetCommandImage(String actionParameter, PluginIma
7491 locationName = $ "{ data . Name } \n \u00a0 \n ";
7592 }
7693
77- iconBuilder . DrawText ( $ "{ locationName } { Math . Round ( data . Temperature . C ) } °C/{ Math . Round ( data . Temperature . F ) } °F", color : BitmapColor . Black , fontSize : 17 ) ;
78- iconBuilder . DrawText ( $ "{ locationName } { Math . Round ( data . Temperature . C ) } °C/{ Math . Round ( data . Temperature . F ) } °F", color : BitmapColor . White ) ;
94+ if ( data . Temperature . C != data . Temperature . F )
95+ {
96+ iconBuilder . DrawText ( $ "{ locationName } { Math . Round ( data . Temperature . C ) } °C/{ Math . Round ( data . Temperature . F ) } °F", color : BitmapColor . Black , fontSize : 17 ) ;
97+ iconBuilder . DrawText ( $ "{ locationName } { Math . Round ( data . Temperature . C ) } °C/{ Math . Round ( data . Temperature . F ) } °F", color : BitmapColor . White ) ;
98+ }
99+ else
100+ {
101+ iconBuilder . DrawText ( $ "PARAMETER\n ERROR", color : BitmapColor . Black , fontSize : 17 ) ;
102+ iconBuilder . DrawText ( $ "PARAMETER\n ERROR", color : BitmapColor . White ) ;
103+ }
79104
80105 var renderedImage = iconBuilder . ToImage ( ) ;
81106 iconBuilder . Dispose ( ) ;
@@ -85,15 +110,19 @@ protected override BitmapImage GetCommandImage(String actionParameter, PluginIma
85110
86111 protected override void RunCommand ( String actionParameter ) => System . Diagnostics . Process . Start ( $ "weather:{ actionParameter . Split ( ':' ) [ 0 ] } ") ;
87112
88- private async void RetrieveData ( string actionParameter )
113+ private async void RetrieveData ( string actionParameter , CancellationToken cancellationToken )
89114 {
90115 if ( string . IsNullOrEmpty ( actionParameter ) )
116+ {
91117 return ;
118+ }
92119
93120 Data data = this . GetData ( actionParameter ) ;
94121
95122 if ( data . IsLoading )
123+ {
96124 return ;
125+ }
97126
98127 data . IsLoading = true ;
99128
@@ -108,22 +137,19 @@ private async void RetrieveData(string actionParameter)
108137 data . HideName = bool . Parse ( paramArgs [ 2 ] ?? "false" ) ;
109138 }
110139
111- Models . Weather weather = null ;
112- try
113- {
114- weather = await this . weatherApiService . GetCurrentWeather ( locationQuery , apiKey ) ;
115- }
116- catch ( Exception ex )
140+ var weather = await this . _weatherApiService . GetCurrentWeather ( locationQuery , apiKey , cancellationToken ) ;
141+ if ( weather == null )
117142 {
143+ // Data is from an older version, clearing.
144+ data . Name = "ERR" ;
145+ data . Temperature = ( C : 0 , F : 0 ) ;
118146 return ;
119147 }
120148
121- data . Name = weather . location . name ;
122- data . Temperature = ( C : weather . current . temp_c , F : weather . current . temp_f ) ;
123-
124- var iconBytes = await this . weatherApiService . GetIconBytes ( weather ) ;
125- data . Icon = BitmapImage . FromBase64String ( Convert . ToBase64String ( iconBytes ) ) ;
149+ data . Name = weather . name ;
150+ data . Temperature = ( C : weather . main . celsius , F : weather . main . fahrenheit ) ;
126151
152+ data . Icon = BitmapImage . FromBase64String ( Convert . ToBase64String ( weather . weather [ 0 ] . iconBytes ) ) ;
127153 }
128154 finally
129155 {
@@ -135,13 +161,15 @@ private async void RetrieveData(string actionParameter)
135161
136162 private Data GetData ( string actionParameter )
137163 {
138- if ( this . DataCache . TryGetValue ( actionParameter , out var data ) )
164+ if ( DataCache . TryGetValue ( actionParameter , out var data ) )
165+ {
139166 return data ;
167+ }
140168
141169 data = new Data ( ) ;
142- this . DataCache . Add ( actionParameter , data ) ;
143-
144- this . RetrieveData ( actionParameter ) ;
170+ DataCache . TryAdd ( actionParameter , data ) ;
171+
172+ this . RetrieveData ( actionParameter , this . _instanceCancellation ) ;
145173
146174 return data ;
147175 }
0 commit comments