static void _osync_obj_engine_sync_done_callback(OSyncClientProxy *proxy, void *userdata, OSyncError *error)
{
  OSyncSinkEngine *sinkengine = userdata;
  OSyncObjEngine *engine = sinkengine->engine;
  OSyncError *locerror = NULL;
	
  osync_trace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, proxy, userdata, error);
	
  if (error) {
    osync_obj_engine_set_error(engine, error);
    engine->sink_errors = engine->sink_errors | (0x1 << sinkengine->position);
    osync_status_update_member(engine->parent, osync_client_proxy_get_member(proxy), OSYNC_CLIENT_EVENT_ERROR, engine->objtype, error);
  } else {
    engine->sink_sync_done = engine->sink_sync_done | (0x1 << sinkengine->position);
    osync_status_update_member(engine->parent, osync_client_proxy_get_member(proxy), OSYNC_CLIENT_EVENT_SYNC_DONE, engine->objtype, NULL);
  }
			
  if (osync_bitcount(engine->sink_errors | engine->sink_sync_done) == g_list_length(engine->sink_engines)) {
    if (osync_bitcount(engine->sink_sync_done) < osync_bitcount(engine->sink_connects)) {
      osync_error_set(&locerror, OSYNC_ERROR_GENERIC, "Fewer sink_engines reported sync_done than connected");
      osync_obj_engine_set_error(engine, locerror);
    }

    osync_obj_engine_event(engine, OSYNC_ENGINE_EVENT_SYNC_DONE, locerror ? locerror : error);
  } else
    osync_trace(TRACE_INTERNAL, "Not yet: %i", osync_bitcount(engine->sink_errors | engine->sink_sync_done));
	
  osync_trace(TRACE_EXIT, "%s", __func__);
}
static void _osync_obj_engine_connect_callback(OSyncClientProxy *proxy, void *userdata, osync_bool slowsync, OSyncError *error)
{
  OSyncSinkEngine *sinkengine = userdata;
  OSyncObjEngine *engine = sinkengine->engine;
  OSyncError *locerror = NULL;
	
  osync_trace(TRACE_ENTRY, "%s(%p, %p, %i, %p)", __func__, proxy, userdata, slowsync, error);
	
  if (error) {
    osync_trace(TRACE_INTERNAL, "Obj Engine received connect error: %s", osync_error_print(&error));
    osync_obj_engine_set_error(engine, error);
    engine->sink_errors = engine->sink_errors | (0x1 << sinkengine->position);
    osync_status_update_member(engine->parent, osync_client_proxy_get_member(proxy), OSYNC_CLIENT_EVENT_ERROR, engine->objtype, error);
  } else {
    engine->sink_connects = engine->sink_connects | (0x1 << sinkengine->position);
    osync_status_update_member(engine->parent, osync_client_proxy_get_member(proxy), OSYNC_CLIENT_EVENT_CONNECTED, engine->objtype, NULL);
  }

  if (slowsync) {
    osync_obj_engine_set_slowsync(engine, TRUE);
    osync_trace(TRACE_INTERNAL, "SlowSync requested during connect.");
  }
			
  if (osync_bitcount(engine->sink_errors | engine->sink_connects) == g_list_length(engine->sink_engines)) {
    if (osync_bitcount(engine->sink_errors)) {
      osync_error_set(&locerror, OSYNC_ERROR_GENERIC, "At least one sink_engine failed while connecting");
      osync_obj_engine_set_error(engine, locerror);
    }

    osync_obj_engine_event(engine, OSYNC_ENGINE_EVENT_CONNECTED, locerror ? locerror : error);
  } else
    osync_trace(TRACE_INTERNAL, "Not yet: %i", osync_bitcount(engine->sink_errors | engine->sink_connects));
	
  osync_trace(TRACE_EXIT, "%s", __func__);
}
static void _osync_obj_engine_commit_change_callback(OSyncClientProxy *proxy, void *userdata, const char *uid, OSyncError *error)
{
  OSyncMappingEntryEngine *entry_engine = userdata;
  OSyncObjEngine *engine = entry_engine->objengine;
  OSyncSinkEngine *sinkengine = entry_engine->sink_engine;
  OSyncError *locerror = NULL;
  OSyncMapping *mapping = NULL;
  OSyncMember *member = NULL;
  OSyncMappingEntry *entry = NULL;
  const char *objtype = NULL;
  long long int id = 0;

	
  osync_trace(TRACE_ENTRY, "%s(%p, %p, %s, %p)", __func__, proxy, userdata, uid, error);
	
  osync_entry_engine_set_dirty(entry_engine, FALSE);
	
  mapping = entry_engine->mapping_engine->mapping;
  member = osync_client_proxy_get_member(proxy);
  entry = entry_engine->entry;
  objtype = osync_change_get_objtype(entry_engine->change);
  id = osync_mapping_entry_get_id(entry);
	
  if (error) {
    /* Error handling (tests: single_commit_error, ...) */

    /* TODO: Review differences between Mapping and Change status events - Are both really needed?! */
    osync_status_update_change(engine->parent, entry_engine->change, osync_client_proxy_get_member(proxy), entry_engine->mapping_engine->mapping, OSYNC_CHANGE_EVENT_ERROR, error);
    osync_status_update_mapping(engine->parent, entry_engine->mapping_engine, OSYNC_MAPPING_EVENT_ERROR, error);

    osync_obj_engine_set_error(engine, error);
    engine->sink_errors = engine->sink_errors | (0x1 << sinkengine->position);
    goto error;
  }
	
  if (uid)
    osync_change_set_uid(entry_engine->change, uid);
	
  if (engine->archive) {
    if (osync_change_get_changetype(entry_engine->change) == OSYNC_CHANGE_TYPE_DELETED) {
      /* TODO error handling */
      osync_archive_delete_change(engine->archive, id, objtype, &locerror);
    } else {

      /* TODO error handling */
      osync_archive_save_change(engine->archive, id, osync_change_get_uid(entry_engine->change), objtype, osync_mapping_get_id(mapping), osync_member_get_id(member), &locerror);
    }
  }

  osync_assert(entry_engine->mapping_engine);
  osync_status_update_change(engine->parent, entry_engine->change, osync_client_proxy_get_member(proxy), entry_engine->mapping_engine->mapping, OSYNC_CHANGE_EVENT_WRITTEN, NULL);
  osync_entry_engine_update(entry_engine, NULL);
	
  osync_trace(TRACE_EXIT, "%s", __func__);
  return;

 error:	
  _osync_obj_engine_generate_written_event(engine, error);
  osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(&error));
}
osync_bool osync_mapping_engine_supports_ignore(OSyncMappingEngine *engine)
{
  OSyncObjEngine *parent = NULL;
  osync_bool ignore_supported = TRUE;
  GList *s = NULL;

  osync_trace(TRACE_ENTRY, "%s(%p)", __func__, engine);
  osync_assert(engine);

  parent = engine->parent;
  for (s = parent->sink_engines; s; s = s->next) {
    OSyncSinkEngine *sink_engine = s->data;
		
    OSyncMember *member = osync_client_proxy_get_member(sink_engine->proxy);
    OSyncMappingEntryEngine *entry_engine = osync_mapping_engine_get_entry(engine, sink_engine);

    /* check if mapping could be solved by "ignore" conflict handler */
    const char *objtype = entry_engine->sink_engine->engine->objtype;
    OSyncObjTypeSink *objtype_sink = osync_member_find_objtype_sink(member, objtype);

    /* if there is no sink read function, ignore is not support for this mapping. */
    if (!objtype_sink || !osync_objtype_sink_get_function_read(objtype_sink))
      ignore_supported = FALSE; 

  }
	
  osync_trace(TRACE_EXIT, "%s: conflict handler ignore supported: %s", __func__, ignore_supported ? "TRUE" : "FALSE");
  return ignore_supported;
}
OSyncMappingEngine *_osync_obj_engine_create_mapping_engine(OSyncObjEngine *engine, OSyncError **error)
{
  /* If there is none, create one */
  OSyncMapping *mapping = osync_mapping_new(error);
  GList *s = NULL;
  OSyncMappingEngine *mapping_engine = NULL;
  if (!mapping)
    goto error;
	
  osync_mapping_set_id(mapping, osync_mapping_table_get_next_id(engine->mapping_table));
  osync_mapping_table_add_mapping(engine->mapping_table, mapping);
	
  for (s = engine->sink_engines; s; s = s->next) {
    OSyncSinkEngine *sink_engine = s->data;
		
    OSyncMember *member = osync_client_proxy_get_member(sink_engine->proxy);
		
    OSyncMappingEntry *mapping_entry = osync_mapping_entry_new(error);
    osync_mapping_entry_set_member_id(mapping_entry, osync_member_get_id(member));
    osync_mapping_add_entry(mapping, mapping_entry);
    osync_mapping_entry_unref(mapping_entry);
  }
	
  mapping_engine = osync_mapping_engine_new(engine, mapping, error);
  if (!mapping_engine)
    goto error_free_mapping;
  osync_mapping_unref(mapping);
	
  return mapping_engine;
	
 error_free_mapping:
  osync_mapping_unref(mapping);
 error:
  return NULL;
}
osync_bool osync_obj_engine_receive_change(OSyncObjEngine *objengine, OSyncClientProxy *proxy, OSyncChange *change, OSyncError **error)
{
  OSyncSinkEngine *sinkengine = NULL;
  GList *s = NULL, *e = NULL;
	
  osync_assert(objengine);
	
  osync_trace(TRACE_ENTRY, "%s(%p, %p, %p, %p)", __func__, objengine, proxy, change, error);
	
  /* Find the sinkengine for the proxy */
  for (s = objengine->sink_engines; s; s = s->next) {
    sinkengine = s->data;
    if (sinkengine->proxy == proxy)
      break;
    sinkengine = NULL;
  }
	
  if (!sinkengine) {
    osync_error_set(error, OSYNC_ERROR_GENERIC, "Unable to find sinkengine");
    osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
    return FALSE;
  }
	
  /* We now have to see if the change matches one of the already existing mappings */
  for (e = sinkengine->entries; e; e = e->next) {
    OSyncMappingEntryEngine *mapping_engine = e->data;
		
    if (osync_entry_engine_matches(mapping_engine, change)) {
      osync_entry_engine_update(mapping_engine, change);
			
      osync_status_update_change(sinkengine->engine->parent, change, osync_client_proxy_get_member(proxy), mapping_engine->mapping_engine->mapping, OSYNC_CHANGE_EVENT_READ, NULL);
			
      osync_trace(TRACE_EXIT, "%s: Updated", __func__);
      return TRUE;
    }
  }
	
  osync_status_update_change(sinkengine->engine->parent, change, osync_client_proxy_get_member(proxy), NULL, OSYNC_CHANGE_EVENT_READ, NULL);
			
  /* If we couldnt find a match entry, we will append it the unmapped changes
   * and take care of it later */
  sinkengine->unmapped = g_list_append(sinkengine->unmapped, change);
  osync_change_ref(change);
	
  osync_trace(TRACE_EXIT, "%s: Unmapped", __func__);
  return TRUE;
}
static void _osync_obj_engine_read_callback(OSyncClientProxy *proxy, void *userdata, OSyncError *error)
{
  OSyncSinkEngine *sinkengine = userdata;
  OSyncObjEngine *engine = sinkengine->engine;
  OSyncError *locerror = NULL;
	
  osync_trace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, proxy, userdata, error);
	
  if (error) {
    osync_obj_engine_set_error(engine, error);
    engine->sink_errors = engine->sink_errors | (0x1 << sinkengine->position);
    osync_status_update_member(engine->parent, osync_client_proxy_get_member(proxy), OSYNC_CLIENT_EVENT_ERROR, engine->objtype, error);
  } else {
    engine->sink_get_changes = engine->sink_get_changes | (0x1 << sinkengine->position);
    osync_status_update_member(engine->parent, osync_client_proxy_get_member(proxy), OSYNC_CLIENT_EVENT_READ, engine->objtype, NULL);
  }
	
  if (osync_bitcount(engine->sink_errors | engine->sink_get_changes) == g_list_length(engine->sink_engines)) {
		
    if (osync_bitcount(engine->sink_get_changes) < osync_bitcount(engine->sink_connects)) {
      osync_error_set(&locerror, OSYNC_ERROR_GENERIC, "Fewer sink_engines reported get_changes than connected");
      osync_obj_engine_set_error(engine, locerror);
    } else {
      /* We are now done reading the changes. so we can now start to create the mappings, conflicts etc */
      if (!osync_obj_engine_map_changes(engine, &locerror)) {
        osync_obj_engine_set_error(engine, locerror);
      } else {
        GList *m;
        for (m = engine->mapping_engines; m; m = m->next) {
          OSyncMappingEngine *mapping_engine = m->data;
          if (!mapping_engine->synced)
            osync_mapping_engine_check_conflict(mapping_engine);
        }
      }

    }

    osync_obj_engine_event(engine, OSYNC_ENGINE_EVENT_READ, locerror ? locerror : error);
  } else
    osync_trace(TRACE_INTERNAL, "Not yet: %i", osync_bitcount(engine->sink_errors | engine->sink_get_changes));
	
  osync_trace(TRACE_EXIT, "%s", __func__);
}
/* Note: This function got shared between _osync_obj_engine_commit_change_callback() and
   osync_obj_engine_written_callback(). Those function call _osync_obj_engine_generate_written_event()
   with the most recent error and pass it to this function as last argument "error". If no error
   appears this function got called with NULL as error.

   It's quite important that this function only get called with the most recent error. This function
   MUST NOT get called with (obj)engine->error.

   If this functions doesn't get called with the most recent commit/committed_all error OSyncEngine
   will get stuck. (testcases: dual_commit_error, dual_commit_timeout, *_commit_*, *_committed_all_*)
*/
static void _osync_obj_engine_generate_written_event(OSyncObjEngine *engine, OSyncError *error)
{
  osync_bool dirty = FALSE;
  GList *p = NULL;
  GList *e = NULL;
  OSyncSinkEngine *sinkengine = NULL;
  OSyncError *locerror = NULL;

  osync_trace(TRACE_ENTRY, "%s(%p, %p)", __func__, engine, error);
  /* We need to make sure that all entries are written ... */
	
  for (p = engine->sink_engines; p; p = p->next) {
    OSyncMember *member = NULL;
    OSyncObjTypeSink *objtype_sink = NULL;

    sinkengine = p->data;
    member = osync_client_proxy_get_member(sinkengine->proxy);
    objtype_sink = osync_member_find_objtype_sink(member, engine->objtype);

    /* If the sink engine isn't able/allowed to write we don't care if everything got written ("how dirty is it!?") */ 
    if (!objtype_sink || !osync_objtype_sink_get_write(objtype_sink)) 
      break;
		
    for (e = sinkengine->entries; e; e = e->next) {
      OSyncMappingEntryEngine *entry_engine = e->data;
      if (osync_entry_engine_is_dirty(entry_engine) == TRUE) {
        dirty = TRUE;
        break;
      }
    }
    if (dirty) {
      osync_trace(TRACE_EXIT, "%s: Still dirty", __func__);
      return;
    }
  }
  osync_trace(TRACE_INTERNAL, "%s: Not dirty anymore", __func__);

  /* And that we received the written replies from all sinks */
  if (osync_bitcount(engine->sink_errors | engine->sink_written) == g_list_length(engine->sink_engines)) {
    if (osync_bitcount(engine->sink_written) < osync_bitcount(engine->sink_connects)) {
      osync_error_set(&locerror, OSYNC_ERROR_GENERIC, "Fewer sink_engines reported committed all than connected");
      osync_obj_engine_set_error(engine, locerror);
    } else if (osync_bitcount(engine->sink_errors)) {
      /* Emit engine-wide error if one of the sinks got an error (tests: single_commit_error, ...) */
      osync_error_set(&locerror, OSYNC_ERROR_GENERIC, "At least one Sink Engine failed while committing");
      osync_obj_engine_set_error(engine, locerror);
    }

    osync_obj_engine_event(engine, OSYNC_ENGINE_EVENT_WRITTEN, locerror ? locerror : error);

  } else
    osync_trace(TRACE_INTERNAL, "Not yet: %i", osync_bitcount(engine->sink_errors | engine->sink_written));

  osync_trace(TRACE_EXIT, "%s", __func__);
}
static void _osync_obj_engine_written_callback(OSyncClientProxy *proxy, void *userdata, OSyncError *error)
{
  OSyncSinkEngine *sinkengine = userdata;
  OSyncObjEngine *engine = sinkengine->engine;
	
  osync_trace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, proxy, userdata, error);
	
  if (error) {
    osync_obj_engine_set_error(engine, error);
    engine->sink_errors = engine->sink_errors | (0x1 << sinkengine->position);
    osync_status_update_member(engine->parent, osync_client_proxy_get_member(proxy), OSYNC_CLIENT_EVENT_ERROR, engine->objtype, error);
  } else {
    engine->sink_written = engine->sink_written | (0x1 << sinkengine->position);
    osync_status_update_member(engine->parent, osync_client_proxy_get_member(proxy), OSYNC_CLIENT_EVENT_WRITTEN, engine->objtype, NULL);
  }
			
  _osync_obj_engine_generate_written_event(engine, error);
	
  osync_trace(TRACE_EXIT, "%s", __func__);
}
osync_bool osync_sink_engine_convert_to_dest(OSyncSinkEngine *engine, OSyncFormatEnv *formatenv, OSyncError **error)
{
    OSyncList *o;
    OSyncMember *member;
    OSyncObjTypeSink *objtype_sink;
    const char *objtype;
    OSyncFormatConverterPath *path = NULL;

    osync_assert(engine);
    osync_assert(formatenv);

    member = osync_client_proxy_get_member(engine->proxy);
    osync_assert(member);

    objtype = osync_obj_engine_get_objtype(engine->engine);
    objtype_sink = osync_member_find_objtype_sink(member, objtype);
    osync_assert(objtype_sink);

    for (o = engine->entries; o; o = o->next) {
        OSyncMappingEntryEngine *entry_engine = o->data;
        osync_assert(entry_engine);

        if (entry_engine->change == NULL)
            continue;

        /* If change not meant to get written (change shared among multiple "same" mapping
           entry engines), prevents conversions see #1207 */
        if (!osync_entry_engine_is_dirty(entry_engine))
            continue;

        if (osync_change_get_changetype(entry_engine->change) == OSYNC_CHANGE_TYPE_DELETED)
            continue;

        if (!osync_entry_engine_convert(entry_engine, formatenv, objtype_sink, &path, error))
            goto error;
    }

    if (path)
        osync_converter_path_unref(path);


    return TRUE;

error:
    if (path)
        osync_converter_path_unref(path);

    return FALSE;
}
OSyncMember *osync_mapping_engine_change_find_member(OSyncMappingEngine *engine, OSyncChange *change)
{
  OSyncMember *member = NULL;
  OSyncMappingEntryEngine *entry = NULL;
  osync_trace(TRACE_ENTRY, "%s(%p, %p)", __func__, engine, change);

  entry = _osync_mapping_engine_find_entry(engine, change);
  if (!entry)
    goto end;

  member = osync_client_proxy_get_member(entry->sink_engine->proxy);

 end:	
  osync_trace(TRACE_EXIT, "%s: %p", __func__, member);
  return member;
}
OSyncMappingEngine *osync_mapping_engine_new(OSyncObjEngine *parent, OSyncMapping *mapping, OSyncError **error)
{
  OSyncMappingEngine *engine = NULL;
  GList *s = NULL;
  osync_trace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, parent, mapping, error);

  osync_assert(parent);
  osync_assert(mapping);
	
  engine = osync_try_malloc0(sizeof(OSyncMappingEngine), error);
  if (!engine)
    goto error;
  engine->ref_count = 1;
	
  engine->mapping = mapping;
  osync_mapping_ref(mapping);
	
  engine->parent = parent;
  engine->synced = TRUE;
	
  for (s = parent->sink_engines; s; s = s->next) {
    OSyncSinkEngine *sink_engine = s->data;
    OSyncMappingEntryEngine *entry_engine  = NULL;
		
    OSyncMember *member = osync_client_proxy_get_member(sink_engine->proxy);
    OSyncMappingEntry *mapping_entry = osync_mapping_find_entry_by_member_id(mapping, osync_member_get_id(member));
    osync_assert(mapping_entry);
		
    entry_engine = osync_entry_engine_new(mapping_entry, engine, sink_engine, parent, error);
    if (!entry_engine)
      goto error_free_engine;

    engine->entries = g_list_append(engine->entries, entry_engine);
  }
	
  osync_trace(TRACE_EXIT, "%s: %p", __func__, engine);
  return engine;

 error_free_engine:
  osync_mapping_engine_unref(engine);
 error:
  osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
  return NULL;
}
osync_bool osync_sink_engine_demerge(OSyncSinkEngine *engine, OSyncArchive *archive, OSyncError **error)
{
    OSyncList *o;
    OSyncMember *member;
    OSyncCapabilities *caps;

    osync_assert(engine);
    osync_assert(archive);

    member = osync_client_proxy_get_member(engine->proxy);
    osync_assert(member);
    caps = osync_member_get_capabilities(member);

    if (!caps)
        return TRUE;

    for (o = engine->entries; o; o = o->next) {
        OSyncMappingEntryEngine *entry_engine = o->data;
        osync_assert(entry_engine);

        if (entry_engine->change == NULL)
            continue;

        if (osync_change_get_changetype(entry_engine->change) == OSYNC_CHANGE_TYPE_DELETED)
            continue;

        /*
        if (!osync_objformat_has_merger(osync_change_get_objformat(entry_engine->change)))
        	continue;
        	*/

        if (!osync_entry_engine_demerge(entry_engine, archive, caps, error))
            goto error;

    }

    return TRUE;
error:
    return FALSE;
}
osync_bool osync_sink_engine_write(OSyncSinkEngine *engine, OSyncArchive *archive, OSyncError **error)
{
    OSyncList *o;
    const char *objtype;
    OSyncMember *member;

    osync_assert(engine);
    osync_assert(archive);

    objtype = osync_obj_engine_get_objtype(engine->engine);
    member = osync_client_proxy_get_member(engine->proxy);

    for (o = engine->entries; o; o = o->next) {
        OSyncMappingEntryEngine *entry_engine = o->data;
        osync_assert(entry_engine);

        if (osync_entry_engine_is_dirty(entry_engine)) {
            OSyncChange *change = osync_entry_engine_get_change(entry_engine);
            osync_assert(change);

            osync_trace(TRACE_INTERNAL, "Writing change %s, changetype %i, format %s , objtype %s from member %i",
                        osync_change_get_uid(change),
                        osync_change_get_changetype(change),
                        osync_objformat_get_name(osync_change_get_objformat(change)),
                        osync_change_get_objtype(change),
                        osync_member_get_id(member));

            if (!osync_client_proxy_commit_change(engine->proxy,
                                                  osync_obj_engine_commit_change_callback,
                                                  entry_engine, change, error))
                goto error;

        } else if (entry_engine->change) {
            OSyncMapping *mapping = entry_engine->mapping_engine->mapping;
            OSyncMappingEntry *entry = entry_engine->entry;

            /* FIXME: Don't mix up in this function objtypes */
            /* osync_assert_msg(!strcmp(objtype, osync_change_get_objtype(entry_engine->change), "Mixed-objtype in final write!")); */

            if (osync_change_get_changetype(entry_engine->change) == OSYNC_CHANGE_TYPE_DELETED) {
                if (!osync_archive_delete_change(archive, osync_mapping_entry_get_id(entry),
                                                 osync_change_get_objtype(entry_engine->change), error))
                    goto error;
            } else {
                if (!osync_archive_save_change(archive,
                                               osync_mapping_entry_get_id(entry),
                                               osync_change_get_uid(entry_engine->change),
                                               osync_change_get_objtype(entry_engine->change),
                                               osync_mapping_get_id(mapping),
                                               osync_member_get_id(member), objtype, error))
                    goto error;
            }
        }
    }

    if (!osync_client_proxy_committed_all(engine->proxy, osync_obj_engine_written_callback, engine, objtype, error))
        goto error;

    return TRUE;

error:
    return FALSE;
}
OSyncMember *osync_sink_engine_get_member(OSyncSinkEngine *engine)
{
    osync_return_val_if_fail(engine, NULL);
    osync_return_val_if_fail(engine->proxy, NULL);
    return osync_client_proxy_get_member(engine->proxy);
}
osync_bool osync_obj_engine_command(OSyncObjEngine *engine, OSyncEngineCmd cmd, OSyncError **error)
{
  GList *p = NULL;
  GList *m = NULL;
  GList *e = NULL;
  OSyncSinkEngine *sinkengine =  NULL;

	
  osync_trace(TRACE_ENTRY, "%s(%p, %i, %p)", __func__, engine, cmd, error);
  osync_assert(engine);
	
  switch (cmd) {
    int write_sinks = 0;
    osync_bool proxy_disconnect = FALSE;
  case OSYNC_ENGINE_COMMAND_CONNECT:
    for (p = engine->sink_engines; p; p = p->next) {
      sinkengine = p->data;

      if (!osync_client_proxy_connect(sinkengine->proxy, _osync_obj_engine_connect_callback, sinkengine, engine->objtype, engine->slowsync, error))
        goto error;
    }
    break;
  case OSYNC_ENGINE_COMMAND_READ:
    for (p = engine->sink_engines; p; p = p->next) {
      sinkengine = p->data;
      for (m = sinkengine->entries; m; m = m->next) {
        OSyncMappingEntryEngine *entry = m->data;
        OSyncChange *change = entry->change;

        if (!change)
          continue;

        if (!osync_client_proxy_read(sinkengine->proxy, _osync_obj_engine_read_ignored_callback, sinkengine, change, error))
          goto error;
      }
    }

    if (engine->archive) {
      /* Flush the changelog - to avoid double entries of ignored entries */
      if (!osync_archive_flush_ignored_conflict(engine->archive, engine->objtype, error))
        goto error;
    }

    write_sinks = _osync_obj_engine_num_write_sinks(engine);

    /* Get change entries since last sync. (get_changes) */
    for (p = engine->sink_engines; p; p = p->next) {
      OSyncMember *member = NULL;
      OSyncObjTypeSink *objtype_sink = NULL;

      sinkengine = p->data;

      member = osync_client_proxy_get_member(sinkengine->proxy);
      objtype_sink = osync_member_find_objtype_sink(member, engine->objtype);

      /* Is there at least one other writeable sink? */
      if (objtype_sink && osync_objtype_sink_get_write(objtype_sink) && write_sinks) {
        _osync_obj_engine_read_callback(sinkengine->proxy, sinkengine, *error);
        osync_trace(TRACE_INTERNAL, "no other writable sinks .... SKIP");
        continue;
      }

      if (!osync_client_proxy_get_changes(sinkengine->proxy, _osync_obj_engine_read_callback, sinkengine, engine->objtype, engine->slowsync, error))
        goto error;
    }

    break;
  case OSYNC_ENGINE_COMMAND_WRITE:
    if (engine->conflicts) {
      osync_trace(TRACE_INTERNAL, "We still have conflict. Delaying write");
      break;
    }
		
    if (engine->written) {
      osync_trace(TRACE_INTERNAL, "Already written");
      break;
    }
				
    engine->written = TRUE;
		
    /* Write the changes. First, we can multiply the winner in the mapping */
    osync_trace(TRACE_INTERNAL, "Preparing write. multiplying %i mappings", g_list_length(engine->mapping_engines));
    for (m = engine->mapping_engines; m; m = m->next) {
      OSyncMappingEngine *mapping_engine = m->data;
      if (!osync_mapping_engine_multiply(mapping_engine, error))
        goto error;
    }
			
    osync_trace(TRACE_INTERNAL, "Starting to write");
    for (p = engine->sink_engines; p; p = p->next) {
      OSyncMember *member = NULL;
      long long int memberid = 0;
      OSyncObjTypeSink *objtype_sink = NULL;
      OSyncFormatConverterPath *path = NULL;

      sinkengine = p->data;
      member = osync_client_proxy_get_member(sinkengine->proxy);
      memberid = osync_member_get_id(member);
      objtype_sink = osync_member_find_objtype_sink(member, engine->objtype);
				
      /* If sink could not be found use "data" sink if available */
      if (!objtype_sink)
        objtype_sink = osync_member_find_objtype_sink(member, "data");
      /* TODO: Review if objtype_sink = NULL is valid at all. */

      for (e = sinkengine->entries; e; e = e->next) {
        OSyncMappingEntryEngine *entry_engine = e->data;
        osync_assert(entry_engine);

        /* Merger - Save the entire xml and demerge */
        /* TODO: is here the right place to save the xml???? */
        if (osync_group_get_merger_enabled(osync_engine_get_group(engine->parent)) &&
            osync_group_get_converter_enabled(osync_engine_get_group(engine->parent)) &&	
            entry_engine->change &&
            (osync_change_get_changetype(entry_engine->change) != OSYNC_CHANGE_TYPE_DELETED) &&
            !strncmp(osync_objformat_get_name(osync_change_get_objformat(entry_engine->change)), "xmlformat-", 10) )
          {
            char *buffer = NULL;
            unsigned int xmlformat_size = 0, size = 0;
            OSyncXMLFormat *xmlformat = NULL;
            const char *objtype = NULL;
            OSyncMapping *mapping = NULL;
            OSyncMerger *merger = NULL; 

            osync_trace(TRACE_INTERNAL, "Entry %s for member %lli: Dirty: %i", osync_change_get_uid(entry_engine->change), memberid, osync_entry_engine_is_dirty(entry_engine));

            osync_trace(TRACE_INTERNAL, "Save the entire XMLFormat and demerge.");
            objtype = osync_change_get_objtype(entry_engine->change);
            mapping = entry_engine->mapping_engine->mapping;
						
            osync_data_get_data(osync_change_get_data(entry_engine->change), (char **) &xmlformat, &xmlformat_size);
            osync_assert(xmlformat_size == osync_xmlformat_size());

            if(!osync_xmlformat_assemble(xmlformat, &buffer, &size)) {
              osync_error_set(error, OSYNC_ERROR_GENERIC, "Could not assamble the xmlformat");
              goto error;	
            }

            if(!osync_archive_save_data(engine->archive, osync_mapping_get_id(mapping), objtype, buffer, size, error)) {
              g_free(buffer);	
              goto error;			
            }
            g_free(buffer);
						
            merger = osync_member_get_merger(osync_client_proxy_get_member(sinkengine->proxy));
            if(merger)
              osync_merger_demerge(merger, xmlformat);
          }


        /* Only commit change if the objtype sink is able/allowed to write. */
        if (objtype_sink && osync_objtype_sink_get_write(objtype_sink) && osync_entry_engine_is_dirty(entry_engine)) {
          OSyncChange *change = entry_engine->change;
          osync_assert(entry_engine->change);

          /* Convert to requested target format if the changetype is not DELETED */
          if (osync_group_get_converter_enabled(osync_engine_get_group(engine->parent)) && (osync_change_get_changetype(change) != OSYNC_CHANGE_TYPE_DELETED)) {

            char *objtype = NULL;
            OSyncList *format_sinks = NULL;
            unsigned int length = 0;
            OSyncFormatConverter *converter = NULL;

            osync_trace(TRACE_INTERNAL, "Starting to convert from objtype %s and format %s", osync_change_get_objtype(entry_engine->change), osync_objformat_get_name(osync_change_get_objformat(entry_engine->change)));
            /* We have to save the objtype of the change so that it does not get
             * overwritten by the conversion */
            objtype = g_strdup(osync_change_get_objtype(change));
							
            /* Now we have to convert to one of the formats
             * that the client can understand */
            format_sinks = osync_objtype_sink_get_objformat_sinks(objtype_sink);
            if (!format_sinks) {
              osync_error_set(error, OSYNC_ERROR_GENERIC, "There are no available format sinks.");
              goto error;
            }
						
            /* We cache the converter path for each sink/member couple */
            if (!path) {
              path = osync_format_env_find_path_formats_with_detectors(engine->formatenv, osync_change_get_data(entry_engine->change), format_sinks, osync_objtype_sink_get_preferred_format(objtype_sink), error);
            }
            if (!path)
              goto error;

            length = osync_converter_path_num_edges(path);
            converter = osync_converter_path_nth_edge(path, length - 1);
            if (converter) {
              OSyncObjFormat *format = osync_converter_get_targetformat(converter);
              OSyncObjFormatSink *formatsink = osync_objtype_sink_find_objformat_sink(objtype_sink, format);
              osync_converter_path_set_config(path, osync_objformat_sink_get_config(formatsink));
            }

            if (!osync_format_env_convert(engine->formatenv, path, osync_change_get_data(entry_engine->change), error)) {
              osync_converter_path_unref(path);
              goto error;
            }
            osync_trace(TRACE_INTERNAL, "converted to format %s", osync_objformat_get_name(osync_change_get_objformat(entry_engine->change)));
							
							
            osync_change_set_objtype(change, objtype);
            g_free(objtype);
          }
						
          osync_trace(TRACE_INTERNAL, "Writing change %s, changetype %i, format %s , objtype %s from member %lli", 
                      osync_change_get_uid(change), 
                      osync_change_get_changetype(change), 
                      osync_objformat_get_name(osync_change_get_objformat(change)), 
                      osync_change_get_objtype(change), 
                      osync_member_get_id(osync_client_proxy_get_member(sinkengine->proxy)));
	
          if (!osync_client_proxy_commit_change(sinkengine->proxy, _osync_obj_engine_commit_change_callback, entry_engine, osync_entry_engine_get_change(entry_engine), error))
            goto error;
        } else if (entry_engine->change) {
          OSyncMapping *mapping = entry_engine->mapping_engine->mapping;
          OSyncMember *member = osync_client_proxy_get_member(sinkengine->proxy);
          OSyncMappingEntry *entry = entry_engine->entry;
          const char *objtype = osync_change_get_objtype(entry_engine->change);
						
          if (engine->archive) {
            if (osync_change_get_changetype(entry_engine->change) == OSYNC_CHANGE_TYPE_DELETED) {
              if (!osync_archive_delete_change(engine->archive, osync_mapping_entry_get_id(entry), objtype, error))
                goto error;
            } else {
              if (!osync_archive_save_change(engine->archive, osync_mapping_entry_get_id(entry), osync_change_get_uid(entry_engine->change), objtype, osync_mapping_get_id(mapping), osync_member_get_id(member), error))
                goto error;
            }
          }
        }
      }
			
      if (path)
        osync_converter_path_unref(path);

      if (!osync_client_proxy_committed_all(sinkengine->proxy, _osync_obj_engine_written_callback, sinkengine, engine->objtype, error))
        goto error;
    }
    break;
  case OSYNC_ENGINE_COMMAND_SYNC_DONE:
    for (p = engine->sink_engines; p; p = p->next) {
      sinkengine = p->data;
      if (!osync_client_proxy_sync_done(sinkengine->proxy, _osync_obj_engine_sync_done_callback, sinkengine, engine->objtype, error))
        goto error;
    }
    break;
  case OSYNC_ENGINE_COMMAND_DISCONNECT:;
    for (p = engine->sink_engines; p; p = p->next) {
      sinkengine = p->data;

      /* Don't call client disconnect functions if the sink is already disconnected.
         This avoids unintended disconnect calls of clients/plugins which might not prepared
         for a disconnect call when their never got connected. (testcases: *_connect_error, *_connect_timeout ..) */
      if (!osync_sink_engine_is_connected(sinkengine))
        continue;

      proxy_disconnect = TRUE;

      if (!osync_client_proxy_disconnect(sinkengine->proxy, _osync_obj_engine_disconnect_callback, sinkengine, engine->objtype, error))
        goto error;
    }
			
    /* If no client needs to be disconnected, we MUST NOT expected any 
       disconnected_callback which generates an OSYNC_ENGINE_EVENT_DISCONNECTED event.
       So we directly generate such event on our own. (testcases: double_connect_*, triple_connnect_*) */ 
    if (!proxy_disconnect)
      _osync_obj_engine_generate_event_disconnected(engine, NULL);

    break;
  case OSYNC_ENGINE_COMMAND_SOLVE:
  case OSYNC_ENGINE_COMMAND_DISCOVER:
  case OSYNC_ENGINE_COMMAND_ABORT:
    break;
  }
	
  osync_trace(TRACE_EXIT, "%s", __func__);
  return TRUE;

 error:
  osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
  return FALSE;
}
/** @brief Solves the conflict by duplicating the conflicting entries
 * 
 * @param engine The engine
 * @param dupe_mapping The conflicting mapping to duplicate
 * 
 */
