11using System ;
2+ using System . Collections . Generic ;
23using System . Diagnostics ;
34using System . IO ;
5+ using System . Linq ;
46using System . Net . Http ;
57using System . Net . Http . Json ;
68using System . Text . Json ;
@@ -25,8 +27,11 @@ public class GoodWeClient
2527 private const string LoginEndpoint = "/api/v1/Common/CrossLogin" ;
2628 private const string DefaultToken = @"{""version"":""v2.0.4"",""client"":""ios"",""language"":""en""}" ;
2729
30+ private const string DateFormatSettingsEndpoint = "/api/v2/Common/GetDateFormatSettingList" ;
31+
2832 private string _token = DefaultToken ;
2933 private readonly JsonSerializerOptions _responseLogJsonFormat = new ( ) { WriteIndented = true } ;
34+ private JsonSerializerOptions ? _serializerOptions ;
3035
3136 // TODO: HttpClientFactory injection, and do we need to dispose/recreate on .NET 6 or not (DNS changes)?
3237 private readonly HttpClient _client ;
@@ -68,7 +73,7 @@ public async Task<GoodWeApiResponse<ReportData>> GetBatchAsync(string plantId, D
6873
6974 _logger . LogDebug ( "Getting statistics data with parameters {request}" , JsonSerializer . SerializeToDocument ( request ) . RootElement . ToString ( ) ) ;
7075
71- var response = await TryRequest < ReportData > ( ( ) => _client . PostAsJsonAsync ( endpoint , request , cancellationToken ) , cancellationToken ) ;
76+ var response = await TryRequest < ReportData > ( ( ) => _client . PostAsJsonAsync ( endpoint , request , _serializerOptions , cancellationToken ) , cancellationToken ) ;
7277
7378 await WriteJson ( "ReportData" , response ) ;
7479
@@ -166,14 +171,14 @@ public async Task<GoodWeApiResponse<InverterData>> GetPlantListAsync(Cancellatio
166171 try
167172 {
168173 var response = await call ( ) ;
169- var localResponseObject = await response . Content . ReadFromJsonAsync < ResponseBase < TResponse ? > ? > ( cancellationToken : cancellationToken ) ;
174+ var localResponseObject = await response . Content . ReadFromJsonAsync < ResponseBase < TResponse ? > ? > ( _serializerOptions , cancellationToken : cancellationToken ) ;
170175 return localResponseObject ;
171176 }
172177 catch ( OperationCanceledException ) when ( cancellationToken . IsCancellationRequested )
173178 {
174179 // Cancellation is requested, fail fast
175180 _logger . LogDebug ( "Cancellation requested, cancelling request" ) ;
176-
181+
177182 throw ;
178183 }
179184 catch ( Exception ex )
@@ -193,41 +198,42 @@ public async Task<GoodWeApiResponse<InverterData>> GetPlantListAsync(Cancellatio
193198
194199 // 100001: "No access, please login."
195200 // 100002: "The authorization has expired, please login again."
196- if ( responseObject ? . Code is 100001 or 100002 )
201+ if ( responseObject ? . Code is " 100001" or " 100002" )
197202 {
198- switch ( responseObject . Code )
203+ if ( responseObject . Code == "100001" )
204+ {
205+ _logger . LogDebug ( "Unauthorized, logging in" ) ;
206+ }
207+ else if ( responseObject . Code == "100002" )
199208 {
200- case 100001 :
201- _logger . LogDebug ( "Unauthorized, logging in" ) ;
202- break ;
203- case 100002 :
204- _logger . LogDebug ( "Token expired, logging in again" ) ;
205- break ;
209+ _logger . LogDebug ( "Token expired, logging in again" ) ;
206210 }
207211
208- if ( ! await Login ( ) )
212+ if ( ! await LoginAsync ( ) )
209213 {
210214 _logger . LogWarning ( "Login failed." ) ;
211215 return default ;
212216 }
213217
218+ await ReadUserDateFormatAsync ( ) ;
219+
214220 // Try again after logging in, letting it fail if that fails.
215221 responseObject = await GetResponse ( ) ;
216222 }
217223
218- if ( responseObject ? . Code != 0 )
224+ if ( responseObject ? . Code != "0" )
219225 {
220226 var jsonString = responseObject != null ? JsonSerializer . Serialize ( responseObject ) : "null" ;
221-
227+
222228 _logger . LogWarning ( "Unexpected response: {jsonString}" , jsonString ) ;
223-
229+
224230 return default ;
225231 }
226232
227233 return responseObject . Data ;
228234 }
229235
230- private async Task < bool > Login ( )
236+ private async Task < bool > LoginAsync ( )
231237 {
232238 // Take first and last character...
233239 var logSafeAccount = _accountConfiguration . Account ! [ ..1 ] ;
@@ -242,17 +248,17 @@ private async Task<bool> Login()
242248
243249 //"code": 100005
244250 //"msg": "Email or password error."
245- if ( responseObject ? . Code == 100005 )
251+ if ( responseObject ? . Code == " 100005" )
246252 {
247253 // TODO: handle login errors
248254 _logger . LogError ( "Login failed: {code}: {msg}" , responseObject . Code , responseObject . Msg ) ;
249-
255+
250256 return false ;
251257 }
252- if ( responseObject ? . Code != 0 )
258+ if ( responseObject ? . Code != "0" )
253259 {
254260 _logger . LogError ( "Unexpected response: {response}" , responseObject != null ? JsonSerializer . Serialize ( responseObject ) : "null" ) ;
255-
261+
256262 return false ;
257263 }
258264
@@ -269,23 +275,72 @@ private async Task<bool> Login()
269275 //_apiRoot = responseObject.Components.MsgSocketAdr ?? DefaultApiRoot;
270276 _client . DefaultRequestHeaders . Remove ( "Token" ) ;
271277 _client . DefaultRequestHeaders . Add ( "Token" , _token ) ;
272-
278+
273279 var logSafeToken = _token . Replace ( token . token , "***" )
274280 . Replace ( token . uid , "***" ) ;
275281
276282 _logger . LogDebug ( "Logged in: {logSafeToken}" , logSafeToken ) ;
277-
283+
278284 return true ;
279285 }
280286
287+ /// <summary>
288+ /// API output date format depends on user UI settings.
289+ /// </summary>
290+ private async Task ReadUserDateFormatAsync ( )
291+ {
292+ _logger . LogDebug ( "Reading date settings" ) ;
293+
294+ string endpoint = _apiRoot + DateFormatSettingsEndpoint ;
295+
296+ var dateSettingsResponse = await _client . PostAsync ( endpoint , null ) ;
297+ var responseObject = await dateSettingsResponse . Content . ReadFromJsonAsync < ResponseBase < DateFormatSettingsList > > ( ) ;
298+
299+ var selected = responseObject . Data . DateFormats . FirstOrDefault ( f => f . isselected ) ;
300+
301+ _logger . LogDebug ( "Translating date format {selectedDateFormat}" , selected . date_text ) ;
302+
303+ var format = _dateFormats [ selected . date_text ] ;
304+
305+ _serializerOptions = new JsonSerializerOptions
306+ {
307+ PropertyNameCaseInsensitive = true ,
308+ Converters =
309+ {
310+ new DateTimeConverter ( format ) ,
311+ new NullableDateTimeConverter ( format ) ,
312+ }
313+ } ;
314+
315+ _logger . LogDebug ( "Dates will be deserialized using format {format}" , format ) ;
316+ }
317+
318+ /// <summary>
319+ /// Map user settings to .NET formats.
320+ /// </summary>
321+ private readonly Dictionary < string , string > _dateFormats = new ( )
322+ {
323+ { "dateYmd1" , "yyyy'/'MM'/'dd" } ,
324+ { "dateYMD" , "yyyy'.'MM'.'dd" } ,
325+ { "dateYmd2" , "yy'/'M'/'d" } ,
326+ { "dateDmy1" , "dd'/'MM'/'yyyy" } ,
327+ { "dateDMY" , "dd'.'MM'.'yyyy" } ,
328+ { "dateMdy1" , "MM'/'dd'/'yyyy" } ,
329+ { "dateMDY" , "MM'.'dd'.'yyyy" } ,
330+ { "dateYDM" , "yyyy'/'dd'/'MM" } ,
331+ { "dateDmy2" , "d'/'M'/'yy" } ,
332+ { "dateMdy2" , "M'/'d'/'yy" } ,
333+ { "dateYdm1" , "yy'/'d'/'M" }
334+ } ;
335+
281336 // TODO: IHttpClientFactory injection
282337 private HttpClient CreateClient ( )
283338 {
284339 var client = new HttpClient ( ) ;
285340
286341 // Make sure you change this to something valid, when you do.
287342 client . DefaultRequestHeaders . Add ( "User-Agent" , "PVMaster/2.0.4 (iPhone; iOS 11.4.1; Scale/2.00)" ) ;
288-
343+
289344 client . DefaultRequestHeaders . Add ( "Token" , _token ) ;
290345
291346 return client ;
0 commit comments