I have made the following changes intended for : CE:MW:Shared / nemo-qml-plugins
Please review and accept or decline. BOSS has already run some checks on this request. See the "Messages from BOSS" section below. https://build.pub.meego.com//request/show/8139 Thank You, John Brooks [This message was auto-generated] --- Request # 8139: Messages from BOSS: State: review at 2013-02-20T10:55:27 by bossbot Reviews: accepted by bossbot : Prechecks succeeded. new for CE-maintainers : Please replace this text with a review and approve/reject the review (not the SR). BOSS will take care of the rest Changes: submit: home:special:branches:CE:MW:Shared / nemo-qml-plugins -> CE:MW:Shared / nemo-qml-plugins changes files: -------------- --- nemo-qml-plugins.changes +++ nemo-qml-plugins.changes @@ -0,0 +1,9 @@ +* Wed Feb 20 2013 John Brooks <[email protected]> - 0.3.1 +- time: Ensure time is updated when WallClock is enabled (from Martin Jones) +- email: Add folderUnreadCount binding (from Valerio Valerio) +- contacts: Expose contact presence state (from Matt Vogt) +- contacts: Check for valid ID before returning a person instance (from Matt Vogt) +- contacts: Match filter against more properties than display name (from Adnrew den Exter) +- contacts: Update the SeasidePerson test to allow for caching of display label (from Andrew den Exter) +- social: Support request paging in Facebook adapter + old: ---- nemo-qml-plugins-0.3.0.tar.bz2 new: ---- nemo-qml-plugins-0.3.1.tar.bz2 spec files: ----------- --- nemo-qml-plugins.spec +++ nemo-qml-plugins.spec @@ -9,7 +9,7 @@ # << macros Summary: Nemo QML plugins source package. -Version: 0.3.0 +Version: 0.3.1 Release: 1 Group: System/Libraries License: BSD other changes: -------------- ++++++ nemo-qml-plugins-0.3.0.tar.bz2 -> nemo-qml-plugins-0.3.1.tar.bz2 --- contacts/src/seasidecache.cpp +++ contacts/src/seasidecache.cpp @@ -42,8 +42,11 @@ #include <QContactAvatar> #include <QContactDetailFilter> +#include <QContactEmailAddress> #include <QContactFavorite> #include <QContactName> +#include <QContactOnlineAccount> +#include <QContactOrganization> #include <QContactPhoneNumber> #include <QVersitContactExporter> @@ -131,7 +134,10 @@ fetchHint.setDetailDefinitionsHint(QStringList() << QContactName::DefinitionName << QContactAvatar::DefinitionName - << QContactPhoneNumber::DefinitionName); + << QContactPhoneNumber::DefinitionName + << QContactEmailAddress::DefinitionName + << QContactOrganization::DefinitionName + << QContactOnlineAccount::DefinitionName); m_fetchRequest.setFetchHint(fetchHint); m_fetchRequest.setFilter(QContactFavorite::match()); @@ -199,6 +205,9 @@ SeasidePerson *SeasideCache::personById(QContactLocalId id) { + if (id == 0) + return 0; + QHash<QContactLocalId, SeasideCacheItem>::iterator it = instance->m_people.find(id); if (it != instance->m_people.end()) { return person(&(*it)); --- contacts/src/seasidefilteredmodel.cpp +++ contacts/src/seasidefilteredmodel.cpp @@ -35,7 +35,11 @@ #include "synchronizelists_p.h" #include <QContactAvatar> +#include <QContactEmailAddress> #include <QContactName> +#include <QContactOnlineAccount> +#include <QContactOrganization> +#include <QContactPhoneNumber> #include <QtDebug> @@ -201,9 +205,21 @@ // TODO: i18n will require different splitting for thai and possibly // other locales, see MBreakIterator - if (item->filterKey.isEmpty()) + if (item->filterKey.isEmpty()) { item->filterKey = item->contact.detail<QContactName>().customLabel().split(QLatin1Char(' ')); + foreach (const QContactPhoneNumber &detail, item->contact.details<QContactPhoneNumber>()) + item->filterKey.append(detail.number()); + foreach (const QContactEmailAddress &detail, item->contact.details<QContactEmailAddress>()) + item->filterKey.append(detail.emailAddress()); + foreach (const QContactOrganization &detail, item->contact.details<QContactOrganization>()) + item->filterKey.append(detail.name().split(QLatin1Char(' '))); + foreach (const QContactOnlineAccount &detail, item->contact.details<QContactOnlineAccount>()) { + item->filterKey.append(detail.accountUri()); + item->filterKey.append(detail.serviceProvider()); + } + } + // search forwards over the label components for each filter word, making // sure to find all filter words before considering it a match. int j = 0; --- contacts/src/seasidepeoplemodel.cpp +++ contacts/src/seasidepeoplemodel.cpp @@ -147,9 +147,7 @@ SeasidePerson *SeasidePeopleModel::personById(int id) const { - // TODO: can this crash? probably - SeasidePerson *person = priv->idToContact.value(id); - return person; + return priv->idToContact.value(id); } SeasidePerson *SeasidePeopleModel::personByPhoneNumber(const QString &msisdn) const @@ -242,12 +240,13 @@ contacts.reserve(priv->contactIds.size()); foreach (const QContactLocalId &contactId, priv->contactIds) { - SeasidePerson *p = personById(contactId); - - if (p->id() == manager()->selfContactId()) - continue; - - contacts.append(p->contact()); + if ((contactId != 0) && (contactId != manager()->selfContactId())) { + if (SeasidePerson *p = personById(contactId)) { + contacts.append(p->contact()); + } else { + qWarning() << Q_FUNC_INFO << "Failed to retrieve contact for export: " << contactId; + } + } } if (!exporter.exportContacts(contacts)) { --- contacts/src/seasideperson.cpp +++ contacts/src/seasideperson.cpp @@ -684,6 +684,19 @@ emit anniversaryChanged(); } +SeasidePerson::PresenceState SeasidePerson::presenceState() const +{ + return static_cast<SeasidePerson::PresenceState>(mContact.detail<QContactPresence>().presenceState()); +} + +void SeasidePerson::setPresenceState(PresenceState state) +{ + QContactPresence presence = mContact.detail<QContactPresence>(); + presence.setPresenceState(static_cast<QContactPresence::PresenceState>(state)); + mContact.saveDetail(&presence); + emit presenceStateChanged(); +} + // TODO: merge with LIST_PROPERTY_FROM_DETAIL_FIELD #define LIST_PROPERTY_FROM_FIELD_NAME(detailType, fieldName) \ QStringList list; \ @@ -744,6 +757,12 @@ if (oldAvatar.imageUrl() != newAvatar.imageUrl()) emit avatarPathChanged(); + QContactPresence oldPresence = oldContact.detail<QContactPresence>(); + QContactPresence newPresence = mContact.detail<QContactPresence>(); + + if (oldPresence.presenceState() != newPresence.presenceState()) + emit presenceStateChanged(); + // TODO: differencing of list type details emit phoneNumbersChanged(); emit emailAddressesChanged(); --- contacts/src/seasideperson.h +++ contacts/src/seasideperson.h @@ -38,6 +38,7 @@ // Mobility #include <QContact> +#include <QContactPresence> // Seaside #include "seasideproxymodel.h" @@ -50,6 +51,7 @@ { Q_OBJECT Q_ENUMS(DetailType) + Q_ENUMS(PresenceState) public: /** @@ -89,7 +91,19 @@ WebsiteOtherType, // Dates BirthdayType, - AnniversaryType + AnniversaryType, + // Presence information + PresenceStateType + }; + + enum PresenceState { + PresenceUnknown = QContactPresence::PresenceUnknown, + PresenceAvailable = QContactPresence::PresenceAvailable, + PresenceHidden = QContactPresence::PresenceHidden, + PresenceBusy = QContactPresence::PresenceBusy, + PresenceAway = QContactPresence::PresenceAway, + PresenceExtendedAway = QContactPresence::PresenceExtendedAway, + PresenceOffline = QContactPresence::PresenceOffline }; explicit SeasidePerson(QObject *parent = 0); @@ -180,6 +194,10 @@ QDateTime anniversary() const; void setAnniversary(const QDateTime &av); + Q_PROPERTY(PresenceState presenceState READ presenceState WRITE setPresenceState NOTIFY presenceStateChanged) + PresenceState presenceState() const; + void setPresenceState(PresenceState state); + Q_PROPERTY(QStringList accountUris READ accountUris NOTIFY accountUrisChanged) QStringList accountUris() const; @@ -219,6 +237,7 @@ void websiteTypesChanged(); void birthdayChanged(); void anniversaryChanged(); + void presenceStateChanged(); void accountUrisChanged(); void accountPathsChanged(); --- contacts/src/sparqlfetchrequest.cpp +++ contacts/src/sparqlfetchrequest.cpp @@ -42,7 +42,10 @@ #include <QSparqlResult> #include <QContactAvatar> +#include <QContactEmailAddress> #include <QContactName> +#include <QContactOnlineAccount> +#include <QContactOrganization> #include <QContactPhoneNumber> #include <QElapsedTimer> @@ -271,7 +274,11 @@ "\n tracker:coalesce(" "\n nie:url(nco:photo(?x))," "\n nco:imAvatar(?imAccount))" - "\n GROUP_CONCAT(nco:phoneNumber(?phoneNumber), ';')"); + "\n GROUP_CONCAT(nco:phoneNumber(?phoneNumber), ';')" + "\n GROUP_CONCAT(nco:emailAddress(?emailAddress), ';')" + "\n GROUP_CONCAT(nco:fullname(?organization), ';')" + "\n GROUP_CONCAT(nco:imID(?imAccount), ';')" + "\n GROUP_CONCAT(nco:imAccountType(?imAccount), ';')"); } queryString += QLatin1String( @@ -356,6 +363,28 @@ contact.saveDetail(&number); } + foreach (const QString &string, results->value(6).toString().split(QLatin1Char(';'))) { + QContactEmailAddress address; + address.setEmailAddress(string); + contact.saveDetail(&address); + } + + foreach (const QString &string, results->value(7).toString().split(QLatin1Char(';'))) { + QContactOrganization organization; + organization.setName(string); + contact.saveDetail(&organization); + } + + const QStringList imIds = results->value(8).toString().split(QLatin1Char(';')); + const QStringList imTypes = results->value(9).toString().split(QLatin1Char(';')); + + for (int i = 0; i < imIds.count() || i < imTypes.count(); ++i) { + QContactOnlineAccount account; + account.setAccountUri(imIds.value(i)); + account.setServiceProvider(imTypes.value(i)); + contact.saveDetail(&account); + } + contacts.append(contact); } --- contacts/tests/tst_seasideperson/tst_seasideperson.cpp +++ contacts/tests/tst_seasideperson/tst_seasideperson.cpp @@ -73,6 +73,7 @@ void birthday(); void anniversary(); void address(); + void presenceState(); void marshalling(); void setContact(); }; @@ -399,6 +400,19 @@ QCOMPARE(person->addressTypes().at(1), (int)SeasidePerson::AddressWorkType); } +void tst_SeasidePerson::presenceState() +{ + QScopedPointer<SeasidePerson> person(new SeasidePerson); + QCOMPARE(person->presenceState(), SeasidePerson::PresenceUnknown); + QSignalSpy spy(person.data(), SIGNAL(presenceStateChanged())); + person->setPresenceState(SeasidePerson::PresenceAvailable); + QCOMPARE(spy.count(), 1); + QCOMPARE(person->presenceState(), SeasidePerson::PresenceAvailable); + person->setPresenceState(SeasidePerson::PresenceBusy); + QCOMPARE(spy.count(), 2); + QCOMPARE(person->presenceState(), SeasidePerson::PresenceBusy); +} + void tst_SeasidePerson::marshalling() { @@ -423,6 +437,12 @@ QCOMPARE(person->displayLabel(), QString::fromLatin1("Hello World")); QVERIFY(person->favorite()); + { // Seaside person saves the display label as the custom label. + QContactName nameDetail = contact.detail<QContactName>(); + nameDetail.setCustomLabel("Hello World"); + contact.saveDetail(&nameDetail); + } + QCOMPARE(person->contact(), contact); } @@ -432,6 +452,12 @@ QContact contact; + { // ### contactChanged is only emitted if the id differs, not any of the members. + QContactId contactId; + contactId.setLocalId(5); + contact.setId(contactId); + } + { QContactName nameDetail; nameDetail.setFirstName("Hello"); --- email/src/folderlistmodel.cpp +++ email/src/folderlistmodel.cpp @@ -157,3 +157,9 @@ { return m_mailFolderIds.count(); } + +QVariant FolderListModel::folderUnreadCount(QVariant folderId) +{ + int folderIndex = indexFromFolderId(folderId); + return data(index(folderIndex), FolderUnreadCount); +} --- email/src/folderlistmodel.h +++ email/src/folderlistmodel.h @@ -41,6 +41,7 @@ Q_INVOKABLE QVariant inboxFolderId (); Q_INVOKABLE QVariant inboxFolderName(); Q_INVOKABLE int totalNumberOfFolders(); + Q_INVOKABLE QVariant folderUnreadCount(QVariant folderId); private: QMailFolderIdList m_mailFolderIds; --- social/src/facebook/facebookinterface.cpp +++ social/src/facebook/facebookinterface.cpp @@ -76,6 +76,8 @@ , q(parent) , d(parentData) , populatePending(false) + , populateDataForUnseenPending(false) + , continuationRequestActive(false) , internalStatus(FacebookInterfacePrivate::Idle) , currentReply(0) { @@ -236,6 +238,7 @@ } QByteArray replyData = currentReply->readAll(); + QUrl requestUrl = currentReply->request().url(); deleteReply(); bool ok = false; QVariantMap responseData = ContentItemInterface::parseReplyData(replyData, &ok); @@ -252,7 +255,7 @@ q->continuePopulateDataForUnseenNode(responseData); } else if (internalStatus == FacebookInterfacePrivate::PopulatingSeenNode) { // This one should be simpler because each of the requested fields/connections is a property. - q->continuePopulateDataForSeenNode(responseData); + q->continuePopulateDataForSeenNode(responseData, requestUrl); } else { qWarning() << Q_FUNC_INFO << "Error: network reply finished while in unexpectant state! Received:" << responseData; } @@ -262,9 +265,6 @@ void FacebookInterfacePrivate::errorHandler(QNetworkReply::NetworkError err) { qWarning() << Q_FUNC_INFO << "Error: network error occurred:" << err; - //deleteReply(); // XXX TODO: we seem to get "UnknownContentError" all the time. - // So, for now, don't delete reply on error, as we still get the - // finished signal, and need to handle the "unknown" data. switch (err) { case QNetworkReply::NoError: d->errorMessage = QLatin1String("QNetworkReply::NoError"); break; @@ -297,6 +297,19 @@ d->error = SocialNetworkInterface::RequestError; d->status = SocialNetworkInterface::Error; + if ((internalStatus == FacebookInterfacePrivate::PopulatingUnseenNode + || internalStatus == FacebookInterfacePrivate::PopulatingSeenNode) + && d->repopulatingCurrentNode) { + // failed repopulating, either at "get node" step, or at "get related data" step. + d->repopulatingCurrentNode = false; + } + + if (continuationRequestActive) { + // failed during a continuation request. This shouldn't be a huge deal, + // since we have been populating the cache as we received more data anyway + continuationRequestActive = false; + } + emit q->statusChanged(); emit q->errorChanged(); emit q->errorMessageChanged(); @@ -305,8 +318,6 @@ /*! \internal */ void FacebookInterfacePrivate::sslErrorsHandler(const QList<QSslError> &errs) { - deleteReply(); - d->errorMessage = QLatin1String("SSL error: "); if (errs.isEmpty()) { d->errorMessage += QLatin1String("unknown SSL error"); @@ -667,54 +678,72 @@ // eg: with currentNode = Photo; connectionTypes == comments,likes,tags; whichFields = id,name; limit = 10: // GET https://graph.facebook.com/<photo_id>/fields=comments.limit(10).fields(id,name),likes.limit(10).fields(id,name),tags.fields(id,name).limit(10) - QString totalFieldsQuery; - for (int i = 0; i < connectionTypes.size(); ++i) { - bool addExtra = false; - bool append = false; - FacebookInterface::ContentItemType cit = static_cast<FacebookInterface::ContentItemType>(connectionTypes.at(i)); - switch (cit) { - case FacebookInterface::NotInitialized: qWarning() << Q_FUNC_INFO << "Invalid content item type specified in filter: NotInitialized"; break; - case FacebookInterface::Unknown: qWarning() << Q_FUNC_INFO << "Invalid content item type specified in filter: NotInitialized"; break; - case FacebookInterface::ObjectReference: qWarning() << Q_FUNC_INFO << "Invalid content item type specified in filter: ObjectReference"; break; - case FacebookInterface::Like: addExtra = true; append = true; totalFieldsQuery.append(QLatin1String("likes")); break; - case FacebookInterface::Tag: addExtra = true; append = true; totalFieldsQuery.append(QLatin1String("tags")); break; - case FacebookInterface::Picture: addExtra = false; append = true; totalFieldsQuery.append(QLatin1String("picture")); break; - case FacebookInterface::Location: addExtra = true; append = false; totalFieldsQuery.append(QLatin1String("location")); break; // not supported? - case FacebookInterface::Comment: addExtra = true; append = true; totalFieldsQuery.append(QLatin1String("comments")); break; - case FacebookInterface::User: addExtra = true; append = true; totalFieldsQuery.append(QLatin1String("friends")); break; // subscriptions etc? - case FacebookInterface::Album: addExtra = true; append = true; totalFieldsQuery.append(QLatin1String("albums")); break; - case FacebookInterface::Photo: addExtra = true; append = true; totalFieldsQuery.append(QLatin1String("photos")); break; - case FacebookInterface::Event: addExtra = true; append = true; totalFieldsQuery.append(QLatin1String("events")); break; - default: break; - } - - QString whichFieldsString; - QStringList whichFields = connectionWhichFields.at(i); - if (!whichFields.isEmpty()) - whichFieldsString = QLatin1String(".fields(") + whichFields.join(",") + QLatin1String(")"); - - QString limitString; - int limit = connectionLimits.at(i); - if (limit > 0) - limitString = QLatin1String(".limit(") + QString::number(limit) + QLatin1String(")"); - - if (append && addExtra && !limitString.isEmpty()) - totalFieldsQuery.append(limitString); - if (append && addExtra && !whichFieldsString.isEmpty()) - totalFieldsQuery.append(whichFieldsString); - if (append) - totalFieldsQuery.append(QLatin1String(",")); - } - - totalFieldsQuery.chop(1); // remove trailing comma. - QVariantMap extraData; - extraData.insert("fields", totalFieldsQuery); - - // now start the request. - f->currentReply = getRequest(whichNode->identifier(), QString(), QStringList(), extraData); - connect(f->currentReply, SIGNAL(finished()), f, SLOT(finishedHandler())); - connect(f->currentReply, SIGNAL(error(QNetworkReply::NetworkError)), f, SLOT(errorHandler(QNetworkReply::NetworkError))); - connect(f->currentReply, SIGNAL(sslErrors(QList<QSslError>)), f, SLOT(sslErrorsHandler(QList<QSslError>))); + // XXX TODO: in the future, each of the Facebook-specific IdentifiableContentItemType classes should + // provide private helper functions for the FacebookInterface to build the appropriate query. + // e.g: QString FacebookAlbumInterface::relatedDataQuery(types, limits, whichfields); + // That way we can provide "special case" code for every type in a neat, modular fashion. + if (connectionTypes.size() == 1 && connectionTypes.at(0) == FacebookInterface::Photo + && connectionLimits.at(0) == -1 && whichNode->type() == FacebookInterface::Album) { + // special case code for FacebookAlbum "populate all photos" request + QVariantMap extraData; + extraData.insert(QLatin1String("limit"), QLatin1String("25")); + f->currentReply = getRequest(whichNode->identifier(), FACEBOOK_ONTOLOGY_CONNECTIONS_PHOTOS, QStringList(), extraData); + connect(f->currentReply, SIGNAL(finished()), f, SLOT(finishedHandler())); + connect(f->currentReply, SIGNAL(error(QNetworkReply::NetworkError)), f, SLOT(errorHandler(QNetworkReply::NetworkError))); + connect(f->currentReply, SIGNAL(sslErrors(QList<QSslError>)), f, SLOT(sslErrorsHandler(QList<QSslError>))); + } else { + // generic query + QString totalFieldsQuery; + for (int i = 0; i < connectionTypes.size(); ++i) { + bool addExtra = false; + bool append = false; + FacebookInterface::ContentItemType cit = static_cast<FacebookInterface::ContentItemType>(connectionTypes.at(i)); + switch (cit) { + case FacebookInterface::NotInitialized: qWarning() << Q_FUNC_INFO << "Invalid content item type specified in filter: NotInitialized"; break; + case FacebookInterface::Unknown: qWarning() << Q_FUNC_INFO << "Invalid content item type specified in filter: NotInitialized"; break; + case FacebookInterface::ObjectReference: qWarning() << Q_FUNC_INFO << "Invalid content item type specified in filter: ObjectReference"; break; + case FacebookInterface::Like: addExtra = true; append = true; totalFieldsQuery.append(QLatin1String("likes")); break; + case FacebookInterface::Tag: addExtra = true; append = true; totalFieldsQuery.append(QLatin1String("tags")); break; + case FacebookInterface::Picture: addExtra = false; append = true; totalFieldsQuery.append(QLatin1String("picture")); break; + case FacebookInterface::Location: addExtra = true; append = false; totalFieldsQuery.append(QLatin1String("location")); break; // not supported? + case FacebookInterface::Comment: addExtra = true; append = true; totalFieldsQuery.append(QLatin1String("comments")); break; + case FacebookInterface::User: addExtra = true; append = true; totalFieldsQuery.append(QLatin1String("friends")); break; // subscriptions etc? + case FacebookInterface::Album: addExtra = true; append = true; totalFieldsQuery.append(QLatin1String("albums")); break; + case FacebookInterface::Photo: addExtra = true; append = true; totalFieldsQuery.append(QLatin1String("photos")); break; + case FacebookInterface::Event: addExtra = true; append = true; totalFieldsQuery.append(QLatin1String("events")); break; + default: break; + } + + QString whichFieldsString; + QStringList whichFields = connectionWhichFields.at(i); + if (!whichFields.isEmpty()) + whichFieldsString = QLatin1String(".fields(") + whichFields.join(",") + QLatin1String(")"); + + QString limitString; + int limit = connectionLimits.at(i); + if (limit == -1) + limitString = QLatin1String(".limit(0)"); + else if (limit > 0) + limitString = QLatin1String(".limit(") + QString::number(limit) + QLatin1String(")"); + + if (append && addExtra && !limitString.isEmpty()) + totalFieldsQuery.append(limitString); + if (append && addExtra && !whichFieldsString.isEmpty()) + totalFieldsQuery.append(whichFieldsString); + if (append) + totalFieldsQuery.append(QLatin1String(",")); + } + + totalFieldsQuery.chop(1); // remove trailing comma. + QVariantMap extraData; + extraData.insert("fields", totalFieldsQuery); + + // now start the request. + f->currentReply = getRequest(whichNode->identifier(), QString(), QStringList(), extraData); + connect(f->currentReply, SIGNAL(finished()), f, SLOT(finishedHandler())); + connect(f->currentReply, SIGNAL(error(QNetworkReply::NetworkError)), f, SLOT(errorHandler(QNetworkReply::NetworkError))); + connect(f->currentReply, SIGNAL(sslErrors(QList<QSslError>)), f, SLOT(sslErrorsHandler(QList<QSslError>))); + } } /*! \reimp */ @@ -744,72 +773,82 @@ // continued in continuePopulateDataForSeenNode(). } +#define FACEBOOK_CREATE_UNCACHED_ENTRY_FROM_DATA(data, type) \ + do { \ + QVariantList itemsList = data.value(FACEBOOK_ONTOLOGY_CONNECTIONS_DATA).toList(); \ + foreach (const QVariant &currVar, itemsList) { \ + QVariantMap currMap = currVar.toMap(); \ + currMap.insert(NEMOQMLPLUGINS_SOCIAL_CONTENTITEMTYPE, type); \ + currMap.insert(NEMOQMLPLUGINS_SOCIAL_CONTENTITEMID, \ + currMap.value(FACEBOOK_ONTOLOGY_METADATA_ID).toString()); \ + relatedContent.append(d->createUncachedEntry(currMap)); \ + } \ + } while (0) + /*! \internal */ -void FacebookInterface::continuePopulateDataForSeenNode(const QVariantMap &relatedData) +void FacebookInterface::continuePopulateDataForSeenNode(const QVariantMap &relatedData, const QUrl &requestUrl) { // We receive the related data and transform it into ContentItems. // Finally, we populate the cache for the node and update the internal model data. + QString continuationRequestUri; QList<CacheEntry *> relatedContent; + if (f->continuationRequestActive) { + // we are continuing a request, and thus don't overwrite the existing + // cache entries, but instead append to them. + bool ok = true; + relatedContent = d->cachedContent(d->currentNode(), &ok); + if (!ok) { + qWarning() << Q_FUNC_INFO << "Clobbering cached content in continuation request for node:" << d->currentNode()->identifier(); + } + } + + // construct related content items from the request results. QStringList keys = relatedData.keys(); foreach (const QString &key, keys) { #if 0 qWarning() << " " << key << " = " << FACEBOOK_DEBUG_VALUE_STRING_FROM_DATA(key, relatedData); #endif - if (key == FACEBOOK_ONTOLOGY_CONNECTIONS_LIKES) { - QVariantMap likesObject = relatedData.value(key).toMap(); - QVariantList likesData = likesObject.value(FACEBOOK_ONTOLOGY_CONNECTIONS_DATA).toList(); - foreach (const QVariant &currLike, likesData) { - QVariantMap clm = currLike.toMap(); - clm.insert(NEMOQMLPLUGINS_SOCIAL_CONTENTITEMTYPE, FacebookInterface::Like); - clm.insert(NEMOQMLPLUGINS_SOCIAL_CONTENTITEMID, clm.value(FACEBOOK_ONTOLOGY_METADATA_ID).toString()); - relatedContent.append(d->createUncachedEntry(clm)); + if (key == FACEBOOK_ONTOLOGY_CONNECTIONS_DATA) { + // contains a list of objects, whose type should be described by the request uri + QString reqPath = requestUrl.path(); + if (reqPath.endsWith(FACEBOOK_ONTOLOGY_CONNECTIONS_LIKES)) { + FACEBOOK_CREATE_UNCACHED_ENTRY_FROM_DATA(relatedData, FacebookInterface::Like); + } else if (reqPath.endsWith(FACEBOOK_ONTOLOGY_CONNECTIONS_COMMENTS)) { + FACEBOOK_CREATE_UNCACHED_ENTRY_FROM_DATA(relatedData, FacebookInterface::Comment); + } else if (reqPath.endsWith(FACEBOOK_ONTOLOGY_CONNECTIONS_TAGS)) { + FACEBOOK_CREATE_UNCACHED_ENTRY_FROM_DATA(relatedData, FacebookInterface::Tag); + } else if (reqPath.endsWith(FACEBOOK_ONTOLOGY_CONNECTIONS_PHOTOS)) { + FACEBOOK_CREATE_UNCACHED_ENTRY_FROM_DATA(relatedData, FacebookInterface::Photo); + } else if (reqPath.endsWith(FACEBOOK_ONTOLOGY_CONNECTIONS_ALBUMS)) { + FACEBOOK_CREATE_UNCACHED_ENTRY_FROM_DATA(relatedData, FacebookInterface::Album); + } else if (reqPath.endsWith(FACEBOOK_ONTOLOGY_CONNECTIONS_FRIENDS)) { + FACEBOOK_CREATE_UNCACHED_ENTRY_FROM_DATA(relatedData, FacebookInterface::User); + } else { + qWarning() << Q_FUNC_INFO << "Informative: Unsupported data retrieved via edge:" << reqPath; } + } else if (key == FACEBOOK_ONTOLOGY_CONNECTIONS_LIKES) { + QVariantMap likesObject = relatedData.value(key).toMap(); + FACEBOOK_CREATE_UNCACHED_ENTRY_FROM_DATA(likesObject, FacebookInterface::Like); } else if (key == FACEBOOK_ONTOLOGY_CONNECTIONS_COMMENTS) { QVariantMap commentsObject = relatedData.value(key).toMap(); - QVariantList commentsData = commentsObject.value(FACEBOOK_ONTOLOGY_CONNECTIONS_DATA).toList(); - foreach (const QVariant &currComm, commentsData) { - QVariantMap ccm = currComm.toMap(); - ccm.insert(NEMOQMLPLUGINS_SOCIAL_CONTENTITEMTYPE, FacebookInterface::Comment); - ccm.insert(NEMOQMLPLUGINS_SOCIAL_CONTENTITEMID, ccm.value(FACEBOOK_ONTOLOGY_METADATA_ID).toString()); - relatedContent.append(d->createUncachedEntry(ccm)); - } + FACEBOOK_CREATE_UNCACHED_ENTRY_FROM_DATA(commentsObject, FacebookInterface::Comment); } else if (key == FACEBOOK_ONTOLOGY_CONNECTIONS_TAGS) { QVariantMap tagsObject = relatedData.value(key).toMap(); - QVariantList tagsData = tagsObject.value(FACEBOOK_ONTOLOGY_CONNECTIONS_DATA).toList(); - foreach (const QVariant &currTag, tagsData) { - QVariantMap ctm = currTag.toMap(); - ctm.insert(NEMOQMLPLUGINS_SOCIAL_CONTENTITEMTYPE, FacebookInterface::Tag); - ctm.insert(NEMOQMLPLUGINS_SOCIAL_CONTENTITEMID, ctm.value(FACEBOOK_ONTOLOGY_METADATA_ID).toString()); - relatedContent.append(d->createUncachedEntry(ctm)); - } + FACEBOOK_CREATE_UNCACHED_ENTRY_FROM_DATA(tagsObject, FacebookInterface::Tag); } else if (key == FACEBOOK_ONTOLOGY_CONNECTIONS_PHOTOS) { QVariantMap photosObject = relatedData.value(key).toMap(); - QVariantList photosData = photosObject.value(FACEBOOK_ONTOLOGY_CONNECTIONS_DATA).toList(); - foreach (const QVariant &currPhoto, photosData) { - QVariantMap cpm = currPhoto.toMap(); - cpm.insert(NEMOQMLPLUGINS_SOCIAL_CONTENTITEMTYPE, FacebookInterface::Photo); - cpm.insert(NEMOQMLPLUGINS_SOCIAL_CONTENTITEMID, cpm.value(FACEBOOK_ONTOLOGY_METADATA_ID).toString()); - relatedContent.append(d->createUncachedEntry(cpm)); - } + FACEBOOK_CREATE_UNCACHED_ENTRY_FROM_DATA(photosObject, FacebookInterface::Photo); } else if (key == FACEBOOK_ONTOLOGY_CONNECTIONS_ALBUMS) { QVariantMap albumsObject = relatedData.value(key).toMap(); QVariantList albumsData = albumsObject.value(FACEBOOK_ONTOLOGY_CONNECTIONS_DATA).toList(); - foreach (const QVariant &currAlbum, albumsData) { - QVariantMap cam = currAlbum.toMap(); - cam.insert(NEMOQMLPLUGINS_SOCIAL_CONTENTITEMTYPE, FacebookInterface::Album); - cam.insert(NEMOQMLPLUGINS_SOCIAL_CONTENTITEMID, cam.value(FACEBOOK_ONTOLOGY_METADATA_ID).toString()); - relatedContent.append(d->createUncachedEntry(cam)); - } + FACEBOOK_CREATE_UNCACHED_ENTRY_FROM_DATA(albumsObject, FacebookInterface::Album); } else if (key == FACEBOOK_ONTOLOGY_CONNECTIONS_FRIENDS) { QVariantMap friendsObject = relatedData.value(key).toMap(); - QVariantList friendsData = friendsObject.value(FACEBOOK_ONTOLOGY_CONNECTIONS_DATA).toList(); - foreach (const QVariant &currFriend, friendsData) { - QVariantMap cfm = currFriend.toMap(); - cfm.insert(NEMOQMLPLUGINS_SOCIAL_CONTENTITEMTYPE, FacebookInterface::User); - cfm.insert(NEMOQMLPLUGINS_SOCIAL_CONTENTITEMID, cfm.value(FACEBOOK_ONTOLOGY_METADATA_ID).toString()); - relatedContent.append(d->createUncachedEntry(cfm)); - } + FACEBOOK_CREATE_UNCACHED_ENTRY_FROM_DATA(friendsObject, FacebookInterface::User); + } else if (key == FACEBOOK_ONTOLOGY_METADATA_PAGING) { + QVariantMap pagingObject = relatedData.value(key).toMap(); + continuationRequestUri = pagingObject.value(FACEBOOK_ONTOLOGY_METADATA_PAGING_NEXT).toString(); } else if (key == FACEBOOK_ONTOLOGY_OBJECTREFERENCE_OBJECTIDENTIFIER || key == FACEBOOK_ONTOLOGY_OBJECTREFERENCE_OBJECTPICTURE) { // can ignore this data - it's for the current node, which we already know. @@ -822,13 +861,34 @@ // XXX TODO: make the entire filter/sort codepath more efficient, by guaranteeing that whatever // comes out of the cache must be sorted/filtered already? Requires invalidating the entire // cache on filter/sorter change, however... hrm... + bool ok = false; d->populateCache(d->currentNode(), relatedContent, &ok); - if (!ok) + if (!ok) { qWarning() << Q_FUNC_INFO << "Error: Unable to populate the cache for the current node:" << d->currentNode()->identifier(); + } - // Finally, update the model data. + // Update the model data. updateInternalData(relatedContent); + + // If we need to request more (paged) data, do so. + if (continuationRequestUri.isEmpty()) { + // there are no more results / result pages to retrieve. + f->continuationRequestActive = false; + d->status = SocialNetworkInterface::Idle; + emit statusChanged(); + } else { + // there are more results to retrieve. Start a continuation request. + f->continuationRequestActive = true; + // grab the relevant parts of the continuation uri to create a new request. + QUrl continuationUrl(continuationRequestUri); + if (continuationUrl.queryItemValue(QLatin1String("access_token")).isEmpty()) + continuationUrl.addQueryItem(QLatin1String("access_token"), f->accessToken); + f->currentReply = d->qnam->get(QNetworkRequest(continuationUrl)); + connect(f->currentReply, SIGNAL(finished()), f, SLOT(finishedHandler())); + connect(f->currentReply, SIGNAL(error(QNetworkReply::NetworkError)), f, SLOT(errorHandler(QNetworkReply::NetworkError))); + connect(f->currentReply, SIGNAL(sslErrors(QList<QSslError>)), f, SLOT(sslErrorsHandler(QList<QSslError>))); + } } /*! \reimp */ --- social/src/facebook/facebookinterface.h +++ social/src/facebook/facebookinterface.h @@ -124,7 +124,7 @@ private: void retrieveRelatedContent(IdentifiableContentItemInterface *whichNode); void continuePopulateDataForUnseenNode(const QVariantMap &nodeData); - void continuePopulateDataForSeenNode(const QVariantMap &nodeData); + void continuePopulateDataForSeenNode(const QVariantMap &nodeData, const QUrl &requestUrl); // private data. private: --- social/src/facebook/facebookinterface_p.h +++ social/src/facebook/facebookinterface_p.h @@ -32,6 +32,8 @@ #ifndef FACEBOOKINTERFACE_P_H #define FACEBOOKINTERFACE_P_H +#include "socialnetworkinterface_p.h" + #include <QtCore/QObject> #include <QtCore/QVariantMap> #include <QtCore/QStringList> @@ -60,6 +62,7 @@ bool populatePending; bool populateDataForUnseenPending; + bool continuationRequestActive; enum InternalStatus { Idle = 0, --- social/src/facebook/facebookontology_p.h +++ social/src/facebook/facebookontology_p.h @@ -55,6 +55,8 @@ #define FACEBOOK_ONTOLOGY_METADATA_FIELDS QLatin1String("fields") #define FACEBOOK_ONTOLOGY_METADATA_TYPE QLatin1String("type") #define FACEBOOK_ONTOLOGY_METADATA_ID QLatin1String("id") +#define FACEBOOK_ONTOLOGY_METADATA_PAGING QLatin1String("paging") +#define FACEBOOK_ONTOLOGY_METADATA_PAGING_NEXT QLatin1String("next") #define FACEBOOK_ONTOLOGY_CONNECTIONS_LIKES QLatin1String("likes") #define FACEBOOK_ONTOLOGY_CONNECTIONS_COMMENTS QLatin1String("comments") --- social/src/socialnetworkinterface.cpp +++ social/src/socialnetworkinterface.cpp @@ -201,6 +201,7 @@ , qnam(new QNetworkAccessManager(parent)) , placeHolderNode(new IdentifiableContentItemInterface(parent)) , initialized(false) + , repopulatingCurrentNode(false) , error(SocialNetworkInterface::NoError) , status(SocialNetworkInterface::Initializing) , currentNodePosition(-1) @@ -399,12 +400,13 @@ currentNode = nodeStack.at(currentNodePosition); if (currentNode == n && currentNode != placeHolderNode) - return; // XXX TODO MAYBE: trigger reload of cache data for this node? + return; // nothing to do. - // Check to see if we need to replace the placeholder node. - if (currentNode == placeHolderNode) { + // Check to see if we need to replace the placeholder or current node. + if (currentNode == placeHolderNode || repopulatingCurrentNode) { // this will happen when the node data that the // derived type requested is received. + repopulatingCurrentNode = false; if (currentNodePosition != (nodeStack.size() - 1)) { qWarning() << Q_FUNC_INFO << "Error: placeholder node not the ToS!"; } else { @@ -555,10 +557,15 @@ /*! \internal */ void SocialNetworkInterfacePrivate::removeEntryFromNodeContent(IdentifiableContentItemInterface *item, CacheEntry *entry) { + if (entry == 0) + return; + int removeCount = nodeContent.remove(item, entry); if (removeCount == 0) { qWarning() << Q_FUNC_INFO << "Entry:" << entry << "is not cached as content for node:" << item; return; + } else if (removeCount > 1) { + qWarning() << Q_FUNC_INFO << "Entry:" << entry << "was cached" << removeCount << "times as content for node:" << item; } derefCacheEntry(entry); @@ -629,16 +636,17 @@ *ok = true; - QList<CacheEntry*> newData; + QList<CacheEntry*> existingGoodEntries; + QList<CacheEntry*> newCacheEntries; if (nodeContent.contains(n)) { // updating existing cache entry. QList<CacheEntry*> oldData = nodeContent.values(n); QList<CacheEntry*> doomedData; foreach (CacheEntry *currData, oldData) { - if (!c.contains(currData)) { - doomedData.append(currData); + if (c.contains(currData)) { + existingGoodEntries.append(currData); } else { - newData.append(currData); + doomedData.append(currData); } } @@ -647,13 +655,20 @@ // not contained in the updated cache. removeEntryFromNodeContent(n, doomedContent); } + + // add new entries to the cache + foreach (CacheEntry *newEntry, c) { + if (!existingGoodEntries.contains(newEntry)) { + newCacheEntries.append(newEntry); + } + } } else { // new cache entry. - newData = c; + newCacheEntries = c; } // populate the cache for the node n from the content c. - foreach (CacheEntry *currData, newData) { + foreach (CacheEntry *currData, newCacheEntries) { addEntryToNodeContent(n, currData); } } @@ -857,6 +872,10 @@ in the node stack, the \c node property will be set to an empty placeholder node until the network request completes and the node can be populated with the downloaded data. + + If the \c nodeIdentifier is set to the identifier of the current node, + the cached data for the node will be cleared and the node and its related + data will be reloaded from the network. */ QString SocialNetworkInterface::nodeIdentifier() const { @@ -868,7 +887,13 @@ void SocialNetworkInterface::setNodeIdentifier(const QString &contentItemIdentifier) { IdentifiableContentItemInterface *cachedNode = d->findCachedNode(contentItemIdentifier); - if (!cachedNode) { + if (d->currentNode() && contentItemIdentifier == d->currentNode()->identifier()) { + // resetting the current node. This tells us to reload the node, clear its cache and repopulate. + d->repopulatingCurrentNode = true; + d->pendingCurrentNodeIdentifier = contentItemIdentifier; + populateDataForNode(contentItemIdentifier); // "unseen node" without pushing placeholder. + } else if (!cachedNode) { + // Unseen node. // call derived class data populate: // d->populateCache() etc once it's finished retrieving. // d->pushNode(newNodePtr). @@ -878,6 +903,7 @@ emit nodeChanged(); populateDataForNode(contentItemIdentifier); // XXX TODO: do we really want to trigger populate? or wait for user to call populate? } else { + // We've seen this node before and have it cached. bool hasCachedContent = false; QList<CacheEntry*> data = d->cachedContent(cachedNode, &hasCachedContent); if (hasCachedContent) { --- social/src/socialnetworkinterface_p.h +++ social/src/socialnetworkinterface_p.h @@ -96,6 +96,7 @@ IdentifiableContentItemInterface *placeHolderNode; bool initialized; + bool repopulatingCurrentNode; QString errorMessage; SocialNetworkInterface::ErrorType error; --- time/src/nemowallclock.cpp +++ time/src/nemowallclock.cpp @@ -67,6 +67,8 @@ m_enabled = e; update(); emit q->enabledChanged(); + if (m_enabled) + emit q->timeChanged(); } QDateTime WallClockPrivate::time() const --- time/tests/wallclock/tst_wallclock.cpp +++ time/tests/wallclock/tst_wallclock.cpp @@ -106,6 +106,16 @@ upCount = updateSpy.count(); QTest::qWait(1100); QVERIFY(updateSpy.count() == upCount); + + // enable and ensure updates happen. + obj->setProperty("enabled", QVariant(true)); + + // should get an update immediately. + QVERIFY(updateSpy.count() == upCount + 1); + + // and another within a second + QTest::qWait(1100); + QVERIFY(updateSpy.count() > upCount + 1); } ++++++ nemo-qml-plugins.yaml --- nemo-qml-plugins.yaml +++ nemo-qml-plugins.yaml @@ -2,7 +2,7 @@ Summary: Nemo QML plugins source package. Group: System/Libraries Description: Do not install this, install the subpackaged plugins. -Version: 0.3.0 +Version: 0.3.1 Release: 1 Sources: - "%{name}-%{version}.tar.bz2"
