Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[buteo-sync-plugins-social] Update Facebook API calls to v2.3 or v2.6…
…. Contributes to JB#35571

This commit updates most Facebook API calls to v2.6, including ensuring
all fields which we need are explicitly listed in the query,
reading the photo source url by parsing the images list,
using the place field instead of location from event nodes.

In one case (notifications) we still use v2.3 because after that
version the API was removed.

Contributes to JB#35571
  • Loading branch information
Chris Adams committed Jun 28, 2016
1 parent 21e115e commit 36f4b8e
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 55 deletions.
85 changes: 42 additions & 43 deletions src/facebook/facebook-calendars/facebookcalendarsyncadaptor.cpp
Expand Up @@ -28,6 +28,8 @@
#include <QtCore/QJsonArray>
#include <QtSql/QSqlQuery>
#include <QtSql/QSqlError>
#include <QtNetwork/QHttpMultiPart>
#include <QtNetwork/QHttpPart>

#include <extendedcalendar.h>
#include <extendedstorage.h>
Expand Down Expand Up @@ -199,23 +201,21 @@ void FacebookCalendarSyncAdaptor::requestEvents(int accountId,
const QString &accessToken,
const QString &batchRequest)
{
QList<QPair<QString, QString> > queryItems;
queryItems.append(QPair<QString, QString>(QString(QLatin1String("access_token")), accessToken));

QString batch = batchRequest;
if (batch.isEmpty()) {
// Create batch query of following format:
// [{ "method":"GET","relative_url":"me/events/created?include_headers=false&limit=200"},
// { "method":"GET","relative_url":"me/events/attending?include_headers=false&limit=200"},
// { "method":"GET","relative_url":"me/events/maybe?include_headers=false&limit=200"},
// { "method":"GET","relative_url":"me/events/not_replied?include_headers=false&limit=200"}]
// [{ "method":"GET","relative_url":"me/events?type=created&include_headers=false&limit=200&fields=..."},
// { "method":"GET","relative_url":"me/events?type=attending&include_headers=false&limit=200&fields=..."},
// { "method":"GET","relative_url":"me/events?type=maybe&include_headers=false&limit=200&fields=..."},
// { "method":"GET","relative_url":"me/events?type=not_replied&include_headers=false&limit=200&fields=..."}]

int sinceSpan = m_accountSyncProfile
? m_accountSyncProfile->key(Buteo::KEY_SYNC_SINCE_DAYS_PAST, QStringLiteral("30")).toInt()
: 30;
uint startTime = QDateTime::currentDateTimeUtc().addDays(sinceSpan * -1).toTime_t();
QString since = QStringLiteral("since=") + QString::number(startTime);
QString calendarQuery = QStringLiteral("{\"method\":\"GET\",\"relative_url\":\"me/events/%1?fields=id,name,start_time,end_time,is_date_only,description,location&include_headers=false&limit=200&")
QString calendarQuery = QStringLiteral("{\"method\":\"GET\",\"relative_url\":\"me/events?type=%1&include_headers=false&limit=200&fields=id,name,start_time,end_time,description,place&")
+ since
+ QStringLiteral("\"}");

Expand All @@ -226,17 +226,27 @@ void FacebookCalendarSyncAdaptor::requestEvents(int accountId,
+ calendarQuery.arg(QStringLiteral("not_replied"))
+ QStringLiteral("]");
}
queryItems.append(QPair<QString, QString>(QString(QLatin1String("batch")), batch));

QUrl url(graphAPI());
QUrlQuery query(url);
query.setQueryItems(queryItems);
url.setQuery(query);
QNetworkRequest request(url);

SOCIALD_LOG_DEBUG("performing request:" << url.toString());
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
multiPart->setBoundary("-------Sska2129ifcalksmqq3");

QNetworkReply *reply = m_networkAccessManager->post(QNetworkRequest(url), QByteArray());
QHttpPart accessTokenPart;
accessTokenPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"access_token\""));
accessTokenPart.setBody(accessToken.toUtf8());

QHttpPart batchPart;
batchPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"batch\""));
batchPart.setBody(batch.toUtf8());

