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