2 Situare - A location system for Facebook
3 Copyright (C) 2010 Ixonos Plc. Authors:
5 Henri Lampela - henri.lampela@ixonos.com
7 Situare is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 version 2 as published by the Free Software Foundation.
11 Situare is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with Situare; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
22 #include <qjson/parser.h>
25 #include <QNetworkReply>
27 #include <QStringList>
28 #include <QtAlgorithms>
32 #include "network/networkaccessmanager.h"
33 #include "situarecommon.h"
34 #include "ui/avatarimage.h"
36 #include "situareservice.h"
38 SituareService::SituareService(QObject *parent)
42 qDebug() << __PRETTY_FUNCTION__;
44 m_networkManager = new NetworkAccessManager(this);
45 connect(m_networkManager, SIGNAL(finished(QNetworkReply*)),
46 this, SLOT(requestFinished(QNetworkReply*)), Qt::QueuedConnection);
48 m_imageFetcher = new ImageFetcher(new NetworkAccessManager(this), this);
49 connect(this, SIGNAL(fetchImage(QUrl)),
50 m_imageFetcher, SLOT(fetchImage(QUrl)));
51 connect(m_imageFetcher, SIGNAL(imageReceived(QUrl,QPixmap)),
52 this, SLOT(imageReceived(QUrl, QPixmap)));
53 connect(m_imageFetcher, SIGNAL(error(int, int)),
54 this, SIGNAL(error(int, int)));
57 SituareService::~SituareService()
59 qDebug() << __PRETTY_FUNCTION__;
66 qDeleteAll(m_friendsList.begin(), m_friendsList.end());
67 m_friendsList.clear();
70 void SituareService::addProfileImages(const QList<QUrl> &imageUrlList)
72 qDebug() << __PRETTY_FUNCTION__;
74 foreach(QUrl url, imageUrlList) {
79 void SituareService::appendAccessToken(QString &requestUrl)
81 qWarning() << __PRETTY_FUNCTION__;
83 // requestUrl.append("access_token=");
84 requestUrl.append(m_session);
86 // qWarning() << __PRETTY_FUNCTION__ << "request url with parameters and access token:" << requestUrl;
89 void SituareService::buildRequest(const QString &script, const QHash<QString, QString> ¶meters)
91 qWarning() << __PRETTY_FUNCTION__;
93 const QString PARAMETER_KEY_API = "api";
94 const QString PARAMETER_VALUE_API = "2.0";
96 QString url = SITUARE_URL;
100 // append default api version parameter if not yet specified
101 // if (!parameters.contains(PARAMETER_KEY_API))
102 // url.append(PARAMETER_KEY_API + "=" + PARAMETER_VALUE_API + "&");
105 if (!parameters.isEmpty()) {
106 QHash<QString, QString>::const_iterator i = parameters.constBegin();
107 while (i != parameters.constEnd()) {
110 url.append(i.value());
116 // qWarning() << __PRETTY_FUNCTION__ << "request url with parameters:" << url;
118 if (!m_session.isEmpty()) {
119 appendAccessToken(url);
122 m_requestsWaitingAccessToken.append(url);
123 ///< @todo emit login request
127 void SituareService::clearUserData()
129 qDebug() << __PRETTY_FUNCTION__;
131 qDeleteAll(m_friendsList.begin(), m_friendsList.end());
132 m_friendsList.clear();
138 emit userDataChanged(m_user, m_friendsList);
141 QString SituareService::degreesToString(double degrees)
143 qDebug() << __PRETTY_FUNCTION__;
145 // one scene pixel is about 5.4e-6 degrees, the integer part is max three digits and one
146 // additional digit is added for maximum precision
147 const int PRECISION = 10;
149 return QString::number(degrees, 'f', PRECISION);
152 void SituareService::fetchLocations()
154 qDebug() << __PRETTY_FUNCTION__;
156 QHash<QString, QString> parameters;
157 parameters.insert("extra_user_data", NORMAL_SIZE_PROFILE_IMAGE);
159 buildRequest(GET_LOCATIONS, parameters);
162 QString SituareService::formCookie(const QString &apiKeyValue, QString expiresValue,
163 QString userValue, QString sessionKeyValue,
164 QString sessionSecretValue, const QString &signatureValue,
165 const QString &localeValue)
167 qDebug() << __PRETTY_FUNCTION__;
174 QString sessionSecret;
177 QString signature = EQUAL_MARK;
178 QStringList variableList;
180 signature.append(signatureValue);
181 apiKey.append(apiKeyValue);
182 apiKey.append(UNDERLINE_MARK);
185 user.append(EQUAL_MARK);
186 expires.append(EXPIRES);
187 expires.append(EQUAL_MARK);
188 sessionKey.append(SESSION_KEY);
189 sessionKey.append(EQUAL_MARK);
190 sessionSecret.append(SESSION_SECRET);
191 sessionSecret.append(EQUAL_MARK);
192 locale.append(LOCALE);
193 locale.append(EQUAL_MARK);
194 locale.append(localeValue);
196 variableList.append(expires.append(expiresValue.append(BREAK_MARK)));
197 variableList.append(sessionKey.append(sessionKeyValue.append(BREAK_MARK)));
198 variableList.append(user.append(userValue).append(BREAK_MARK));
199 variableList.append(sessionSecret.append(sessionSecretValue.append(BREAK_MARK)));
201 cookie.append(BREAK_MARK);
203 foreach(variable, variableList) {
204 cookie.append(apiKey);
205 cookie.append(variable);
207 apiKey.remove(UNDERLINE_MARK);
208 cookie.append(apiKey);
209 cookie.append(signature);
210 cookie.append(BREAK_MARK);
211 cookie.append(locale);
218 QUrl SituareService::formUrl(const QString &baseUrl, const QString &phpScript,
219 QString urlParameters)
221 qDebug() << __PRETTY_FUNCTION__;
224 urlString.append(baseUrl);
225 urlString.append(phpScript);
226 if(!urlParameters.isEmpty())
227 urlString.append(urlParameters);
229 QUrl url = QUrl(urlString);
236 QString SituareService::formUrlParameters(const GeoCoordinate &coordinates, QString status,
239 qDebug() << __PRETTY_FUNCTION__;
241 // one scene pixel is about 5.4e-6 degrees, the integer part is max three digits and one
242 // additional digit is added for maximum precision
243 const int COORDINATE_PRECISION = 10;
247 parameters.append(QUESTION_MARK);
248 parameters.append(LATITUDE);
249 parameters.append(EQUAL_MARK);
250 parameters.append(QString::number(coordinates.latitude(), 'f', COORDINATE_PRECISION));
251 parameters.append(AMBERSAND_MARK);
252 parameters.append(LONGTITUDE);
253 parameters.append(EQUAL_MARK);
254 parameters.append(QString::number(coordinates.longitude(), 'f', COORDINATE_PRECISION));
256 parameters.append(AMBERSAND_MARK);
257 parameters.append(PUBLISH);
258 parameters.append(EQUAL_MARK);
261 parameters.append(PUBLISH_TRUE);
263 parameters.append(PUBLISH_FALSE);
265 if(!status.isEmpty()) {
266 parameters.append(AMBERSAND_MARK);
267 parameters.append(DATA);
268 parameters.append(EQUAL_MARK);
269 parameters.append(status);
275 void SituareService::imageReceived(const QUrl &url, const QPixmap &image)
277 qDebug() << __PRETTY_FUNCTION__;
278 qDebug() << "Image URL: " << url << " size :" << image.size();
280 // assign facebook silhouette image to all who doesn't have a profile image
281 if(url == QUrl(SILHOUETTE_URL)) {
282 if(m_user->profileImageUrl().isEmpty()) {
283 m_user->setProfileImage(AvatarImage::create(image, AvatarImage::Large));
284 emit imageReady(m_user);
286 foreach(User *friendItem, m_friendsList) {
287 if(friendItem->profileImageUrl().isEmpty()) {
288 friendItem->setProfileImage(AvatarImage::create(image, AvatarImage::Small));
289 emit imageReady(friendItem);
294 if (m_user->profileImageUrl() == url) {
295 m_user->setProfileImage(AvatarImage::create(image, AvatarImage::Large));
296 emit imageReady(m_user);
299 foreach(User *friendItem, m_friendsList) {
300 if(friendItem->profileImageUrl() == url) {
301 friendItem->setProfileImage(AvatarImage::create(image, AvatarImage::Small));
302 emit imageReady(friendItem);
307 void SituareService::parseUserData(const QByteArray &jsonReply)
309 qDebug() << __PRETTY_FUNCTION__;
311 m_defaultImage = false;
313 QJson::Parser parser;
316 QVariantMap result = parser.parse (jsonReply, &ok).toMap();
318 emit error(ErrorContext::SITUARE, SituareError::INVALID_JSON);
322 if(result.contains("ErrorCode")) {
323 QVariant errorVariant = result.value("ErrorCode");
324 emit error(ErrorContext::SITUARE, errorVariant.toInt());
326 } else if(result.contains("user")) {
328 QVariant userVariant = result.value("user");
329 QMap<QString, QVariant> userMap = userVariant.toMap();
331 GeoCoordinate coordinates(userMap["latitude"].toReal(), userMap["longitude"].toReal());
333 QUrl imageUrl = userMap[NORMAL_SIZE_PROFILE_IMAGE].toUrl();
335 if(imageUrl.isEmpty()) {
336 // user doesn't have profile image, so we need to get him a silhouette image
337 m_defaultImage = true;
340 QString address = userMap["address"].toString();
341 if(address.isEmpty()) {
342 QStringList location;
343 location.append(QString::number(coordinates.latitude()));
344 location.append(QString::number(coordinates.longitude()));
345 address = location.join(", ");
348 User user = User(address, coordinates, userMap["name"].toString(),
349 userMap["note"].toString(), imageUrl, userMap["timestamp"].toString(),
350 true, userMap["uid"].toString());
352 QList<User> tmpFriendsList;
354 foreach (QVariant friendsVariant, result["friends"].toList()) {
355 QMap<QString, QVariant> friendMap = friendsVariant.toMap();
356 QVariant distance = friendMap["distance"];
357 QMap<QString, QVariant> distanceMap = distance.toMap();
359 GeoCoordinate coordinates(friendMap["latitude"].toReal(),friendMap["longitude"].toReal());
361 QUrl imageUrl = friendMap["profile_pic"].toUrl();
363 if(imageUrl.isEmpty()) {
364 // friend doesn't have profile image, so we need to get him a silhouette image
365 m_defaultImage = true;
368 QString address = friendMap["address"].toString();
369 if(address.isEmpty()) {
370 QStringList location;
371 location.append(QString::number(coordinates.latitude()));
372 location.append(QString::number(coordinates.longitude()));
373 address = location.join(", ");
376 User buddy = User(address, coordinates, friendMap["name"].toString(),
377 friendMap["note"].toString(), imageUrl,
378 friendMap["timestamp"].toString(),
379 false, friendMap["uid"].toString(), distanceMap["units"].toString(),
380 distanceMap["value"].toDouble());
382 tmpFriendsList.append(buddy);
385 QList<QUrl> imageUrlList; // url list for images
387 // set unchanged profile images or add new images to imageUrlList for downloading
389 if(m_user->profileImageUrl() != user.profileImageUrl()) {
390 if(!user.profileImageUrl().isEmpty())
391 imageUrlList.append(user.profileImageUrl());
393 user.setProfileImage(m_user->profileImage());
396 if(!user.profileImageUrl().isEmpty())
397 imageUrlList.append(user.profileImageUrl());
400 // clear old user object
406 // create new user object from temporary user object
407 m_user = new User(user);
409 // set unchanged profile images or add new images to imageUrlList for downloading
410 if(!m_friendsList.isEmpty()) {
411 foreach(User tmpBuddy, tmpFriendsList) {
412 if(!tmpBuddy.profileImageUrl().isEmpty()) {
414 foreach(User *buddy, m_friendsList) {
415 if(tmpBuddy.profileImageUrl() == buddy->profileImageUrl()) {
416 tmpBuddy.setProfileImage(buddy->profileImage());
421 if(!found && !tmpBuddy.profileImageUrl().isEmpty())
422 imageUrlList.append(tmpBuddy.profileImageUrl());
426 foreach(User buddy, tmpFriendsList) {
427 if(!buddy.profileImageUrl().isEmpty())
428 imageUrlList.append(buddy.profileImageUrl());
432 // clear old friendlist
433 qDeleteAll(m_friendsList.begin(), m_friendsList.end());
434 m_friendsList.clear();
436 // populate new friendlist with temporary friendlist's data
437 foreach(User tmpFriendItem, tmpFriendsList) {
438 User *friendItem = new User(tmpFriendItem);
439 m_friendsList.append(friendItem);
441 tmpFriendsList.clear();
443 emit userDataChanged(m_user, m_friendsList);
445 // set silhouette image to imageUrlList for downloading
447 imageUrlList.append(QUrl(SILHOUETTE_URL));
449 addProfileImages(imageUrlList);
450 imageUrlList.clear();
452 QVariant address = result.value("address");
453 if(!address.toString().isEmpty()) {
454 emit reverseGeoReady(address.toString());
456 QStringList coordinates;
457 coordinates.append(result.value("lat").toString());
458 coordinates.append(result.value("lon").toString());
460 emit error(ErrorContext::SITUARE, SituareError::ADDRESS_RETRIEVAL_FAILED);
461 emit reverseGeoReady(coordinates.join(", "));
467 void SituareService::requestFinished(QNetworkReply *reply)
469 qDebug() << __PRETTY_FUNCTION__;
472 if (m_currentRequests.contains(reply)) {
474 qDebug() << "BytesAvailable: " << reply->bytesAvailable();
476 if (reply->error()) {
477 emit error(ErrorContext::NETWORK, reply->error());
479 QByteArray replyArray = reply->readAll();
480 qDebug() << "Reply from: " << reply->url() << "reply " << replyArray;
482 if(replyArray == ERROR_LAT.toAscii()) {
483 qDebug() << "Error: " << ERROR_LAT;
484 emit error(ErrorContext::SITUARE, SituareError::UPDATE_FAILED);
485 } else if(replyArray == ERROR_LON.toAscii()) {
486 qDebug() << "Error: " << ERROR_LON;
487 emit error(ErrorContext::SITUARE, SituareError::UPDATE_FAILED);
488 } else if(replyArray.contains(ERROR_SESSION.toAscii())) {
489 qDebug() << "Error: " << ERROR_SESSION;
490 emit error(ErrorContext::SITUARE, SituareError::SESSION_EXPIRED);
491 } else if(replyArray.startsWith(OPENING_BRACE_MARK.toAscii())) {
492 qDebug() << "JSON string";
493 parseUserData(replyArray);
494 } else if(replyArray.isEmpty()) {
495 if(reply->url().toString().contains(UPDATE_LOCATION.toAscii())) {
496 emit updateWasSuccessful();
498 // session credentials are invalid
499 emit error(ErrorContext::SITUARE, SituareError::SESSION_EXPIRED);
503 emit error(ErrorContext::SITUARE, SituareError::ERROR_GENERAL);
506 m_currentRequests.removeAll(reply);
507 reply->deleteLater();
511 void SituareService::reverseGeo(const GeoCoordinate &coordinates)
513 qDebug() << __PRETTY_FUNCTION__;
515 QHash<QString, QString> parameters;
516 parameters.insert("lat", degreesToString(coordinates.latitude()));
517 parameters.insert("lon", degreesToString(coordinates.longitude()));
518 parameters.insert("format", "json");
520 buildRequest(REVERSE_GEO, parameters);
523 void SituareService::sendRequest(const QUrl &url, const QString &cookieType, const QString &cookie)
525 qDebug() << __PRETTY_FUNCTION__;
527 QNetworkRequest request;
530 request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
531 request.setRawHeader(cookieType.toAscii(), cookie.toUtf8());
533 QNetworkReply *reply = m_networkManager->get(request, true);
535 m_currentRequests.append(reply);
538 void SituareService::sendRequest(const QString &requestUrl)
540 qWarning() << __PRETTY_FUNCTION__ << "requestUrl" << requestUrl;
542 // make and send the request
543 QNetworkRequest request;
544 request.setUrl(QUrl(requestUrl));
545 request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
546 QNetworkReply *reply = m_networkManager->get(request, true);
547 m_currentRequests.append(reply);
550 void SituareService::updateSession(const QString &session)
552 qWarning() << __PRETTY_FUNCTION__;
556 foreach (QString request, m_requestsWaitingAccessToken) {
557 appendAccessToken(request);
558 sendRequest(request);
561 m_requestsWaitingAccessToken.clear();
564 void SituareService::updateLocation(const GeoCoordinate &coordinates, const QString &status,
567 qDebug() << __PRETTY_FUNCTION__;
569 QHash<QString, QString> parameters;
570 parameters.insert("lat", degreesToString(coordinates.latitude()));
571 parameters.insert("lon", degreesToString(coordinates.longitude()));
572 parameters.insert("publish", publish ? "true" : "false");
573 parameters.insert("data", status); ///< @todo if !empty ???
575 buildRequest(UPDATE_LOCATION, parameters);