multiPart->append(accessTokenPart);
multiPart->append(batchPart);
request.setRawHeader("Content-Type", "multipart/form-data; boundary="+multiPart->boundary());
QNetworkReply *reply = m_networkAccessManager->post(request, multiPart);
if (reply) {
multiPart->setParent(reply);
reply->setProperty("accountId", accountId);
reply->setProperty("accessToken", accessToken);
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
Expand All @@ -251,6 +261,7 @@ void FacebookCalendarSyncAdaptor::requestEvents(int accountId,
}
setupReplyTimeout(accountId, reply);
} else {
delete multiPart;
SOCIALD_LOG_ERROR("unable to request events from Facebook account" << accountId);
}
}
Expand Down Expand Up @@ -343,39 +354,27 @@ void FacebookCalendarSyncAdaptor::finishedHandler()
// workaround for empty ET events
endTimeString = startTimeString;
}
parsedEvent.m_isDateOnly = dataMap.value(QLatin1String("is_date_only")).toBool();
parsedEvent.m_endExists = true;
if (!parsedEvent.m_isDateOnly) {
KDateTime parsedStartTime = KDateTime::fromString(startTimeString);
KDateTime parsedEndTime = KDateTime::fromString(endTimeString);

// Sometimes KDateTime cannot parse the timezone
// even if it should support it
// We are then doing it manually
if (parsedStartTime.isNull()) {
parsedStartTime = KDateTime::fromString(startTimeString,
QLatin1String("%Y-%m-%dT%H:%M:%S%z"));
}
if (parsedEndTime.isNull()) {
parsedEndTime = KDateTime::fromString(endTimeString,
QLatin1String("%Y-%m-%dT%H:%M:%S%z"));
}
parsedEvent.m_startTime = parsedStartTime.toLocalZone();
parsedEvent.m_endTime = parsedEndTime.toLocalZone();
} else {
// mkcal date-only event semantics:
// if a date-only event lasts only one day, set isAllDay to true, but don't set an end date.
// if a date-only event lasts multiple days, set isAllDay to true, and set an end date.
// Use ClockTime format, so that it doesn't get offset according to timezone.
parsedEvent.m_startTime = KDateTime(QLocale::c().toDate(startTimeString, QLatin1String("yyyy-MM-dd")), QTime(), KDateTime::ClockTime);
parsedEvent.m_endTime = KDateTime(QLocale::c().toDate(endTimeString, QLatin1String("yyyy-MM-dd")), QTime(), KDateTime::ClockTime);
if (parsedEvent.m_endTime == parsedEvent.m_startTime) {
parsedEvent.m_endExists = false; // single-day all day event; don't set endDt.
}

KDateTime parsedStartTime = KDateTime::fromString(startTimeString);
KDateTime parsedEndTime = KDateTime::fromString(endTimeString);

// Sometimes KDateTime cannot parse the timezone
// even if it should support it
// We are then doing it manually
if (parsedStartTime.isNull()) {
parsedStartTime = KDateTime::fromString(startTimeString,
QLatin1String("%Y-%m-%dT%H:%M:%S%z"));
}
if (parsedEndTime.isNull()) {
parsedEndTime = KDateTime::fromString(endTimeString,
QLatin1String("%Y-%m-%dT%H:%M:%S%z"));
}

parsedEvent.m_startTime = parsedStartTime.toLocalZone();
parsedEvent.m_endTime = parsedEndTime.toLocalZone();
parsedEvent.m_summary = dataMap.value(QLatin1String("name")).toString();
parsedEvent.m_description = dataMap.value(QLatin1String("description")).toString();
parsedEvent.m_location = dataMap.value(QLatin1String("location")).toString();
parsedEvent.m_location = dataMap.value(QLatin1String("place")).toObject().toVariantMap().value("name").toString();
m_parsedEvents[eventId] = parsedEvent;
}
}
Expand Down
37 changes: 26 additions & 11 deletions src/facebook/facebook-images/facebookimagesyncadaptor.cpp
Expand Up @@ -150,6 +150,13 @@ void FacebookImageSyncAdaptor::requestData(int accountId,
QUrlQuery query(url);
queryItems.append(QPair<QString, QString>(QString(QLatin1String("access_token")), accessToken));
queryItems.append(QPair<QString, QString>(QString(QLatin1String("limit")), QString(QLatin1String("2000"))));
if (fbAlbumId.isEmpty()) {
queryItems.append(QPair<QString, QString>(QString(QLatin1String("fields")),
QString(QLatin1String("id,from,name,created_time,updated_time,count"))));
} else {
queryItems.append(QPair<QString, QString>(QString(QLatin1String("fields")),
QString(QLatin1String("id,picture,source,images,width,height,created_time,updated_time,name"))));
}
query.setQueryItems(queryItems);
url.setQuery(query);
}
Expand Down Expand Up @@ -231,7 +238,7 @@ void FacebookImageSyncAdaptor::albumsFinishedHandler()
QString createdTimeStr = albumObject.value(QLatin1String("created_time")).toString();
QString updatedTimeStr = albumObject.value(QLatin1String("updated_time")).toString();
int imageCount = static_cast<int>(albumObject.value(QLatin1String("count")).toDouble());

SOCIALD_LOG_DEBUG("Got album information:" << userId << albumId << albumName << createdTimeStr << updatedTimeStr << imageCount);

// check to see whether we need to sync (any changes since last sync)
// Note that we also check if the image count is the same, since, when
Expand All @@ -257,7 +264,6 @@ void FacebookImageSyncAdaptor::albumsFinishedHandler()
// TODO: After successfully added an album, we should begin a new query to get the image
// information (based on cover image id).
requestData(accountId, accessToken, QString(), fbUserId, fbAlbumId);

}

