virtual std::string getOAuth2Bearer(const PasswordUpdateCallback &passwordUpdateCallback)
    {
        SE_LOG_DEBUG(NULL, "retrieving OAuth2 token");

        if (!m_accessToken.empty() && !m_invalidateCache) {
            return m_accessToken;
        }

        // Retry login if even the refreshed token failed.
        g_hash_table_insert(m_sessionData, g_strdup("ForceTokenRefresh"),
                            g_variant_ref_sink(g_variant_new_boolean(m_invalidateCache)));

        // We get assigned a plain pointer to an instance that we'll own,
        // so we have to use the "steal" variant to enable that assignment.
        GVariantStealCXX resultDataVar;
        GErrorCXX gerror;
        GVariantCXX sessionDataVar(HashTable2Variant(m_sessionData));
        PlainGStr buffer(g_variant_print(sessionDataVar, true));
        SE_LOG_DEBUG(NULL, "asking for OAuth2 token with method %s, mechanism %s and parameters %s",
                     signon_auth_session_get_method(m_authSession),
                     m_mechanism.c_str(),
                     buffer.get());

#define signon_auth_session_process_async_finish signon_auth_session_process_finish
        SYNCEVO_GLIB_CALL_SYNC(resultDataVar, gerror, signon_auth_session_process_async,
                               m_authSession, sessionDataVar, m_mechanism.c_str(), NULL);
        buffer.reset(resultDataVar ? g_variant_print(resultDataVar, true) : NULL);
        SE_LOG_DEBUG(NULL, "OAuth2 token result: %s, %s",
                     buffer.get() ? buffer.get() : "<<null>>",
                     gerror ? gerror->message : "???");
        if (!resultDataVar || gerror) {
            SE_THROW_EXCEPTION_STATUS(StatusException,
                                      StringPrintf("could not obtain OAuth2 token: %s", gerror ? gerror->message : "???"),
                                      STATUS_FORBIDDEN);
        }
        GHashTableCXX resultData(Variant2HashTable(resultDataVar));
        GVariant *tokenVar = static_cast<GVariant *>(g_hash_table_lookup(resultData, (gpointer)"AccessToken"));
        if (!tokenVar) {
            SE_THROW("no AccessToken in OAuth2 response");
        }
        std::string newToken = g_variant_get_string(tokenVar, NULL);
        if (newToken.empty()) {
            SE_THROW("AccessToken did not contain a string value");
        } else if (m_invalidateCache && newToken == m_accessToken) {
            SE_THROW("Got the same invalid AccessToken");
        }
        m_accessToken = newToken;
        return m_accessToken;
    }
