Changeset View
Changeset View
Standalone View
Standalone View
kmymoney/converter/webpricequote.cpp
Show First 20 Lines • Show All 47 Lines • ▼ Show 20 Line(s) | |||||
48 | 48 | | |||
49 | #include "mymoneyexception.h" | 49 | #include "mymoneyexception.h" | ||
50 | #include "mymoneyqifprofile.h" | 50 | #include "mymoneyqifprofile.h" | ||
51 | 51 | | |||
52 | // define static members | 52 | // define static members | ||
53 | QString WebPriceQuote::m_financeQuoteScriptPath; | 53 | QString WebPriceQuote::m_financeQuoteScriptPath; | ||
54 | QStringList WebPriceQuote::m_financeQuoteSources; | 54 | QStringList WebPriceQuote::m_financeQuoteSources; | ||
55 | 55 | | |||
56 | class WebPriceQuote::Private | 56 | class WebPriceQuote::Private | ||
tbaumgart: I see some potential problem here that once noError is used in a method call as default and an… | |||||
57 | { | 57 | { | ||
58 | public: | 58 | public: | ||
59 | WebPriceQuoteProcess m_filter; | 59 | WebPriceQuoteProcess m_filter; | ||
60 | QString m_quoteData; | 60 | QString m_quoteData; | ||
61 | QString m_symbol; | 61 | QString m_symbol; | ||
62 | QString m_id; | 62 | QString m_id; | ||
63 | QDate m_date; | 63 | QDate m_date; | ||
64 | double m_price; | 64 | double m_price; | ||
Show All 31 Lines | 95 | else | |||
96 | return (launchNative(_symbol, _id, _sourcename)); | 96 | return (launchNative(_symbol, _id, _sourcename)); | ||
97 | } | 97 | } | ||
98 | 98 | | |||
99 | bool WebPriceQuote::launchNative(const QString& _symbol, const QString& _id, const QString& _sourcename) | 99 | bool WebPriceQuote::launchNative(const QString& _symbol, const QString& _id, const QString& _sourcename) | ||
100 | { | 100 | { | ||
101 | bool result = true; | 101 | bool result = true; | ||
102 | d->m_symbol = _symbol; | 102 | d->m_symbol = _symbol; | ||
103 | d->m_id = _id; | 103 | d->m_id = _id; | ||
104 | m_errors = Errors::None; | ||||
104 | 105 | | |||
105 | // emit status(QString("(Debug) symbol=%1 id=%2...").arg(_symbol,_id)); | 106 | emit status(QString("(Debug) symbol=%1 id=%2...").arg(_symbol,_id)); | ||
106 | 107 | | |||
107 | // Get sources from the config file | 108 | // Get sources from the config file | ||
108 | QString sourcename = _sourcename; | 109 | QString sourcename = _sourcename; | ||
109 | if (sourcename.isEmpty()) | 110 | if (sourcename.isEmpty()) | ||
110 | sourcename = "KMyMoney Currency"; | 111 | sourcename = "KMyMoney Currency"; | ||
111 | 112 | | |||
112 | if (quoteSources().contains(sourcename)) | 113 | if (quoteSources().contains(sourcename)) | ||
113 | d->m_source = WebPriceQuoteSource(sourcename); | 114 | d->m_source = WebPriceQuoteSource(sourcename); | ||
114 | else | 115 | else { | ||
115 | emit error(i18n("Source <placeholder>%1</placeholder> does not exist.", sourcename)); | 116 | emit error(i18n("Source <placeholder>%1</placeholder> does not exist.", sourcename)); | ||
117 | m_errors |= Errors::Source; | ||||
118 | result = false; | ||||
119 | } | ||||
116 | 120 | | |||
117 | KUrl url; | 121 | KUrl url; | ||
118 | 122 | | |||
119 | // if the source has room for TWO symbols.. | 123 | // if the source has room for TWO symbols.. | ||
120 | if (d->m_source.m_url.contains("%2")) { | 124 | if (d->m_source.m_url.contains("%2")) { | ||
121 | // this is a two-symbol quote. split the symbol into two. valid symbol | 125 | // this is a two-symbol quote. split the symbol into two. valid symbol | ||
122 | // characters are: 0-9, A-Z and the dot. anything else is a separator | 126 | // characters are: 0-9, A-Z and the dot. anything else is a separator | ||
123 | QRegExp splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)", Qt::CaseInsensitive); | 127 | QRegExp splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)", Qt::CaseInsensitive); | ||
Show All 15 Lines | 137 | if (url.isLocalFile()) { | |||
139 | 143 | | |||
140 | d->m_filter.setOutputChannelMode(KProcess::MergedChannels); | 144 | d->m_filter.setOutputChannelMode(KProcess::MergedChannels); | ||
141 | d->m_filter.start(); | 145 | d->m_filter.start(); | ||
142 | 146 | | |||
143 | if (d->m_filter.waitForStarted()) { | 147 | if (d->m_filter.waitForStarted()) { | ||
144 | result = true; | 148 | result = true; | ||
145 | } else { | 149 | } else { | ||
146 | emit error(i18n("Unable to launch: %1", url.toLocalFile())); | 150 | emit error(i18n("Unable to launch: %1", url.toLocalFile())); | ||
147 | slotParseQuote(QString()); | 151 | m_errors |= Errors::Script; | ||
152 | result = slotParseQuote(QString()); | ||||
148 | } | 153 | } | ||
149 | } else { | 154 | } else { | ||
150 | emit status(i18n("Fetching URL %1...", url.prettyUrl())); | 155 | emit status(i18n("Fetching URL %1...", url.prettyUrl())); | ||
151 | 156 | | |||
152 | QString tmpFile; | 157 | QString tmpFile; | ||
153 | if (KIO::NetAccess::download(url, tmpFile, 0)) { | 158 | if (KIO::NetAccess::download(url, tmpFile, 0)) { | ||
154 | // kDebug(Private::dbgArea()) << "Downloaded " << tmpFile; | 159 | // kDebug(Private::dbgArea()) << "Downloaded " << tmpFile; | ||
155 | kDebug(Private::dbgArea()) << "Downloaded" << tmpFile << "from" << url; | 160 | kDebug(Private::dbgArea()) << "Downloaded" << tmpFile << "from" << url; | ||
156 | QFile f(tmpFile); | 161 | QFile f(tmpFile); | ||
157 | if (f.open(QIODevice::ReadOnly)) { | 162 | if (f.open(QIODevice::ReadOnly)) { | ||
158 | result = true; | | |||
159 | // Find out the page encoding and convert it to unicode | 163 | // Find out the page encoding and convert it to unicode | ||
160 | QByteArray page = f.readAll(); | 164 | QByteArray page = f.readAll(); | ||
161 | KEncodingProber prober(KEncodingProber::Universal); | 165 | KEncodingProber prober(KEncodingProber::Universal); | ||
162 | prober.feed(page); | 166 | prober.feed(page); | ||
163 | QTextCodec* codec = QTextCodec::codecForName(prober.encoding()); | 167 | QTextCodec* codec = QTextCodec::codecForName(prober.encoding()); | ||
164 | if (!codec) | 168 | if (!codec) | ||
165 | codec = QTextCodec::codecForLocale(); | 169 | codec = QTextCodec::codecForLocale(); | ||
166 | QString quote = codec->toUnicode(page); | 170 | QString quote = codec->toUnicode(page); | ||
167 | f.close(); | 171 | f.close(); | ||
168 | slotParseQuote(quote); | 172 | emit status(i18n("URL found: %1...", url.prettyUrl())); | ||
173 | result = slotParseQuote(quote); | ||||
169 | } else { | 174 | } else { | ||
170 | emit error(i18n("Failed to open downloaded file")); | 175 | emit error(i18n("Failed to open downloaded file")); | ||
171 | slotParseQuote(QString()); | 176 | m_errors |= Errors::URL; | ||
177 | result = slotParseQuote(QString()); | ||||
172 | } | 178 | } | ||
173 | KIO::NetAccess::removeTempFile(tmpFile); | 179 | KIO::NetAccess::removeTempFile(tmpFile); | ||
174 | } else { | 180 | } else { | ||
175 | emit error(KIO::NetAccess::lastErrorString()); | 181 | emit error(KIO::NetAccess::lastErrorString()); | ||
176 | slotParseQuote(QString()); | 182 | m_errors |= Errors::URL; | ||
183 | result = slotParseQuote(QString()); | ||||
177 | } | 184 | } | ||
178 | } | 185 | } | ||
179 | return result; | 186 | return result; | ||
180 | } | 187 | } | ||
181 | 188 | | |||
182 | bool WebPriceQuote::launchFinanceQuote(const QString& _symbol, const QString& _id, | 189 | bool WebPriceQuote::launchFinanceQuote(const QString& _symbol, const QString& _id, | ||
183 | const QString& _sourcename) | 190 | const QString& _sourcename) | ||
184 | { | 191 | { | ||
185 | bool result = true; | 192 | bool result = true; | ||
186 | d->m_symbol = _symbol; | 193 | d->m_symbol = _symbol; | ||
187 | d->m_id = _id; | 194 | d->m_id = _id; | ||
195 | m_errors = Errors::None; | ||||
196 | | ||||
188 | QString FQSource = _sourcename.section(' ', 1); | 197 | QString FQSource = _sourcename.section(' ', 1); | ||
189 | d->m_source = WebPriceQuoteSource(_sourcename, m_financeQuoteScriptPath, | 198 | d->m_source = WebPriceQuoteSource(_sourcename, m_financeQuoteScriptPath, | ||
190 | "\"([^,\"]*)\",.*", // symbol regexp | 199 | "\"([^,\"]*)\",.*", // symbol regexp | ||
191 | "[^,]*,[^,]*,\"([^\"]*)\"", // price regexp | 200 | "[^,]*,[^,]*,\"([^\"]*)\"", // price regexp | ||
192 | "[^,]*,([^,]*),.*", // date regexp | 201 | "[^,]*,([^,]*),.*", // date regexp | ||
193 | "%y-%m-%d"); // date format | 202 | "%y-%m-%d"); // date format | ||
194 | 203 | | |||
195 | //emit status(QString("(Debug) symbol=%1 id=%2...").arg(_symbol,_id)); | 204 | //emit status(QString("(Debug) symbol=%1 id=%2...").arg(_symbol,_id)); | ||
196 | 205 | | |||
197 | d->m_filter.clearProgram(); | 206 | d->m_filter.clearProgram(); | ||
198 | d->m_filter << "perl" << m_financeQuoteScriptPath << FQSource << KShell::quoteArg(_symbol); | 207 | d->m_filter << "perl" << m_financeQuoteScriptPath << FQSource << KShell::quoteArg(_symbol); | ||
199 | d->m_filter.setSymbol(d->m_symbol); | 208 | d->m_filter.setSymbol(d->m_symbol); | ||
200 | emit status(i18nc("Executing 'script' 'online source' 'investment symbol' ", "Executing %1 %2 %3...", m_financeQuoteScriptPath, FQSource, _symbol)); | 209 | emit status(i18nc("Executing 'script' 'online source' 'investment symbol' ", "Executing %1 %2 %3...", m_financeQuoteScriptPath, FQSource, _symbol)); | ||
201 | 210 | | |||
202 | d->m_filter.setOutputChannelMode(KProcess::MergedChannels); | 211 | d->m_filter.setOutputChannelMode(KProcess::MergedChannels); | ||
203 | d->m_filter.start(); | 212 | d->m_filter.start(); | ||
204 | 213 | | |||
205 | // This seems to work best if we just block until done. | 214 | // This seems to work best if we just block until done. | ||
206 | if (d->m_filter.waitForFinished()) { | 215 | if (d->m_filter.waitForFinished()) { | ||
207 | result = true; | | |||
208 | } else { | 216 | } else { | ||
209 | emit error(i18n("Unable to launch: %1", m_financeQuoteScriptPath)); | 217 | emit error(i18n("Unable to launch: %1", m_financeQuoteScriptPath)); | ||
210 | slotParseQuote(QString()); | 218 | m_errors |= Errors::Script; | ||
219 | result = slotParseQuote(QString()); | ||||
211 | } | 220 | } | ||
212 | 221 | | |||
213 | return result; | 222 | return result; | ||
214 | } | 223 | } | ||
215 | 224 | | |||
216 | void WebPriceQuote::slotParseQuote(const QString& _quotedata) | 225 | /** | ||
226 | * Parse quote data according to currently selected web price quote source | ||||
227 | * | ||||
228 | * @param _quotedata quote data to parse | ||||
229 | * @return true parsing successful | ||||
230 | * @return false parsing unsuccessful | ||||
231 | */ | ||||
232 | bool WebPriceQuote::slotParseQuote(const QString& _quotedata) | ||||
217 | { | 233 | { | ||
218 | QString quotedata = _quotedata; | 234 | QString quotedata = _quotedata; | ||
219 | d->m_quoteData = quotedata; | 235 | d->m_quoteData = quotedata; | ||
220 | bool gotprice = false; | 236 | bool gotprice = false; | ||
221 | bool gotdate = false; | 237 | bool gotdate = false; | ||
238 | bool result = true; | ||||
222 | 239 | | |||
223 | kDebug(Private::dbgArea()) << "quotedata" << _quotedata; | 240 | kDebug(Private::dbgArea()) << "quotedata" << _quotedata; | ||
224 | 241 | | |||
225 | if (! quotedata.isEmpty()) { | 242 | if (! quotedata.isEmpty()) { | ||
226 | if (!d->m_source.m_skipStripping) { | 243 | if (!d->m_source.m_skipStripping) { | ||
227 | // | 244 | // | ||
228 | // First, remove extranous non-data elements | 245 | // First, remove extranous non-data elements | ||
229 | // | 246 | // | ||
Show All 11 Lines | |||||
241 | 258 | | |||
242 | QRegExp symbolRegExp(d->m_source.m_sym); | 259 | QRegExp symbolRegExp(d->m_source.m_sym); | ||
243 | QRegExp dateRegExp(d->m_source.m_date); | 260 | QRegExp dateRegExp(d->m_source.m_date); | ||
244 | QRegExp priceRegExp(d->m_source.m_price); | 261 | QRegExp priceRegExp(d->m_source.m_price); | ||
245 | 262 | | |||
246 | if (symbolRegExp.indexIn(quotedata) > -1) { | 263 | if (symbolRegExp.indexIn(quotedata) > -1) { | ||
247 | kDebug(Private::dbgArea()) << "Symbol" << symbolRegExp.cap(1); | 264 | kDebug(Private::dbgArea()) << "Symbol" << symbolRegExp.cap(1); | ||
248 | emit status(i18n("Symbol found: '%1'", symbolRegExp.cap(1))); | 265 | emit status(i18n("Symbol found: '%1'", symbolRegExp.cap(1))); | ||
266 | } else { | ||||
267 | m_errors |= Errors::Symbol; | ||||
This might not work (or-ing multiple errors) if you do not define them as bit-values but plain enums. Since you evaluate the bits in KSettingsOnlineQuotes::setupIcons(), I suggest you use class Errors { public: enum Type { None = 0x00000000, Data = 0x00000001, Date = 0x00000002, DateFormat = 0x00000004, Price = 0x00000008, Script = 0x00000010, Source = 0x00000020, Symbol = 0x00000040, Success = 0x00000080, URL = 0x00000100 }; as defintion for the enum. tbaumgart: This might not work (or-ing multiple errors) if you do not define them as bit-values but plain… | |||||
There is no issue here, because class Errors stores the enum values of type 'Type' in a QList<Type>, see webpriceqoute.h:140ff. The statement errors |= Errors:Symbol. is managed by class Errors operator |= and simply add the enum value to the internal list. habacker: There is no issue here, because class Errors stores the enum values of type 'Type' in a… | |||||
The fun of operator overloading. I did not spot this but you're right. Case closed. tbaumgart: The fun of operator overloading. I did not spot this but you're right. Case closed. | |||||
268 | emit error(i18n("Unable to parse symbol for %1", d->m_symbol)); | ||||
249 | } | 269 | } | ||
250 | 270 | | |||
251 | if (priceRegExp.indexIn(quotedata) > -1) { | 271 | if (priceRegExp.indexIn(quotedata) > -1) { | ||
252 | gotprice = true; | 272 | gotprice = true; | ||
253 | 273 | | |||
254 | // Deal with european quotes that come back as X.XXX,XX or XX,XXX | 274 | // Deal with european quotes that come back as X.XXX,XX or XX,XXX | ||
255 | // | 275 | // | ||
256 | // We will make the assumption that ALL prices have a decimal separator. | 276 | // We will make the assumption that ALL prices have a decimal separator. | ||
Show All 11 Lines | |||||
268 | while (pos > 0) { | 288 | while (pos > 0) { | ||
269 | pricestr.remove(pos, 1); | 289 | pricestr.remove(pos, 1); | ||
270 | pos = pricestr.lastIndexOf(QRegExp("\\D"), pos); | 290 | pos = pricestr.lastIndexOf(QRegExp("\\D"), pos); | ||
271 | } | 291 | } | ||
272 | 292 | | |||
273 | d->m_price = pricestr.toDouble(); | 293 | d->m_price = pricestr.toDouble(); | ||
274 | kDebug(Private::dbgArea()) << "Price" << pricestr; | 294 | kDebug(Private::dbgArea()) << "Price" << pricestr; | ||
275 | emit status(i18n("Price found: '%1' (%2)", pricestr, d->m_price)); | 295 | emit status(i18n("Price found: '%1' (%2)", pricestr, d->m_price)); | ||
296 | } else { | ||||
297 | m_errors |= Errors::Price; | ||||
298 | emit error(i18n("Unable to parse price for %1", d->m_symbol)); | ||||
299 | result = false; | ||||
276 | } | 300 | } | ||
277 | 301 | | |||
278 | if (dateRegExp.indexIn(quotedata) > -1) { | 302 | if (dateRegExp.indexIn(quotedata) > -1) { | ||
279 | QString datestr = dateRegExp.cap(1); | 303 | QString datestr = dateRegExp.cap(1); | ||
304 | emit status(i18n("Date found: '%1'", datestr)); | ||||
280 | 305 | | |||
281 | MyMoneyDateFormat dateparse(d->m_source.m_dateformat); | 306 | MyMoneyDateFormat dateparse(d->m_source.m_dateformat); | ||
282 | try { | 307 | try { | ||
283 | d->m_date = dateparse.convertString(datestr, false /*strict*/); | 308 | d->m_date = dateparse.convertString(datestr, false /*strict*/); | ||
284 | gotdate = true; | 309 | gotdate = true; | ||
285 | kDebug(Private::dbgArea()) << "Date" << datestr; | 310 | kDebug(Private::dbgArea()) << "Date" << datestr; | ||
286 | emit status(i18n("Date found: '%1'", d->m_date.toString()));; | 311 | emit status(i18n("Date format found: '%1' -> %2", datestr, d->m_date.toString())); | ||
287 | } catch (const MyMoneyException &) { | 312 | } catch (const MyMoneyException &e) { | ||
288 | // emit error(i18n("Unable to parse date %1 using format %2: %3").arg(datestr,dateparse.format(),e.what())); | 313 | m_errors |= Errors::DateFormat; | ||
314 | emit error(i18n("Unable to parse date %1 using format %2: %3").arg(datestr,dateparse.format(), e.what())); | ||||
289 | d->m_date = QDate::currentDate(); | 315 | d->m_date = QDate::currentDate(); | ||
316 | emit status(i18n("Using current date for %1").arg(d->m_symbol)); | ||||
290 | gotdate = true; | 317 | gotdate = true; | ||
291 | } | 318 | } | ||
319 | } else { | ||||
320 | m_errors |= Errors::Date; | ||||
321 | emit error(i18n("Unable to parse date for %1").arg(d->m_symbol)); | ||||
322 | d->m_date = QDate::currentDate(); | ||||
323 | emit status(i18n("Using current date for %1").arg(d->m_symbol)); | ||||
324 | gotdate = true; | ||||
292 | } | 325 | } | ||
293 | 326 | | |||
294 | if (gotprice && gotdate) { | 327 | if (gotprice && gotdate) { | ||
295 | emit quote(d->m_id, d->m_symbol, d->m_date, d->m_price); | 328 | emit quote(d->m_id, d->m_symbol, d->m_date, d->m_price); | ||
296 | } else { | 329 | } else { | ||
297 | emit error(i18n("Unable to update price for %1 (no price or no date)", d->m_symbol)); | | |||
298 | emit failed(d->m_id, d->m_symbol); | 330 | emit failed(d->m_id, d->m_symbol); | ||
331 | result = false; | ||||
299 | } | 332 | } | ||
300 | } else { | 333 | } else { | ||
334 | m_errors |= Errors::Data; | ||||
301 | emit error(i18n("Unable to update price for %1 (empty quote data)", d->m_symbol)); | 335 | emit error(i18n("Unable to update price for %1 (empty quote data)", d->m_symbol)); | ||
302 | emit failed(d->m_id, d->m_symbol); | 336 | emit failed(d->m_id, d->m_symbol); | ||
337 | result = false; | ||||
303 | } | 338 | } | ||
339 | return result; | ||||
304 | } | 340 | } | ||
305 | 341 | | |||
306 | const QMap<QString, WebPriceQuoteSource> WebPriceQuote::defaultQuoteSources() | 342 | const QMap<QString, WebPriceQuoteSource> WebPriceQuote::defaultQuoteSources() | ||
307 | { | 343 | { | ||
308 | QMap<QString, WebPriceQuoteSource> result; | 344 | QMap<QString, WebPriceQuoteSource> result; | ||
309 | 345 | | |||
310 | // Use fx-rate.net as the standard currency exchange rate source until | 346 | // Use fx-rate.net as the standard currency exchange rate source until | ||
311 | // we have the capability to use more than one source. Use a neutral | 347 | // we have the capability to use more than one source. Use a neutral | ||
▲ Show 20 Lines • Show All 631 Lines • Show Last 20 Lines |
I see some potential problem here that once noError is used in a method call as default and an error occurs, noError will keep the error for as long as the application lives. Use a temporary in the case of default value for parameters to avoid this, e.g.
as you do with QStrings already.