Subversion Repositories currency_converter

Rev

Rev 2 | Rev 16 | Go to most recent revision | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

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