Skip to content

Commit

Permalink
[buteo-sync-plugins-social] Fix Google Calendar synchronization issue…
Browse files Browse the repository at this point in the history
…s. Contributes to JB#33080

This commit fixes several issues related to both downsync and upsync
with Google Calendar service.

a) sequence (revision) field is now enforced by Google server.
   This commit ensures that we parse it correctly.

b) updatedMin must be no older than about 20 days if we request
   deleted events from Google, otherwise Google will respond with
   a 410 GONE error.  This commit now handles this by triggering
   a clean sync in that case, and by ensuring that we store a
   better "last sync date" in the notebook metadata (previously,
   we just used the timestamp reported by Google - but then if
   a notebook wasn't modified for more than a month, we'd start
   spontaneously hitting this error).

c) error reporting has been improved to work around the newline
   restriction in the journal.  Full error information should now
   be visible in the log.

Contributes to JB#33080
  • Loading branch information
Chris Adams committed Feb 1, 2016
1 parent 2cdfab4 commit dd021f1
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 19 deletions.
66 changes: 52 additions & 14 deletions src/google/google-calendars/googlecalendarsyncadaptor.cpp
Expand Up @@ -717,7 +717,7 @@ void jsonToKCal(const QJsonObject &json, KCalCore::Event::Ptr event, int default
UPDATE_EVENT_PROPERTY_IF_REQUIRED(event, summary, setSummary, json.value(QLatin1String("summary")).toVariant().toString(), changed)
UPDATE_EVENT_PROPERTY_IF_REQUIRED(event, description, setDescription, json.value(QLatin1String("description")).toVariant().toString(), changed)
UPDATE_EVENT_PROPERTY_IF_REQUIRED(event, location, setLocation, json.value(QLatin1String("location")).toVariant().toString(), changed)
UPDATE_EVENT_PROPERTY_IF_REQUIRED(event, revision, setRevision, json.value(QLatin1String("revision")).toVariant().toInt(), changed)
UPDATE_EVENT_PROPERTY_IF_REQUIRED(event, revision, setRevision, json.value(QLatin1String("sequence")).toVariant().toInt(), changed)
if (startExists) {
UPDATE_EVENT_PROPERTY_IF_REQUIRED(event, dtStart, setDtStart, start, changed)
}
Expand Down Expand Up @@ -803,6 +803,19 @@ void setLastSyncSuccessful(QList<int> accountIds)
settingsFile.sync();
}

void setLastSyncRequiresCleanSync(QList<int> accountIds)
{
QString settingsFileName = QString::fromLatin1("%1/%2/gcal.ini")
.arg(QString::fromLatin1(PRIVILEGED_DATA_DIR))
.arg(QString::fromLatin1(SYNC_DATABASE_DIR));
QSettings settingsFile(settingsFileName, QSettings::IniFormat);
Q_FOREACH(int accountId, accountIds) {
settingsFile.setValue(QString::fromLatin1("%1-needCleanSync").arg(accountId), QVariant::fromValue<bool>(true));
settingsFile.setValue(QString::fromLatin1("%1-success").arg(accountId), QVariant::fromValue<bool>(false));
}
settingsFile.sync();
}

}

