summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCaroline Chao <caroline.chao@digia.com>2014-09-22 14:24:56 +0200
committerCaroline Chao <caroline.chao@digia.com>2014-09-24 15:07:29 +0200
commit5c90b15c31eca21cc8294624d34ec9417e5120a8 (patch)
tree61bf2f76e6422b6c0269e86e4c73df1262bf87d2
parent235dcb8a5c6797eb5725f22ee356cd9fe11e241b (diff)
Handle offline mode
- Load data from cache at startup even if not connection is there. - Add time lists to cache - Ignore Twitter reply when empty data are received from a request - Cache feedback when no network is available and push feedback to the cloud when the connection is restored. - Cache favorite when offline Change-Id: I7892881d710eb4fc96628078f59064ff9e8cd196 Reviewed-by: Niels Weber <niels.weber@digia.com>
-rw-r--r--qml/components/ModelsSingleton.qml109
-rw-r--r--qml/components/TrackSwitcher.qml6
-rw-r--r--qml/components/TweetModel.qml36
-rw-r--r--qml/main.qml1
-rw-r--r--src/applicationclient.cpp161
-rw-r--r--src/applicationclient.h14
-rw-r--r--src/model.cpp45
-rw-r--r--src/model.h4
8 files changed, 303 insertions, 73 deletions
diff --git a/qml/components/ModelsSingleton.qml b/qml/components/ModelsSingleton.qml
index 4dbf25b..ce2c0e7 100644
--- a/qml/components/ModelsSingleton.qml
+++ b/qml/components/ModelsSingleton.qml
@@ -45,7 +45,7 @@ import TalkSchedule 1.0
QtObject {
id: object
- property string conferenceId: ""
+ property string conferenceId: applicationClient.currentConferenceId
property var currentConferenceTracks: []
property var currentConferenceEvents: []
property var currentConferenceDays: []
@@ -87,7 +87,7 @@ QtObject {
}
property var favoriteModel: Model {
- // do not save favorite
+ fileNameTag: "FavoriteObject"
onDataReady: getFavoriteIds()
}
@@ -154,24 +154,17 @@ QtObject {
function queryUserConferenceFavorites()
{
var favQuery = applicationClient.client.query({ "objectType": "objects.Favorite",
- "query": {
- "favoriteEvent.id" : { "$in" : currentConferenceEvents }
- },
- "include": {
- "events": {
- "objectType": "objects.Event",
- "query": {"id": "$.favoriteEvent.id"},
- "result": "selectOne",
- "include": {
- "tracks": {
- "objectType": "objects.Track",
- "query": {"id": "$.track.id"},
- "result": "selectOne"
- }
- }
- }
- }
- })
+ "query": {
+ "favoriteEvent.id" : { "$in" : currentConferenceEvents }
+ },
+ "include": {
+ "events": {
+ "objectType": "objects.Event",
+ "query": {"id": "$.favoriteEvent.id"},
+ "result": "selectOne",
+ }
+ }
+ })
favQuery.finished.connect(function() {
favoriteModel.onFinished(favQuery)
})
@@ -180,22 +173,25 @@ QtObject {
function saveFeedback(fbtext, eventId, rating)
{
//console.log("saveFeedback")
- var reply = applicationClient.client.create({
- "objectType": "objects.Feedback",
- "event": {
- "id": eventId,
- "objectType": "objects.Event"
- },
- "rating": rating,
- "feedbackText": fbtext
- })
-// reply.finished.connect(function() {
-// if (reply.errorType !== EnginioReply.NoError) {
-// console.log("Failed to save feedback.\n")
-// } else {
-// console.log("Successfully saved feedback.\n")
-// }
-// })
+ var feedback = {
+ "objectType": "objects.Feedback",
+ "event": {
+ "id": eventId,
+ "objectType": "objects.Event"
+ },
+ "rating": rating,
+ "feedbackText": fbtext
+ }
+ var reply = applicationClient.client.create(feedback)
+ reply.finished.connect(function() {
+ if (reply.errorType !== EnginioReply.NoError) {
+ console.log("Failed to save feedback.\n")
+ if (reply.errorType === EnginioReply.NetworkError)
+ applicationClient.cacheFeedback(JSON.stringify(feedback))
+ } else {
+ console.log("Successfully saved feedback.\n")
+ }
+ })
}
function saveFavorite(saveEventId)
@@ -206,19 +202,27 @@ QtObject {
}
busy = true
//console.log("start saving favorite")
- var reply = applicationClient.client.create({
- "objectType": "objects.Favorite",
- "favoriteEvent": {
- "id": saveEventId,
- "objectType": "objects.Event"
- }
- })
+ var favorite = {
+ "objectType": "objects.Favorite",
+ "favoriteEvent": {
+ "id": saveEventId,
+ "objectType": "objects.Event"
+ }
+ }
+ var reply = applicationClient.client.create(favorite)
+ var addFavorite = true
+
+ eventModel.addFavorite(saveEventId)
+ // save to file so favorite data can be retrieves at startup
+ favoriteModel.appendAndSaveFavorites(saveEventId, addFavorite)
+ favoriteModel.addRow({"events_id":saveEventId})
reply.finished.connect(function() {
- if (reply.errorType !== EnginioReply.NoError)
+ if (reply.errorType !== EnginioReply.NoError) {
console.log("Failed to create an Favorite:\n" + JSON.stringify(reply.data, undefined, 2) + "\n\n")
- else
- eventModel.addFavorite(saveEventId)
+ if (reply.errorType === EnginioReply.NetworkError)
+ applicationClient.cacheFavorite(saveEventId, addFavorite)
+ }
busy = false
//console.log("favorite save done")
})
@@ -241,9 +245,15 @@ QtObject {
}
})
+ eventModel.removeFavorite(removeEventId)
+ var removeFavorite = false
+ favoriteModel.appendAndSaveFavorites(removeEventId, removeFavorite)
+ favoriteModel.removeRow(favoriteModel.indexOf("events_id", removeEventId))
favoriteQuery.finished.connect(function() {
if (favoriteQuery.errorType !== EnginioReply.NoError) {
console.log("Failed to query an Favorite:\n" + JSON.stringify(favoriteQuery.data, undefined, 2) + "\n\n")
+ if (favoriteQuery.errorType === EnginioReply.NetworkError)
+ applicationClient.cacheFavorite(removeEventId, removeFavorite)
}
else {
if (favoriteQuery.data.results.length > 0) {
@@ -252,10 +262,11 @@ QtObject {
"id": favoriteQuery.data.results[0].id
})
reply.finished.connect(function() {
- if (favoriteQuery.errorType !== EnginioReply.NoError)
+ if (favoriteQuery.errorType !== EnginioReply.NoError) {
console.log("Failed to remove an Favorite:\n" + JSON.stringify(reply.data, undefined, 2) + "\n\n")
- else
- eventModel.removeFavorite(removeEventId)
+ if (favoriteQuery.errorType === EnginioReply.NetworkError)
+ applicationClient.cacheFavorite(removeEventId, removeFavorite)
+ }
})
}
//console.log("favorite remove done")
diff --git a/qml/components/TrackSwitcher.qml b/qml/components/TrackSwitcher.qml
index 4d5d5c2..5a5c3bf 100644
--- a/qml/components/TrackSwitcher.qml
+++ b/qml/components/TrackSwitcher.qml
@@ -95,7 +95,11 @@ Rectangle {
Connections {
target: daysWitcher
- onDayIdChanged: ModelsSingleton.timeListModel.tracksTodayModel = currentDayTracksModel
+ onDayIdChanged: {
+ ModelsSingleton.timeListModel.fileNameTag = "TimeObject." + daysWitcher.dayId
+ ModelsSingleton.timeListModel.load()
+ ModelsSingleton.timeListModel.tracksTodayModel = currentDayTracksModel
+ }
}
function setFirstEvent(time)
diff --git a/qml/components/TweetModel.qml b/qml/components/TweetModel.qml
index e9aec27..15fd310 100644
--- a/qml/components/TweetModel.qml
+++ b/qml/components/TweetModel.qml
@@ -66,17 +66,19 @@ Item {
req.onreadystatechange = function() {
status = req.readyState;
if (status === XMLHttpRequest.DONE) {
- var objectArray = JSON.parse(req.responseText);
- if (objectArray.errors !== undefined)
- console.log("Error fetching tweets: " + objectArray.errors[0].message)
- else {
- for (var key in objectArray.statuses) {
- var jsonObject = objectArray.statuses[key];
- tweets.append(jsonObject);
+ if (req.responseText !== "") { // Nothing was retrieved, network error
+ var objectArray = JSON.parse(req.responseText);
+ if (objectArray.errors !== undefined)
+ console.log("Error fetching tweets: " + objectArray.errors[0].message)
+ else {
+ for (var key in objectArray.statuses) {
+ var jsonObject = objectArray.statuses[key];
+ tweets.append(jsonObject);
+ }
}
+ if (wasLoading == true)
+ tweetModel.isLoaded()
}
- if (wasLoading == true)
- tweetModel.isLoaded()
}
wasLoading = (status === XMLHttpRequest.LOADING);
}
@@ -90,13 +92,15 @@ Item {
authReq.setRequestHeader("Authorization", "Basic " + Qt.btoa(consumerKey + ":" + consumerSecret));
authReq.onreadystatechange = function() {
if (authReq.readyState === XMLHttpRequest.DONE) {
- var jsonResponse = JSON.parse(authReq.responseText);
- if (jsonResponse.errors !== undefined)
- console.log("Authentication error: " + jsonResponse.errors[0].message)
- else
- bearerToken = jsonResponse.access_token;
-
- reload()
+ if (authReq.responseText !== "") { // Nothing was retrieved, network error
+ var jsonResponse = JSON.parse(authReq.responseText);
+ if (jsonResponse.errors !== undefined) {
+ console.log("Authentication error: " + jsonResponse.errors[0].message)
+ } else {
+ bearerToken = jsonResponse.access_token;
+ reload()
+ }
+ }
}
}
authReq.send("grant_type=client_credentials");
diff --git a/qml/main.qml b/qml/main.qml
index 2553430..c931b35 100644
--- a/qml/main.qml
+++ b/qml/main.qml
@@ -258,6 +258,7 @@ ApplicationWindow {
stack.forceActiveFocus()
}
}
+ Component.onCompleted: if (visible) animation.running = true
}
onVisibleChanged: if (visible) animation.running = true
}
diff --git a/src/applicationclient.cpp b/src/applicationclient.cpp
index 4d5989a..079241f 100644
--- a/src/applicationclient.cpp
+++ b/src/applicationclient.cpp
@@ -44,12 +44,15 @@
#include <Enginio/enginiomodel.h>
#include <Enginio/enginioreply.h>
#include <Enginio/enginiooauth2authentication.h>
+#include <QJsonArray>
+#include <QJsonDocument>
#include <QJsonObject>
#include "fileio.h"
#include "model.h"
#include <QStringList>
#include <QDebug>
#include <QTimer>
+#include <QIODevice>
#define QUOTE_(x) #x
#define QUOTE(x) QUOTE_(x)
@@ -59,6 +62,8 @@ ApplicationClient::ApplicationClient()
: init(true)
{
m_settings = new FileIO(this, "settings.txt");
+ m_feedbackCache = new FileIO(this, "feedback.txt");
+ m_favoriteCache = new FileIO(this, "favorite.txt");
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(authenticate()));
@@ -69,19 +74,24 @@ ApplicationClient::ApplicationClient()
const QByteArray backId = QByteArray(BACKEND_ID);
m_client->setBackendId(backId);
+ m_details = new QQmlPropertyMap(this);
+ m_details->insert(QLatin1String("location"), QVariant(""));
+ m_details->insert(QLatin1String("title"), QVariant(""));
+ m_details->insert(QLatin1String("TwitterTag"), QVariant(""));
+ m_details->insert(QLatin1String("infopage"), QVariant(""));
+
m_conferenceModel = new Model(this);
m_conferenceModel->setFileNameTag("ConferencesObject");
+ QString cachedConferenceId = m_settings->read();
+ if (!cachedConferenceId.isEmpty()) {
+ m_conferenceModel->load();
+ setCurrentConferenceId(cachedConferenceId);
+ }
connect(m_client, SIGNAL(sessionAuthenticated(EnginioReply*)), this, SLOT(authenticationSuccess(EnginioReply*)));
connect(m_client, SIGNAL(sessionAuthenticationError(EnginioReply*)), this, SLOT(errorClient(EnginioReply*)));
connect(m_client, SIGNAL(error(EnginioReply*)), this, SLOT(errorClient(EnginioReply*)));
getUserCredentials();
-
- m_details = new QQmlPropertyMap(this);
- m_details->insert(QLatin1String("location"), QVariant(""));
- m_details->insert(QLatin1String("title"), QVariant(""));
- m_details->insert(QLatin1String("TwitterTag"), QVariant(""));
- m_details->insert(QLatin1String("infopage"), QVariant(""));
}
void ApplicationClient::errorClient(EnginioReply *reply)
@@ -143,6 +153,9 @@ void ApplicationClient::authenticate()
void ApplicationClient::authenticationSuccess(EnginioReply *reply)
{
//qDebug() << "Query the conference";
+ emptyFavoriteCache();
+ emptyFeedbackCache();
+
int timeout = (reply->data().value("expires_in").toInt() - 20*60)*1000;
timer->setSingleShot(true);
timer->start(timeout);
@@ -201,3 +214,139 @@ bool ApplicationClient::eventFilter(QObject *object, QEvent *event)
#endif
return QObject::eventFilter(object, event);
}
+
+void ApplicationClient::cacheFeedback(QString feedback)
+{
+ QString content = m_feedbackCache->read();
+ feedbackArray = QJsonDocument::fromJson(content.toUtf8()).array();
+ feedbackArray.append(QJsonValue::fromVariant(feedback.toUtf8()));
+ QJsonDocument doc(feedbackArray);
+ m_feedbackCache->write(QString::fromUtf8(doc.toJson()));
+}
+
+void ApplicationClient::emptyFeedbackCache()
+{
+ QString content = m_feedbackCache->read();
+ if (content.isEmpty())
+ return;
+ feedbackArray = QJsonDocument::fromJson(content.toUtf8()).array();
+ if (!feedbackArray.isEmpty()) {
+ QString object = feedbackArray.at(0).toString();
+ QJsonObject query = QJsonDocument::fromJson(object.toUtf8()).object();
+ query["objectType"] = QString::fromUtf8("objects.Feedback");
+ const EnginioReply *replyFeedback = m_client->create(query);
+ connect(replyFeedback, SIGNAL(finished(EnginioReply*)), this, SLOT(createFeedbackReply(EnginioReply*)));
+ }
+}
+
+void ApplicationClient::createFeedbackReply(EnginioReply *reply)
+{
+ if (reply->errorType() == Enginio::NoError) {
+ feedbackArray.takeAt(0);
+ QJsonDocument doc(feedbackArray);
+ m_feedbackCache->write(QString::fromUtf8(doc.toJson()));
+ emptyFeedbackCache();
+ }
+ reply->deleteLater();
+}
+
+void ApplicationClient::cacheFavorite(QString eventId, bool isAdded)
+{
+ QString content = m_favoriteCache->read();
+ favoriteArray = QJsonDocument::fromJson(content.toUtf8()).array();
+ bool hasBeenFound = false;
+ for (int i = 0; i < favoriteArray.count(); i++) {
+ QJsonObject favObject = favoriteArray.at(i).toObject();
+ if (favObject.value("eventId").toString() == eventId) {
+ bool status = favObject.value("isAdded").toBool();
+ if (status == isAdded) { // no change. should not happen though
+ return;
+ } else {
+ favoriteArray.removeAt(i);
+ hasBeenFound = true;
+ }
+ }
+ }
+ if (!hasBeenFound) {
+ QJsonObject objectToAdd;
+ objectToAdd["eventId"] = eventId;
+ objectToAdd["isAdded"] = isAdded;
+ favoriteArray.append(objectToAdd);
+ }
+ QJsonDocument doc(favoriteArray);
+ m_favoriteCache->write(QString::fromUtf8(doc.toJson()));
+}
+
+void ApplicationClient::emptyFavoriteCache()
+{
+ QString content = m_favoriteCache->read();
+ if (content.isEmpty())
+ return;
+
+ favoriteArray = QJsonDocument::fromJson(content.toUtf8()).array();
+ if (!favoriteArray.isEmpty()) {
+ QJsonObject object = favoriteArray.at(0).toObject();
+
+ QJsonObject queryFav;
+ queryFav["objectType"] = QString::fromUtf8("objects.Favorite");
+ QJsonObject favEvent;
+ favEvent["id"] = object.value("eventId").toString();
+ favEvent["objectType"] = QString::fromUtf8("objects.Event");
+ QJsonObject favEventObject;
+ favEventObject["favoriteEvent"] = favEvent;
+
+ bool isAdded = object.value("isAdded").toBool();
+
+ if (isAdded) {
+ queryFav["favoriteEvent"] = favEvent;
+ const EnginioReply *replyCreateFavorite = m_client->create(queryFav);
+ connect(replyCreateFavorite, SIGNAL(finished(EnginioReply*)), this, SLOT(createFavoriteReply(EnginioReply*)));
+ } else {
+ queryFav["query"] = favEventObject;
+ const EnginioReply *replyQueryFavorite = m_client->query(queryFav);
+ connect(replyQueryFavorite, SIGNAL(finished(EnginioReply*)), this, SLOT(queryFavoriteReply(EnginioReply*)));
+ }
+ }
+}
+
+void ApplicationClient::queryFavoriteReply(EnginioReply *reply)
+{
+ if (reply->errorType() == Enginio::NoError) {
+ QJsonArray array = reply->data().value("results").toArray();
+ if (array.count() == 0) {
+ favoriteArray.takeAt(0);
+ QJsonDocument doc(favoriteArray);
+ m_favoriteCache->write(QString::fromUtf8(doc.toJson()));
+ emptyFavoriteCache();
+ return;
+ }
+ QJsonObject queryFav;
+ queryFav["objectType"] = QString::fromUtf8("objects.Favorite");
+ queryFav["id"] = array.at(0).toObject().value("id").toString();
+ const EnginioReply *replyDeleteFavorite = m_client->remove(queryFav);
+ connect(replyDeleteFavorite, SIGNAL(finished(EnginioReply*)), this, SLOT(removeFavoriteReply(EnginioReply*)));
+ }
+ reply->deleteLater();
+}
+
+void ApplicationClient::createFavoriteReply(EnginioReply *reply)
+{
+ if (reply->errorType() == Enginio::NoError) {
+ favoriteArray.takeAt(0);
+ QJsonDocument doc(favoriteArray);
+ m_favoriteCache->write(QString::fromUtf8(doc.toJson()));
+ emptyFavoriteCache();
+ }
+ reply->deleteLater();
+}
+
+void ApplicationClient::removeFavoriteReply(EnginioReply *reply)
+{
+ if (reply->errorType() == Enginio::NoError) {
+ favoriteArray.takeAt(0);
+ QJsonDocument doc(favoriteArray);
+ m_favoriteCache->write(QString::fromUtf8(doc.toJson()));
+ emptyFavoriteCache();
+ }
+ reply->deleteLater();
+}
diff --git a/src/applicationclient.h b/src/applicationclient.h
index 6492f3f..e1ab3c1 100644
--- a/src/applicationclient.h
+++ b/src/applicationclient.h
@@ -44,6 +44,7 @@
#include <QObject>
#include <QString>
#include <QtQml/QQmlPropertyMap>
+#include <QJsonArray>
class EnginioClient;
class EnginioModel;
@@ -72,6 +73,9 @@ public:
QQmlPropertyMap *currentConferenceDetails() const { return m_details; }
+ Q_INVOKABLE void cacheFeedback(QString feedback);
+ Q_INVOKABLE void cacheFavorite(QString favorite, bool isAdded);
+
protected:
void getUserCredentials();
void createUser();
@@ -90,13 +94,21 @@ public slots:
void errorClient(EnginioReply *reply);
void userCreationReply(EnginioReply *reply);
void queryConferenceReply(EnginioReply *reply);
+ void createFeedbackReply(EnginioReply *reply);
+ void createFavoriteReply(EnginioReply *reply);
+ void removeFavoriteReply(EnginioReply *reply);
+ void queryFavoriteReply(EnginioReply *reply);
void authenticate();
+ void emptyFeedbackCache();
+ void emptyFavoriteCache();
private:
EnginioClient *m_client;
Model *m_conferenceModel;
FileIO *m_userData;
FileIO *m_settings;
+ FileIO *m_feedbackCache;
+ FileIO *m_favoriteCache;
QString currentUsername;
QString currentPassword;
EnginioOAuth2Authentication *authenticator;
@@ -104,6 +116,8 @@ private:
QQmlPropertyMap *m_details;
QTimer *timer;
bool init;
+ QJsonArray feedbackArray;
+ QJsonArray favoriteArray;
};
#endif // APPLICATIONCLIENT_H
diff --git a/src/model.cpp b/src/model.cpp
index 70637b6..46cff58 100644
--- a/src/model.cpp
+++ b/src/model.cpp
@@ -226,6 +226,51 @@ bool Model::load()
return true;
}
+bool Model::appendAndSaveFavorites(const QString &data, bool isAdded)
+{
+ if (fileNameTag().isEmpty())
+ return false;
+
+ QString path = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
+ QFile file(QString("%1/%2.%3").arg(path).arg(m_fileNameTag).arg(m_conferenceId));
+ if (!file.open(QIODevice::ReadOnly)) {
+ qWarning() << "On load couldn't open file" << file.fileName();
+ return false;
+ }
+
+ QString fileContent;
+ QString line;
+ QTextStream t(&file);
+ do {
+ line = t.readLine();
+ fileContent += line;
+ } while (!line.isNull());
+
+ file.close();
+
+ QJsonObject object = QJsonDocument::fromJson(fileContent.toUtf8()).object();
+ QJsonArray array = object.value("results").toArray();
+ if (isAdded) {
+ QJsonObject newFav;
+ QJsonObject fav;
+ fav["id"] = data;
+ fav["objectType"] = QString::fromUtf8("objects.Event");
+ newFav["favoriteEvent"] = fav;
+ newFav["events_id"] = data;
+ array.append(newFav);
+ } else {
+ for (int i = 0; i < array.count(); i++) {
+ QJsonObject tempObject = array.at(i).toObject();
+ if (tempObject.value("favoriteEvent").toObject().value("id") == data) {
+ array.removeAt(i);
+ break;
+ }
+ }
+ }
+ object["results"] = array;
+ return save(object);
+}
+
bool Model::parse(const QJsonObject &object)
{
bool dataHasChanged = true;
diff --git a/src/model.h b/src/model.h
index 60b968a..f608840 100644
--- a/src/model.h
+++ b/src/model.h
@@ -77,6 +77,9 @@ public:
Q_INVOKABLE void removeRow(int index);
Q_INVOKABLE QVariant indexOf(const QString &role, QVariant value);
+ Q_INVOKABLE bool load();
+ Q_INVOKABLE bool appendAndSaveFavorites(const QString &data, bool isAdded);
+
Q_SIGNALS:
void clientChanged();
void conferenceIdChanged();
@@ -88,7 +91,6 @@ public Q_SLOTS:
private:
bool save(const QJsonObject &object);
- bool load();
bool parse(const QJsonObject &object);
QHash<int, QByteArray> m_roleNames;