void ActiveSyncSource::readItem(const std::string &luid, std::string &item)
{
    // return straight from cache?
    std::map<std::string, std::string>::iterator it = m_items.find(luid);
    if (it == m_items.end()) {
        // no, must fetch
        EASItemPtr tmp(eas_item_info_new(), "EasItem");
        GErrorCXX gerror;
        if (!eas_sync_handler_fetch_item(m_handler,
                                         m_folder.c_str(),
                                         luid.c_str(),
                                         tmp,
                                         getEasType(),
                                         gerror)) {
            if (gerror.m_gerror->message &&
                strstr(gerror.m_gerror->message, "ObjectNotFound")
                /* gerror.matches(EAS_CONNECTION_ERROR, EAS_CONNECTION_ITEMOPERATIONS_ERROR_OBJECTNOTFOUND)
                   (gdb) p *m_gerror
                   $7 = {domain = 156, code = 36, 
                   message = 0xda2940 "GDBus.Error:org.meego.activesyncd.ItemOperationsError.ObjectNotFound: Document library - The object was not found or access denied."}

                */) {
                throwError(STATUS_NOT_FOUND, "item not found: " + luid);
            } else {
                gerror.throwError(StringPrintf("reading eas item %s", luid.c_str()));
            }
        }
        if (!tmp->data) {
            throwError(StringPrintf("no body returned for eas item %s", luid.c_str()));
        }
        item = tmp->data;
    } else {
        item = it->second;
    }
}
Esempio n. 2
0
void ForkExecParent::setupPipe(GIOChannel *&channel, guint &sourceID, int fd)
{
    if (fd == -1) {
        // nop
        return;
    }

    channel = g_io_channel_unix_new(fd);
    if (!channel) {
        // failure
        SE_LOG_DEBUG(NULL, NULL, "g_io_channel_unix_new() returned NULL");
        close(fd);
        return;
    }
    // Close fd when freeing the channel (done by caller).
    g_io_channel_set_close_on_unref(channel, true);
    // Don't block in outputReady().
    GErrorCXX error;
    g_io_channel_set_flags(channel, G_IO_FLAG_NONBLOCK, error);
    // We assume that the helper is writing data in the same encoding
    // and thus avoid any kind of conversion. Necessary to avoid
    // buffering.
    error.clear();
    g_io_channel_set_encoding(channel, NULL, error);
    g_io_channel_set_buffered(channel, true);
    sourceID = g_io_add_watch(channel, (GIOCondition)(G_IO_IN|G_IO_ERR|G_IO_HUP), outputReady, this);
}
void EvolutionContactSource::listAllItems(RevisionMap_t &revisions)
{
#ifdef USE_EBOOK_CLIENT
    GErrorCXX gerror;
    EBookClientView *view;

    EBookQueryCXX allItemsQuery(e_book_query_any_field_contains(""), false);
    PlainGStr sexp(e_book_query_to_string (allItemsQuery.get()));
    
    if (!e_book_client_get_view_sync(m_addressbook, sexp, &view, NULL, gerror)) {
        throwError( "getting the view" , gerror);
    }
    EBookClientViewCXX viewPtr = EBookClientViewCXX::steal(view);

    // Optimization: set fields_of_interest (UID / REV)
    GListCXX<const char, GSList> interesting_field_list;
    interesting_field_list.push_back(e_contact_field_name (E_CONTACT_UID));
    interesting_field_list.push_back(e_contact_field_name (E_CONTACT_REV));
    e_book_client_view_set_fields_of_interest (viewPtr, interesting_field_list, gerror);
    if (gerror) {
        SE_LOG_ERROR(this, NULL, "e_book_client_view_set_fields_of_interest: %s", (const char*)gerror);
        gerror.clear();
    }

    EBookClientViewSyncHandler handler(viewPtr, list_revisions, &revisions);
    if (!handler.process(gerror)) {
        throwError("watching view", gerror);
    }
#else
    GErrorCXX gerror;
    eptr<EBookQuery> allItemsQuery(e_book_query_any_field_contains(""), "query");
    GList *nextItem;
    if (!e_book_get_contacts(m_addressbook, allItemsQuery, &nextItem, gerror)) {
        throwError( "reading all items", gerror );
    }
    eptr<GList> listptr(nextItem);
    while (nextItem) {
        EContact *contact = E_CONTACT(nextItem->data);
        if (!contact) {
            throwError("contact entry without data");
        }
        pair<string, string> revmapping;
        const char *uid = (const char *)e_contact_get_const(contact,
                                                            E_CONTACT_UID);
        if (!uid || !uid[0]) {
            throwError("contact entry without UID");
        }
        revmapping.first = uid;
        const char *rev = (const char *)e_contact_get_const(contact,
                                                            E_CONTACT_REV);
        if (!rev || !rev[0]) {
            throwError(string("contact entry without REV: ") + revmapping.first);
        }
        revmapping.second = rev;
        revisions.insert(revmapping);
        nextItem = nextItem->next;
    }
#endif
}
boost::shared_ptr<AuthProvider> createSignonAuthProvider(const InitStateString &username,
                                                         const InitStateString &password)
{
    // Expected content of parameter GVariant.
    boost::shared_ptr<GVariantType> hashtype(g_variant_type_new("a{sv}"), g_variant_type_free);

    // 'username' is the part after signon: which we can parse directly.
    GErrorCXX gerror;
    GVariantCXX parametersVar(g_variant_parse(hashtype.get(), username.c_str(), NULL, NULL, gerror),
                              TRANSFER_REF);
    if (!parametersVar) {
        gerror.throwError(SE_HERE, "parsing 'signon:' username");
    }
    GHashTableCXX parameters(Variant2HashTable(parametersVar));

    // Extract the values that we expect in the parameters hash.
    guint32 signonID;
    const char *method;
    const char *mechanism;

    GVariant *value;
    value = (GVariant *)g_hash_table_lookup(parameters, "identity");
    if (!value ||
        !g_variant_type_equal(G_VARIANT_TYPE_UINT32, g_variant_get_type(value))) {
        SE_THROW("need 'identity: <numeric ID>' in 'signon:' parameters");
    }
    signonID = g_variant_get_uint32(value);

    value = (GVariant *)g_hash_table_lookup(parameters, "method");
    if (!value ||
        !g_variant_type_equal(G_VARIANT_TYPE_STRING, g_variant_get_type(value))) {
        SE_THROW("need 'method: <string>' in 'signon:' parameters");
    }
    method = g_variant_get_string(value, NULL);

    value = (GVariant *)g_hash_table_lookup(parameters, "mechanism");
    if (!value ||
        !g_variant_type_equal(G_VARIANT_TYPE_STRING, g_variant_get_type(value))) {
        SE_THROW("need 'mechanism: <string>' in 'signon:' parameters");
    }
    mechanism = g_variant_get_string(value, NULL);

    value = (GVariant *)g_hash_table_lookup(parameters, "session");
    if (!value ||
        !g_variant_type_equal(hashtype.get(), g_variant_get_type(value))) {
        SE_THROW("need 'session: <hash>' in 'signon:' parameters");
    }
    GHashTableCXX sessionData(Variant2HashTable(value));

    SE_LOG_DEBUG(NULL, "using identity %u, method %s, mechanism %s",
                 signonID, method, mechanism);
    SignonIdentityCXX identity(signon_identity_new_from_db(signonID), TRANSFER_REF);
    SE_LOG_DEBUG(NULL, "using signond identity %d", signonID);
    SignonAuthSessionCXX authSession(signon_identity_create_session(identity, method, gerror), TRANSFER_REF);

    boost::shared_ptr<AuthProvider> provider(new SignonAuthProvider(authSession, sessionData, mechanism));
    return provider;
}
GLibNotify::GLibNotify(const char *file, 
                       const callback_t &callback) :
    m_callback(callback)
{
    GFileCXX filecxx(g_file_new_for_path(file), TRANSFER_REF);
    GErrorCXX gerror;
    GFileMonitorCXX monitor(g_file_monitor_file(filecxx.get(), G_FILE_MONITOR_NONE, NULL, gerror), TRANSFER_REF);
    m_monitor.swap(monitor);
    if (!m_monitor) {
        gerror.throwError(SE_HERE, std::string("monitoring ") + file);
    }
    g_signal_connect_after(m_monitor.get(),
                           "changed",
                           G_CALLBACK(changed),
                           (void *)&m_callback);
}
void ActiveSyncSource::deleteItem(const string &luid)
{
    // asking to delete a non-existent item via ActiveSync does not
    // trigger an error; this is expected by the caller, so detect
    // the problem by looking up the item in our list (and keep the
    // list up-to-date elsewhere)
    if (m_ids && m_ids->readProperty(luid).empty()) {
        throwError(STATUS_NOT_FOUND, "item not found: " + luid);
    }

    // send delete request
    // TODO (?): batch delete requests
    GListCXX<char, GSList> items;
    items.push_back((char *)luid.c_str());

    GErrorCXX gerror;
    char *buffer;
    if (!eas_sync_handler_delete_items(m_handler,
                                       m_currentSyncKey.c_str(),
                                       &buffer,
                                       getEasType(),
                                       m_folder.c_str(),
                                       items,
                                       gerror)) {
        gerror.throwError("deleting eas item");
    }
    GStringPtr bufferOwner(buffer, "delete items: empty sync key returned");

    // remove from item list
    if (m_ids) {
        m_items.erase(luid);
        m_ids->removeProperty(luid);
    }

    // update key
    m_currentSyncKey = buffer;
}
Esempio n. 7
0
void ForkExecParent::start()
{
    if (m_watchChild) {
        SE_THROW("child already started");
    }

    // boost::shared_ptr<ForkExecParent> me = ...;
    GDBusCXX::DBusErrorCXX dbusError;

    SE_LOG_DEBUG(NULL, NULL, "ForkExecParent: preparing for child process %s", m_helper.c_str());
    m_server = GDBusCXX::DBusServerCXX::listen("", &dbusError);
    if (!m_server) {
        dbusError.throwFailure("starting server");
    }
    m_server->setNewConnectionCallback(boost::bind(&ForkExecParent::newClientConnection, this, _2));

    // look for helper binary
    std::string helper;
    GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD;
    if (m_helper.find('/') == m_helper.npos) {
        helper = getEnv("SYNCEVOLUTION_LIBEXEC_DIR", "");
        if (helper.empty()) {
            // env variable not set, look in libexec dir
            helper = SYNCEVO_LIBEXEC;
            helper += "/";
            helper += m_helper;
            if (access(helper.c_str(), R_OK)) {
                // some error, try PATH
                flags = (GSpawnFlags)(flags | G_SPAWN_SEARCH_PATH);
                helper = m_helper;
            }
        } else {
            // use env variable without further checks, must work
            helper += "/";
            helper += m_helper;
        }
    } else {
        // absolute path, use it
        helper = m_helper;
    }

    m_argvStrings.push_back(helper);
    m_argv.reset(AllocStringArray(m_argvStrings));
    for (char **env = environ;
         *env;
         env++) {
        if (!boost::starts_with(*env, ForkExecEnvVar)) {
            m_envStrings.push_back(*env);
        }
    }

    // pass D-Bus address via env variable
    m_envStrings.push_back(ForkExecEnvVar + m_server->getAddress());
    m_env.reset(AllocStringArray(m_envStrings));

    SE_LOG_DEBUG(NULL, NULL, "ForkExecParent: running %s with D-Bus address %s",
                 helper.c_str(), m_server->getAddress().c_str());

    // Check which kind of output redirection is wanted.
    m_mergedStdoutStderr = !m_onOutput.empty();
    if (!m_onOutput.empty()) {
        m_mergedStdoutStderr = true;
    }

    GErrorCXX gerror;
    int err = -1, out = -1;
    if (!g_spawn_async_with_pipes(NULL, // working directory
                                  static_cast<gchar **>(m_argv.get()),
                                  static_cast<gchar **>(m_env.get()),
                                  flags,
                                  // child setup function: redirect stdout to stderr
                                  m_mergedStdoutStderr ? setStdoutToStderr : NULL,
                                  NULL, // child setup user data
                                  &m_childPid,
                                  NULL, // set stdin to /dev/null
                                  (m_mergedStdoutStderr || m_onStdout.empty()) ? NULL : &out,
                                  (m_mergedStdoutStderr || !m_onStderr.empty()) ? &err : NULL,
                                  gerror)) {
        m_childPid = 0;
        gerror.throwError("spawning child");
    }
    // set up output redirection, ignoring failures
    setupPipe(m_err, m_errID, err);
    setupPipe(m_out, m_outID, out);

    SE_LOG_DEBUG(NULL, NULL, "ForkExecParent: child process for %s has pid %ld",
                 helper.c_str(), (long)m_childPid);

    // TODO: introduce C++ wrapper around GSource
    m_watchChild = g_child_watch_source_new(m_childPid);
    g_source_set_callback(m_watchChild, (GSourceFunc)watchChildCallback, this, NULL);
    g_source_attach(m_watchChild, NULL);
}
EvolutionSyncSource::Databases EvolutionContactSource::getDatabases()
{
    ESourceList *sources = NULL;

#ifdef USE_EBOOK_CLIENT
    if (!e_book_client_get_sources(&sources, NULL)) {
#else
    if (!e_book_get_addressbooks(&sources, NULL)) {
#endif
        SyncContext::throwError("unable to access address books");
    }

    Databases secondary;
    Databases result;
    for (GSList *g = e_source_list_peek_groups (sources); g; g = g->next) {
        ESourceGroup *group = E_SOURCE_GROUP (g->data);
        for (GSList *s = e_source_group_peek_sources (group); s; s = s->next) {
            ESource *source = E_SOURCE (s->data);
            eptr<char> uri(e_source_get_uri(source));
            string uristr;
            if (uri) {
                uristr = uri.get();
            }
            Database entry(e_source_peek_name(source),
                           uristr,
                           false);
            if (boost::starts_with(uristr, "couchdb://")) {
                // Append CouchDB address books at the end of the list,
                // otherwise preserving the order of address books.
                //
                // The reason is Moblin Bugzilla #7877 (aka CouchDB
                // feature request #479110): the initial release of
                // evolution-couchdb in Ubuntu 9.10 is unusable because
                // it does not support the REV property.
                //
                // Reordering the entries ensures that the CouchDB
                // address book is not used as the default database by
                // SyncEvolution, as it happened in Ubuntu 9.10.
                // Users can still pick it intentionally via
                // "evolutionsource".
                secondary.push_back(entry);
            } else {
                result.push_back(entry);
            }
        }
    }
    result.insert(result.end(), secondary.begin(), secondary.end());

    // No results? Try system address book (workaround for embedded Evolution Dataserver).
    if (!result.size()) {
#ifdef USE_EBOOK_CLIENT
        EBookClientCXX book;
        const char *name;

        name = "<<system>>";
        book = EBookClientCXX::steal(e_book_client_new_system (NULL));
        if (!book) {
            name = "<<default>>";
            book = EBookClientCXX::steal(e_book_client_new_default (NULL));
        }

        if (book) {
            const char *uri = e_client_get_uri (E_CLIENT ((EBookClient*)book));
            result.push_back(Database(name, uri, true));
        }
#else
        eptr<EBook, GObject> book;
        GErrorCXX gerror;
        const char *name;

        name = "<<system>>";
        book = e_book_new_system_addressbook (gerror);
        gerror.clear();
        if (!book) {
            name = "<<default>>";
            book = e_book_new_default_addressbook (gerror);
        }

        if (book) {
            const char *uri = e_book_get_uri (book);
            result.push_back(Database(name, uri, true));
        }
#endif
    } else {
        //  the first DB found is the default
        result[0].m_isDefault = true;
    }
    
    return result;
}

#ifdef USE_EBOOK_CLIENT
static void
handle_error_cb (EClient */*client*/, const gchar *error_msg, gpointer user_data)
{
    EvolutionContactSource *that = static_cast<EvolutionContactSource *>(user_data);
    SE_LOG_ERROR(that, NULL, "%s", error_msg);
}
void EvolutionContactSource::open()
{
    GErrorCXX gerror;
    bool created = false;
    bool onlyIfExists = false; // always try to create address book, because even if there is
                               // a source there's no guarantee that the actual database was
                               // created already; the original logic below for only setting
                               // this when explicitly requesting a new address book
                               // therefore failed in some cases
#ifdef USE_EBOOK_CLIENT
    ESourceList *tmp;
    if (!e_book_client_get_sources(&tmp, gerror)) {
        throwError("unable to access address books", gerror);
    }
    ESourceListCXX sources(tmp, false);

    string id = getDatabaseID();
    ESource *source = findSource(sources, id);
    if (!source) {
        // might have been special "<<system>>" or "<<default>>", try that and
        // creating address book from file:// URI before giving up
        if (id.empty() || id == "<<system>>") {
            m_addressbook = EBookClientCXX::steal(e_book_client_new_system (gerror));
        } else if (id.empty() || id == "<<default>>") {
            m_addressbook = EBookClientCXX::steal(e_book_client_new_default (gerror));
        } else if (boost::starts_with(id, "file://")) {
            m_addressbook = EBookClientCXX::steal(e_book_client_new_from_uri(id.c_str(), gerror));
        } else {
            throwError(string(getName()) + ": no such address book: '" + id + "'");
        }
        created = true;
    } else {
        m_addressbook = EBookClientCXX::steal(e_book_client_new( source, gerror ));
    }

    if (gerror) {
        throwError("create addressbook", gerror);
    }

    // Listen for errors
    g_signal_connect (m_addressbook, "backend-error", G_CALLBACK (handle_error_cb), this); 

    // Handle authentication requests from the backend
    g_signal_connect (m_addressbook, "authenticate", G_CALLBACK (handle_authentication_cb), this);
 
    // Open the address book
    if (!e_client_open_sync( E_CLIENT ((EBookClient*)m_addressbook), onlyIfExists, NULL, gerror) ) {
        if (created) {
            // opening newly created address books often fails, try again once more
            gerror.clear();
            sleep(5);
            if (!e_client_open_sync( E_CLIENT ((EBookClient*)m_addressbook), onlyIfExists, NULL, gerror)) {
                throwError("opening address book", gerror);
            }
        } else {
            throwError("opening address book", gerror);
        }
    }

    g_signal_connect_after(m_addressbook,
                           "backend-died",
                           G_CALLBACK(SyncContext::fatalError),
                           (void *)"Evolution Data Server has died unexpectedly, contacts no longer available.");
#else
    ESourceList *tmp;
    if (!e_book_get_addressbooks(&tmp, gerror)) {
        throwError("unable to access address books", gerror);
    }
    ESourceListCXX sources(tmp, false);

    string id = getDatabaseID();
    ESource *source = findSource(sources, id);
    if (!source) {
        // might have been special "<<system>>" or "<<default>>", try that and
        // creating address book from file:// URI before giving up
        if (id.empty() || id == "<<system>>") {
            m_addressbook.set( e_book_new_system_addressbook (gerror), "system address book" );
        } else if (id.empty() || id == "<<default>>") {
            m_addressbook.set( e_book_new_default_addressbook (gerror), "default address book" );
        } else if (boost::starts_with(id, "file://")) {
            m_addressbook.set(e_book_new_from_uri(id.c_str(), gerror), "creating address book");
        } else {
            throwError(string(getName()) + ": no such address book: '" + id + "'");
        }
        created = true;
    } else {
        m_addressbook.set( e_book_new( source, gerror ), "address book" );
    }
 
    if (!e_book_open( m_addressbook, onlyIfExists, gerror) ) {
        if (created) {
            // opening newly created address books often fails, try again once more
            sleep(5);
            if (!e_book_open(m_addressbook, onlyIfExists, gerror)) {
                throwError("opening address book", gerror);
            }
        } else {
            throwError("opening address book", gerror);
        }
    }

    // users are not expected to configure an authentication method,
    // so pick one automatically if the user indicated that he wants authentication
    // by setting user or password
    std::string user = getUser(),
        passwd = getPassword();
    if (!user.empty() || !passwd.empty()) {
        GList *authmethod;
        if (!e_book_get_supported_auth_methods(m_addressbook, &authmethod, gerror)) {
            throwError("getting authentication methods", gerror);
        }
        while (authmethod) {
            const char *method = (const char *)authmethod->data;
            SE_LOG_DEBUG(this, NULL, "trying authentication method \"%s\", user %s, password %s",
                         method,
                         !user.empty() ? "configured" : "not configured",
                         !passwd.empty() ? "configured" : "not configured");
            if (e_book_authenticate_user(m_addressbook,
                                         user.c_str(),
                                         passwd.c_str(),
                                         method,
                                         gerror)) {
                SE_LOG_DEBUG(this, NULL, "authentication succeeded");
                break;
            } else {
                SE_LOG_ERROR(this, NULL, "authentication failed: %s", gerror->message);
            }
            authmethod = authmethod->next;
        }
    }

    g_signal_connect_after(m_addressbook,
                           "backend-died",
                           G_CALLBACK(SyncContext::fatalError),
                           (void *)"Evolution Data Server has died unexpectedly, contacts no longer available.");
#endif
}
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);
    }
}
SyncSourceSerialize::InsertItemResult ActiveSyncSource::insertItem(const std::string &luid, const std::string &data)
{
    SyncSourceSerialize::InsertItemResult res;

    EASItemPtr tmp(eas_item_info_new(), "EasItem");
    EasItemInfo *item = tmp.get();
    if (!luid.empty()) {
        // update
        item->server_id = g_strdup(luid.c_str());
    } else {
        // add
        // TODO: is a local id needed? We don't have one.
    }
    item->data = g_strdup(data.c_str());
    EASItemsCXX items;
    items.push_front(tmp.release());

    GErrorCXX gerror;
    char *buffer;

    // distinguish between update (existing luid)
    // or creation (empty luid)
    if (luid.empty()) {
        // send item to server
        if (!eas_sync_handler_add_items(m_handler,
                                        m_currentSyncKey.c_str(),
                                        &buffer,
                                        getEasType(),
                                        m_folder.c_str(),
                                        items,
                                        gerror)) {
            gerror.throwError("adding eas item");
        }
        if (!item->server_id) {
            throwError("no server ID for new eas item");
        }
        // get new ID from updated item
        res.m_luid = item->server_id;
        if (res.m_luid.empty()) {
            throwError("empty server ID for new eas item");
        }

        // TODO: if someone else has inserted a new calendar item
        // with the same UID as the one we are trying to insert here,
        // what will happen? Does the ActiveSync server prevent
        // adding our own version of the item or does it merge?
        // res.m_merged = ???
    } else {
        // update item on server
        if (!eas_sync_handler_update_items(m_handler,
                                           m_currentSyncKey.c_str(),
                                           &buffer,
                                           getEasType(),
                                           m_folder.c_str(),
                                           items,
                                           gerror)) {
            gerror.throwError("updating eas item");
        }
        res.m_luid = luid;
    }
    GStringPtr bufferOwner(buffer, "insert item: empty sync key returned");

    // add/update in cache
    if (m_ids) {
        m_items[res.m_luid] = data;
        m_ids->setProperty(res.m_luid, "1");
    }

    // update key
    m_currentSyncKey = buffer;

    return res;
}