GoogleCalendarSyncAdaptor::GoogleCalendarSyncAdaptor(QObject *parent)
Expand Down Expand Up @@ -854,16 +867,23 @@ void GoogleCalendarSyncAdaptor::finalCleanup()
} else {
// also update the remote sync timestamp in each notebook.
Q_FOREACH (const QString &updatedCalendarId, m_calendarsFinishedRequested.keys()) {
// update the sync date for the notebook, to the timestamp reported by Google
// in the calendar request for the remote calendar associated with the notebook.
// Update the sync date for the notebook, to the timestamp reported by Google
// in the calendar request for the remote calendar associated with the notebook,
// if that timestamp is recent (within the last week). If it is older than that,
// update it to the current date minus one day, otherwise Google will return
// 410 GONE "UpdatedMin too old" error on subsequent requests.
QString updateTimestamp = m_calendarsFinishedRequested.value(updatedCalendarId);
mKCal::Notebook::Ptr notebook = notebookForCalendarId(accountId, updatedCalendarId);
if (!notebook) {
// may have been deleted due to a purge operation.
continue;
}
KDateTime syncDate = datetimeFromUpdateStr(updateTimestamp);
KDateTime oldSyncDate = notebook->syncDate();
KDateTime syncDate = datetimeFromUpdateStr(updateTimestamp);
KDateTime yesterdayDate = KDateTime::currentDateTime(KDateTime::Spec::UTC()).addDays(-1);
if (qAbs(syncDate.daysTo(yesterdayDate)) >= 7) {
syncDate = yesterdayDate;
}
if (oldSyncDate < syncDate) {
notebook->setSyncDate(syncDate);
}
Expand All @@ -889,6 +909,15 @@ void GoogleCalendarSyncAdaptor::finalCleanup()
if (succeededAccounts.size()) {
setLastSyncSuccessful(succeededAccounts);
}
QList<int> cleanSyncAccounts;
Q_FOREACH (int accountId, m_cleanSyncRequired.keys()) {
if (m_cleanSyncRequired.value(accountId)) {
cleanSyncAccounts.append(accountId);
}
}
if (cleanSyncAccounts.size()) {
setLastSyncRequiresCleanSync(cleanSyncAccounts);
}

if (!ghostEventCleanupPerformed()) {
// Delete any events which are not associated with a notebook.
Expand Down Expand Up @@ -1069,8 +1098,8 @@ void GoogleCalendarSyncAdaptor::calendarsFinishedHandler()
}
} else {
// error occurred during request.
SOCIALD_LOG_ERROR("unable to parse calendar data from request with account" << accountId <<
"; got:" << QString::fromLatin1(replyData.constData()));
SOCIALD_LOG_ERROR("unable to parse calendar data from request with account" << accountId << "; got:");
errorDumpStr(QString::fromLatin1(replyData.constData()));
m_syncSucceeded[accountId] = false;
}

Expand Down Expand Up @@ -1252,11 +1281,12 @@ void GoogleCalendarSyncAdaptor::eventsFinishedHandler()
bool needCleanSync = reply->property("needCleanSync").toBool();
QByteArray replyData = reply->readAll();
bool isError = reply->property("isError").toBool();
int httpCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();

QString replyString = QString::fromUtf8(replyData);
SOCIALD_LOG_TRACE("-------------------------------");
SOCIALD_LOG_TRACE("Events response for calendar:" << calendarId << "from account:" << accountId);
SOCIALD_LOG_TRACE("HTTP CODE:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
SOCIALD_LOG_TRACE("HTTP CODE:" << httpCode);
Q_FOREACH (QString line, replyString.split('\n', QString::SkipEmptyParts)) {
SOCIALD_LOG_TRACE(line.replace('\r', ' '));
}
Expand Down Expand Up @@ -1302,9 +1332,16 @@ void GoogleCalendarSyncAdaptor::eventsFinishedHandler()
}
} else {
// error occurred during request.
SOCIALD_LOG_ERROR("unable to parse event data from request with account" << accountId <<
"; got: " << QString::fromUtf8(replyData.constData()));
SOCIALD_LOG_ERROR("unable to parse event data from request with account" << accountId << "; got:");
errorDumpStr(QString::fromUtf8(replyData.constData()));
m_syncSucceeded[accountId] = false;

if (httpCode == 410) {
// HTTP 410 GONE is emitted if the syncToken or updatedMin parameters are invalid.
// We should trigger a clean sync if we hit this error.
SOCIALD_LOG_ERROR("received 410 GONE from server; marking account for clean sync:" << accountId);
m_cleanSyncRequired[accountId] = true;
}
}

