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 qWarning() << __PRETTY_FUNCTION__ << "Server reply:" << jsonReply;
313 m_defaultImage = false;
315 QJson::Parser parser;
318 QVariantMap result = parser.parse (jsonReply, &ok).toMap();
320 emit error(ErrorContext::SITUARE, SituareError::INVALID_JSON);
324 if(result.contains("ErrorCode")) {
325 QVariant errorVariant = result.value("ErrorCode");
326 emit error(ErrorContext::SITUARE, errorVariant.toInt());
328 } else if(result.contains("user")) {
330 QVariant userVariant = result.value("user");
331 QMap<QString, QVariant> userMap = userVariant.toMap();
333 GeoCoordinate coordinates(userMap["latitude"].toReal(), userMap["longitude"].toReal());
335 QUrl imageUrl = userMap[NORMAL_SIZE_PROFILE_IMAGE].toUrl();
337 if(imageUrl.isEmpty()) {
338 // user doesn't have profile image, so we need to get him a silhouette image
339 m_defaultImage = true;
342 QString address = userMap["address"].toString();
343 if(address.isEmpty()) {
344 QStringList location;
345 location.append(QString::number(coordinates.latitude()));
346 location.append(QString::number(coordinates.longitude()));
347 address = location.join(", ");
350 User user = User(address, coordinates, userMap["name"].toString(),
351 userMap["note"].toString(), imageUrl, userMap["timestamp"].toString(),
352 true, userMap["uid"].toString());
354 QList<User> tmpFriendsList;
356 foreach (QVariant friendsVariant, result["friends"].toList()) {
357 QMap<QString, QVariant> friendMap = friendsVariant.toMap();
358 QVariant distance = friendMap["distance"];
359 QMap<QString, QVariant> distanceMap = distance.toMap();
361 GeoCoordinate coordinates(friendMap["latitude"].toReal(),friendMap["longitude"].toReal());
363 QUrl imageUrl = friendMap["profile_pic"].toUrl();
365 if(imageUrl.isEmpty()) {
366 // friend doesn't have profile image, so we need to get him a silhouette image
367 m_defaultImage = true;
370 QString address = friendMap["address"].toString();
371 if(address.isEmpty()) {
372 QStringList location;
373 location.append(QString::number(coordinates.latitude()));
374 location.append(QString::number(coordinates.longitude()));
375 address = location.join(", ");
378 User buddy = User(address, coordinates, friendMap["name"].toString(),
379 friendMap["note"].toString(), imageUrl,
380 friendMap["timestamp"].toString(),
381 false, friendMap["uid"].toString(), distanceMap["units"].toString(),
382 distanceMap["value"].toDouble());
384 tmpFriendsList.append(buddy);
387 QList<QUrl> imageUrlList; // url list for images
389 // set unchanged profile images or add new images to imageUrlList for downloading
391 if(m_user->profileImageUrl() != user.profileImageUrl()) {
392 if(!user.profileImageUrl().isEmpty())
393 imageUrlList.append(user.profileImageUrl());
395 user.setProfileImage(m_user->profileImage());
398 if(!user.profileImageUrl().isEmpty())
399 imageUrlList.append(user.profileImageUrl());
402 // clear old user object
408 // create new user object from temporary user object
409 m_user = new User(user);
411 // set unchanged profile images or add new images to imageUrlList for downloading
412 if(!m_friendsList.isEmpty()) {
413 foreach(User tmpBuddy, tmpFriendsList) {
414 if(!tmpBuddy.profileImageUrl().isEmpty()) {
416 foreach(User *buddy, m_friendsList) {
417 if(tmpBuddy.profileImageUrl() == buddy->profileImageUrl()) {
418 tmpBuddy.setProfileImage(buddy->profileImage());
423 if(!found && !tmpBuddy.profileImageUrl().isEmpty())
424 imageUrlList.append(tmpBuddy.profileImageUrl());
428 foreach(User buddy, tmpFriendsList) {
429 if(!buddy.profileImageUrl().isEmpty())
430 imageUrlList.append(buddy.profileImageUrl());
434 // clear old friendlist
435 qDeleteAll(m_friendsList.begin(), m_friendsList.end());
436 m_friendsList.clear();
438 // populate new friendlist with temporary friendlist's data
439 foreach(User tmpFriendItem, tmpFriendsList) {
440 User *friendItem = new User(tmpFriendItem);
441 m_friendsList.append(friendItem);
443 tmpFriendsList.clear();
445 emit userDataChanged(m_user, m_friendsList);
447 // set silhouette image to imageUrlList for downloading
449 imageUrlList.append(QUrl(SILHOUETTE_URL));
451 addProfileImages(imageUrlList);
452 imageUrlList.clear();
454 QVariant address = result.value("address");
455 if(!address.toString().isEmpty()) {
456 emit reverseGeoReady(address.toString());
458 QStringList coordinates;
459 coordinates.append(result.value("lat").toString());
460 coordinates.append(result.value("lon").toString());
462 emit error(ErrorContext::SITUARE, SituareError::ADDRESS_RETRIEVAL_FAILED);
463 emit reverseGeoReady(coordinates.join(", "));
469 void SituareService::requestFinished(QNetworkReply *reply)
471 qDebug() << __PRETTY_FUNCTION__;
474 if (m_currentRequests.contains(reply)) {
476 qDebug() << "BytesAvailable: " << reply->bytesAvailable();
478 if (reply->error()) {
479 emit error(ErrorContext::NETWORK, reply->error());
481 QByteArray replyArray = reply->readAll();
482 qDebug() << "Reply from: " << reply->url() << "reply " << replyArray;
484 if(replyArray == ERROR_LAT.toAscii()) {
485 qDebug() << "Error: " << ERROR_LAT;
486 emit error(ErrorContext::SITUARE, SituareError::UPDATE_FAILED);
487 } else if(replyArray == ERROR_LON.toAscii()) {
488 qDebug() << "Error: " << ERROR_LON;
489 emit error(ErrorContext::SITUARE, SituareError::UPDATE_FAILED);
490 } else if(replyArray.contains(ERROR_SESSION.toAscii())) {
491 qDebug() << "Error: " << ERROR_SESSION;
492 emit error(ErrorContext::SITUARE, SituareError::SESSION_EXPIRED);
493 } else if(replyArray.startsWith(OPENING_BRACE_MARK.toAscii())) {
494 qDebug() << "JSON string";
495 parseUserData(replyArray);
496 } else if(replyArray.isEmpty()) {
497 if(reply->url().toString().contains(UPDATE_LOCATION.toAscii())) {
498 emit updateWasSuccessful();
500 // session credentials are invalid
501 emit error(ErrorContext::SITUARE, SituareError::SESSION_EXPIRED);
505 emit error(ErrorContext::SITUARE, SituareError::ERROR_GENERAL);
508 m_currentRequests.removeAll(reply);
509 reply->deleteLater();
513 void SituareService::reverseGeo(const GeoCoordinate &coordinates)
515 qDebug() << __PRETTY_FUNCTION__;
517 QHash<QString, QString> parameters;
518 parameters.insert("lat", degreesToString(coordinates.latitude()));
519 parameters.insert("lon", degreesToString(coordinates.longitude()));
520 parameters.insert("format", "json");
522 buildRequest(REVERSE_GEO, parameters);
525 void SituareService::sendRequest(const QUrl &url, const QString &cookieType, const QString &cookie)
527 qDebug() << __PRETTY_FUNCTION__;
529 QNetworkRequest request;
532 request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
533 request.setRawHeader(cookieType.toAscii(), cookie.toUtf8());
535 QNetworkReply *reply = m_networkManager->get(request, true);
537 m_currentRequests.append(reply);
540 void SituareService::sendRequest(const QString &requestUrl)
542 qWarning() << __PRETTY_FUNCTION__ << "requestUrl" << requestUrl;
544 // make and send the request
545 QNetworkRequest request;
546 request.setUrl(QUrl(requestUrl));
547 request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
548 QNetworkReply *reply = m_networkManager->get(request, true);
549 m_currentRequests.append(reply);
552 void SituareService::updateSession(const QString &session)
554 qWarning() << __PRETTY_FUNCTION__;
558 foreach (QString request, m_requestsWaitingAccessToken) {
559 appendAccessToken(request);
560 sendRequest(request);
563 m_requestsWaitingAccessToken.clear();
566 void SituareService::updateLocation(const GeoCoordinate &coordinates, const QString &status,
569 qDebug() << __PRETTY_FUNCTION__;
571 QHash<QString, QString> parameters;
572 parameters.insert("lat", degreesToString(coordinates.latitude()));
573 parameters.insert("lon", degreesToString(coordinates.longitude()));
574 parameters.insert("publish", publish ? "true" : "false");
575 parameters.insert("data", status); ///< @todo if !empty ???
577 buildRequest(UPDATE_LOCATION, parameters);