2 * This file is part of Jenirok.
4 * Jenirok is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Jenirok is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Jenirok. If not, see <http://www.gnu.org/licenses/>.
19 #include <QtCore/QDebug>
24 static const QString SITE_URLS[Eniro::SITE_COUNT] =
26 "http://wap.eniro.fi/",
27 "http://wap.eniro.se/",
28 "http://wap.eniro.dk/"
31 static const QString SITE_NAMES[Eniro::SITE_COUNT] =
38 static const QString SITE_IDS[Eniro::SITE_COUNT] =
45 static const QString INVALID_LOGIN_STRING = QObject::tr("Invalid login details");
46 static const QString TIMEOUT_STRING = QObject::tr("Request timed out");
47 static const QString PERSON_REGEXP = "<td class=\"hTd2\">(.*)<b>(.*)</td>";
48 static const QString YELLOW_REGEXP = "<td class=\"hTd2\">(.*)<span class=\"gray\"\\}>(.*)</td>";
49 static const QString NUMBER_REGEXP = "<div class=\"callRow\">(.*)</div>";
50 static const QString LOGIN_CHECK = "<input class=\"inpTxt\" id=\"loginformUsername\"";
53 // Regexp used to remove numbers from string
54 QRegExp Eniro::numberCleaner_ = QRegExp("([^0-9]+)");
56 // Removes html tags from string
57 QRegExp Eniro::tagStripper_ = QRegExp("<([^>]+)>");
59 Eniro::Eniro(Site site, QObject *parent): QObject(parent), site_(site),
60 username_(""), password_(""), loggedIn_(false), error_(NO_ERROR),
61 errorString_(""), maxResults_(10), timeout_(0), timerId_(0), findNumber_(true),
62 pendingSearches_(), pendingNumberRequests_()
64 connect(&http_, SIGNAL(requestFinished(int, bool)), this, SLOT(httpReady(int, bool)));
76 for(searchMap::iterator sit = pendingSearches_.begin();
77 sit != pendingSearches_.end(); sit++)
86 pendingSearches_.clear();
88 for(numberMap::iterator nit = pendingNumberRequests_.begin();
89 nit != pendingNumberRequests_.end(); nit++)
98 pendingNumberRequests_.clear();
99 pendingLoginRequests_.clear();
102 void Eniro::setMaxResults(unsigned int value)
107 void Eniro::setFindNumber(bool value)
112 void Eniro::setSite(Eniro::Site site)
117 void Eniro::setTimeout(unsigned int ms)
123 void Eniro::resetTimeout()
131 timerId_ = startTimer(timeout_);
135 void Eniro::timerEvent(QTimerEvent* t)
137 if(t->timerId() == timerId_)
139 int currentId = http_.currentId();
143 searchMap::const_iterator it = pendingSearches_.find(currentId);
145 if(it != pendingSearches_.end())
147 QVector <Eniro::Result> results = it.value()->results;
148 SearchDetails details = it.value()->details;
153 errorString_ = TIMEOUT_STRING;
155 emit requestFinished(results, details, true);
162 void Eniro::login(QString const& username,
163 QString const& password)
165 username_ = username;
166 password_ = password;
177 void Eniro::testLogin()
179 QUrl url = createUrl("", "");
181 url.addQueryItem("what", "mobwp");
182 http_.setHost(url.host(), url.port(80));
183 int id = http_.get(url.encodedPath() + '?' + url.encodedQuery());
185 pendingLoginRequests_.insert(id);
188 bool Eniro::search(SearchDetails const& details)
192 SearchType type = details.type;
194 // Only logged in users can use other than person search
200 QUrl url = createUrl(details.query, details.location);
225 url.addQueryItem("what", what);
227 http_.setHost(url.host(), url.port(80));
228 int id = http_.get(url.encodedPath() + '?' + url.encodedQuery());
230 QVector <Result> results;
232 // Store search data for later identification
233 SearchData* newData = new SearchData;
234 newData->details = details;
235 newData->results = results;
236 newData->foundNumbers = 0;
237 newData->numbersTotal = 0;
239 // Store request id so that it can be identified later
240 pendingSearches_[id] = newData;
245 Eniro::Error Eniro::error() const
250 const QString& Eniro::errorString() const
255 void Eniro::httpReady(int id, bool error)
259 qDebug() << "Error: " << http_.errorString();
262 searchMap::const_iterator searchIt;
263 numberMap::const_iterator numberIt;
265 // Check if request is pending search request
266 if((searchIt = pendingSearches_.find(id)) !=
267 pendingSearches_.end())
271 error_ = CONNECTION_FAILURE;
272 errorString_ = http_.errorString();
273 emitRequestFinished(id, searchIt.value(), true);
277 QString result(http_.readAll());
279 // Load results from html data
280 loadResults(id, result);
283 // Check if request is pending number requests
284 else if((numberIt = pendingNumberRequests_.find(id)) !=
285 pendingNumberRequests_.end())
289 error_ = CONNECTION_FAILURE;
290 errorString_ = http_.errorString();
291 delete pendingNumberRequests_[id];
292 pendingNumberRequests_.remove(id);
296 QString result(http_.readAll());
298 // Load number from html data
299 loadNumber(id, result);
302 // Check for login request
303 else if(pendingLoginRequests_.find(id) !=
304 pendingLoginRequests_.end())
310 QString result(http_.readAll());
312 // If html source contains LOGIN_CHECK, login failed
313 if(result.indexOf(LOGIN_CHECK) != -1)
323 emit loginStatus(success);
328 // Loads results from html source code
329 void Eniro::loadResults(int id, QString const& httpData)
331 searchMap::iterator it = pendingSearches_.find(id);
334 switch(it.value()->details.type)
337 expr = YELLOW_REGEXP;
340 expr = PERSON_REGEXP;
349 bool requestsPending = false;
354 while((pos = rx.indexIn(httpData, pos)) != -1)
356 pos += rx.matchedLength();
359 data = stripTags(data);
360 QStringList rows = data.split('\n');
362 for(int i = 0; i < rows.size(); i++)
364 // Remove white spaces
365 QString trimmed = rows.at(i).trimmed().toLower();
367 // Remove empty strings
368 if(trimmed.isEmpty())
375 // Convert words to uppercase
376 rows[i] = ucFirst(trimmed);
382 int size = rows.size();
387 result.name = rows[0];
391 result.name = rows[0];
392 result.city = rows[1];
396 result.name = rows[0];
397 result.street = rows[1];
398 result.city = rows[2];
402 result.name = rows[0];
403 // Remove slashes and spaces from number
404 result.number = cleanUpNumber(rows[1]);
405 result.street = rows[2];
406 result.city = rows[3];
414 it.value()->results.push_back(result);
416 unsigned int foundResults = ++(it.value()->numbersTotal);
418 // If phone number searh is enabled, we have to make another
419 // request to find it out
420 if(findNumber_ && size < 4 && loggedIn_ &&
421 it.value()->details.type != YELLOW_PAGES)
423 requestsPending = true;
424 getNumberForResult(id, it.value()->results.size() - 1, it.value()->details);
426 // Otherwise result is ready
429 emit resultAvailable(result, it.value()->details);
432 // Stop searching if max results is reached
433 if(maxResults_ && (foundResults >= maxResults_))
439 // If there were no results or no phone numbers needed to
440 // be fetched, the whole request is ready
441 if(it.value()->numbersTotal == 0 || !requestsPending)
445 if(httpData.indexOf(LOGIN_CHECK) != -1)
447 error_ = INVALID_LOGIN;
448 errorString_ = INVALID_LOGIN_STRING;
452 emitRequestFinished(it.key(), it.value(), error);
456 // Loads phone number from html source
457 void Eniro::loadNumber(int id, QString const& result)
459 numberMap::iterator numberIt = pendingNumberRequests_.find(id);
461 // Make sure that id exists in pending number requests
462 if(numberIt == pendingNumberRequests_.end() || numberIt.value() == 0)
467 searchMap::iterator searchIt = pendingSearches_.find(numberIt.value()->searchId);
469 if(searchIt == pendingSearches_.end() || searchIt.value() == 0)
474 QRegExp rx(NUMBER_REGEXP);
480 if((pos = rx.indexIn(result, pos)) != -1)
482 QString data = rx.cap(1);
483 data = stripTags(data);
485 QString trimmed = data.trimmed();
487 if(!trimmed.isEmpty())
489 // Remove whitespaces from number
490 searchIt.value()->results[numberIt.value()->index].number = cleanUpNumber(trimmed);
492 emit resultAvailable(searchIt.value()->results[numberIt.value()->index], searchIt.value()->details);
494 unsigned int found = ++searchIt.value()->foundNumbers;
496 // Check if all numbers have been found
497 if(found >= searchIt.value()->numbersTotal)
499 emitRequestFinished(searchIt.key(), searchIt.value(), false);
502 // If number was found, there was no error
509 error_ = INVALID_LOGIN;
510 errorString_ = INVALID_LOGIN;
511 emitRequestFinished(searchIt.key(), searchIt.value(), true);
514 // Remove number request
515 int key = numberIt.key();
517 delete pendingNumberRequests_[key];
518 pendingNumberRequests_[key] = 0;
519 pendingNumberRequests_.remove(key);
523 QUrl Eniro::createUrl(QString const& query, QString const& location)
525 QUrl url(SITE_URLS[site_] + "query");
529 url.addQueryItem("search_word", query);
532 if(!location.isEmpty())
534 url.addQueryItem("geo_area", location);
539 url.addQueryItem("hpp", QString::number(maxResults_));
543 url.addQueryItem("login_name", username_);
544 url.addQueryItem("login_password", password_);
547 QByteArray path = url.encodedQuery().replace('+', "%2B");
548 url.setEncodedQuery(path);
553 // Creates a new request for phone number retrieval
554 void Eniro::getNumberForResult(int id, int index, SearchDetails const& details)
556 QUrl url = createUrl(details.query, details.location);
557 url.addQueryItem("what", "mobwpinfo");
558 url.addQueryItem("search_number", QString::number(index + 1));
560 http_.setHost(url.host(), url.port(80));
561 int requestId = http_.get(url.encodedPath() + '?' + url.encodedQuery());
562 NumberData* number = new NumberData;
563 number->searchId = id;
564 number->index = index;
565 pendingNumberRequests_[requestId] = number;
569 void Eniro::emitRequestFinished(int key, SearchData* data, bool error)
572 // Do not emit "Request aborted" error
573 if(!(error && (http_.error() == QHttp::Aborted)))
575 emit requestFinished(data->results, data->details, error);
578 delete pendingSearches_[key];
579 pendingSearches_[key] = 0;
580 pendingSearches_.remove(key);
583 QString Eniro::ucFirst(QString& str)
585 if (str.size() < 1) {
589 QStringList tokens = str.split(" ");
590 QList<QString>::iterator tokItr;
592 for (tokItr = tokens.begin(); tokItr != tokens.end(); ++tokItr)
594 (*tokItr) = (*tokItr).at(0).toUpper() + (*tokItr).mid(1);
597 return tokens.join(" ");
600 QString& Eniro::cleanUpNumber(QString& number)
602 return number.replace(numberCleaner_, "");
605 QString& Eniro::stripTags(QString& string)
607 return string.replace(tagStripper_, "");
610 QMap <Eniro::Site, Eniro::SiteDetails> Eniro::getSites()
612 QMap <Site, SiteDetails> sites;
615 for(int i = 0; i < SITE_COUNT; i++)
618 details.name = SITE_NAMES[i];
619 details.id = SITE_IDS[i];
620 sites[static_cast<Site>(i)] = details;
626 Eniro::Site Eniro::stringToSite(QString const& str)
629 QString lower = str.toLower();
631 for(int i = 0; i < SITE_COUNT; i++)
633 if(lower == SITE_NAMES[i] || lower == SITE_IDS[i])
635 site = static_cast <Site> (i);
642 Eniro::SearchDetails::SearchDetails(QString const& q,