2 Situare - A location system for Facebook
3 Copyright (C) 2010 Ixonos Plc. Authors:
5 Henri Lampela - henri.lampela@ixonos.com
6 Sami Rämö - sami.ramo@ixonos.com
8 Situare is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 version 2 as published by the Free Software Foundation.
12 Situare is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with Situare; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
23 #include <qjson/parser.h>
26 #include <QtNetwork/QNetworkReply>
28 #include <QStringList>
29 #include <QtAlgorithms>
34 #include "network/networkaccessmanager.h"
35 #include "situarecommon.h"
36 #include "ui/avatarimage.h"
38 #include "situareservice.h"
40 SituareService::SituareService(NetworkAccessManager *networkManager, ImageFetcher *imageFetcher,
45 qDebug() << __PRETTY_FUNCTION__;
47 m_networkManager = networkManager;
48 connect(m_networkManager, SIGNAL(finished(QNetworkReply*)),
49 this, SLOT(requestFinished(QNetworkReply*)), Qt::QueuedConnection);
51 m_imageFetcher = imageFetcher;
52 connect(this, SIGNAL(fetchImage(QString, QUrl)),
53 m_imageFetcher, SLOT(fetchImage(QString, QUrl)));
54 connect(m_imageFetcher, SIGNAL(imageReceived(QString,QPixmap)),
55 this, SLOT(imageReceived(QString,QPixmap)));
56 connect(m_imageFetcher, SIGNAL(error(int, int)),
57 this, SIGNAL(error(int, int)));
59 m_database = new Database(this);
60 m_database->openDatabase();
61 m_database->createNotificationTable();
62 m_database->createUserTable();
63 m_database->createTagTable();
64 m_database->createUserTagTable();
67 SituareService::~SituareService()
69 qDebug() << __PRETTY_FUNCTION__;
76 qDeleteAll(m_friendsList.begin(), m_friendsList.end());
77 m_friendsList.clear();
80 void SituareService::addProfileImages(const QHash<QString, QUrl> &imageUrlList)
82 qDebug() << __PRETTY_FUNCTION__;
84 QHashIterator<QString, QUrl> imageUrlListIterator(imageUrlList);
86 while (imageUrlListIterator.hasNext()) {
87 imageUrlListIterator.next();
88 emit fetchImage(imageUrlListIterator.key(), imageUrlListIterator.value());
92 void SituareService::addTags(const QStringList &tags)
94 qDebug() << __PRETTY_FUNCTION__;
96 QHash<QString, QString> parameters;
97 parameters.insert("tags", tags.join(","));
99 buildRequest(ADD_TAGS, parameters);
102 void SituareService::appendAccessToken(QString &requestUrl)
104 qDebug() << __PRETTY_FUNCTION__;
106 requestUrl.append(m_session);
109 void SituareService::buildRequest(const QString &script, const QHash<QString, QString> ¶meters)
111 qDebug() << __PRETTY_FUNCTION__;
113 const QString PARAMETER_KEY_API = "api";
114 const QString PARAMETER_VALUE_API = "2.0";
116 QString url = SITUARE_URL;
120 // append default api version parameter if not yet specified
121 if (!parameters.contains(PARAMETER_KEY_API))
122 url.append(PARAMETER_KEY_API + "=" + PARAMETER_VALUE_API + "&");
125 if (!parameters.isEmpty()) {
126 QHash<QString, QString>::const_iterator i = parameters.constBegin();
127 while (i != parameters.constEnd()) {
130 url.append(i.value());
136 /// @todo BUG: Url parameter strings are not url escaped
138 // qWarning() << __PRETTY_FUNCTION__ << "request url with parameters:" << url;
140 if (!m_session.isEmpty()) {
141 appendAccessToken(url);
144 emit error(ErrorContext::SITUARE, SituareError::SESSION_EXPIRED);
148 void SituareService::clearUserData()
150 qDebug() << __PRETTY_FUNCTION__;
152 qDeleteAll(m_friendsList.begin(), m_friendsList.end());
153 m_friendsList.clear();
159 emit userDataChanged(m_user, m_friendsList);
162 QString SituareService::degreesToString(double degrees)
164 qDebug() << __PRETTY_FUNCTION__;
166 // one scene pixel is about 5.4e-6 degrees, the integer part is max three digits and one
167 // additional digit is added for maximum precision
168 const int PRECISION = 10;
170 return QString::number(degrees, 'f', PRECISION);
173 void SituareService::fetchMessages()
175 qDebug() << __PRETTY_FUNCTION__;
177 buildRequest(GET_MESSAGES, QHash<QString, QString>());
180 void SituareService::fetchPeopleWithSimilarInterest(const qreal distance)
182 qDebug() << __PRETTY_FUNCTION__;
184 const int TO_KM_DIVIDER = 1000;
185 QHash<QString, QString> parameters;
186 parameters.insert("distance", degreesToString(distance / TO_KM_DIVIDER));
188 buildRequest(GET_PEOPLE_WITH_SIMILAR_INTEREST, parameters);
191 void SituareService::fetchPopularTags()
193 qDebug() << __PRETTY_FUNCTION__;
195 buildRequest(GET_POPULAR_TAGS, QHash<QString, QString>());
198 void SituareService::fetchLocations()
200 qDebug() << __PRETTY_FUNCTION__;
202 QHash<QString, QString> parameters;
203 parameters.insert("extra_user_data", NORMAL_SIZE_PROFILE_IMAGE);
205 buildRequest(GET_LOCATIONS, parameters);
208 SituareService::RequestName SituareService::getRequestName(const QUrl &url) const
210 qDebug() << __PRETTY_FUNCTION__;
212 if (url.toString().contains(GET_LOCATIONS))
213 return SituareService::RequestGetLocations;
214 else if (url.toString().contains(UPDATE_LOCATION))
215 return SituareService::RequestUpdateLocation;
216 else if (url.toString().contains(REVERSE_GEO))
217 return SituareService::RequestReverseGeo;
218 else if (url.toString().contains(GET_MESSAGES))
219 return SituareService::RequestGetMessages;
220 else if (url.toString().contains(REMOVE_MESSAGE))
221 return SituareService::RequestRemoveMessage;
222 else if (url.toString().contains(SEND_MESSAGE))
223 return SituareService::RequestSendMessage;
224 else if (url.toString().contains(GET_PEOPLE_WITH_SIMILAR_INTEREST))
225 return SituareService::RequestGetPeopleWithSimilarInterest;
226 else if (url.toString().contains(GET_POPULAR_TAGS))
227 return SituareService::RequestGetPopularTags;
228 else if (url.toString().contains(ADD_TAGS))
229 return SituareService::RequestAddTags;
230 else if (url.toString().contains(GET_TAGS))
231 return SituareService::RequestGetTags;
232 else if (url.toString().contains(REMOVE_TAGS))
233 return SituareService::RequestRemoveTags;
234 else if (url.toString().contains(SEARCH_PEOPLE_WITH_TAG))
235 return SituareService::RequestGetPeopleWithTag;
237 return SituareService::RequestUnknown;
240 void SituareService::getTags()
242 qDebug() << __PRETTY_FUNCTION__;
244 QHash<QString, QString> parameters;
245 buildRequest(GET_TAGS, parameters);
248 void SituareService::imageReceived(const QString &id, const QPixmap &image)
250 qDebug() << __PRETTY_FUNCTION__;
252 if (m_user->userId() == id)
253 emit imageReady(QString(), AvatarImage::create(image, AvatarImage::Large));
255 emit imageReady(id, AvatarImage::create(image, AvatarImage::Small));
258 void SituareService::parseInterestingPeopleData(const QVariant &interestingPeopleData)
260 qDebug() << __PRETTY_FUNCTION__;
262 QVariant people = interestingPeopleData.toMap();
267 foreach (QVariant personVariant, people.toMap().value("friends").toList()) {
269 QMap<QString, QVariant> person = personVariant.toMap();
270 user.setUserId(person["uid"].toString());
271 user.setName(person["name"].toString());
272 user.setProfileImage(AvatarImage::create(
273 QPixmap(":/res/images/empty_avatar.png"), AvatarImage::Small));
274 user.setProfileImageUrl(person["pic_square"].toUrl());
275 user.setTimestamp(person["timestamp"].toString());
276 user.setTags(person["tags"].toString());
277 user.setAddress(person["address"].toString());
278 user.setNote(person["note"].toString());
281 qreal latitude = person["lat"].toReal(&latOk);
283 qreal longitude = person["lon"].toReal(&lonOk);
285 if (latOk && lonOk) {
286 user.setCoordinates(GeoCoordinate(latitude, longitude));
288 qreal meters = person["distance"].toReal() * 1000;
298 user.setDistance(value, unit);
301 friends.append(user);
303 //Remove comment when the actual server is used
304 //emit fetchImage(user.userId(), user.profileImageUrl());
307 foreach (QVariant personVariant, people.toMap().value("others").toList()) {
309 QMap<QString, QVariant> person = personVariant.toMap();
310 user.setUserId(person["uid"].toString());
311 user.setName(person["name"].toString());
312 user.setProfileImage(AvatarImage::create(
313 QPixmap(":/res/images/empty_avatar.png"), AvatarImage::Small));
314 user.setProfileImageUrl(person["pic_square"].toUrl());
315 user.setTimestamp(person["timestamp"].toString());
316 user.setTags(person["tags"].toString());
320 emit fetchImage(user.userId(), user.profileImageUrl());
323 emit interestingPeopleReceived(friends, others);
326 void SituareService::parseReply(const QByteArray &jsonReply, RequestName requestName)
328 qDebug() << __PRETTY_FUNCTION__ << jsonReply;
330 QJson::Parser parser;
333 QVariantMap result = parser.parse(jsonReply, &ok).toMap();
336 emit error(ErrorContext::SITUARE, SituareError::INVALID_JSON);
338 QVariant resultStatus = result["ResultStatus"];
339 QVariant resultData = result["ResultData"];
341 if (resultStatus.toString() == "ERROR") {
342 QVariantMap errorData = resultData.toMap();
343 emit error(ErrorContext::SITUARE, errorData["ErrorCode"].toInt());
344 } else if (resultStatus.toString() == "OK") {
345 if (requestName == SituareService::RequestGetLocations)
346 parseUserData(resultData);
347 else if (requestName == SituareService::RequestUpdateLocation)
348 emit updateWasSuccessful(SituareService::SuccessfulUpdateLocation);
349 else if (requestName == SituareService::RequestRemoveMessage)
350 emit updateWasSuccessful(SituareService::SuccessfulRemoveMessage);
351 else if (requestName == SituareService::RequestReverseGeo)
352 parseReverseGeoData(resultData);
353 else if (requestName == SituareService::RequestGetMessages)
354 parseMessagesData(resultData);
355 else if (requestName == SituareService::RequestSendMessage)
356 emit updateWasSuccessful((SituareService::SuccessfulSendMessage));
357 else if (requestName == SituareService::RequestGetPeopleWithSimilarInterest)
358 parseInterestingPeopleData(resultData);
359 else if (requestName == SituareService::RequestGetPopularTags)
360 parsePopularTagsData(resultData);
361 else if (requestName == SituareService::RequestAddTags)
362 emit updateWasSuccessful(SituareService::SuccessfulAddTags);
363 else if (requestName == SituareService::RequestGetTags)
364 parseUserTagsData(resultData);
365 else if (requestName == SituareService::RequestRemoveTags)
366 emit updateWasSuccessful(SituareService::SuccessfulRemoveTags);
367 else if (requestName == SituareService::RequestGetPeopleWithTag)
368 parseInterestingPeopleData(resultData);
373 void SituareService::parseReverseGeoData(const QVariant &reverseGeoData)
375 qDebug() << __PRETTY_FUNCTION__;
377 QVariantMap result = reverseGeoData.toMap();
379 if (result.contains("address") && !result["address"].toString().isEmpty()) {
380 emit reverseGeoReady(result["address"].toString());
382 QStringList coordinates;
383 coordinates.append(result["lat"].toString());
384 coordinates.append(result["lon"].toString());
385 emit error(ErrorContext::SITUARE, SituareError::ADDRESS_RETRIEVAL_FAILED);
386 emit reverseGeoReady(coordinates.join(", "));
390 void SituareService::parseUserData(const QVariant &userData)
392 qDebug() << __PRETTY_FUNCTION__;
394 m_defaultImage = false;
396 QVariantMap result = userData.toMap();
398 if (result.contains("user")) {
400 QVariant userVariant = result.value("user");
401 QMap<QString, QVariant> userMap = userVariant.toMap();
403 GeoCoordinate coordinates(userMap["latitude"].toReal(), userMap["longitude"].toReal());
405 QUrl imageUrl = userMap[NORMAL_SIZE_PROFILE_IMAGE].toUrl();
407 if(imageUrl.isEmpty()) {
408 // user doesn't have profile image, so we need to get him a silhouette image
409 m_defaultImage = true;
412 QString address = userMap["address"].toString();
413 if(address.isEmpty()) {
414 QStringList location;
415 location.append(QString::number(coordinates.latitude()));
416 location.append(QString::number(coordinates.longitude()));
417 address = location.join(", ");
420 User user = User(address, coordinates, userMap["name"].toString(),
421 userMap["note"].toString(), imageUrl, userMap["timestamp"].toString(),
422 true, userMap["uid"].toString());
424 if(imageUrl.isEmpty()) {
425 // user doesn't have profile image, so we need to get him a silhouette image
426 m_defaultImage = true;
427 user.setProfileImage(AvatarImage::create(
428 QPixmap(":/res/images/empty_avatar_big.png"), AvatarImage::Large));
431 QList<User> tmpFriendsList;
433 foreach (QVariant friendsVariant, result["friends"].toList()) {
434 QMap<QString, QVariant> friendMap = friendsVariant.toMap();
435 QVariant distance = friendMap["distance"];
436 QMap<QString, QVariant> distanceMap = distance.toMap();
438 GeoCoordinate coordinates(friendMap["latitude"].toReal(),friendMap["longitude"].toReal());
440 QUrl imageUrl = friendMap["profile_pic"].toUrl();
442 if(imageUrl.isEmpty()) {
443 // friend doesn't have profile image, so we need to get him a silhouette image
444 m_defaultImage = true;
447 QString address = friendMap["address"].toString();
448 if(address.isEmpty()) {
449 QStringList location;
450 location.append(QString::number(coordinates.latitude()));
451 location.append(QString::number(coordinates.longitude()));
452 address = location.join(", ");
455 User buddy = User(address, coordinates, friendMap["name"].toString(),
456 friendMap["note"].toString(), imageUrl,
457 friendMap["timestamp"].toString(),
458 false, friendMap["uid"].toString(), distanceMap["units"].toString(),
459 distanceMap["value"].toDouble());
461 if(imageUrl.isEmpty()) {
462 // friend doesn't have profile image, so we need to get him a silhouette image
463 m_defaultImage = true;
464 buddy.setProfileImage(AvatarImage::create(
465 QPixmap(":/res/images/empty_avatar.png"), AvatarImage::Small));
468 tmpFriendsList.append(buddy);
471 QHash<QString, QUrl> imageUrlList; // url list for images
473 // set unchanged profile images or add new images to imageUrlList for downloading
475 if(m_user->profileImageUrl() != user.profileImageUrl()) {
476 if(!user.profileImageUrl().isEmpty())
477 imageUrlList.insert(user.userId(), user.profileImageUrl());
479 user.setProfileImage(m_user->profileImage());
482 if(!user.profileImageUrl().isEmpty())
483 imageUrlList.insert(user.userId(), user.profileImageUrl());
486 // clear old user object
492 // create new user object from temporary user object
493 m_user = new User(user);
495 // set unchanged profile images or add new images to imageUrlList for downloading
496 if(!m_friendsList.isEmpty()) {
497 foreach(User tmpBuddy, tmpFriendsList) {
498 if(!tmpBuddy.profileImageUrl().isEmpty()) {
500 foreach(User *buddy, m_friendsList) {
501 if(tmpBuddy.profileImageUrl() == buddy->profileImageUrl()) {
502 tmpBuddy.setProfileImage(buddy->profileImage());
507 if(!found && !tmpBuddy.profileImageUrl().isEmpty())
508 imageUrlList.insert(tmpBuddy.userId(), tmpBuddy.profileImageUrl());
512 foreach(User buddy, tmpFriendsList) {
513 if(!buddy.profileImageUrl().isEmpty())
514 imageUrlList.insert(buddy.userId(), buddy.profileImageUrl());
518 // clear old friendlist
519 qDeleteAll(m_friendsList.begin(), m_friendsList.end());
520 m_friendsList.clear();
522 // populate new friendlist with temporary friendlist's data
523 foreach(User tmpFriendItem, tmpFriendsList) {
524 User *friendItem = new User(tmpFriendItem);
525 m_friendsList.append(friendItem);
527 tmpFriendsList.clear();
529 emit userDataChanged(m_user, m_friendsList);
531 // set silhouette image to imageUrlList for downloading
533 imageUrlList.insert("", QUrl(SILHOUETTE_URL));
535 addProfileImages(imageUrlList);
536 imageUrlList.clear();
540 void SituareService::parseMessagesData(const QVariant &messagesData)
542 QVariantMap result = messagesData.toMap();
544 QList<Message> received;
547 foreach (QVariant messageVariant, result["received"].toList()) {
548 Message message(Message::MessageTypeReceived);
549 QMap<QString, QVariant> messageMap = messageVariant.toMap();
550 message.setId(messageMap["mid"].toString());
551 message.setSenderId(messageMap["sid"].toString());
552 message.setReceiverId(messageMap["rid"].toString());
553 message.setSenderName(messageMap["name"].toString());
554 uint timestampSeconds = messageMap["timestamp"].toUInt();
555 message.setTimestamp(QDateTime::fromTime_t(timestampSeconds));
556 message.setText(messageMap["message"].toString());
557 message.setImage(AvatarImage::create(
558 QPixmap(":/res/images/empty_avatar.png"), AvatarImage::Small));
561 qreal latitude = messageMap["lat"].toReal(&latOk);
563 qreal longitude = messageMap["lon"].toReal(&lonOk);
565 if (latOk && lonOk) {
566 message.setAddress(messageMap["address"].toString());
567 message.setCoordinates(GeoCoordinate(latitude, longitude));
570 received.append(message);
572 emit fetchImage(message.id(), messageMap["pic_square"].toString());
575 foreach (QVariant messageVariant, result["sent"].toList()) {
576 Message message(Message::MessageTypeSent);
577 QMap<QString, QVariant> messageMap = messageVariant.toMap();
578 message.setId(messageMap["mid"].toString());
579 message.setSenderId(messageMap["sid"].toString());
580 message.setReceiverId(messageMap["rid"].toString());
581 message.setSenderName(messageMap["name"].toString());
582 uint timestampSeconds = messageMap["timestamp"].toUInt();
583 message.setTimestamp(QDateTime::fromTime_t(timestampSeconds));
584 message.setText(messageMap["message"].toString());
585 message.setImage(AvatarImage::create(
586 QPixmap(":/res/images/empty_avatar.png"), AvatarImage::Small));
589 qreal latitude = messageMap["lat"].toReal(&latOk);
591 qreal longitude = messageMap["lon"].toReal(&lonOk);
593 if (latOk && lonOk) {
594 message.setAddress(messageMap["address"].toString());
595 message.setCoordinates(GeoCoordinate(latitude, longitude));
598 sent.append(message);
600 emit fetchImage(message.id(), messageMap["pic_square"].toString());
603 emit messagesReceived(received, sent);
606 void SituareService::parsePopularTagsData(const QVariant &popularTagsData)
608 qDebug() << __PRETTY_FUNCTION__;
610 QVariantMap result = popularTagsData.toMap();
611 QHash<QString, QString> popularTags;
613 foreach (QVariant tagVariant, result["popular_tags"].toList()) {
614 QMap<QString, QVariant> tag = tagVariant.toMap();
615 popularTags.insert(tag["tid"].toString(), tag["name"].toString());
618 emit popularTagsReceived(popularTags);
621 void SituareService::parseUserTagsData(const QVariant &userTagsData)
623 qDebug() << __PRETTY_FUNCTION__;
625 QVariantMap result = userTagsData.toMap();
626 QHash<QString, QString> userTags;
628 foreach (QVariant tagVariant, result["user_tags"].toList()) {
629 QMap<QString, QVariant> tag = tagVariant.toMap();
630 userTags.insert(tag["tid"].toString(), tag["name"].toString());
633 emit userTagsReceived(userTags);
636 void SituareService::removeMessage(const QString &id)
638 qDebug() << __PRETTY_FUNCTION__;
640 QHash<QString, QString> parameters;
641 parameters.insert("mid", id);
643 buildRequest(REMOVE_MESSAGE, parameters);
647 void SituareService::removeTags(const QStringList &tags)
649 qDebug() << __PRETTY_FUNCTION__;
651 QHash<QString, QString> parameters;
652 parameters.insert("tags", tags.join(","));
654 buildRequest(REMOVE_TAGS, parameters);
657 void SituareService::requestFinished(QNetworkReply *reply)
659 qDebug() << __PRETTY_FUNCTION__;
662 if (m_currentRequests.contains(reply)) {
664 emit error(ErrorContext::NETWORK, reply->error());
666 parseReply(reply->readAll(), getRequestName(reply->url()));
668 m_currentRequests.removeAll(reply);
669 reply->deleteLater();
673 void SituareService::reverseGeo(const GeoCoordinate &coordinates)
675 qDebug() << __PRETTY_FUNCTION__;
677 QHash<QString, QString> parameters;
678 parameters.insert("lat", degreesToString(coordinates.latitude()));
679 parameters.insert("lon", degreesToString(coordinates.longitude()));
680 parameters.insert("format", "json");
682 buildRequest(REVERSE_GEO, parameters);
685 void SituareService::searchPeopleByTag(const QString &tag)
687 qDebug() << __PRETTY_FUNCTION__;
689 QHash<QString, QString> parameters;
690 parameters.insert("tag",tag);
692 buildRequest(SEARCH_PEOPLE_WITH_TAG, parameters);
695 void SituareService::sendMessage(const QString &receiverId, const QString &message,
696 const GeoCoordinate &coordinates)
698 qDebug() << __PRETTY_FUNCTION__;
700 QHash<QString, QString> parameters;
701 parameters.insert("rid", receiverId);
702 parameters.insert("message", message);
704 if (coordinates.isValid()) {
705 parameters.insert("lat", degreesToString(coordinates.latitude()));
706 parameters.insert("lon", degreesToString(coordinates.longitude()));
709 buildRequest(SEND_MESSAGE, parameters);
712 void SituareService::sendRequest(const QString &requestUrl)
714 qDebug() << __PRETTY_FUNCTION__ << "requestUrl" << requestUrl;
716 // make and send the request
717 QNetworkRequest request;
718 request.setUrl(QUrl(requestUrl));
719 request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
720 QNetworkReply *reply = m_networkManager->get(request, true);
721 m_currentRequests.append(reply);
724 void SituareService::updateSession(const QString &session)
726 qDebug() << __PRETTY_FUNCTION__;
730 if (m_session.isEmpty())
734 void SituareService::updateLocation(const GeoCoordinate &coordinates, const QString &status,
737 qDebug() << __PRETTY_FUNCTION__;
739 QHash<QString, QString> parameters;
740 parameters.insert("lat", degreesToString(coordinates.latitude()));
741 parameters.insert("lon", degreesToString(coordinates.longitude()));
742 parameters.insert("publish", publish ? "true" : "false");
743 parameters.insert("data", status); ///< @todo if !empty ???
745 buildRequest(UPDATE_LOCATION, parameters);