Rev 22 | Rev 25 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
2 | daniel-mar | 1 | unit VtsCurConv; |
2 | |||
3 | interface |
||
4 | |||
5 | uses |
||
18 | daniel-mar | 6 | SysUtils, Classes; |
2 | daniel-mar | 7 | |
8 | type |
||
9 | EVtsCurConvException = class(Exception); |
||
10 | |||
11 | TVtsCurApiKey = string; |
||
12 | TVtsCur = string; |
||
13 | TVtsRate = double; |
||
14 | |||
15 | TVtsCurConv = class(TObject) |
||
16 | private |
||
17 | FSecure: boolean; |
||
18 | FMaxAgeSeconds: integer; |
||
19 | FConfirmWebAccess: boolean; |
||
20 | FFallBackToCache: boolean; |
||
21 | FInteractiveAPIKeyInput: boolean; |
||
22 | protected |
||
23 | function GetJsonRaw(HistoricDate: TDate=0): string; |
||
24 | procedure QueryAPIKey(msg: string=''); virtual; |
||
25 | public |
||
26 | property Secure: boolean read FSecure write FSecure; |
||
27 | property MaxAgeSeconds: integer read FMaxAgeSeconds write FMaxAgeSeconds; |
||
28 | property ConfirmWebAccess: boolean read FConfirmWebAccess write FConfirmWebAccess; |
||
29 | property FallBackToCache: boolean read FFallBackToCache write FFallBackToCache; |
||
30 | property InteractiveAPIKeyInput: boolean read FInteractiveAPIKeyInput write FInteractiveAPIKeyInput; |
||
31 | class procedure WriteAPIKey(key: TVtsCurApiKey; UserMode: boolean=true); |
||
32 | class function ReadAPIKey: TVtsCurApiKey; |
||
33 | class function DeleteAPIKey(UserMode: boolean=true): boolean; |
||
34 | function Convert(value: Currency; fromCur, toCur: TVtsCur; HistoricDate: TDate=0): Currency; |
||
35 | function GetAcceptedCurrencies(sl: TStringList=nil; HistoricDate: TDate=0): integer; |
||
36 | end; |
||
37 | |||
38 | implementation |
||
39 | |||
40 | uses |
||
41 | Windows, Registry, uLkJSON, Dialogs, IdHTTP, DateUtils; |
||
42 | |||
43 | function FileGetContents(filename: string): string; |
||
44 | var |
||
45 | sl: TStringList; |
||
46 | begin |
||
47 | sl := TStringList.Create; |
||
48 | try |
||
49 | sl.LoadFromFile(filename); |
||
50 | result := sl.Text; |
||
51 | finally |
||
52 | sl.Free; |
||
53 | end; |
||
54 | end; |
||
55 | |||
56 | procedure FilePutContents(filename, content: string); |
||
57 | var |
||
58 | sl: TStringList; |
||
59 | begin |
||
60 | sl := TStringList.Create; |
||
61 | try |
||
62 | sl.Text := content; |
||
63 | sl.SaveToFile(filename); |
||
64 | finally |
||
65 | sl.Free; |
||
66 | end; |
||
67 | end; |
||
68 | |||
69 | function GetPage(aURL: string): string; |
||
70 | var |
||
71 | Response: TStringStream; |
||
72 | HTTP: TIdHTTP; |
||
73 | const |
||
74 | HTTP_RESPONSE_OK = 200; |
||
13 | daniel-mar | 75 | resourcestring |
76 | S_CANNOT_DOWNLOAD = 'Cannot download from %s'; |
||
2 | daniel-mar | 77 | begin |
78 | // https://stackoverflow.com/questions/9239267/how-to-download-a-web-page-into-a-variable |
||
79 | Result := ''; |
||
80 | Response := TStringStream.Create(''); |
||
81 | try |
||
82 | HTTP := TIdHTTP.Create(nil); |
||
83 | try |
||
84 | HTTP.Get(aURL, Response); |
||
85 | if HTTP.ResponseCode = HTTP_RESPONSE_OK then |
||
86 | Result := Response.DataString |
||
87 | else |
||
13 | daniel-mar | 88 | raise EVtsCurConvException.CreateFmt(S_CANNOT_DOWNLOAD, [aURL]); |
2 | daniel-mar | 89 | finally |
90 | HTTP.Free; |
||
91 | end; |
||
92 | finally |
||
93 | Response.Free; |
||
94 | end; |
||
95 | end; |
||
96 | |||
97 | function GetTempDir: string; |
||
98 | var |
||
99 | Dir: string; |
||
100 | Len: DWord; |
||
101 | begin |
||
102 | SetLength(Dir, MAX_PATH); |
||
103 | Len := GetTempPath(MAX_PATH, PChar(Dir)); |
||
104 | if Len > 0 then |
||
105 | begin |
||
106 | SetLength(Dir, Len); |
||
107 | Result := Dir; |
||
108 | end |
||
109 | else |
||
110 | RaiseLastOSError; |
||
111 | end; |
||
112 | |||
113 | { TVtsCurConv } |
||
114 | |||
115 | class procedure TVtsCurConv.WriteAPIKey(key: TVtsCurApiKey; UserMode: boolean=true); |
||
116 | procedure _WriteAPIKey(root: HKEY); |
||
117 | var |
||
118 | reg: TRegistry; |
||
13 | daniel-mar | 119 | resourcestring |
120 | S_CANNOT_OPEN_REGISTRY = 'Cannot open registry key'; |
||
2 | daniel-mar | 121 | begin |
122 | reg := TRegistry.Create; |
||
123 | try |
||
124 | reg.RootKey := root; |
||
125 | if reg.OpenKey('Software\ViaThinkSoft\CurrencyConverter', true) then |
||
126 | begin |
||
127 | reg.WriteString('APIKey', key); |
||
128 | reg.CloseKey; |
||
129 | end |
||
13 | daniel-mar | 130 | else raise EVtsCurConvException.Create(S_CANNOT_OPEN_REGISTRY); |
2 | daniel-mar | 131 | finally |
132 | reg.Free; |
||
133 | end; |
||
134 | end; |
||
135 | begin |
||
136 | if UserMode then |
||
137 | _WriteAPIKey(HKEY_CURRENT_USER) |
||
138 | else |
||
139 | _WriteAPIKey(HKEY_LOCAL_MACHINE); |
||
140 | end; |
||
141 | |||
142 | class function TVtsCurConv.DeleteAPIKey(UserMode: boolean=true): boolean; |
||
143 | procedure _DeleteAPIKey(root: HKEY); |
||
144 | var |
||
145 | reg: TRegistry; |
||
146 | begin |
||
147 | reg := TRegistry.Create; |
||
148 | try |
||
149 | reg.RootKey := root; |
||
150 | if reg.OpenKey('Software\ViaThinkSoft\CurrencyConverter', true) then |
||
151 | begin |
||
152 | result := reg.DeleteValue('APIKey'); |
||
153 | reg.CloseKey; |
||
154 | end; |
||
155 | finally |
||
156 | reg.Free; |
||
157 | end; |
||
158 | end; |
||
159 | begin |
||
160 | result := false; |
||
161 | if UserMode then |
||
162 | _DeleteAPIKey(HKEY_CURRENT_USER) |
||
163 | else |
||
164 | _DeleteAPIKey(HKEY_LOCAL_MACHINE); |
||
165 | end; |
||
166 | |||
167 | class function TVtsCurConv.ReadAPIKey: TVtsCurApiKey; |
||
168 | function _ReadAPIKey(root: HKEY): string; |
||
169 | var |
||
170 | reg: TRegistry; |
||
171 | begin |
||
172 | result := ''; |
||
173 | reg := TRegistry.Create; |
||
174 | try |
||
175 | reg.RootKey := root; |
||
176 | if reg.OpenKeyReadOnly('Software\ViaThinkSoft\CurrencyConverter') then |
||
177 | begin |
||
178 | if reg.ValueExists('APIKey') then result := reg.ReadString('APIKey'); |
||
179 | reg.CloseKey; |
||
180 | end; |
||
181 | finally |
||
182 | reg.Free; |
||
183 | end; |
||
184 | end; |
||
185 | begin |
||
186 | result := _ReadAPIKey(HKEY_CURRENT_USER); |
||
187 | if result = '' then result := _ReadAPIKey(HKEY_LOCAL_MACHINE); |
||
188 | end; |
||
189 | |||
190 | function TVtsCurConv.Convert(value: Currency; fromCur, toCur: TVtsCur; HistoricDate: TDate=0): Currency; |
||
191 | var |
||
192 | rateTo, rateFrom: TVtsRate; |
||
193 | i: Integer; |
||
194 | rateToFound: Boolean; |
||
195 | rateFromFound: Boolean; |
||
196 | sJSON: String; |
||
197 | xSource: TlkJSONstring; |
||
198 | xRoot: TlkJSONobject; |
||
199 | xQuotes: TlkJSONobject; |
||
200 | xRate: TlkJSONnumber; |
||
13 | daniel-mar | 201 | resourcestring |
202 | S_JSON_ENTRY_MISSING = 'JSON entry "%s" is missing!'; |
||
203 | S_WRONG_QUOTE_LEN = 'Length of quotes-entry is unexpected!'; |
||
204 | S_JSON_RATE_MISSING = 'JSON entry quotes->rate is missing!'; |
||
205 | S_CURRENCY_NOT_SUPPORTED = 'Currency "%s" not supported'; |
||
2 | daniel-mar | 206 | begin |
207 | result := 0; // to avoid that the compiler shows a warning |
||
208 | |||
209 | fromCur := Trim(UpperCase(fromCur)); |
||
210 | toCur := Trim(UpperCase(toCur)); |
||
16 | daniel-mar | 211 | |
212 | if fromCur = toCur then |
||
213 | begin |
||
214 | result := value; |
||
215 | exit; |
||
216 | end; |
||
2 | daniel-mar | 217 | |
218 | sJSON := GetJsonRaw(HistoricDate); |
||
219 | |||
220 | xRoot := TlkJSON.ParseText(sJSON) as TlkJSONobject; |
||
221 | try |
||
222 | xSource := xRoot.Field['source'] as TlkJSONstring; |
||
13 | daniel-mar | 223 | if not assigned(xSource) then raise EVtsCurConvException.CreateFmt(S_JSON_ENTRY_MISSING, ['source']); |
2 | daniel-mar | 224 | |
225 | xQuotes := xRoot.Field['quotes'] as TlkJSONobject; |
||
13 | daniel-mar | 226 | if not assigned(xQuotes) then raise EVtsCurConvException.CreateFmt(S_JSON_ENTRY_MISSING, ['quotes']); |
2 | daniel-mar | 227 | |
228 | rateToFound := false; |
||
229 | rateFromFound := false; |
||
230 | rateTo := 0.00; // to avoid that the compiler shows a warning |
||
231 | rateFrom := 0.00; // to avoid that the compiler shows a warning |
||
232 | |||
233 | for i := 0 to xQuotes.Count - 1 do |
||
234 | begin |
||
13 | daniel-mar | 235 | if Length(xQuotes.NameOf[i]) <> 6 then raise EVtsCurConvException.Create(S_WRONG_QUOTE_LEN); |
2 | daniel-mar | 236 | |
237 | xRate := xQuotes.Field[xQuotes.NameOf[i]] as TlkJSONnumber; |
||
13 | daniel-mar | 238 | if not Assigned(xRate) then raise EVtsCurConvException.Create(S_JSON_RATE_MISSING); |
2 | daniel-mar | 239 | |
240 | if Copy(xQuotes.NameOf[i], 1, 3) = xSource.Value then |
||
241 | begin |
||
242 | if Copy(xQuotes.NameOf[i], 4, 3) = toCur then |
||
243 | begin |
||
244 | rateTo := xRate.Value; |
||
245 | rateToFound := true; |
||
246 | end; |
||
247 | if Copy(xQuotes.NameOf[i], 4, 3) = fromCur then |
||
248 | begin |
||
249 | rateFrom := xRate.Value; |
||
250 | rateFromFound := true; |
||
251 | end; |
||
252 | end; |
||
253 | end; |
||
254 | |||
13 | daniel-mar | 255 | if not rateToFound then raise EVtsCurConvException.CreateFmt(S_CURRENCY_NOT_SUPPORTED, [toCur]); |
256 | if not rateFromFound then raise EVtsCurConvException.CreateFmt(S_CURRENCY_NOT_SUPPORTED, [fromCur]); |
||
2 | daniel-mar | 257 | |
258 | result := value * rateTo / rateFrom; |
||
259 | finally |
||
260 | xRoot.Free; |
||
261 | end; |
||
262 | end; |
||
263 | |||
264 | procedure TVtsCurConv.QueryAPIKey(msg: string=''); |
||
265 | var |
||
266 | s: string; |
||
13 | daniel-mar | 267 | resourcestring |
268 | S_CURRENCYLAYER = 'currencylayer.com'; |
||
269 | S_ENTER_KEY = 'Please enter your API key:'; |
||
270 | S_NO_API_KEY = 'No API key provided.'; |
||
2 | daniel-mar | 271 | begin |
13 | daniel-mar | 272 | s := Trim(InputBox(S_CURRENCYLAYER, Trim(msg + ' ' + S_ENTER_KEY), '')); |
273 | if s = '' then raise EVtsCurConvException.Create(S_NO_API_KEY); |
||
2 | daniel-mar | 274 | WriteAPIKey(s); |
275 | end; |
||
276 | |||
277 | function TVtsCurConv.GetAcceptedCurrencies(sl: TStringList=nil; HistoricDate: TDate=0): integer; |
||
278 | var |
||
279 | i: Integer; |
||
280 | sJSON: String; |
||
281 | xSource: TlkJSONstring; |
||
282 | xRoot: TlkJSONobject; |
||
283 | xQuotes: TlkJSONobject; |
||
13 | daniel-mar | 284 | resourcestring |
285 | S_JSON_ENTRY_MISSING = 'JSON entry "%s" is missing!'; |
||
286 | S_WRONG_QUOTE_LEN = 'Length of quotes-entry is unexpected!'; |
||
2 | daniel-mar | 287 | begin |
288 | result := 0; |
||
289 | |||
290 | sJSON := GetJsonRaw(HistoricDate); |
||
291 | |||
292 | xRoot := TlkJSON.ParseText(sJSON) as TlkJSONobject; |
||
293 | try |
||
294 | xSource := xRoot.Field['source'] as TlkJSONstring; |
||
13 | daniel-mar | 295 | if not assigned(xSource) then raise EVtsCurConvException.CreateFmt(S_JSON_ENTRY_MISSING, ['source']); |
2 | daniel-mar | 296 | |
297 | xQuotes := xRoot.Field['quotes'] as TlkJSONobject; |
||
13 | daniel-mar | 298 | if not assigned(xQuotes) then raise EVtsCurConvException.CreateFmt(S_JSON_ENTRY_MISSING, ['quotes']); |
2 | daniel-mar | 299 | |
300 | for i := 0 to xQuotes.Count - 1 do |
||
301 | begin |
||
13 | daniel-mar | 302 | if Length(xQuotes.NameOf[i]) <> 6 then raise EVtsCurConvException.Create(S_WRONG_QUOTE_LEN); |
2 | daniel-mar | 303 | |
304 | if Copy(xQuotes.NameOf[i], 1, 3) = xSource.Value then |
||
305 | begin |
||
306 | Inc(result); |
||
307 | if Assigned(sl) then sl.Add(Copy(xQuotes.NameOf[i], 4, 3)); |
||
308 | end; |
||
309 | end; |
||
310 | finally |
||
311 | xRoot.Free; |
||
312 | end; |
||
313 | end; |
||
314 | |||
315 | function TVtsCurConv.GetJsonRaw(HistoricDate: TDate=0): string; |
||
316 | |||
317 | procedure _HandleKeyInvalidOrMissing(cacheFileName: string; msg: string; out doRetry: boolean; out json: string); |
||
13 | daniel-mar | 318 | resourcestring |
319 | S_ENTER_NEW_KEY = 'Do you want to enter a new one?'; |
||
17 | daniel-mar | 320 | S_NO_CACHE_EXISTING = 'Cannot get the data online, and no cache exists. Please check your internet connection and API key.'; |
2 | daniel-mar | 321 | begin |
322 | if FallBackToCache then |
||
323 | begin |
||
324 | if not InteractiveAPIKeyInput then |
||
325 | begin |
||
17 | daniel-mar | 326 | if not FileExists(cacheFileName) then |
327 | begin |
||
328 | raise Exception.Create(S_NO_CACHE_EXISTING); |
||
329 | end; |
||
2 | daniel-mar | 330 | json := FileGetContents(cacheFileName); |
331 | doRetry := false; |
||
332 | end |
||
333 | else |
||
334 | begin |
||
13 | daniel-mar | 335 | if MessageDlg(Trim(msg + ' ' + S_ENTER_NEW_KEY), mtError, mbYesNoCancel, 0) = ID_YES then |
2 | daniel-mar | 336 | begin |
337 | QueryAPIKey; |
||
338 | doRetry := true; |
||
339 | end |
||
340 | else |
||
341 | begin |
||
17 | daniel-mar | 342 | if not FileExists(cacheFileName) then |
343 | begin |
||
344 | raise Exception.Create(S_NO_CACHE_EXISTING); |
||
345 | end; |
||
2 | daniel-mar | 346 | json := FileGetContents(cacheFileName); |
347 | doRetry := false; |
||
348 | end; |
||
349 | end; |
||
350 | end |
||
351 | else // if not FallBackToCache then |
||
352 | begin |
||
353 | if not InteractiveAPIKeyInput then |
||
354 | begin |
||
355 | raise EVtsCurConvException.Create(msg); |
||
356 | end |
||
357 | else |
||
358 | begin |
||
359 | QueryAPIKey(msg); |
||
360 | doRetry := true; |
||
361 | end; |
||
362 | end; |
||
363 | end; |
||
364 | |||
17 | daniel-mar | 365 | function protocol: string; |
366 | begin |
||
367 | if Secure then result := 'https' else result := 'http'; |
||
368 | end; |
||
369 | |||
370 | function url: string; |
||
371 | var |
||
372 | sDate: string; |
||
373 | begin |
||
374 | if HistoricDate = 0 then |
||
375 | begin |
||
376 | sDate := ''; |
||
377 | result := protocol + '://www.apilayer.net/api/live?access_key=' + ReadAPIKey; |
||
378 | end |
||
379 | else |
||
380 | begin |
||
381 | DateTimeToString(sDate, 'YYYY-MM-DD', HistoricDate); |
||
382 | result := protocol + '://www.apilayer.net/api/historical?date=' + sDate + '&access_key=' + ReadAPIKey; |
||
383 | end; |
||
384 | end; |
||
385 | |||
386 | function cacheFileName: string; |
||
387 | resourcestring |
||
388 | S_CANNOT_CREATE_DIR = 'Cannot create directory %s'; |
||
389 | var |
||
390 | sDate: string; |
||
391 | cacheDirName: string; |
||
392 | begin |
||
393 | // cacheDirName := IncludeTrailingPathDelimiter(GetSpecialPath(CSIDL_PROGRAM_FILES_COMMON)) + 'ViaThinkSoft\CurrencyConverter\'; |
||
394 | cacheDirName := IncludeTrailingPathDelimiter(GetTempDir) + 'ViaThinkSoft\CurrencyConverter\'; |
||
395 | if not ForceDirectories(cacheDirName) then |
||
396 | begin |
||
397 | raise EVtsCurConvException.CreateFmt(S_CANNOT_CREATE_DIR, [cacheDirName]); |
||
398 | end; |
||
399 | |||
400 | if HistoricDate = 0 then |
||
401 | begin |
||
402 | sDate := ''; |
||
403 | result := IncludeTrailingPathDelimiter(cacheDirName) + 'live.json'; |
||
404 | end |
||
405 | else |
||
406 | begin |
||
407 | DateTimeToString(sDate, 'YYYY-MM-DD', HistoricDate); |
||
408 | result := IncludeTrailingPathDelimiter(cacheDirName) + 'historical-' + sDate + '.json'; |
||
409 | end; |
||
410 | end; |
||
411 | |||
2 | daniel-mar | 412 | var |
17 | daniel-mar | 413 | sJSON, msg: string; |
2 | daniel-mar | 414 | xRoot: TlkJSONobject; |
415 | xSuccess: TlkJSONboolean; |
||
416 | keyInvalid, doRetry: boolean; |
||
417 | needDownload: boolean; |
||
418 | mTime: TDateTime; |
||
13 | daniel-mar | 419 | resourcestring |
420 | S_INVALID_MAXAGE = 'Invalid maxage'; |
||
421 | S_NO_API_KEY_PROVIDED = 'No API key provided.'; |
||
422 | S_DOWNLOAD_QUERY = 'Download %s to %s ?'; |
||
423 | S_JSON_FILE_INVALID = 'JSON file invalid'; |
||
424 | S_UNKNOWN_SUCCESS = 'Cannot determinate status of the query.'; |
||
425 | S_JSON_UNKNOWN_ERROR = 'Unknown error while loading JSON.'; |
||
426 | S_API_KEY_INVALID = 'API key invalid.'; |
||
2 | daniel-mar | 427 | begin |
19 | daniel-mar | 428 | {$REGION 'Determinate if we need to download or not'} |
429 | if HistoricDate = 0 then |
||
430 | begin |
||
431 | needDownload := true; |
||
432 | if MaxAgeSeconds < -1 then |
||
2 | daniel-mar | 433 | begin |
19 | daniel-mar | 434 | raise EVtsCurConvException.Create(S_INVALID_MAXAGE); |
435 | end |
||
436 | else if MaxAgeSeconds = -1 then |
||
437 | begin |
||
438 | // Only download once |
||
439 | needDownload := not FileExists(cacheFileName); |
||
440 | end |
||
441 | else if MaxAgeSeconds = 0 then |
||
442 | begin |
||
443 | // Always download |
||
2 | daniel-mar | 444 | needDownload := true; |
445 | end |
||
19 | daniel-mar | 446 | else if MaxAgeSeconds > 0 then |
2 | daniel-mar | 447 | begin |
19 | daniel-mar | 448 | // Download if older than <MaxAge> seconds |
449 | FileAge(cacheFileName, mTime); |
||
450 | needDownload := not FileExists(cacheFileName) or (SecondsBetween(Now, mTime) > MaxAgeSeconds); |
||
2 | daniel-mar | 451 | end; |
19 | daniel-mar | 452 | end |
453 | else |
||
454 | begin |
||
455 | needDownload := not FileExists(cacheFileName) |
||
456 | end; |
||
457 | {$ENDREGION} |
||
2 | daniel-mar | 458 | |
19 | daniel-mar | 459 | if not needDownload and FileExists(cacheFileName) then |
460 | begin |
||
461 | sJSON := FileGetContents(cacheFileName); |
||
462 | end |
||
463 | else |
||
464 | begin |
||
465 | doRetry := false; |
||
466 | |||
467 | {$REGION 'Is an API key available?'} |
||
468 | if ReadAPIKey = '' then |
||
2 | daniel-mar | 469 | begin |
19 | daniel-mar | 470 | _HandleKeyInvalidOrMissing(cacheFileName, S_NO_API_KEY_PROVIDED, doRetry, sJSON); |
471 | if not doRetry then |
||
472 | begin |
||
473 | result := sJSON; |
||
474 | Exit; |
||
475 | end; |
||
476 | end; |
||
477 | {$ENDREGION} |
||
2 | daniel-mar | 478 | |
19 | daniel-mar | 479 | {$REGION 'Download and check if everything is OK'} |
480 | repeat |
||
481 | {$REGION 'Confirm web access?'} |
||
482 | if ConfirmWebAccess and (MessageDlg(Format(S_DOWNLOAD_QUERY, [url, cacheFileName]), mtConfirmation, mbYesNoCancel, 0) <> ID_YES) then |
||
2 | daniel-mar | 483 | begin |
19 | daniel-mar | 484 | if FallBackToCache and FileExists(cacheFileName) then |
2 | daniel-mar | 485 | begin |
19 | daniel-mar | 486 | result := FileGetContents(cacheFileName); |
2 | daniel-mar | 487 | Exit; |
19 | daniel-mar | 488 | end |
489 | else Abort; |
||
2 | daniel-mar | 490 | end; |
491 | {$ENDREGION} |
||
492 | |||
19 | daniel-mar | 493 | doRetry := false; |
2 | daniel-mar | 494 | |
19 | daniel-mar | 495 | sJSON := GetPage(url); |
2 | daniel-mar | 496 | |
19 | daniel-mar | 497 | xRoot := TlkJSON.ParseText(sJSON) as TlkJSONobject; |
498 | if not assigned(xRoot) then raise EVtsCurConvException.Create(S_JSON_FILE_INVALID); |
||
499 | try |
||
2 | daniel-mar | 500 | xSuccess := xRoot.Field['success'] as TlkJSONboolean; |
13 | daniel-mar | 501 | if not assigned(xSuccess) then raise EVtsCurConvException.Create(S_UNKNOWN_SUCCESS); |
2 | daniel-mar | 502 | |
503 | if xSuccess.Value then |
||
504 | begin |
||
505 | try |
||
506 | FilePutContents(cacheFileName, sJSON); |
||
507 | except |
||
508 | // Since this is only a cache, we should not break the whole process if only the saving fails |
||
509 | end; |
||
510 | end |
||
511 | else |
||
512 | begin |
||
513 | {$REGION 'Get information of the error'} |
||
514 | try |
||
515 | keyInvalid := xRoot.Field['error'].Field['code'].Value = 101; |
||
516 | msg := Format('%s (%s, %s)', [ |
||
517 | xRoot.Field['error'].Field['info'].Value, |
||
518 | xRoot.Field['error'].Field['code'].Value, |
||
519 | xRoot.Field['error'].Field['type'].Value]); |
||
520 | except |
||
521 | keyInvalid := false; |
||
13 | daniel-mar | 522 | msg := S_JSON_UNKNOWN_ERROR; |
2 | daniel-mar | 523 | end; |
524 | {$ENDREGION} |
||
525 | |||
526 | if keyInvalid then |
||
527 | begin |
||
13 | daniel-mar | 528 | _HandleKeyInvalidOrMissing(cacheFileName, S_API_KEY_INVALID, doRetry, sJSON); |
2 | daniel-mar | 529 | end |
530 | else // if not keyInvalid then |
||
531 | begin |
||
17 | daniel-mar | 532 | if FallBackToCache and FileExists(cacheFileName) then |
2 | daniel-mar | 533 | begin |
534 | result := FileGetContents(cacheFileName); |
||
535 | Exit; |
||
536 | end |
||
537 | else |
||
538 | begin |
||
539 | raise EVtsCurConvException.Create(msg); |
||
540 | end; |
||
541 | end; |
||
542 | end; |
||
19 | daniel-mar | 543 | finally |
544 | FreeAndNil(xRoot); |
||
545 | end; |
||
546 | until not doRetry; |
||
547 | {$ENDREGION} |
||
548 | end; |
||
2 | daniel-mar | 549 | |
19 | daniel-mar | 550 | result := sJSON; |
22 | daniel-mar | 551 | |
552 | // "USDUSD" missing 06 Aug 2022. A bug?? Reported https://github.com/apilayer/currencylayer-API/issues/16 |
||
553 | if Pos('"USDUSD"', result) = 0 then |
||
23 | daniel-mar | 554 | result := StringReplace(result, '"quotes":{', '"quotes":{"USDUSD":1,', []); |
2 | daniel-mar | 555 | end; |
556 | |||
557 | end. |