2
* Copyright (C) 2013 Jolla Ltd. <chris.adams@jollamobile.com>
4
* You may use this file under the terms of the BSD license as follows:
6
* "Redistribution and use in source and binary forms, with or without
7
* modification, are permitted provided that the following conditions are
9
* * Redistributions of source code must retain the above copyright
10
* notice, this list of conditions and the following disclaimer.
11
* * Redistributions in binary form must reproduce the above copyright
12
* notice, this list of conditions and the following disclaimer in
13
* the documentation and/or other materials provided with the
15
* * Neither the name of Nemo Mobile nor the names of its contributors
16
* may be used to endorse or promote products derived from this
17
* software without specific prior written permission.
19
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
32
#include "socialnetworkinterface.h"
33
#include "socialnetworkinterface_p.h"
35
#include "contentiteminterface.h"
36
#include "identifiablecontentiteminterface.h"
38
#include <QtCore/QByteArray>
39
#include <QtCore/QUrl>
40
#include <QtNetwork/QNetworkAccessManager>
41
#include <QtNetwork/QNetworkReply>
42
#include <QtNetwork/QNetworkRequest>
49
The data in the model is actually a list of cache entries.
50
Each entry consists of the QVariantMap of data for the object,
51
and a (lazily constructed) ContentItem ptr.
53
Specific implementations of the SocialNetwork interface should
54
use \c SocialNetworkInterfacePrivate::createUncachedEntry() to
55
create cache entries for data associated with a particular node,
56
within their implementation of the \c populateDataForNode()
59
Once they have constructed cache entries for the related data
60
objects, the implementation should call
61
\c SocialNetworkInterfacePrivate::populateCache().
63
CacheEntry::CacheEntry(const QVariantMap &d, ContentItemInterface *i)
64
: data(d), item(i), refcount(0)
68
CacheEntry::~CacheEntry()
74
ArbitraryRequestHandler::ArbitraryRequestHandler(SocialNetworkInterface *parent)
75
: QObject(parent), q(parent), reply(0), isError(false)
79
ArbitraryRequestHandler::~ArbitraryRequestHandler()
87
bool ArbitraryRequestHandler::request(int requestType, const QString &requestUri, const QVariantMap &queryItems, const QString &postData)
90
qWarning() << Q_FUNC_INFO << "Warning: cannot start arbitrary request: another arbitrary request is in progress";
94
QList<QPair<QString, QString> > qil;
95
QStringList queryItemKeys = queryItems.keys();
96
foreach (const QString &qik, queryItemKeys)
97
qil.append(qMakePair<QString, QString>(qik, queryItems.value(qik).toString()));
100
url.setQueryItems(qil);
102
QNetworkReply *sniReply = 0;
103
switch (requestType) {
104
case SocialNetworkInterface::Get: sniReply = q->d->qnam->get(QNetworkRequest(url)); break;
105
case SocialNetworkInterface::Post: sniReply = q->d->qnam->post(QNetworkRequest(url), QByteArray::fromBase64(postData.toLatin1())); break;
106
default: sniReply = q->d->qnam->deleteResource(QNetworkRequest(url)); break;
111
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(errorHandler(QNetworkReply::NetworkError)));
112
connect(reply, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(sslErrorsHandler(QList<QSslError>)));
113
connect(reply, SIGNAL(finished()), this, SLOT(finishedHandler()));
117
qWarning() << Q_FUNC_INFO << "Warning: cannot start arbitrary request: null reply";
121
void ArbitraryRequestHandler::finishedHandler()
123
QByteArray replyData;
125
replyData = reply->readAll();
127
reply->deleteLater();
131
QVariantMap responseData;
132
bool errorOccurred = isError;
134
// note that errors to arbitrary requests don't cause the SocialNetwork
135
// to transition to the Error state. They are unrelated to the model.
136
responseData.insert(QLatin1String("error"), errorMessage);
137
errorMessage = QString();
141
QVariantMap parsed = ContentItemInterface::parseReplyData(replyData, &ok);
143
responseData.insert(QLatin1String("response"), replyData);
145
responseData = parsed;
149
emit q->arbitraryRequestResponseReceived(errorOccurred, responseData);
152
void ArbitraryRequestHandler::errorHandler(QNetworkReply::NetworkError err)
155
case QNetworkReply::NoError: errorMessage = QLatin1String("QNetworkReply::NoError"); break;
156
case QNetworkReply::ConnectionRefusedError: errorMessage = QLatin1String("QNetworkReply::ConnectionRefusedError"); break;
157
case QNetworkReply::RemoteHostClosedError: errorMessage = QLatin1String("QNetworkReply::RemoteHostClosedError"); break;
158
case QNetworkReply::HostNotFoundError: errorMessage = QLatin1String("QNetworkReply::HostNotFoundError"); break;
159
case QNetworkReply::TimeoutError: errorMessage = QLatin1String("QNetworkReply::TimeoutError"); break;
160
case QNetworkReply::OperationCanceledError: errorMessage = QLatin1String("QNetworkReply::OperationCanceledError"); break;
161
case QNetworkReply::SslHandshakeFailedError: errorMessage = QLatin1String("QNetworkReply::SslHandshakeFailedError"); break;
162
case QNetworkReply::TemporaryNetworkFailureError: errorMessage = QLatin1String("QNetworkReply::TemporaryNetworkFailureError"); break;
163
case QNetworkReply::ProxyConnectionRefusedError: errorMessage = QLatin1String("QNetworkReply::ProxyConnectionRefusedError"); break;
164
case QNetworkReply::ProxyConnectionClosedError: errorMessage = QLatin1String("QNetworkReply::ProxyConnectionClosedError"); break;
165
case QNetworkReply::ProxyNotFoundError: errorMessage = QLatin1String("QNetworkReply::ProxyNotFoundError"); break;
166
case QNetworkReply::ProxyTimeoutError: errorMessage = QLatin1String("QNetworkReply::ProxyTimeoutError"); break;
167
case QNetworkReply::ProxyAuthenticationRequiredError: errorMessage = QLatin1String("QNetworkReply::ProxyAuthenticationRequiredError"); break;
168
case QNetworkReply::ContentAccessDenied: errorMessage = QLatin1String("QNetworkReply::ContentAccessDenied"); break;
169
case QNetworkReply::ContentOperationNotPermittedError: errorMessage = QLatin1String("QNetworkReply::ContentOperationNotPermittedError"); break;
170
case QNetworkReply::ContentNotFoundError: errorMessage = QLatin1String("QNetworkReply::ContentNotFoundError"); break;
171
case QNetworkReply::AuthenticationRequiredError: errorMessage = QLatin1String("QNetworkReply::AuthenticationRequiredError"); break;
172
case QNetworkReply::ContentReSendError: errorMessage = QLatin1String("QNetworkReply::ContentReSendError"); break;
173
case QNetworkReply::ProtocolUnknownError: errorMessage = QLatin1String("QNetworkReply::ProtocolUnknownError"); break;
174
case QNetworkReply::ProtocolInvalidOperationError: errorMessage = QLatin1String("QNetworkReply::ProtocolInvalidOperationError"); break;
175
case QNetworkReply::UnknownNetworkError: errorMessage = QLatin1String("QNetworkReply::UnknownNetworkError"); break;
176
case QNetworkReply::UnknownProxyError: errorMessage = QLatin1String("QNetworkReply::UnknownProxyError"); break;
177
case QNetworkReply::UnknownContentError: errorMessage = QLatin1String("QNetworkReply::UnknownContentError"); break;
178
case QNetworkReply::ProtocolFailure: errorMessage = QLatin1String("QNetworkReply::ProtocolFailure"); break;
179
default: errorMessage = QLatin1String("Unknown QNetworkReply::NetworkError"); break;
185
void ArbitraryRequestHandler::sslErrorsHandler(const QList<QSslError> &sslErrors)
187
errorMessage = QLatin1String("SSL error: ");
188
if (sslErrors.isEmpty()) {
189
errorMessage += QLatin1String("unknown SSL error");
191
foreach (const QSslError &sslE, sslErrors)
192
errorMessage += sslE.errorString() + QLatin1String("; ");
193
errorMessage.chop(2);
199
SocialNetworkInterfacePrivate::SocialNetworkInterfacePrivate(SocialNetworkInterface *parent)
201
, qnam(new QNetworkAccessManager(parent))
202
, placeHolderNode(new IdentifiableContentItemInterface(parent))
204
, repopulatingCurrentNode(false)
205
, error(SocialNetworkInterface::NoError)
206
, status(SocialNetworkInterface::Initializing)
207
, currentNodePosition(-1)
209
, arbitraryRequestHandler(0)
211
// Construct the placeholder node. This node is used as a placeholder
212
// when the client sets a specific nodeIdentifier until the node can
213
// be retrieved from the social network.
214
placeHolderNode->classBegin();
215
placeHolderNode->componentComplete();
218
SocialNetworkInterfacePrivate::~SocialNetworkInterfacePrivate()
220
// remove all cache entries.
221
QList<IdentifiableContentItemInterface*> cacheEntries = nodeContent.keys();
222
foreach (IdentifiableContentItemInterface *nodePtr, cacheEntries)
223
purgeDoomedNode(nodePtr);
227
void SocialNetworkInterfacePrivate::filters_append(QDeclarativeListProperty<FilterInterface> *list, FilterInterface *filter)
229
SocialNetworkInterface *sni = qobject_cast<SocialNetworkInterface *>(list->object);
231
if (!filter->parent()) {
232
filter->setParent(sni);
233
filter->m_ownedBySni = true;
235
sni->d->filters.append(filter);
240
FilterInterface *SocialNetworkInterfacePrivate::filters_at(QDeclarativeListProperty<FilterInterface> *list, int index)
242
SocialNetworkInterface *sni = qobject_cast<SocialNetworkInterface *>(list->object);
243
if (sni && sni->d->filters.count() > index && index >= 0)
244
return sni->d->filters.at(index);
249
void SocialNetworkInterfacePrivate::filters_clear(QDeclarativeListProperty<FilterInterface> *list)
251
SocialNetworkInterface *sni = qobject_cast<SocialNetworkInterface *>(list->object);
253
foreach (FilterInterface *cf, sni->d->filters) {
254
if (cf->m_ownedBySni) {
258
sni->d->filters.clear();
263
int SocialNetworkInterfacePrivate::filters_count(QDeclarativeListProperty<FilterInterface> *list)
265
SocialNetworkInterface *sni = qobject_cast<SocialNetworkInterface *>(list->object);
267
return sni->d->filters.count();
272
void SocialNetworkInterfacePrivate::sorters_append(QDeclarativeListProperty<SorterInterface> *list, SorterInterface *sorter)
274
SocialNetworkInterface *sni = qobject_cast<SocialNetworkInterface *>(list->object);
276
if (!sorter->parent()) {
277
sorter->setParent(sni);
278
sorter->m_ownedBySni = true;
280
sni->d->sorters.append(sorter);
285
SorterInterface *SocialNetworkInterfacePrivate::sorters_at(QDeclarativeListProperty<SorterInterface> *list, int index)
287
SocialNetworkInterface *sni = qobject_cast<SocialNetworkInterface *>(list->object);
288
if (sni && sni->d->sorters.count() > index && index >= 0)
289
return sni->d->sorters.at(index);
294
void SocialNetworkInterfacePrivate::sorters_clear(QDeclarativeListProperty<SorterInterface> *list)
296
SocialNetworkInterface *sni = qobject_cast<SocialNetworkInterface *>(list->object);
298
foreach (SorterInterface *cs, sni->d->sorters) {
299
if (cs->m_ownedBySni) {
303
sni->d->sorters.clear();
308
int SocialNetworkInterfacePrivate::sorters_count(QDeclarativeListProperty<SorterInterface> *list)
310
SocialNetworkInterface *sni = qobject_cast<SocialNetworkInterface *>(list->object);
312
return sni->d->sorters.count();
317
Returns the current node which is the central content item.
318
The model is populated with data related to the node.
319
The current node is always owned by the SocialNetwork base class;
320
it might be deleted at any time (e.g., if the cache entry gets purged).
322
IdentifiableContentItemInterface *SocialNetworkInterfacePrivate::currentNode() const
324
// caller does NOT take ownership. We can (and will) delete it whenever we like.
326
if (currentNodePosition >= 0 && currentNodePosition < nodeStack.size())
327
return nodeStack.at(currentNodePosition);
332
QString SocialNetworkInterfacePrivate::currentNodeIdentifier() const
334
if (currentNodePosition >= 0 && currentNodePosition < nodeStack.size())
335
return nodeStack.at(currentNodePosition)->identifier();
340
void SocialNetworkInterfacePrivate::purgeDoomedNode(IdentifiableContentItemInterface *n)
342
QList<CacheEntry*> cacheData = nodeContent.values(n);
343
foreach (CacheEntry *currData, cacheData)
344
removeEntryFromNodeContent(n, currData);
345
CacheEntry *nodeCacheEntry = findCacheEntry(n, false);
347
derefCacheEntry(nodeCacheEntry);
351
void SocialNetworkInterfacePrivate::maybePurgeDoomedNodes(int count, int direction)
353
// Removes \a count nodes from the node stack. The nodes
354
// will be removed from the top (most recent) of the stack
355
// if direction == 1, and from the bottom (least recent)
356
// of the stack if direction == 0.
357
// Also purges the associated cached content data, if they
358
// don't also appear elsewhere in the stack.
360
if (direction != 0 && direction != 1) {
361
qWarning() << Q_FUNC_INFO << "Error: invalid direction specified!";
365
if (count > nodeStack.size()) {
366
qWarning() << Q_FUNC_INFO << "Error: not that many nodes in the stack!";
370
// XXX TODO: this is a terrible algorithm, that iterates over the nodeStack way too many times.
371
for (int i = count; i > 0; --i) {
372
// remove the ToS (or BoS) node.
373
IdentifiableContentItemInterface *doomedNode = direction > 0 ? nodeStack.takeLast() : nodeStack.takeFirst();
374
if (direction == 0) {
375
// the current node position needs to be reduced, as all nodes' positions shift down.
376
currentNodePosition--;
379
// determine whether we need to purge the doomed node.
380
if (!nodeStack.contains(doomedNode)) {
381
// the node doesn't appear anywhere else in the navigation breadcrumb trail.
382
// so we have to delete it and purge our cache of content items for the node.
383
purgeDoomedNode(doomedNode);
390
void SocialNetworkInterfacePrivate::pushPlaceHolderNode()
392
pushNode(placeHolderNode);
396
void SocialNetworkInterfacePrivate::pushNode(IdentifiableContentItemInterface *n)
398
// the caller is responsible for emitting dataChanged() etc.
401
qWarning() << Q_FUNC_INFO << "Attempted to push null node!";
405
if (currentNodePosition >= nodeStack.size()) {
406
qWarning() << Q_FUNC_INFO << "Current node not on stack!";
410
IdentifiableContentItemInterface *currentNode = 0;
411
if (currentNodePosition >= 0)
412
currentNode = nodeStack.at(currentNodePosition);
414
if (currentNode == n && currentNode != placeHolderNode)
415
return; // nothing to do.
417
// Check to see if we need to replace the placeholder or current node.
418
if (currentNode == placeHolderNode || repopulatingCurrentNode) {
419
// this will happen when the node data that the
420
// derived type requested is received.
421
repopulatingCurrentNode = false;
422
if (currentNodePosition != (nodeStack.size() - 1)) {
423
qWarning() << Q_FUNC_INFO << "Error: placeholder node not the ToS!";
425
nodeStack.removeLast();
431
// Otherwise, we're pushing a new navigable node to the nodeStack.
432
if (currentNodePosition != (nodeStack.size()-1)) {
433
// current navigation position is not the top of stack
434
// ie, they pushed a bunch of nodes, then they called
435
// SNI::previousNode() one or more times, and now they
436
// are pushing a node. This node becomes the new top
437
// of stack. Purge any cache entries beyond the current
438
// position if applicable.
439
maybePurgeDoomedNodes(currentNodePosition+1, 1);
440
} else if (nodeStack.size() == nodeStackSize) {
441
// current node position is already top of stack, and
442
// we've reached our max for cached navigation steps.
443
maybePurgeDoomedNodes(1, 0); // purge the bottom (least recently used) node.
447
currentNodePosition = nodeStack.size() - 1; // end up pointing to ToS.
451
void SocialNetworkInterfacePrivate::nextNode()
453
// the caller is responsible for emitting dataChanged() etc.
455
if (currentNodePosition == -1 || nodeStack.size() == 0) {
456
qWarning() << Q_FUNC_INFO << "No nodes in cache!";
460
if (currentNodePosition == (nodeStack.size() - 1)) {
461
qWarning() << Q_FUNC_INFO << "Already at last node in cache!";
465
currentNodePosition++;
469
void SocialNetworkInterfacePrivate::prevNode()
471
// the caller is responsible for emitting dataChanged() etc.
473
if (currentNodePosition == -1 || nodeStack.size() == 0) {
474
qWarning() << Q_FUNC_INFO << "No nodes in cache!";
478
if (currentNodePosition == 0) {
479
qWarning() << Q_FUNC_INFO << "Already at first node in cache!";
483
currentNodePosition--;
487
IdentifiableContentItemInterface *SocialNetworkInterfacePrivate::findCachedNode(const QString &nodeIdentifier)
489
for (int i = 0; i < nodeStack.size(); ++i) {
490
if (nodeStack.at(i)->identifier() == nodeIdentifier) {
491
return nodeStack.at(i);
499
QList<CacheEntry*> SocialNetworkInterfacePrivate::cachedContent(IdentifiableContentItemInterface *n, bool *ok) const
501
// Types derived from SocialNetworkInterface should call this to retrieve cached content for the node
503
if (!nodeContent.contains(n)) {
505
return QList<CacheEntry*>();
509
return nodeContent.values(n);
513
CacheEntry *SocialNetworkInterfacePrivate::findCacheEntry(const QVariantMap &data, bool create)
515
// have to do a slow search. avoid this if possible.
516
foreach (CacheEntry *e, cache) {
524
// no such cache entry. create it, but DON'T append it to the cache.
525
// we append it to the cache (and take ownership of it) when they call
526
// addEntryToNodeContent().
527
CacheEntry *newEntry = new CacheEntry(data);
532
CacheEntry *SocialNetworkInterfacePrivate::findCacheEntry(ContentItemInterface *item, bool create)
534
if (cachedItems.contains(item))
535
return cachedItems.value(item);
540
// no such cache entry. create it, but DON'T append it to the cache.
541
// we append it to the cache (and take ownership of it) when they call
542
// addEntryToNodeContent().
543
CacheEntry *newEntry = new CacheEntry(item->data(), item);
548
void SocialNetworkInterfacePrivate::addEntryToNodeContent(IdentifiableContentItemInterface *item, CacheEntry *entry)
550
if (!nodeContent.contains(item) && currentNode() != item) {
551
qWarning() << Q_FUNC_INFO << "No such node:" << item;
555
if (nodeContent.find(item, entry) != nodeContent.end()) {
556
qWarning() << Q_FUNC_INFO << "Entry:" << entry << "already cached as content for node:" << item;
561
if (entry->refcount == 1) {
566
nodeContent.insert(item, entry);
570
void SocialNetworkInterfacePrivate::removeEntryFromNodeContent(IdentifiableContentItemInterface *item, CacheEntry *entry)
575
int removeCount = nodeContent.remove(item, entry);
576
if (removeCount == 0) {
577
qWarning() << Q_FUNC_INFO << "Entry:" << entry << "is not cached as content for node:" << item;
579
} else if (removeCount > 1) {
580
qWarning() << Q_FUNC_INFO << "Entry:" << entry << "was cached" << removeCount << "times as content for node:" << item;
583
derefCacheEntry(entry);
587
void SocialNetworkInterfacePrivate::updateCacheEntry(CacheEntry *entry, ContentItemInterface *item, const QVariantMap &data)
591
if (data != QVariantMap())
596
void SocialNetworkInterfacePrivate::derefCacheEntry(CacheEntry *entry)
598
if (entry->refcount == 0)
599
qWarning() << Q_FUNC_INFO << "Entry:" << entry << "has not been referenced in the cache";
602
if (entry->refcount <= 0) {
603
cache.removeAll(entry);
609
This function should be called by specific implementations of the
610
SocialNetwork interface, to create a cache entry for the current
611
node whose data is the given \a data, as part of the implementation
612
for the \c populateDataForNode() functions.
614
After the cache entries for the current node have all been created,
615
the implementation should then call
616
\c SocialNetworkInterfacePrivate::populateCache().
618
CacheEntry *SocialNetworkInterfacePrivate::createUncachedEntry(const QVariantMap &data)
620
// this function should be called by SocialNetworkInterface derived-types when
621
// they retrieve data from the service during populateDataForNode().
622
// After creating an uncached entry for each related content data object they receive
623
// from the service, they should call populateCache().
624
CacheEntry *newEntry = new CacheEntry(data);
629
This function should be called by specific implementations of the
630
SocialNetwork interface, to populate the cache for the current
631
node \a n with the cache entries \a c, as part of the implementation
632
for the \c populateDataForNode() functions.
634
This function will set \a ok to true if the node \a n is the current
635
node as expected, and the cache could be populated.
637
void SocialNetworkInterfacePrivate::populateCache(IdentifiableContentItemInterface *n, const QList<CacheEntry*> c, bool *ok)
639
// Types derived from SocialNetworkInterface should call this to populate the cache
640
// NOTE: we don't have any limits on cache size. XXX TODO: something sensible?
642
if (currentNode() != n) {
643
// the populated node is not the current node... this is an error.
644
qWarning() << Q_FUNC_INFO << "Attempted to populate cache for non-current node!";
651
QList<CacheEntry*> existingGoodEntries;
652
QList<CacheEntry*> newCacheEntries;
653
if (nodeContent.contains(n)) {
654
// updating existing cache entry.
655
QList<CacheEntry*> oldData = nodeContent.values(n);
656
QList<CacheEntry*> doomedData;
657
foreach (CacheEntry *currData, oldData) {
658
if (c.contains(currData)) {
659
existingGoodEntries.append(currData);
661
doomedData.append(currData);
665
// purge old entries from the cache
666
foreach (CacheEntry *doomedContent, doomedData) {
667
// not contained in the updated cache.
668
removeEntryFromNodeContent(n, doomedContent);
671
// add new entries to the cache
672
foreach (CacheEntry *newEntry, c) {
673
if (!existingGoodEntries.contains(newEntry)) {
674
newCacheEntries.append(newEntry);
682
// populate the cache for the node n from the content c.
683
foreach (CacheEntry *currData, newCacheEntries) {
684
addEntryToNodeContent(n, currData);
688
//----------------------------------------------------
691
\qmltype SocialNetwork
692
\instantiates SocialNetworkInterface
693
\inqmlmodule org.nemomobile.social 1
694
\brief Provides an abstraction API for graph- or model-based social
697
The SocialNetwork type should never be used directly by clients.
698
Instead, clients should use specific implementations of the SocialNetwork
699
interface, such as the Facebook adapter.
701
The SocialNetwork type provides a generic API which allows content
702
to be retrieved from a social network and exposed via a model.
703
The API consists of a central \c node which is an IdentifiableContentItem,
704
which may be specified by the client via the \c nodeIdentifier property.
705
The data in the model will be populated from the graph connections of
708
The model roles are as follows:
710
\li contentItem - the instantiated ContentItem related to the node
711
\li contentItemType - the type of the ContentItem related to the node
712
\li contentItemData - the underlying QVariantMap data of the ContentItem related to the node
713
\li contentItemIdentifier - the identifier of the ContentItem related to the node, or an empty string
716
Please see the documentation of the Facebook adapter for an example
717
of how clients can use the SocialNetwork model in an application.
720
SocialNetworkInterface::SocialNetworkInterface(QObject *parent)
721
: QAbstractListModel(parent), d(new SocialNetworkInterfacePrivate(this))
723
d->headerData.insert(ContentItemRole, "contentItem");
724
d->headerData.insert(ContentItemTypeRole, "contentItemType");
725
d->headerData.insert(ContentItemDataRole, "contentItemData" );
726
d->headerData.insert(ContentItemIdentifierRole, "contentItemIdentifier");
727
setRoleNames(d->headerData);
730
SocialNetworkInterface::~SocialNetworkInterface()
736
void SocialNetworkInterface::classBegin()
738
d->initialized = false;
741
void SocialNetworkInterface::componentComplete()
743
// If you override this implementation, you MUST set d->initialized=true.
744
d->initialized = true;
748
\qmlmethod void SocialNetwork::nextNode()
749
Navigates to the next node in the node stack, if it exists.
750
The data in the model will be populated from the cache, if cached
751
data for the node exists. If you want to repopulate the data from
752
the social network, you must call \c setNodeIdentifier() manually.
754
The node stack is built up from successive changes to the
755
\c nodeIdentifier property.
757
void SocialNetworkInterface::nextNode()
759
IdentifiableContentItemInterface *oldNode = d->currentNode();
761
IdentifiableContentItemInterface *newNode = d->currentNode();
762
if (oldNode != newNode) {
763
bool hasCachedContent = false;
764
QList<CacheEntry*> data = d->cachedContent(newNode, &hasCachedContent);
765
if (hasCachedContent) {
766
// call derived class data update:
767
// perform filtering/sorting based on the defined stuff.
768
// and then emit dataChanged() etc signals.
769
updateInternalData(data);
771
// call derived class data populate:
772
// d->populateCache() etc once it's finished retrieving.
773
// and then updateInternalData() itself.
774
populateDataForNode(newNode);
780
\qmlmethod void SocialNetwork::previousNode()
781
Navigates to the previous node in the node stack, if it exists.
782
The data in the model will be populated from the cache, if cached
783
data for the node exists. If you want to repopulate the data from
784
the social network, you must call \c setNodeIdentifier() manually.
786
The node stack is built up from successive changes to the
787
\c nodeIdentifier property.
789
void SocialNetworkInterface::previousNode()
791
IdentifiableContentItemInterface *oldNode = d->currentNode();
793
IdentifiableContentItemInterface *newNode = d->currentNode();
794
if (oldNode != newNode) {
795
bool hasCachedContent = false;
796
QList<CacheEntry*> data = d->cachedContent(newNode, &hasCachedContent);
797
if (hasCachedContent) {
798
// call derived class data update:
799
// perform filtering/sorting based on the defined stuff.
800
// and then emit dataChanged() etc signals.
801
updateInternalData(data);
803
// call derived class data populate:
804
// d->populateCache() etc once it's finished retrieving.
805
// and then updateInternalData() itself.
806
populateDataForNode(newNode);
812
\qmlmethod QObject *SocialNetwork::relatedItem(int index)
813
Returns the ContentItem which is related to the node from the given
814
\a index of the model data. This is identical to calling
815
\c data() for the given model index and specifying the \c contentItem
818
\note Although this function will always return a pointer to a
819
ContentItem, the return type of the function is QObject*, so that
820
this function can be used via QMetaObject::invokeMethod().
822
QObject *SocialNetworkInterface::relatedItem(int index) const
824
QVariant cv = data(QAbstractListModel::index(index), SocialNetworkInterface::ContentItemRole);
827
ContentItemInterface *ci = cv.value<ContentItemInterface*>();
833
\qmlproperty SocialNetwork::Status SocialNetwork::status
834
Holds the current status of the social network.
836
SocialNetworkInterface::Status SocialNetworkInterface::status() const
842
\qmlproperty SocialNetwork::ErrorType SocialNetwork::error
843
Holds the most recent error which occurred during initialization
844
or a network request. Note that it will not be reset if subsequent
845
operations are successful.
847
SocialNetworkInterface::ErrorType SocialNetworkInterface::error() const
853
\qmlproperty QString SocialNetwork::errorMessage
854
Holds the message associated with the most recent error which occurred
855
during initialization or a network request. Note that it will not be
856
reset if subsequent operations are successful.
858
QString SocialNetworkInterface::errorMessage() const
860
return d->errorMessage;
864
\qmlproperty QString SocialNetwork::nodeIdentifier
865
Holds the identifier of the "central" content item. This content item
866
is the \c node of the current view of the social network graph.
867
The data in the model will be populated from the graph connections
870
If this property is not set, the node will be initialized to the
871
current user node by the specific social network implementation adapter.
873
As the client changes the \c nodeIdentifier, the SocialNetwork will
874
request the related data from the network, and build up a node stack
875
of visited nodes. For each visited node, a cache of related content
876
items (model data) is stored. The size of the cache is implementation
879
Clients can later navigate down and up the stack using the \l previousNode() and
880
\l nextNode() functions respectively. Those operations are very cheap
881
as they do not trigger any network requests in the common case.
883
If the \c nodeIdentifier is set to an identifier which isn't represented
884
in the node stack, the \c node property will be set to an empty placeholder
885
node until the network request completes and the node can be populated with
888
If the \c nodeIdentifier is set to the identifier of the current node,
889
the cached data for the node will be cleared and the node and its related
890
data will be reloaded from the network.
892
QString SocialNetworkInterface::nodeIdentifier() const
894
if (d->pendingCurrentNodeIdentifier.isEmpty())
895
return d->currentNodeIdentifier(); // normal case.
896
return d->pendingCurrentNodeIdentifier; // status == Fetching, not sure if it's real yet.
899
void SocialNetworkInterface::setNodeIdentifier(const QString &contentItemIdentifier)
901
IdentifiableContentItemInterface *cachedNode = d->findCachedNode(contentItemIdentifier);
902
if (d->currentNode() && contentItemIdentifier == d->currentNode()->identifier()) {
903
// resetting the current node. This tells us to reload the node, clear its cache and repopulate.
904
d->repopulatingCurrentNode = true;
905
d->pendingCurrentNodeIdentifier = contentItemIdentifier;
906
populateDataForNode(contentItemIdentifier); // "unseen node" without pushing placeholder.
907
} else if (!cachedNode) {
909
// call derived class data populate:
910
// d->populateCache() etc once it's finished retrieving.
911
// d->pushNode(newNodePtr).
912
// and then updateInternalData() itself.
913
d->pendingCurrentNodeIdentifier = contentItemIdentifier;
914
d->pushPlaceHolderNode();
916
populateDataForNode(contentItemIdentifier); // XXX TODO: do we really want to trigger populate? or wait for user to call populate?
918
// We've seen this node before and have it cached.
919
bool hasCachedContent = false;
920
QList<CacheEntry*> data = d->cachedContent(cachedNode, &hasCachedContent);
921
if (hasCachedContent) {
922
// call derived class data update:
923
// perform filtering/sorting based on the defined stuff.
924
// and then emit dataChanged() etc signals.
925
d->pushNode(cachedNode);
926
updateInternalData(data);
928
qWarning() << Q_FUNC_INFO << "Error: cached node has no cached content!";
934
\qmlproperty IdentifiableContentItem *SocialNetwork::node
935
Holds the "central" content item, or node, which defines the
936
current view of the social network graph. The data exposed in the
937
SocialNetwork model will reflect the connections to the node.
939
The node must be an identifiable content item (that is, it must
940
have a unique identifier in the social network graph).
941
Clients cannot set the node property directly, but instead must
942
set the \c nodeIdentifier property.
944
IdentifiableContentItemInterface *SocialNetworkInterface::node() const
946
return d->currentNode();
950
\qmlproperty QVariantMap SocialNetwork::relevanceCriteria
951
Holds the social-network-specific relevance criteria which will
952
be used to calculate the relevance of related content items.
953
This relevance can be important in filtering and sorting operations.
955
QVariantMap SocialNetworkInterface::relevanceCriteria() const
957
return d->relevanceCriteria;
960
void SocialNetworkInterface::setRelevanceCriteria(const QVariantMap &rc)
962
d->relevanceCriteria = rc;
966
\qmlproperty QDeclarativeListProperty<Filter> SocialNetwork::filters
967
Holds the list of filters which will be applied to the related content
968
of the node. Only those related content items which match each of the
969
filters will be exposed as data in the model.
971
Specific implementations of the SocialNetwork interface may not support
972
certain standard filter types, or they may not support filtering at all.
974
QDeclarativeListProperty<FilterInterface> SocialNetworkInterface::filters()
976
return QDeclarativeListProperty<FilterInterface>(this, 0,
977
&SocialNetworkInterfacePrivate::filters_append,
978
&SocialNetworkInterfacePrivate::filters_count,
979
&SocialNetworkInterfacePrivate::filters_at,
980
&SocialNetworkInterfacePrivate::filters_clear);
984
\qmlproperty QDeclarativeListProperty<Sorter> SocialNetwork::sorters
985
Holds the list of sorters which will be applied to the related content
986
of the node. The order of sorters in the list is important, as it
987
defines which sorting is applied first.
989
Specific implementations of the SocialNetwork interface may not support
990
certain standard sorter types, or they may not support sorting at all.
992
QDeclarativeListProperty<SorterInterface> SocialNetworkInterface::sorters()
994
return QDeclarativeListProperty<SorterInterface>(this, 0,
995
&SocialNetworkInterfacePrivate::sorters_append,
996
&SocialNetworkInterfacePrivate::sorters_count,
997
&SocialNetworkInterfacePrivate::sorters_at,
998
&SocialNetworkInterfacePrivate::sorters_clear);
1002
\qmlproperty int SocialNetwork::count
1003
Returns the number of content items related to the \c node
1004
are exposed in the model. Only those content items which
1005
match the specified \c filters will be exposed in the model,
1006
if the specific implementation of the SocialNetwork interface
1007
supports every filter in the \c filters list.
1009
int SocialNetworkInterface::count() const
1011
return d->internalData.count();
1014
int SocialNetworkInterface::rowCount(const QModelIndex &index) const
1016
// only allow non-valid (default) parent index.
1017
if (index.isValid())
1019
return d->internalData.count();
1022
int SocialNetworkInterface::columnCount(const QModelIndex &index) const
1024
// only allow non-valid (default) parent index.
1025
if (index.isValid())
1030
QVariant SocialNetworkInterface::data(const QModelIndex &index, int role) const
1032
if (!index.isValid() || index.row() >= d->internalData.count() || index.row() < 0)
1035
CacheEntry *cacheEntry = d->internalData.at(index.row());
1038
case ContentItemTypeRole: return QVariant::fromValue(cacheEntry->data.value(NEMOQMLPLUGINS_SOCIAL_CONTENTITEMTYPE).toInt());
1039
case ContentItemDataRole: return QVariant::fromValue(cacheEntry->data);
1040
case ContentItemIdentifierRole: return QVariant::fromValue(cacheEntry->data.value(NEMOQMLPLUGINS_SOCIAL_CONTENTITEMID).toString());
1041
case ContentItemRole: {
1042
if (cacheEntry->item)
1043
return QVariant::fromValue(cacheEntry->item);
1044
// instantiate the item.
1045
ContentItemInterface *newItem = contentItemFromData(const_cast<SocialNetworkInterface*>(this), cacheEntry->data);
1046
d->updateCacheEntry(cacheEntry, newItem); // update the cache.
1047
return QVariant::fromValue(newItem);
1050
default: return QVariant();
1054
QVariant SocialNetworkInterface::headerData(int section, Qt::Orientation orientation, int role) const
1056
// Not a table model, so perhaps this is wrong.
1058
if (orientation != Qt::Horizontal)
1061
if (role == Qt::DisplayRole) {
1062
if (section < d->headerData.size()) {
1063
return d->headerData.value(section);
1071
\qmlmethod bool SocialNetwork::arbitraryRequest(SocialNetwork::RequestType requestType, const QString &requestUri, const QVariantMap &queryItems = QVariantMap(), const QString &postData = QString())
1073
Performs the HTTP request of the specified \a requestType (\c Get, \c Post or \c Delete) with
1074
the specified \a requestUri and \a queryItems. If the request is a Post request, the given
1075
\a postData will be converted to a QByteArray via \c{QByteArray::fromBase64(postData.toLatin1())}
1076
and used as the \c Post data.
1078
When a successfully started request is completed, the \c arbitraryRequestResponseReceived()
1079
signal will be emitted, with the response data included as the \c data parameter.
1081
The request will not be started successfully if another arbitrary request is in progress.
1082
Returns true if the request could be started successfully, false otherwise.
1084
bool SocialNetworkInterface::arbitraryRequest(int requestType, const QString &requestUri, const QVariantMap &queryItems, const QString &postData)
1086
if (!d->arbitraryRequestHandler)
1087
d->arbitraryRequestHandler = new ArbitraryRequestHandler(this);
1088
return d->arbitraryRequestHandler->request(requestType, requestUri, queryItems, postData);
1091
QVariantMap SocialNetworkInterface::contentItemData(ContentItemInterface *contentItem) const
1093
// Helper function for SocialNetworkInterface-derived types
1094
return contentItem->dataPrivate();
1097
void SocialNetworkInterface::setContentItemData(ContentItemInterface *contentItem, const QVariantMap &data) const
1099
// Helper function for SocialNetworkInterface-derived types
1100
contentItem->setDataPrivate(data);
1103
bool SocialNetworkInterface::isInitialized() const
1105
// Helper function for ContentItemInterface
1106
return d->initialized;
1110
Specific implementations of the SocialNetwork interface MUST implement this
1111
function. It will be called to populate the model data as a filtered and
1112
sorted view of the content items related to the specified node in the
1115
void SocialNetworkInterface::populate()
1117
qWarning() << Q_FUNC_INFO << "Error: this function MUST be implemented by derived types!";
1121
Specific implementations of the SocialNetwork interface MUST implement this
1122
function. It must be implemented such that it performs the appropriate
1123
get request to retrieve the data for the specified \c objectIdentifier, or
1124
data related to that object according to the given \c extraPath parameter.
1125
If possible, only the data specified by the \c whichFields parameter should
1126
be retrieved, to minimise network usage. The \c extraData parameter is
1127
implementation specific, and may be used to modify the behaviour of the request.
1129
QNetworkReply *SocialNetworkInterface::getRequest(const QString &, const QString &, const QStringList &, const QVariantMap &)
1131
qWarning() << Q_FUNC_INFO << "Error: this function MUST be implemented by derived types!";
1136
Specific implementations of the SocialNetwork interface MUST implement this
1137
function. It must be implemented such that it performs the appropriate
1138
post request to upload the \c data for the specified \c objectIdentifier, or
1139
data related to that object according to the given \c extraPath parameter.
1140
The \c extraData parameter is implementation specific, and may be used to
1141
modify the behaviour of the request.
1143
QNetworkReply *SocialNetworkInterface::postRequest(const QString &, const QString &, const QVariantMap &, const QVariantMap &)
1145
qWarning() << Q_FUNC_INFO << "Error: this function MUST be implemented by derived types!";
1150
Specific implementations of the SocialNetwork interface MUST implement this
1151
function. It must be implemented such that it performs the appropriate
1152
delete request to delete the object identified by the specified
1153
\c objectIdentifier, or data related to that object according to the given
1154
\c extraPath parameter. The \c extraData parameter is implementation specific,
1155
and may be used to modify the behaviour of the request.
1157
QNetworkReply *SocialNetworkInterface::deleteRequest(const QString &, const QString &, const QVariantMap &)
1159
qWarning() << Q_FUNC_INFO << "Error: this function MUST be implemented by derived types!";
1164
Specific implementations of the SocialNetwork interface MUST implement this
1165
function. It must return an instance of the correct ContentItem-derived type
1166
given the QVariantMap of data. This function is called when the \c contentItem
1167
role for a specific model index is requested via the model data() function, to
1168
instantiate the content item from the content item data lazily.
1170
ContentItemInterface *SocialNetworkInterface::contentItemFromData(QObject *, const QVariantMap &) const
1172
qWarning() << Q_FUNC_INFO << "Error: this function MUST be implemented by derived types!";
1177
Specific implementations of the SocialNetwork interface MUST implement this
1178
function. It must be implemented so that:
1179
1) the provided data should have non-filters-matching-entries removed
1180
2) the filtered data should then be sorted according to the sorters
1181
3) the d->internalData list should be set
1182
4) finally, dataChanged() and any other model signals should be emitted
1184
void SocialNetworkInterface::updateInternalData(QList<CacheEntry*>)
1186
qWarning() << Q_FUNC_INFO << "Error: this function MUST be implemented by derived types!";
1190
Specific implementations of the SocialNetwork interface MUST implement this
1191
function. It must be implemented so that:
1192
0) the current model data should be set to empty
1193
1) the related content data should be requested from the service, according to the filters
1194
2) when received, the related content data should be used to populate the cache via d->populateCache()
1195
3) finally, updateInternalData() should be called, passing in the new cache data.
1197
void SocialNetworkInterface::populateDataForNode(IdentifiableContentItemInterface *)
1199
qWarning() << Q_FUNC_INFO << "Error: this function MUST be implemented by derived types!";
1203
Specific implementations of the SocialNetwork interface MUST implement this
1204
function. It must be implemented so that:
1205
0) the current model data should be set to empty
1206
1) the given node is requested from the service
1207
2) when received, the node should be pushed to the nodeStack via d->pushNode(n)
1208
3) the related content data should be requested from the service, according to the filters
1209
4) when received, the related content data should be used to populate the cache via d->populateCache()
1210
5) finally, updateInternalData() should be called, passing in the new cache data.
1212
void SocialNetworkInterface::populateDataForNode(const QString &)
1214
qWarning() << Q_FUNC_INFO << "Error: this function MUST be implemented by derived types!";