Cleaning out old login code ongoing
[situare] / src / situareservice / situareservice.cpp
1 /*
2    Situare - A location system for Facebook
3    Copyright (C) 2010  Ixonos Plc. Authors:
4
5       Henri Lampela - henri.lampela@ixonos.com
6
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.
10
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.
15
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,
19    USA.
20 */
21
22 #include "parser.h"
23
24 #include <QtAlgorithms>
25 #include <QDebug>
26 #include <QNetworkReply>
27 #include <QPixmap>
28 #include <QStringList>
29 #include <QtGlobal>
30
31 #include "error.h"
32 #include "network/networkaccessmanager.h"
33 #include "situarecommon.h"
34 #include "ui/avatarimage.h"
35
36 #include "situareservice.h"
37
38 SituareService::SituareService(QObject *parent)
39         : QObject(parent),
40         m_user(0)
41 {
42     qDebug() << __PRETTY_FUNCTION__;
43
44     m_networkManager = new NetworkAccessManager(this);
45     connect(m_networkManager, SIGNAL(finished(QNetworkReply*)),
46             this, SLOT(requestFinished(QNetworkReply*)), Qt::QueuedConnection);
47
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)));
55 }
56
57 SituareService::~SituareService()
58 {
59     qDebug() << __PRETTY_FUNCTION__;
60
61     if(m_user) {
62         delete m_user;
63         m_user = 0;
64     }
65
66     qDeleteAll(m_friendsList.begin(), m_friendsList.end());
67     m_friendsList.clear();
68 }
69
70 void SituareService::addProfileImages(const QList<QUrl> &imageUrlList)
71 {
72     qDebug() << __PRETTY_FUNCTION__;
73
74     foreach(QUrl url, imageUrlList) {
75         emit fetchImage(url);
76     }
77 }
78
79 void SituareService::appendAccessToken(QString &requestUrl)
80 {
81     qWarning() << __PRETTY_FUNCTION__;
82
83 //    requestUrl.append("access_token=");
84     requestUrl.append(m_session);
85
86 //    qWarning() << __PRETTY_FUNCTION__ << "request url with parameters and access token:" << requestUrl;
87 }
88
89 void SituareService::buildRequest(const QString &script, const QHash<QString, QString> &parameters)
90 {
91     qWarning() << __PRETTY_FUNCTION__;
92
93     QString url = SITUARE_URL;
94     url.append(script);
95     url.append("?");
96
97     // append parameters
98     if (!parameters.isEmpty()) {
99         QHash<QString, QString>::const_iterator i = parameters.constBegin();
100         while (i != parameters.constEnd()) {
101             url.append(i.key());
102             url.append("=");
103             url.append(i.value());
104             url.append("&");
105             i++;
106         }
107     }
108
109 //    qWarning() << __PRETTY_FUNCTION__ << "request url with parameters:" << url;
110
111     if (!m_session.isEmpty()) {
112         appendAccessToken(url);
113         sendRequest(url);
114     } else {
115         m_requestsWaitingAccessToken.append(url);
116         ///< @todo emit login request
117     }
118 }
119
120 void SituareService::clearUserData()
121 {
122     qDebug() << __PRETTY_FUNCTION__;
123
124     qDeleteAll(m_friendsList.begin(), m_friendsList.end());
125     m_friendsList.clear();
126
127     if(m_user) {
128         delete m_user;
129         m_user = 0;
130     }
131     emit userDataChanged(m_user, m_friendsList);
132 }
133
134 QString SituareService::degreesToString(double degrees)
135 {
136     qDebug() << __PRETTY_FUNCTION__;
137
138     // one scene pixel is about 5.4e-6 degrees, the integer part is max three digits and one
139     // additional digit is added for maximum precision
140     const int PRECISION = 10;
141
142     return QString::number(degrees, 'f', PRECISION);
143 }
144
145 void SituareService::fetchLocations()
146 {
147     qDebug() << __PRETTY_FUNCTION__;
148
149     QHash<QString, QString> parameters;
150     parameters.insert("extra_user_data", NORMAL_SIZE_PROFILE_IMAGE);
151
152     buildRequest(GET_LOCATIONS, parameters);
153 }
154
155 QString SituareService::formCookie(const QString &apiKeyValue, QString expiresValue,
156                                    QString userValue, QString sessionKeyValue,
157                                    QString sessionSecretValue, const QString &signatureValue,
158                                    const QString &localeValue)
159 {
160     qDebug() << __PRETTY_FUNCTION__;
161
162     QString cookie;
163     QString apiKey;
164     QString user;
165     QString expires;
166     QString sessionKey;
167     QString sessionSecret;
168     QString locale;
169     QString variable;
170     QString signature = EQUAL_MARK;
171     QStringList variableList;
172
173     signature.append(signatureValue);
174     apiKey.append(apiKeyValue);
175     apiKey.append(UNDERLINE_MARK);
176
177     user.append(USER);
178     user.append(EQUAL_MARK);
179     expires.append(EXPIRES);
180     expires.append(EQUAL_MARK);
181     sessionKey.append(SESSION_KEY);
182     sessionKey.append(EQUAL_MARK);
183     sessionSecret.append(SESSION_SECRET);
184     sessionSecret.append(EQUAL_MARK);
185     locale.append(LOCALE);
186     locale.append(EQUAL_MARK);
187     locale.append(localeValue);
188
189     variableList.append(expires.append(expiresValue.append(BREAK_MARK)));
190     variableList.append(sessionKey.append(sessionKeyValue.append(BREAK_MARK)));
191     variableList.append(user.append(userValue).append(BREAK_MARK));
192     variableList.append(sessionSecret.append(sessionSecretValue.append(BREAK_MARK)));
193
194     cookie.append(BREAK_MARK);
195
196     foreach(variable, variableList) {
197         cookie.append(apiKey);
198         cookie.append(variable);
199     }
200     apiKey.remove(UNDERLINE_MARK);
201     cookie.append(apiKey);
202     cookie.append(signature);
203     cookie.append(BREAK_MARK);
204     cookie.append(locale);
205
206     qDebug() << cookie;
207
208     return cookie;
209 }
210
211 QUrl SituareService::formUrl(const QString &baseUrl, const QString &phpScript,
212                              QString urlParameters)
213 {
214     qDebug() << __PRETTY_FUNCTION__;
215     QString urlString;
216
217     urlString.append(baseUrl);
218     urlString.append(phpScript);
219     if(!urlParameters.isEmpty())
220         urlString.append(urlParameters);
221
222     QUrl url = QUrl(urlString);
223
224     qDebug() << url;
225
226     return url;
227 }
228
229 QString SituareService::formUrlParameters(const GeoCoordinate &coordinates, QString status,
230                                           bool publish)
231 {
232     qDebug() << __PRETTY_FUNCTION__;
233
234     // one scene pixel is about 5.4e-6 degrees, the integer part is max three digits and one
235     // additional digit is added for maximum precision
236     const int COORDINATE_PRECISION = 10;
237
238     QString parameters;
239
240     parameters.append(QUESTION_MARK);
241     parameters.append(LATITUDE);
242     parameters.append(EQUAL_MARK);
243     parameters.append(QString::number(coordinates.latitude(), 'f', COORDINATE_PRECISION));
244     parameters.append(AMBERSAND_MARK);
245     parameters.append(LONGTITUDE);
246     parameters.append(EQUAL_MARK);
247     parameters.append(QString::number(coordinates.longitude(), 'f', COORDINATE_PRECISION));
248
249     parameters.append(AMBERSAND_MARK);
250     parameters.append(PUBLISH);
251     parameters.append(EQUAL_MARK);
252
253     if(publish)
254         parameters.append(PUBLISH_TRUE);
255     else
256         parameters.append(PUBLISH_FALSE);
257
258     if(!status.isEmpty()) {
259         parameters.append(AMBERSAND_MARK);
260         parameters.append(DATA);
261         parameters.append(EQUAL_MARK);
262         parameters.append(status);
263     }
264
265     return parameters;
266 }
267
268 void SituareService::imageReceived(const QUrl &url, const QPixmap &image)
269 {
270     qDebug() << __PRETTY_FUNCTION__;
271     qDebug() << "Image URL: " << url << " size :" << image.size();
272
273     // assign facebook silhouette image to all who doesn't have a profile image
274     if(url == QUrl(SILHOUETTE_URL)) {
275         if(m_user->profileImageUrl().isEmpty()) {
276             m_user->setProfileImage(AvatarImage::create(image, AvatarImage::Large));
277             emit imageReady(m_user);
278         }
279         foreach(User *friendItem, m_friendsList) {
280             if(friendItem->profileImageUrl().isEmpty()) {
281                 friendItem->setProfileImage(AvatarImage::create(image, AvatarImage::Small));
282                 emit imageReady(friendItem);
283             }
284         }
285     }
286
287     if (m_user->profileImageUrl() == url) {
288         m_user->setProfileImage(AvatarImage::create(image, AvatarImage::Large));
289         emit imageReady(m_user);
290     }
291
292     foreach(User *friendItem, m_friendsList) {
293         if(friendItem->profileImageUrl() == url) {
294             friendItem->setProfileImage(AvatarImage::create(image, AvatarImage::Small));
295             emit imageReady(friendItem);
296         }
297     }
298 }
299
300 void SituareService::parseUserData(const QByteArray &jsonReply)
301 {
302     qDebug() << __PRETTY_FUNCTION__;
303
304     m_defaultImage = false;
305
306     QJson::Parser parser;
307     bool ok;
308
309     QVariantMap result = parser.parse (jsonReply, &ok).toMap();
310     if (!ok) {
311         emit error(ErrorContext::SITUARE, SituareError::INVALID_JSON);
312         return;
313     } else {
314
315         if(result.contains("ErrorCode")) {
316             QVariant errorVariant = result.value("ErrorCode");
317             emit error(ErrorContext::SITUARE, errorVariant.toInt());
318             return;
319         } else if(result.contains("user")) {
320
321             QVariant userVariant = result.value("user");
322             QMap<QString, QVariant> userMap = userVariant.toMap();
323
324             GeoCoordinate coordinates(userMap["latitude"].toReal(), userMap["longitude"].toReal());
325
326             QUrl imageUrl = userMap[NORMAL_SIZE_PROFILE_IMAGE].toUrl();
327
328             if(imageUrl.isEmpty()) {
329                 // user doesn't have profile image, so we need to get him a silhouette image
330                 m_defaultImage = true;
331             }
332
333             QString address = userMap["address"].toString();
334             if(address.isEmpty()) {
335                 QStringList location;
336                 location.append(QString::number(coordinates.latitude()));
337                 location.append(QString::number(coordinates.longitude()));
338                 address = location.join(", ");
339             }
340
341             User user = User(address, coordinates, userMap["name"].toString(),
342                           userMap["note"].toString(), imageUrl, userMap["timestamp"].toString(),
343                           true, userMap["uid"].toString());
344
345             QList<User> tmpFriendsList;
346
347             foreach (QVariant friendsVariant, result["friends"].toList()) {
348               QMap<QString, QVariant> friendMap = friendsVariant.toMap();
349               QVariant distance = friendMap["distance"];
350               QMap<QString, QVariant> distanceMap = distance.toMap();
351
352               GeoCoordinate coordinates(friendMap["latitude"].toReal(),friendMap["longitude"].toReal());
353
354               QUrl imageUrl = friendMap["profile_pic"].toUrl();
355
356               if(imageUrl.isEmpty()) {
357                   // friend doesn't have profile image, so we need to get him a silhouette image
358                   m_defaultImage = true;
359               }
360
361               QString address = friendMap["address"].toString();
362               if(address.isEmpty()) {
363                   QStringList location;
364                   location.append(QString::number(coordinates.latitude()));
365                   location.append(QString::number(coordinates.longitude()));
366                   address = location.join(", ");
367               }
368
369               User buddy = User(address, coordinates, friendMap["name"].toString(),
370                                friendMap["note"].toString(), imageUrl,
371                                friendMap["timestamp"].toString(),
372                                false, friendMap["uid"].toString(), distanceMap["units"].toString(),
373                                distanceMap["value"].toDouble());
374
375               tmpFriendsList.append(buddy);
376             }
377
378             QList<QUrl> imageUrlList; // url list for images
379
380             // set unchanged profile images or add new images to imageUrlList for downloading
381             if(m_user) {
382                 if(m_user->profileImageUrl() != user.profileImageUrl()) {
383                     if(!user.profileImageUrl().isEmpty())
384                         imageUrlList.append(user.profileImageUrl());
385                 } else {
386                     user.setProfileImage(m_user->profileImage());
387                 }
388             } else {
389                 if(!user.profileImageUrl().isEmpty())
390                     imageUrlList.append(user.profileImageUrl());
391             }
392
393             // clear old user object
394             if(m_user) {
395                 delete m_user;
396                 m_user = 0;
397             }
398
399             // create new user object from temporary user object
400             m_user = new User(user);
401
402             // set unchanged profile images or add new images to imageUrlList for downloading
403             if(!m_friendsList.isEmpty()) {
404                 foreach(User tmpBuddy, tmpFriendsList) {
405                     if(!tmpBuddy.profileImageUrl().isEmpty()) {
406                         bool found = false;
407                         foreach(User *buddy, m_friendsList) {
408                             if(tmpBuddy.profileImageUrl() == buddy->profileImageUrl()) {
409                                 tmpBuddy.setProfileImage(buddy->profileImage());
410                                 found = true;
411                                 break;
412                             }
413                         }
414                         if(!found && !tmpBuddy.profileImageUrl().isEmpty())
415                             imageUrlList.append(tmpBuddy.profileImageUrl());
416                     }
417                 }
418             } else {
419                 foreach(User buddy, tmpFriendsList) {
420                     if(!buddy.profileImageUrl().isEmpty())
421                         imageUrlList.append(buddy.profileImageUrl());
422                 }
423             }
424
425             // clear old friendlist
426             qDeleteAll(m_friendsList.begin(), m_friendsList.end());
427             m_friendsList.clear();
428
429             // populate new friendlist with temporary friendlist's data
430             foreach(User tmpFriendItem, tmpFriendsList) {
431                 User *friendItem = new User(tmpFriendItem);
432                 m_friendsList.append(friendItem);
433             }
434             tmpFriendsList.clear();
435
436             emit userDataChanged(m_user, m_friendsList);
437
438             // set silhouette image to imageUrlList for downloading
439             if(m_defaultImage)
440                 imageUrlList.append(QUrl(SILHOUETTE_URL));
441
442             addProfileImages(imageUrlList);
443             imageUrlList.clear();
444         } else {
445             QVariant address = result.value("address");
446             if(!address.toString().isEmpty()) {
447                 emit reverseGeoReady(address.toString());
448             } else {
449                 QStringList coordinates;
450                 coordinates.append(result.value("lat").toString());
451                 coordinates.append(result.value("lon").toString());
452
453                 emit error(ErrorContext::SITUARE, SituareError::ADDRESS_RETRIEVAL_FAILED);
454                 emit reverseGeoReady(coordinates.join(", "));
455             }
456         }
457     }
458 }
459
460 void SituareService::requestFinished(QNetworkReply *reply)
461 {
462     qDebug() << __PRETTY_FUNCTION__;
463
464     //Reply from situare
465     if (m_currentRequests.contains(reply)) {
466
467         qDebug() << "BytesAvailable: " << reply->bytesAvailable();
468
469         if (reply->error()) {
470             emit error(ErrorContext::NETWORK, reply->error());
471         } else {
472             QByteArray replyArray = reply->readAll();
473             qDebug() << "Reply from: " << reply->url() << "reply " << replyArray;
474
475             if(replyArray == ERROR_LAT.toAscii()) {
476                 qDebug() << "Error: " << ERROR_LAT;
477                 emit error(ErrorContext::SITUARE, SituareError::UPDATE_FAILED);
478             } else if(replyArray == ERROR_LON.toAscii()) {
479                 qDebug() << "Error: " << ERROR_LON;
480                 emit error(ErrorContext::SITUARE, SituareError::UPDATE_FAILED);
481             } else if(replyArray.contains(ERROR_SESSION.toAscii())) {
482                 qDebug() << "Error: " << ERROR_SESSION;
483                 emit error(ErrorContext::SITUARE, SituareError::SESSION_EXPIRED);
484             } else if(replyArray.startsWith(OPENING_BRACE_MARK.toAscii())) {
485                 qDebug() << "JSON string";
486                 parseUserData(replyArray);
487             } else if(replyArray.isEmpty()) {
488                 if(reply->url().toString().contains(UPDATE_LOCATION.toAscii())) {
489                     emit updateWasSuccessful();
490                 } else {
491                     // session credentials are invalid
492                     emit error(ErrorContext::SITUARE, SituareError::SESSION_EXPIRED);
493                 }
494             } else {
495                 // unknown reply
496                 emit error(ErrorContext::SITUARE, SituareError::ERROR_GENERAL);
497             }
498         }
499         m_currentRequests.removeAll(reply);
500         reply->deleteLater();
501     }
502 }
503
504 void SituareService::reverseGeo(const GeoCoordinate &coordinates)
505 {
506     qDebug() << __PRETTY_FUNCTION__;
507
508     QHash<QString, QString> parameters;
509     parameters.insert("lat", degreesToString(coordinates.latitude()));
510     parameters.insert("lon", degreesToString(coordinates.longitude()));
511     parameters.insert("format", "json");
512
513     buildRequest(REVERSE_GEO, parameters);
514 }
515
516 void SituareService::sendRequest(const QUrl &url, const QString &cookieType, const QString &cookie)
517 {
518     qDebug() << __PRETTY_FUNCTION__;
519
520     QNetworkRequest request;
521
522     request.setUrl(url);
523     request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
524     request.setRawHeader(cookieType.toAscii(), cookie.toUtf8());
525
526     QNetworkReply *reply = m_networkManager->get(request, true);
527
528     m_currentRequests.append(reply);
529 }
530
531 void SituareService::sendRequest(const QString &requestUrl)
532 {
533     qWarning() << __PRETTY_FUNCTION__ << requestUrl;
534
535     // make and send the request
536     QNetworkRequest request;
537     request.setUrl(QUrl(requestUrl));
538     request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
539     QNetworkReply *reply = m_networkManager->get(request, true);
540     m_currentRequests.append(reply);
541 }
542
543 void SituareService::updateSession(const QString &session)
544 {
545     qWarning() << __PRETTY_FUNCTION__;
546
547     m_session = session;
548
549     foreach (QString request, m_requestsWaitingAccessToken) {
550         appendAccessToken(request);
551         sendRequest(request);
552     }
553
554     m_requestsWaitingAccessToken.clear();
555 }
556
557 void SituareService::updateLocation(const GeoCoordinate &coordinates, const QString &status,
558                                     const bool &publish)
559 {
560     qDebug() << __PRETTY_FUNCTION__;
561
562     QHash<QString, QString> parameters;
563     parameters.insert("lat", degreesToString(coordinates.latitude()));
564     parameters.insert("lon", degreesToString(coordinates.longitude()));
565     parameters.insert("publish", publish ? "true" : "false");
566     parameters.insert("data", status); ///< @todo if !empty ???
567
568     buildRequest(UPDATE_LOCATION, parameters);
569 }