// Perform a continuation request if required.
Expand Down Expand Up @@ -291,6 +297,7 @@ void FacebookImageSyncAdaptor::imagesFinishedHandler()
QJsonObject parsed = parseJsonObjectReplyData(replyData, &ok);
if (isError || !ok || !parsed.contains(QLatin1String("data"))) {
SOCIALD_LOG_ERROR("unable to read photos response for Facebook account with id" << accountId);
SOCIALD_LOG_DEBUG(replyData);
clearRemovalDetectionLists(); // don't perform server-side removal detection during this sync run.
decrementSemaphore(accountId);
return;
Expand All @@ -305,8 +312,8 @@ void FacebookImageSyncAdaptor::imagesFinishedHandler()
}

// read the photos information
foreach (const QJsonValue imageValue, data) {
QJsonObject imageObject = imageValue.toObject();
foreach (const QJsonValue &photoValue, data) {
QJsonObject imageObject = photoValue.toObject();
if (imageObject.isEmpty()) {
continue;
}
Expand All @@ -319,6 +326,10 @@ void FacebookImageSyncAdaptor::imagesFinishedHandler()
QString photoName = imageObject.value(QLatin1String("name")).toString();
int imageWidth = 0;
int imageHeight = 0;
if (photoId.isEmpty()) {
SOCIALD_LOG_ERROR("Unable to parse photo id from data:" << photoValue.toObject().toVariantMap());
continue;
}

// Find optimal thumbnail and image source urls based on dimensions.
QList<ImageSource> imageSources;
Expand All @@ -344,11 +355,11 @@ void FacebookImageSyncAdaptor::imagesFinishedHandler()
imageSrcUrl = img.sourceUrl;
}
}
if (!foundOptimalThumbnail) {
if (!foundOptimalThumbnail && imageSources.size()) {
// just choose the largest one.
thumbnailUrl = imageSources.last().sourceUrl;
}
if (!foundOptimalImage) {
if (!foundOptimalImage && imageSources.size()) {
// just choose the largest one.
imageSrcUrl = imageSources.last().sourceUrl;
imageWidth = imageSources.last().width;
Expand All @@ -362,12 +373,16 @@ void FacebookImageSyncAdaptor::imagesFinishedHandler()
}

// check if we need to sync, and write to the database.
if (haveAlreadyCachedImage(photoId, imageSrcUrl)) {
SOCIALD_LOG_DEBUG("have previously cached photo" << photoId << ":" << imageSrcUrl);
if (!imageSrcUrl.isEmpty()) {
if (haveAlreadyCachedImage(photoId, imageSrcUrl)) {
SOCIALD_LOG_DEBUG("have previously cached photo" << photoId << ":" << imageSrcUrl);
} else {
SOCIALD_LOG_DEBUG("caching new photo" << photoId << ":" << imageSrcUrl << "->" << imageWidth << "x" << imageHeight);
m_db.addImage(photoId, fbAlbumId, fbUserId, createdTime, updatedTime,
photoName, imageWidth, imageHeight, thumbnailUrl, imageSrcUrl);
}
} else {
SOCIALD_LOG_DEBUG("caching new photo" << photoId << ":" << imageSrcUrl << "->" << imageWidth << "x" << imageHeight);
m_db.addImage(photoId, fbAlbumId, fbUserId, createdTime, updatedTime,
photoName, imageWidth, imageHeight, thumbnailUrl, imageSrcUrl);
SOCIALD_LOG_ERROR("Cannot add photo to database:" << photoId << "- empty image source url!");
}
}
// perform a continuation request if required.
Expand Down
Expand Up @@ -86,7 +86,7 @@ void FacebookNotificationSyncAdaptor::requestNotifications(int accountId, const
queryItems.append(QPair<QString, QString>(QString(QLatin1String("include_read")), QString(QLatin1String("true"))));
queryItems.append(QPair<QString, QString>(QString(QLatin1String("access_token")), accessToken));
queryItems.append(QPair<QString, QString>(QString(QLatin1String("locale")), QLocale::system().name()));
QUrl url(graphAPI(QLatin1String("/me/notifications")));
QUrl url(QLatin1String("https://graph.facebook.com/v2.3/me/notifications")); // removed after v2.3, cannot use in v2.6!
if (pagingToken.isEmpty()) {
QDateTime since = lastSuccessfulSyncTime(accountId);
if (!since.isValid()) {
Expand All @@ -103,6 +103,8 @@ void FacebookNotificationSyncAdaptor::requestNotifications(int accountId, const
queryItems.append(QPair<QString, QString>(QString(QLatin1String("until")), until));
queryItems.append(QPair<QString, QString>(QString(QLatin1String("__paging_token")), pagingToken));
}
queryItems.append(QPair<QString, QString>(QString(QLatin1String("fields")),
QString(QLatin1String("id,from,to,application,object,created_time,updated_time,title,link"))));

QUrlQuery query(url);
query.setQueryItems(queryItems);
Expand Down

0 comments on commit 36f4b8e

Please sign in to comment.