osync_bool osync_mapping_engine_duplicate(OSyncMappingEngine *existingMapping, OSyncError **error)
{
  int elevation = 0;
  OSyncObjEngine *objengine = NULL;
  GList *entries = NULL, *e = NULL, *mappings = NULL;

  osync_trace(TRACE_ENTRY, "%s(%p, %p)", __func__, existingMapping, error);
  g_assert(existingMapping);
	
  objengine = existingMapping->parent;
	
  /* Remove all deleted items first and copy the changes to a list */
  e = existingMapping->entries;
  for (; e; e = e->next) {
    OSyncMappingEntryEngine *entry = e->data;
    if (entry->change) {
      if (osync_change_get_changetype(entry->change) == OSYNC_CHANGE_TYPE_MODIFIED || osync_change_get_changetype(entry->change) == OSYNC_CHANGE_TYPE_ADDED) {
        osync_trace(TRACE_INTERNAL, "Appending entry %s, changetype %i from member %lli", osync_change_get_uid(entry->change), osync_change_get_changetype(entry->change), osync_member_get_id(osync_client_proxy_get_member(entry->sink_engine->proxy)));
		
        entries = g_list_append(entries, entry);
      } else {
        osync_trace(TRACE_INTERNAL, "Removing entry %s, changetype %i from member %lli", osync_change_get_uid(entry->change), osync_change_get_changetype(entry->change), osync_member_get_id(osync_client_proxy_get_member(entry->sink_engine->proxy)));
        osync_entry_engine_update(entry, NULL);
      }
    } else {
      osync_trace(TRACE_INTERNAL, "member %lli does not have a entry", osync_member_get_id(osync_client_proxy_get_member(entry->sink_engine->proxy)));
    }
  }
	
  /* Create a list with mappings. In the beginning, only the exisiting mapping is in the list */
  mappings = g_list_append(NULL, existingMapping);
  osync_mapping_engine_ref(existingMapping);
	
  while (entries) {
    OSyncMappingEntryEngine *existingEntry = entries->data;
		
    /* Now lets see which mapping is the correct one for the entry */
    GList *m = NULL;
    OSyncMappingEngine *mapping = NULL;
    OSyncChange *existingChange = NULL;
    osync_bool dirty = FALSE;
    OSyncMappingEntryEngine *newEntry = NULL;

    elevation = 0;
    for (m = mappings; m; m = m->next) {
      GList *e = NULL;
      OSyncChange *change = NULL;
      OSyncMappingEntryEngine *entry = NULL;
      mapping = m->data;
			
      /* Get the first change of the mapping to test. Compare the given change with this change.
       * If they are not the same, we have found a new mapping */
      for (e = mapping->entries; e; e = e->next) {
        entry = e->data;
        change = entry->change;
        if (change)
          break;
      }
			
      if (!change || osync_change_compare(existingEntry->change, change) == OSYNC_CONV_DATA_SAME){
        existingChange = existingEntry->change;
        osync_change_ref(existingChange);
        osync_assert(osync_change_get_uid(existingChange));
        break;
      }


      mapping = NULL;
      elevation++;
		
      existingChange = osync_change_clone(existingEntry->change, error);
      osync_assert(osync_change_get_uid(existingChange));
    }
		
		
    if (!mapping) {
      /* Unable to find a mapping. We have to create a new one */
      mapping = _osync_obj_engine_create_mapping_engine(objengine, error);
      if (!mapping)
        goto error;
      mappings = g_list_append(mappings, mapping);
      objengine->mapping_engines = g_list_append(objengine->mapping_engines, mapping);
      osync_mapping_engine_ref(mapping);
    }
		
    /* update the uid and the content to suit the new level */
    if (!_osync_change_elevate(existingChange, elevation, &dirty, error))
      goto error;

    /* Lets add the entry to the mapping */
    newEntry = osync_mapping_engine_get_entry(mapping, existingEntry->sink_engine);
    osync_assert(newEntry);
    osync_entry_engine_update(newEntry, existingChange);
    osync_mapping_entry_set_uid(newEntry->entry, osync_change_get_uid(existingChange));
    osync_change_unref(existingChange);
		
    /* Set the last entry as the master */
    osync_mapping_engine_set_master(mapping, newEntry);
		
    /* Update the dirty status. If the duplicate function said
     * that the returned item needs to be written, we will set
     * this information here */
    newEntry->dirty = dirty;
		
    entries = g_list_remove(entries, existingEntry);
  }
	
	
  while (mappings) {
    OSyncMappingEngine *mapping = mappings->data;
    osync_mapping_engine_unref(mapping);
    mappings = g_list_remove(mappings, mapping);
  }
	
  objengine->conflicts = g_list_remove(objengine->conflicts, existingMapping);
  osync_status_update_mapping(objengine->parent, existingMapping, OSYNC_MAPPING_EVENT_SOLVED, NULL);
	
  if (osync_engine_check_get_changes(objengine->parent) && osync_bitcount(objengine->sink_errors | objengine->sink_get_changes) == g_list_length(objengine->sink_engines)) {
    if (!osync_obj_engine_command(objengine, OSYNC_ENGINE_COMMAND_WRITE, error))
      goto error;
  } else
    osync_trace(TRACE_INTERNAL, "Not triggering write. didnt receive all reads yet");

  osync_trace(TRACE_EXIT, "%s", __func__);
  return TRUE;

 error:
  while (mappings) {
    OSyncMappingEngine *mapping = mappings->data;
    osync_mapping_engine_unref(mapping);
    mappings = g_list_remove(mappings, mapping);
  }
  osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
  return FALSE;
}
osync_bool osync_obj_engine_map_changes(OSyncObjEngine *engine, OSyncError **error)
{
  OSyncMappingEngine *mapping_engine = NULL;
  GList *new_mappings = NULL, *v = NULL;
	
  osync_trace(TRACE_ENTRY, "%s(%p)", __func__, engine);
  //osync_trace_disable();

  /* Go through all sink engines that are available */
  for (v = engine->sink_engines; v; v = v->next) {
    OSyncSinkEngine *sinkengine = v->data;
		
    /* We use a temp list to speed things up. We dont have to compare with newly created mappings for
     * the current sinkengine, since there will be only one entry (for the current sinkengine) so there
     * is no need to compare */
    new_mappings = NULL;
		
    /* For each sinkengine, go through all unmapped changes */
    while (sinkengine->unmapped) {
      OSyncChange *change = sinkengine->unmapped->data;
      OSyncConvCmpResult result = 0;
      OSyncMappingEntryEngine *entry_engine = NULL;
			
      osync_trace(TRACE_INTERNAL, "Looking for mapping for change %s, changetype %i from member %lli", osync_change_get_uid(change), osync_change_get_changetype(change), osync_member_get_id(osync_client_proxy_get_member(sinkengine->proxy)));
	
      /* See if there is an exisiting mapping, which fits the unmapped change */
      result = _osync_obj_engine_mapping_find(engine, change, sinkengine, &mapping_engine);
      if (result == OSYNC_CONV_DATA_MISMATCH) {
        /* If there is none, create one */
        mapping_engine = _osync_obj_engine_create_mapping_engine(engine, error);
        if (!mapping_engine)
          goto error;
				
        osync_trace(TRACE_INTERNAL, "Unable to find mapping. Creating new mapping with id %lli", osync_mapping_get_id(mapping_engine->mapping));
				
        new_mappings = g_list_append(new_mappings, mapping_engine);
      } else if (result == OSYNC_CONV_DATA_SIMILAR) {
        mapping_engine->conflict = TRUE;
      }
      /* Update the entry which belongs to our sinkengine with the the change */
      entry_engine = osync_mapping_engine_get_entry(mapping_engine, sinkengine);
      osync_assert(entry_engine);
			
      osync_entry_engine_update(entry_engine, change);
      sinkengine->unmapped = g_list_remove(sinkengine->unmapped, sinkengine->unmapped->data);
      osync_change_unref(change);
    }
		
    engine->mapping_engines = g_list_concat(engine->mapping_engines, new_mappings);
  }
	
  //osync_trace_enable();
  osync_trace(TRACE_EXIT, "%s", __func__);
  return TRUE;

 error:
  osync_trace_enable();
  osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
  return FALSE;
}