if (!fetchingNextPage) {
Expand Down Expand Up @@ -1864,6 +1901,7 @@ void GoogleCalendarSyncAdaptor::upsyncFinishedHandler()
QString calendarId = reply->property("calendarId").toString();
int upsyncType = reply->property("upsyncType").toInt();
QByteArray replyData = reply->readAll();
int httpCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
bool isError = reply->property("isError").toBool();

// QNetworkReply can report an error even if there isn't one...
Expand All @@ -1879,14 +1917,14 @@ void GoogleCalendarSyncAdaptor::upsyncFinishedHandler()
// parse the calendars' metadata from the response.
if (isError) {
// error occurred during request.
SOCIALD_LOG_ERROR("error occurred while upsyncing calendar data to Google account" << accountId <<
"; got:" << QString::fromLatin1(replyData.constData()));
SOCIALD_LOG_ERROR("error" << httpCode << "occurred while upsyncing calendar data to Google account" << accountId << "; got:");
errorDumpStr(QString::fromUtf8(replyData));
m_syncSucceeded[accountId] = false;
} else if (upsyncType == GoogleCalendarSyncAdaptor::Delete) {
// we expect an empty response body on success for Delete operations
if (!replyData.isEmpty()) {
SOCIALD_LOG_ERROR("error occurred while upsyncing calendar event deletion to Google account" << accountId << "; got:");
errorDumpStr(QString::fromLatin1(replyData.constData()));
SOCIALD_LOG_ERROR("error" << httpCode << "occurred while upsyncing calendar event deletion to Google account" << accountId << "; got:");
errorDumpStr(QString::fromUtf8(replyData));
m_syncSucceeded[accountId] = false;
}
} else {
Expand All @@ -1899,7 +1937,7 @@ void GoogleCalendarSyncAdaptor::upsyncFinishedHandler()
: QString::fromLatin1("modification");
SOCIALD_LOG_ERROR("error occurred while upsyncing calendar event" << typeStr <<
"to Google account" << accountId << "; got:");
errorDumpStr(QString::fromLatin1(replyData.constData()));
errorDumpStr(QString::fromUtf8(replyData));
m_syncSucceeded[accountId] = false;
} else {
// TODO: reduce code duplication between here and the other function.
Expand Down
1 change: 1 addition & 0 deletions src/google/google-calendars/googlecalendarsyncadaptor.h
Expand Up @@ -118,6 +118,7 @@ private Q_SLOTS:
QMap<int, QMultiMap<QString, QJsonObject> > m_calendarIdToEventObjects;
QMap<int, QMap<QString, QString> > m_recurringEventIdToKCalUid;
QMap<int, bool> m_syncSucceeded;
QMap<int, bool> m_cleanSyncRequired;
QMap<int, QDateTime> m_prevSinceTimestamp;
QMap<int, QDateTime> m_newSinceTimestamp;

Expand Down
9 changes: 4 additions & 5 deletions src/google/googledatatypesyncadaptor.cpp
Expand Up @@ -119,15 +119,14 @@ void GoogleDataTypeSyncAdaptor::errorHandler(QNetworkReply::NetworkError err)
int httpCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
QByteArray jsonBody = reply->readAll();
qWarning() << "sociald:Google: would normally set CredentialsNeedUpdate for account"
<< reply->property("accountId").toInt() << "but could be spurious\n"
<< " Http code:" << httpCode << "\n"
<< " Json body:\n" << jsonBody << "\n";
<< reply->property("accountId").toInt() << "but could be spurious";
qWarning() << " Http code:" << httpCode;
qWarning() << " Json body:" << QString::fromUtf8(jsonBody).replace('\r', ' ').replace('\n', ' ');
}

SOCIALD_LOG_ERROR(SocialNetworkSyncAdaptor::dataTypeName(m_dataType) <<
"request with account" << sender()->property("accountId").toInt() <<
"experienced error:" << err << "\n" <<
QString::fromUtf8(reply->readAll()));
"experienced error:" << err);
// set "isError" on the reply so that adapters know to ignore the result in the finished() handler
reply->setProperty("isError", QVariant::fromValue<bool>(true));
// Note: not all errors are "unrecoverable" errors, so we don't change the status here.
Expand Down

0 comments on commit dd021f1

Please sign in to comment.