void ActiveSyncSource::beginSync(const std::string &lastToken, const std::string &resumeToken)
{
    // erase content which might have been set in a previous call
    reset();

    // claim item node for ids, if not done yet
    if (m_itemNode && !m_ids) {
        m_ids.swap(m_itemNode);
    }

    // incremental sync (non-empty token) or start from scratch
    m_startSyncKey = lastToken;
    if (lastToken.empty()) {
        // slow sync: wipe out cached list of IDs, will be filled anew below
        SE_LOG_DEBUG(this, NULL, "sync key empty, starting slow sync");
        m_ids->clear();
    } else {
        SE_LOG_DEBUG(this, NULL, "sync key %s for account '%s' folder '%s', starting incremental sync",
                     lastToken.c_str(),
                     m_account.c_str(),
                     m_folder.c_str());
    }

    gboolean moreAvailable = TRUE;

    m_currentSyncKey = m_startSyncKey;

    // same logic as in ActiveSyncCalendarSource::beginSync()

    bool slowSync = false;
    for (bool firstIteration = true;
         moreAvailable;
         firstIteration = false) {
        gchar *buffer = NULL;
        GErrorCXX gerror;
        EASItemsCXX created, updated;
        EASIdsCXX deleted;
        bool wasSlowSync = m_currentSyncKey.empty();

        if (!eas_sync_handler_get_items(m_handler,
                                        m_currentSyncKey.c_str(),
                                        &buffer,
                                        getEasType(),
                                        m_folder.c_str(),
                                        created, updated, deleted,
                                        &moreAvailable,
                                        gerror)) {
            if (gerror.m_gerror &&
                /*
                gerror.m_gerror->domain == EAS_TYPE_CONNECTION_ERROR &&
                gerror.m_gerror->code == EAS_CONNECTION_SYNC_ERROR_INVALIDSYNCKEY && */
                gerror.m_gerror->message &&
                strstr(gerror.m_gerror->message, "Sync error: Invalid synchronization key") &&
                firstIteration) {
                // fall back to slow sync
                slowSync = true;
                m_currentSyncKey = "";
                m_ids->clear();
                continue;
            }

            gerror.throwError("reading ActiveSync changes");
        }
        GStringPtr bufferOwner(buffer, "reading changes: empty sync key returned");

        // TODO: Test that we really get an empty token here for an unexpected slow
        // sync. If not, we'll start an incremental sync here and later the engine
        // will ask us for older, unmodified item content which we won't have.

        // populate ID lists and content cache
        BOOST_FOREACH(EasItemInfo *item, created) {
            if (!item->server_id) {
                throwError("no server ID for new eas item");
            }
            string luid(item->server_id);
            if (luid.empty()) {
                throwError("empty server ID for new eas item");
            }
            SE_LOG_DEBUG(this, NULL, "new item %s", luid.c_str());
            addItem(luid, NEW);
            m_ids->setProperty(luid, "1");
            if (!item->data) {
                throwError(StringPrintf("no body returned for new eas item %s", luid.c_str()));
            }
            m_items[luid] = item->data;
        }
        BOOST_FOREACH(EasItemInfo *item, updated) {
            if (!item->server_id) {
                throwError("no server ID for updated eas item");
            }
            string luid(item->server_id);
            if (luid.empty()) {
                throwError("empty server ID for updated eas item");
            }
            SE_LOG_DEBUG(this, NULL, "updated item %s", luid.c_str());
            addItem(luid, UPDATED);
            // m_ids.setProperty(luid, "1"); not necessary, should already exist (TODO: check?!)
            if (!item->data) {
                throwError(StringPrintf("no body returned for updated eas item %s", luid.c_str()));
            }
            m_items[luid] = item->data;
        }
        BOOST_FOREACH(const char *serverID, deleted) {
            if (!serverID) {
                throwError("no server ID for deleted eas item");
            }
            string luid(serverID);
            if (luid.empty()) {
                throwError("empty server ID for deleted eas item");
            }
            SE_LOG_DEBUG(this, NULL, "deleted item %s", luid.c_str());
            addItem(luid, DELETED);
            m_ids->removeProperty(luid);
        }

        // update key
        m_currentSyncKey = buffer;

        // Google  hack: if we started with an empty sync key (= slow sync)
        // and got no results (= existing items), then try one more time,
        // because Google only seems to report results when asked with
        // a valid sync key. As an additional sanity check make sure that
        // we have a valid sync key now.
        if (wasSlowSync &&
            created.empty() &&
            !m_currentSyncKey.empty()) {
            moreAvailable = true;
        }
    }

    // now also generate full list of all current items:
    // old items + new (added to m_ids above) - deleted (removed above)
    ConfigProps props;
    m_ids->readProperties(props);
    BOOST_FOREACH(const StringPair &entry, props) {
        const std::string &luid = entry.first;
        SE_LOG_DEBUG(this, NULL, "existing item %s", luid.c_str());
        addItem(luid, ANY);
    }

    if (slowSync) {
        // tell engine that we need a slow sync, if it didn't know already
        SE_THROW_EXCEPTION_STATUS(StatusException,
                                  "ActiveSync error: Invalid synchronization key",
                                  STATUS_SLOW_SYNC_508);
    }
}