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; } }
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; }
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; }