/*
   MultiSync - A PIM data synchronization program
   Copyright (C) 2002-2003 Bo Lincoln <lincoln@lysator.liu.se>

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License version 2 as
   published by the Free Software Foundation;

   In addition, as a special exception, Bo Lincoln <lincoln@lysator.liu.se>
   gives permission to link the code of this program with
   the OpenSSL library (or with modified versions of OpenSSL that use the
   same license as OpenSSL), and distribute linked combinations including
   the two.  You must obey the GNU General Public License in all
   respects for all of the code used other than OpenSSL.  If you modify
   this file, you may extend this exception to your version of the
   file, but you are not obligated to do so.  If you do not wish to
   do so, delete this exception statement from your version.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
   IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
   CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES 
   WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 
   ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 
   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

   ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, 
   COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS 
   SOFTWARE IS DISCLAIMED.
*/

/*
 *  $Id: syncengine.c,v 1.81 2004/04/12 02:51:37 irix Exp $
 */

#include <stdio.h>
#include <stdlib.h>
#include <glib.h>
#include <time.h>
#include <signal.h>
#include <sys/wait.h>
#include "syncengine.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <bonobo/bonobo-main.h>
#include <gnome.h>
#include <popt.h>
#include <errno.h>
#include <gconf/gconf-client.h>
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif
#include <gtk/gtk.h>
#include <pthread.h>
#include <dlfcn.h>

#include "sync_vtype.h"
#include "interface.h"
#include "support.h"
#include "gui.h"
#include "tray.h"

#define IDFILE "ids"


gpointer (*plugin_function)();
#define CALL_PLUGIN_ASYNC(mod, name, args)  ((plugin_function=dlsym(mod->plugin,name))?(*plugin_function)args:sync_set_requestfailed(thissync))
#define CALL_PLUGIN(mod, name, args) ((plugin_function=dlsym(mod->plugin,name))?(*plugin_function)args:NULL)
#define LOG_ERROR(logstring) { char *txt = g_strdup_printf("%s: %s.", logstring, thissync->errorstr?thissync->errorstr:sync_error_string(ret)); async_add_pairlist_log(thissync, txt, SYNC_LOG_ERROR); g_free(txt); }

GList *pluginlist = NULL;
GList *syncpairlist = NULL;
char *datadir;

gboolean multisync_debug = FALSE;
#define dd(x) (multisync_debug?(x):0)

GList* sync_load_ids(sync_pair *thissync) {
  char *filename;
  FILE *f;
  GList* list = NULL;
  char line[256];

  filename = g_strdup_printf ("%s/%s", thissync->datapath, IDFILE);
  if ((f = fopen(filename, "r"))) {
    while (fgets(line, 256, f)) {
      char uid[128], luid[128], enddate[32], objtype;
      if (sscanf(line, "%c %32s %128s = %128s", &objtype, 
		 enddate, uid, luid) >= 3) {
	idpair *idp = g_malloc(sizeof(idpair));
	g_assert(idp);
	idp->uid = g_malloc(strlen(uid)+1);
	g_assert(idp->uid);
	memcpy(idp->uid, uid, strlen(uid)+1);
	idp->luid = g_malloc(strlen(luid)+1);
	g_assert(idp->luid);
	memcpy(idp->luid, luid, strlen(luid)+1);
	if (strcmp(enddate, "X") != 0)
	  idp->enddate = g_strdup(enddate);
	else
	  idp->enddate = NULL;
	idp->object_type = (objtype=='C'?SYNC_OBJECT_TYPE_CALENDAR:
			    (objtype=='T'?SYNC_OBJECT_TYPE_TODO:
			     SYNC_OBJECT_TYPE_PHONEBOOK));
	list = g_list_append(list ,idp);
      }
    }
    fclose(f);
  }
  g_free(filename);
  return(list);
}

void sync_save_ids(GList* list, sync_pair *thissync) {
  guint len = g_list_length(list);
  guint t;
  char *filename;
  FILE *f;

  filename = g_strdup_printf ("%s/%s", thissync->datapath, IDFILE);
  if ((f = fopen(filename, "w"))) {
    for (t = 0; t < len; t++) {
      char chartype;
      idpair *idp;
      idp = g_list_nth_data(list, t);
      chartype = (idp->object_type==SYNC_OBJECT_TYPE_CALENDAR?'C':
		  (idp->object_type==SYNC_OBJECT_TYPE_TODO?'T':'P'));
      if (idp && idp->uid && idp->luid)
	fprintf(f, "%c %s %s = %s\n", chartype, idp->enddate?idp->enddate:"X", 
		idp->uid, idp->luid);
    }
    fclose(f);
  }
  g_free(filename);
}

char* sync_get_luid(char *uid, sync_object_type objtype, sync_pair *handle) {
  GList *list = handle->iddb;
  guint len = g_list_length(list);
  guint t;

  for (t = 0; t < len; t++) {
    idpair *idp;
    idp = g_list_nth_data(list, t);
    if (idp && idp->uid && idp->luid && objtype == idp->object_type &&
	!strcmp(uid,idp->uid))
      return(idp->luid);
  }
  return(0);
}

GList *sync_get_recur_luids(char *uid, sync_object_type objtype, 
			    sync_pair *handle) {
  GList *list = handle->iddb;
  GList *luidlist = NULL;
  guint len = g_list_length(list);
  guint t;
  char *recuruid;

  recuruid = g_strdup_printf("SYNCRECUR-%s", uid);
  for (t = 0; t < len; t++) {
    idpair *idp;
    idp = g_list_nth_data(list, t);
    if (idp && idp->uid && idp->luid && objtype == idp->object_type &&
	!strcmp(recuruid,idp->uid)) {
      luidlist = g_list_append(luidlist, idp->luid);
    }
  }
  g_free(recuruid);
  return(luidlist);
}

GList *sync_get_recur_uids(char *luid,sync_object_type objtype,
			   sync_pair *handle) {
  GList *list = handle->iddb;
  GList *uidlist = NULL;
  guint len = g_list_length(list);
  guint t;
  char *recurluid;

  recurluid = g_strdup_printf("SYNCRECUR-%s", luid);
  for (t = 0; t < len; t++) {
    idpair *idp;
    idp = g_list_nth_data(list, t);
    if (idp && idp->uid && idp->luid && objtype == idp->object_type &&
	!strcmp(recurluid,idp->luid)) {
      uidlist = g_list_append(uidlist, idp->uid);
    }
  }
  g_free(recurluid);
  return(uidlist);
}

// Get the LUID for the oldest entry not in the changelist
char* sync_get_oldest_luid(sync_object_type objtype, GList *changelist,
			  sync_pair *handle) {
  GList *list = handle->iddb;
  guint len = 0;
  gint t;
  idpair *oldidp = 0;
  char *enddate = 0;

  len = g_list_length(list);
  for (t = 0; t < len; t++) {
    idpair *idp;
    idp = g_list_nth_data(list, t);
    if (idp->enddate && objtype == idp->object_type &&
	(!enddate || strcmp(enddate,idp->enddate)>0)) {
      GList *changes = changelist;
      gboolean found = FALSE;
      while (changes && !found) {
	changed_object *obj = changes->data;
	if (obj && obj->uid && !strcmp(obj->uid, idp->luid))
	  found = TRUE;
	changes = changes->next;
      }
      if (!found) {
	// OK, this is old and not in the change list
	enddate = idp->enddate;
	oldidp = idp;
      }
    }
  }
  if (oldidp)
    return(oldidp->luid);
  return(0);
}

// Get the UID for the oldest entry not in the changelist
char* sync_get_oldest_uid(sync_object_type objtype, GList *changelist,
			  sync_pair *handle) {
  GList *list = handle->iddb;
  guint len = g_list_length(list);
  guint t;
  idpair *oldidp = 0;
  char *enddate = 0;

  for (t = 0; t < len; t++) {
    idpair *idp;
    idp = g_list_nth_data(list, t);
    if (idp && idp->enddate && objtype == idp->object_type &&
	(!enddate || strcmp(enddate,idp->enddate)>0)) {
      GList *changes = changelist;
      gboolean found = FALSE;
      while (changes && !found) {
	changed_object *obj = changes->data;
	if (obj && obj->uid && !strcmp(obj->uid, idp->uid))
	  found = TRUE;
	changes = changes->next;
      }
      if (!found) {
	// OK, this is old and not in the change list
	enddate = idp->enddate;
	oldidp = idp;
      }
    }
  }
  if (oldidp)
    return(oldidp->uid);
  return(0);
}

char* sync_get_uid(char *luid, sync_object_type objtype, sync_pair *handle) {
  GList *list = handle->iddb;
  guint len = g_list_length(list);
  guint t;
  
  for (t = 0; t < len; t++) {
    idpair *idp;
    idp = g_list_nth_data(list, t);
    if (idp && objtype == idp->object_type && 
	idp->uid && idp->luid && !strcmp(luid,idp->luid)) {
      return(idp->uid);
    }
  }
  return(0);
}

gboolean sync_is_luid_fake_recur(char *luid, sync_object_type objtype, 
				 sync_pair *handle) {
  char *uid;
  char realuid[128];

  uid = sync_get_uid(luid, objtype, handle);
  if (uid && sscanf(uid, "SYNCRECUR-%128s", realuid))
    return(TRUE);
  return(FALSE);
}

int sync_get_nopairs(sync_pair *handle) {
  return(g_list_length(handle->iddb));
}

void sync_insert_idpair(char *uid, char *luid, sync_object_type objtype, 
			char *removepriority, sync_recur_type recur, 
			sync_pair *handle) {

  idpair *idp = g_malloc0(sizeof(idpair));
  g_assert(idp);
  if (recur == SYNC_RECUR_UID)
    idp->uid = g_strdup_printf("SYNCRECUR-%s", uid);
  else {
    idp->uid = g_strdup(uid);
    // If defined already, remove
    sync_delete_idpair(idp->uid, NULL, objtype, handle);
  }
  if (recur == SYNC_RECUR_LUID)
    idp->luid = g_strdup_printf("SYNCRECUR-%s", luid);
  else {
    idp->luid = g_strdup(luid);
    // If defined already, remove
    sync_delete_idpair(NULL, idp->luid, objtype, handle);
  }

  if (removepriority)
    idp->enddate = g_strdup(removepriority);
  idp->object_type = objtype;
  handle->iddb = g_list_append(handle->iddb ,idp);

  sync_save_ids(handle->iddb, handle);
}


void sync_delete_all_idpairs(sync_pair *handle, sync_object_type objtypes) {
  guint t;
  gboolean removed = FALSE;
  
  for (t = 0; t < g_list_length(handle->iddb); t++) {
    idpair *idp;
    idp = g_list_nth_data(handle->iddb, t);
    if (idp && (objtypes & idp->object_type)) {
      g_free(idp->uid);
      g_free(idp->luid);
      g_free(idp);
      handle->iddb = g_list_remove(handle->iddb, idp);
      removed = TRUE;
      t--;
    }
  }
  if (removed)
    sync_save_ids(handle->iddb, handle);
}

// Delete all idpairs with either matching UID or matching LUID
void sync_delete_idpair(char *uid, char *luid, sync_object_type objtype, 
			sync_pair *handle) {
  guint t;
  gboolean removed = FALSE;
  
  for (t = 0; t < g_list_length(handle->iddb); t++) {
    idpair *idp;
    idp = g_list_nth_data(handle->iddb, t);
    if (idp && idp->uid && idp->luid && objtype == idp->object_type &&
	((uid && !strcmp(uid,idp->uid)) || 
	 (luid && !strcmp(luid,idp->luid)))) {
      g_free(idp->uid);
      g_free(idp->luid);
      g_free(idp);
      handle->iddb = g_list_remove(handle->iddb, idp);
      removed = TRUE;
      t--;
    }
  }
  if (removed)
    sync_save_ids(handle->iddb, handle);
  return;
}


void sync_msg_init(sync_msg_port *msg) {
  msg->msg_queue = 0;
  msg->msg_status = 0;
  msg->msg_signal = g_cond_new();
  msg->msg_mutex = g_mutex_new();
}

sync_pair* sync_init(sync_pair *pair) {
  pair->iddb = sync_load_ids(pair);
  sync_msg_init(&pair->msg_callret);
  sync_msg_init(&pair->msg_objchange);
  return(pair);
}


int sync_connect_plugin(sync_pair *thissync, connection_type type,
			client_connection **conn) {
  int ret;
  if (*conn)
    return(0);
  if (type == CONNECTION_TYPE_LOCAL)
    async_set_pairlist_status(thissync, 
			      "Connecting to first client...", 1);
  else
    async_set_pairlist_status(thissync, 
			      "Connecting to second client...", 1);
  *conn = (client_connection*) 
    CALL_PLUGIN_ASYNC((type==CONNECTION_TYPE_LOCAL?
		      thissync->localclient:thissync->remoteclient),"sync_connect",
		      (thissync, type, thissync->object_types));
  ret = sync_wait_msg(&thissync->msg_callret, NULL);
  if (ret < 0) {
    *conn = NULL;
  }
  return(ret);
}

void sync_disconnect_plugin(sync_pair *thissync, connection_type type,
			    client_connection **conn) {
  int ret;
  if (!(*conn))
    return;
  if (type == CONNECTION_TYPE_LOCAL)
    CALL_PLUGIN_ASYNC(thissync->localclient, "sync_disconnect",
		      (conn));
  else
    CALL_PLUGIN_ASYNC(thissync->remoteclient, "sync_disconnect",
		      (conn));
  ret = sync_wait_msg(&thissync->msg_callret, NULL);
  *conn = NULL;
}

char *sync_error_string(sync_msg_type err) {
  char *ret = NULL;
  switch(err) {
  case SYNC_MSG_REQFAILED:
    ret = "Unknown error";
    break;
  case SYNC_MSG_CONNECTIONERROR:
    ret = "Connection error";
    break;
  case SYNC_MSG_MODIFYERROR:
    ret = "Failed to modify entry";
    break;
  case SYNC_MSG_PROTOCOLERROR:
    ret = "Protocol error";
    break;
  case SYNC_MSG_NORIGHTSERROR:
    ret = "Not authorized";
    break;
  case SYNC_MSG_DONTEXISTERROR:
    ret = "File does not exist";
    break;
  case SYNC_MSG_DATABASEFULLERROR:
    ret = "Database is full";
    break;
  case SYNC_MSG_USERDEFERRED:
    ret = "User refused";
    break;
  default:
    ret = "No error";
  }
  return(ret);
}

gpointer sync_main(gpointer data) {
  sync_pair *thissync = data;
  client_connection *remoteconn = NULL;
  client_connection *localconn = NULL;
  gboolean localalwaysconn = FALSE;
  gboolean remotealwaysconn = FALSE;
  gboolean localfeedthrough = FALSE;
  gboolean remotefeedthrough = FALSE;
  int ret = 0, cmd = SYNC_MSG_OBJECTCHANGE;
  int lastsyncmissed = 0;
  gpointer cmddata = NULL;
  int feedthroughentries = 0;
  gboolean feedthroughsyncing = FALSE;

  thissync->thread_running = TRUE;
  if (!thissync->localclient || !thissync->remoteclient) {
    async_set_pairlist_status(thissync, 
			      "Plugin missing.", 0);
    cmd = SYNC_MSG_QUIT;
  }    

  sync_init(thissync);
  
  if (thissync->localclient==0) {
    fprintf(stderr, "The local client [%s] has failed to load!\n", thissync->localname);
    return NULL;
  }

  localalwaysconn = (gboolean) CALL_PLUGIN(thissync->localclient, 
					   "always_connected",());
  if (localalwaysconn) {
    localconn = (client_connection*) 
      CALL_PLUGIN_ASYNC(thissync->localclient, "sync_connect", 
			(thissync, CONNECTION_TYPE_LOCAL, 
			 thissync->object_types));
    ret = sync_wait_msg(&thissync->msg_callret, NULL);
    if (ret < 0) {
      dd(printf("Could not connect first client.\n"));
      cmd = SYNC_MSG_QUIT;
    }
    if (localconn)
      localfeedthrough = localconn->is_feedthrough;
  }

  remotealwaysconn = (gboolean) CALL_PLUGIN(thissync->remoteclient, 
					    "always_connected",());
  if (remotealwaysconn) {
    remoteconn = (client_connection*)
      CALL_PLUGIN_ASYNC(thissync->remoteclient,"sync_connect", 
			(thissync, CONNECTION_TYPE_REMOTE,
			 thissync->object_types));
    ret = sync_wait_msg(&thissync->msg_callret, NULL);
    if (ret < 0) {
      dd(printf("Could not connect second client.\n"));
      cmd = SYNC_MSG_QUIT;
    }
    if (remoteconn)
      remotefeedthrough = remoteconn->is_feedthrough;
  }
  
  while(cmd != SYNC_MSG_QUIT) {
    change_info *remote_changes = NULL, *local_changes = NULL;
    GTimeVal gt;
    lastsyncmissed = thissync->syncmissed;
    thissync->syncmissed = 0;

    if (((cmd == SYNC_MSG_OBJECTCHANGE && !thissync->manualonly) || 
	 cmd == SYNC_MSG_FORCESYNC ||
	 cmd == SYNC_MSG_FORCERESYNC) &&
	(localfeedthrough || remotefeedthrough)) {
      // If we are only fed through, send message
      if (localfeedthrough) {
	CALL_PLUGIN_ASYNC(thissync->localclient,"resp_objchanged",
			  (localconn));
	ret = sync_wait_msg(&thissync->msg_callret, NULL);
      }
      if (remotefeedthrough) {
	CALL_PLUGIN_ASYNC(thissync->remoteclient,"resp_objchanged",
			  (remoteconn));
	ret = sync_wait_msg(&thissync->msg_callret, NULL);
      }
      cmd = 0;
    }

    // Don't sync if syncinterval is set to manual
    // unless we do it manually :)
    if ((cmd == SYNC_MSG_OBJECTCHANGE && !thissync->manualonly) 
	|| cmd == SYNC_MSG_FORCESYNC ||
	cmd == SYNC_MSG_FORCERESYNC) {
      if (!remotealwaysconn) {
	async_set_pairlist_status(thissync, "Connecting to second client...", 1);
	remoteconn = (client_connection*)
	  CALL_PLUGIN_ASYNC(thissync->remoteclient,"sync_connect", 
			    (thissync, CONNECTION_TYPE_REMOTE,
			     thissync->object_types));
	ret = sync_wait_msg(&thissync->msg_callret, NULL);
	if (ret < 0) {
	  remoteconn = NULL;
	  thissync->syncmissed = 1;
	  LOG_ERROR("Failed to connect remote");
	}
    }
      if (remoteconn) {
	if (!localalwaysconn) {
	  async_set_pairlist_status(thissync, 
				    "Connecting to first client...", 1);
	  localconn = (client_connection*) 
	    CALL_PLUGIN_ASYNC(thissync->localclient,"sync_connect",
			      (thissync, CONNECTION_TYPE_LOCAL,
			       thissync->object_types));
	  ret = sync_wait_msg(&thissync->msg_callret, NULL);
	  if (ret < 0) {
	    localconn = NULL;
	    thissync->syncmissed = 1;
	    LOG_ERROR("Failed to connect local");
	  }
	}
	if (localconn) {
	  sync_object_type newdbs = 0;
	  if (cmd == SYNC_MSG_FORCERESYNC)
	    newdbs = SYNC_OBJECT_TYPE_ANY;
	  async_set_pairlist_status(thissync, "Getting second plugin changes...", 1);
	  CALL_PLUGIN_ASYNC(thissync->remoteclient, "get_changes", 
			    (remoteconn,newdbs));
	  ret = sync_wait_msg_data(&thissync->msg_callret, NULL,
				   (gpointer*) &remote_changes);
	  if (ret == SYNC_MSG_CONNECTIONERROR)
	    thissync->syncmissed = 1;
	  if (ret >= 0 && remote_changes) {
	    newdbs |= remote_changes->newdbs;
	    
	    dd(printf("New DBs: %d\n", newdbs));
	    async_set_pairlist_status(thissync, "Getting first plugin changes...", 1);
	    CALL_PLUGIN_ASYNC(thissync->localclient, "get_changes", 
			      (localconn,newdbs));
	    ret=sync_wait_msg_data(&thissync->msg_callret, NULL, 
				   (gpointer*) &local_changes);
	    if (ret == SYNC_MSG_CONNECTIONERROR)
	      thissync->syncmissed = 1;
	    if (ret >= 0 && local_changes) {
	      if (local_changes->newdbs & (~newdbs)) {
		newdbs |= local_changes->newdbs;
		// Refetch remote databases which have been reset locally
		sync_free_change_info(remote_changes);
		remote_changes = NULL;
		async_set_pairlist_status(thissync, 
					  "Getting full second database...",
					  1);
		CALL_PLUGIN_ASYNC(thissync->remoteclient, "get_changes",
				  (remoteconn,newdbs));
		ret = sync_wait_msg_data(&thissync->msg_callret, NULL,
					 (gpointer*) &remote_changes);
	      }
	      if (ret >= 0) {
		// We got changes successfully
		if (thissync->playwelcomesound && thissync->welcomesound &&
		    lastsyncmissed) {
		  // Play welcome sound if we were unreachable for a while
		  gnome_sound_play(thissync->welcomesound);
		}

		sync_process_changes(localconn, remoteconn, 
				     local_changes->changes,
				     remote_changes->changes, 
				     newdbs, thissync);
	      } else {
		LOG_ERROR("Failed to get full second changelist");
	      }
	    } else {
	      LOG_ERROR("Failed to get first plugin changes");
	    }
	    if (!localalwaysconn) {
	      CALL_PLUGIN_ASYNC(thissync->localclient, "sync_disconnect",(localconn));
	      ret = sync_wait_msg(&thissync->msg_callret, NULL);
	      localconn = NULL;
	    }
	  } else {
	    LOG_ERROR("Failed to get second plugin changes");
	  }
	  if (local_changes)
	    sync_free_change_info(local_changes);
	  local_changes = NULL;
	  if (remote_changes)
	    sync_free_change_info(remote_changes);
	  remote_changes = NULL;
	}
	if (!remotealwaysconn) {
	  CALL_PLUGIN_ASYNC(thissync->remoteclient, "sync_disconnect", (remoteconn));
	  ret = sync_wait_msg(&thissync->msg_callret, NULL);
	  remoteconn = NULL;
	}
      } 
    }
    // Specific LOCAL and REMOTE to know who's calling
    if (cmd == SYNC_MSG_GET_LOCAL_CHANGES) {
      sync_object_type newdbs = (sync_object_type) cmddata;
      // A remote feedthrough plugin asks to get changes
      ret = sync_connect_plugin(thissync, CONNECTION_TYPE_LOCAL, &localconn);
      async_set_pairlist_status(thissync, 
				"Getting first plugin changes...", 1);
      local_changes = NULL;
      if (localconn) {
	CALL_PLUGIN_ASYNC(thissync->localclient, "get_changes", 
			  (localconn,newdbs));
	ret = sync_wait_msg_data(&thissync->msg_callret, NULL,
				 (gpointer*) &local_changes);
	if (ret < 0)
	  local_changes = NULL;
      }
      if (local_changes)
	feedthroughentries = g_list_length(local_changes->changes);
      else
	feedthroughentries = 0;
      if (ret >= 0)
	feedthroughsyncing = TRUE;
      async_set_pairlist_status(thissync, 
				"Synchronizing...", 1);
      // Send the local change list to the remote plugin
      CALL_PLUGIN_ASYNC(thissync->remoteclient,"resp_get_changes",
			(remoteconn, ret, local_changes));
      ret = sync_wait_msg(&thissync->msg_callret, NULL);
      sync_free_change_info(local_changes);
      local_changes = NULL;
    }
    if (cmd == SYNC_MSG_GET_REMOTE_CHANGES) {
      sync_object_type newdbs = (sync_object_type) cmddata;
      // A local feedthrough plugin asks to get changes
      ret = sync_connect_plugin(thissync, CONNECTION_TYPE_REMOTE, 
				&remoteconn);
      async_set_pairlist_status(thissync, 
				"Getting second plugin changes...", 1);
      remote_changes = NULL;
      if (remoteconn) {
	CALL_PLUGIN_ASYNC(thissync->remoteclient, "get_changes", 
			  (remoteconn,newdbs));
	ret = sync_wait_msg_data(&thissync->msg_callret, NULL,
				 (gpointer*) &remote_changes);
	if (ret < 0)
	  remote_changes = NULL;
      }
      if (remote_changes)
	feedthroughentries = g_list_length(remote_changes->changes);
      else
	feedthroughentries = 0;
      if (ret >= 0)
	feedthroughsyncing = TRUE;
      async_set_pairlist_status(thissync, 
				"Synchronizing...", 1);
      // Send the remote change list to the local plugin
      CALL_PLUGIN_ASYNC(thissync->localclient,"resp_get_changes",
			(localconn, ret, remote_changes));
      ret = sync_wait_msg(&thissync->msg_callret, NULL);
      sync_free_change_info(remote_changes);
      remote_changes = NULL;
    }
    
    if (cmd == SYNC_MSG_LOCAL_MODIFY) {
      GList *results = NULL;
      // A remote feedthrough plugin asks to do a modify
      ret = sync_connect_plugin(thissync, CONNECTION_TYPE_LOCAL,
				&localconn);
      local_changes = NULL;
      if (localconn) {
	results = 
	  sync_do_syncobj_modifies(thissync, localconn, 
				   thissync->localclient, cmddata);
	ret = 0;
	sync_free_changes(cmddata);
      }
      if (results) // Should be only successful
	feedthroughentries += g_list_length(results);
      CALL_PLUGIN_ASYNC(thissync->remoteclient,"resp_modify",
			(remoteconn, ret, results));
      ret=sync_wait_msg(&thissync->msg_callret, NULL);
    } 
    if (cmd == SYNC_MSG_REMOTE_MODIFY) {
      GList *results = NULL;
      // A local feedthrough plugin asks to do a modify
      ret = sync_connect_plugin(thissync, CONNECTION_TYPE_REMOTE,
				&remoteconn);
      remote_changes = NULL;
      if (remoteconn) {
	results = 
	  sync_do_syncobj_modifies(thissync, remoteconn, 
				   thissync->remoteclient, cmddata);
	ret = 0;
	sync_free_changes(cmddata);
      }
      if (results) // Should be only successful
	feedthroughentries += g_list_length(results);
      CALL_PLUGIN_ASYNC(thissync->localclient,"resp_modify",
			(localconn, ret, results));
      ret=sync_wait_msg(&thissync->msg_callret, NULL);
    }
    if (cmd == SYNC_MSG_LOCAL_SYNCDONE) {
      if (localconn) {
	gboolean success = TRUE;
	success = ((gboolean) cmddata);
	CALL_PLUGIN_ASYNC(thissync->localclient, "sync_done", 
			  (localconn,success));
	ret = sync_wait_msg(&thissync->msg_callret, NULL);
	if (cmddata)
	  sync_log_sync_success(feedthroughentries, thissync);
	if (!localalwaysconn) {
	  sync_disconnect_plugin(thissync, CONNECTION_TYPE_LOCAL, &localconn);
	}
	feedthroughsyncing = FALSE;
      }
    }
    if (cmd == SYNC_MSG_REMOTE_SYNCDONE) {
      if (remoteconn) {
	gboolean success = TRUE;
	success = ((gboolean) cmddata);
	CALL_PLUGIN_ASYNC(thissync->remoteclient, "sync_done", 
			  (remoteconn,success));
	ret = sync_wait_msg(&thissync->msg_callret, NULL);
	if (cmddata)
	  sync_log_sync_success(feedthroughentries, thissync);
	if (!remotealwaysconn) {
	  sync_disconnect_plugin(thissync, CONNECTION_TYPE_REMOTE,&remoteconn);
	}
	feedthroughsyncing = FALSE;
      }
    }

    if (!thissync->manualonly) {
      if (thissync->syncmissed) {
	async_set_pairlist_status(thissync, "Searching for client...",0);
	dd(printf("Searching for units...\n"));
	g_get_current_time(&gt);
	gt.tv_sec+=thissync->reconninterval;
      } else {
	if (!feedthroughsyncing) {
	  async_set_pairlist_status(thissync, "Waiting for change.",0);
	  dd(printf("Waiting for change...\n"));
	}
	g_get_current_time(&gt);
	gt.tv_sec+=thissync->syncinterval;
      }
      if (thissync->syncinterval)
	cmd = sync_wait_msg_data(&thissync->msg_objchange, &gt, &cmddata);
      else
	cmd = sync_wait_msg_data(&thissync->msg_objchange, NULL, &cmddata);

    } else {
      async_set_pairlist_status(thissync, "Press \"Sync\" to synchronize.",0);
      cmd = sync_wait_msg_data(&thissync->msg_objchange, NULL, &cmddata);
    }
    dd(printf("Got message %d\n", cmd));
    if (cmd == SYNC_MSG_OBJECTCHANGE) {
      if (thissync->dwelltime) {
	ret = 0;
	do {
	  async_set_pairlist_status(thissync, "Change detected, waiting for more...",1);
	  dd(printf("Dwelling...\n"));
	  g_get_current_time(&gt);
	  gt.tv_sec+=thissync->dwelltime;
	} while ((ret = sync_wait_msg_data(&thissync->msg_objchange, &gt, 
					   &cmddata)) &&
		 ret == SYNC_MSG_OBJECTCHANGE);
	if (ret && ret != SYNC_MSG_OBJECTCHANGE) {
	  // Refeed unexpected message
	  sync_send_msg_data(&thissync->msg_objchange, ret, cmddata);
	}
      }
    }
    if (!cmd)
      cmd = SYNC_MSG_OBJECTCHANGE;
  }
  
  dd(printf("Syncthread: Exiting.\n"));
  if (localconn) {
    CALL_PLUGIN_ASYNC(thissync->localclient, "sync_disconnect", (localconn));
    ret = sync_wait_msg(&thissync->msg_callret, NULL);
  }
  if (remoteconn) {
    CALL_PLUGIN_ASYNC(thissync->remoteclient, "sync_disconnect", (remoteconn));
    ret = sync_wait_msg(&thissync->msg_callret, NULL);
  }

  thissync->thread_running = FALSE;
  if (thissync->callback)
    g_idle_add(thissync->callback, thissync->callbackdata);
  return(NULL);
}

void sync_free_change_info(change_info *change) {
  if (change) {
    sync_free_changes(change->changes);
    g_free(change);
  }
}

changed_object* sync_copy_changed_object(changed_object *change) {
  changed_object* c = g_malloc0(sizeof(changed_object));
  if (change->comp)
    c->comp = g_strdup(change->comp);
  if (change->uid)
    c->uid = g_strdup(change->uid);
  if (change->removepriority)
    c->removepriority = g_strdup(change->removepriority);
  c->change_type = change->change_type;
  c->object_type = change->object_type;
  return(c);
}

void sync_free_changed_object(changed_object *change) {
  if (!change)
    return;
  if (change->comp)
    g_free(change->comp);
  if (change->removepriority)
    g_free(change->removepriority);
  if (change->uid)
    g_free(change->uid);
  g_free(change);
}

void sync_free_changes(GList *changes) {
  while (changes) {
    GList *change;
    changed_object *obj;
    
    change = g_list_first(changes);
    if (change->data) {
      obj = change->data;
      sync_free_changed_object(obj);
    }
    changes = g_list_remove(changes, change->data);
  }
}

void sync_print_changes(GList *changes) {
  GList *ch = changes;
  while (ch) {
    changed_object *obj = ch->data; 
    
    if (obj) {
      dd(printf("Change type: %d, object type: %d\n", obj->change_type, 
	     obj->object_type));
      if (obj->uid)
	dd(printf("UID: \n%s\n", obj->uid));
      if (obj->comp)
	dd(printf("Comp: \n%s\n", obj->comp));
    }
    ch = ch->next;
  }
}

GList* sync_adjust_capacity(GList *change_list, GList **extra,
			    int fromlocal, client_connection *otherconn,
			    sync_plugin* otherplugin,
			    sync_object_type objtype,
			    sync_pair *thissync) {

  float full = 0.0;
  int ret;
  int loops = 20; // Max deletes
  int records=0, *maxrecords=NULL;
  int deltachange = 0;
  GList *change;

  if (!otherconn->managedbsize)
    return(change_list);
  
  // How many additions and deletions are allready in the list?
  change = change_list;
  while (change) {
    changed_object *obj = change->data;
    if (obj && obj->object_type == objtype) {
      if (obj->change_type == SYNC_OBJ_HARDDELETED ||
	  obj->change_type == SYNC_OBJ_SOFTDELETED)
	deltachange--;
      if ((obj->change_type == SYNC_OBJ_ADDED ||
	   obj->change_type == SYNC_OBJ_MODIFIED) && (!obj->uid))
	deltachange++;
    }
    change = change->next;
  }

  switch(objtype) {
  case SYNC_OBJECT_TYPE_CALENDAR:
    records = (otherconn->calendarrecords);
    maxrecords = &(otherconn->maxcalendarrecords);
    break;
  case SYNC_OBJECT_TYPE_TODO:
    records = (otherconn->todorecords);
    maxrecords = &(otherconn->maxtodorecords);
    break;
  case SYNC_OBJECT_TYPE_PHONEBOOK:
    records = (otherconn->phonebookrecords);
    maxrecords = &(otherconn->maxphonebookrecords);
    break;
  }
  records += deltachange;
  if (maxrecords && *maxrecords > 0) {
    full=(((float) records)/
	  ((float) *maxrecords));
    dd(printf("List fullness: %f %d %d\n", full, records,
	   *maxrecords));
    while (full > 0.75 && loops-- > 0) { // 75% full store
      char *delluid;
      delluid = fromlocal?sync_get_oldest_luid(objtype,change_list,thissync):
	sync_get_oldest_uid(objtype,change_list,thissync);
      if (delluid) {
	dd(printf("Delete %s for space reasons.\n", delluid));
	change_list = sync_add_to_change_list(change_list, NULL,
					      delluid, NULL, 
					      SYNC_OBJ_SOFTDELETED,
					      objtype, extra, NULL,
					      SYNC_RECUR_NONE);
	records--;
	full=(((float) records)/
	      ((float) *maxrecords));
      }
      else
	full = 0.0;
    }
  }
  return(change_list);
}


GList* sync_add_to_change_list(GList *list, char* comp, char* uid,
			       char* removepriority, int change_type, 
			       sync_object_type object_type,
			       GList **extra, char *origuid, 
			       sync_recur_type recur) {
  changed_object* c = g_malloc0(sizeof(changed_object));
  if (comp)
    c->comp = g_strdup(comp);
  if (uid)
    c->uid = g_strdup(uid);
  if (removepriority)
    c->removepriority = g_strdup(removepriority);
  c->change_type = change_type;
  c->object_type = object_type;
  if (extra) {
    change_list_extra* e = g_malloc0(sizeof(change_list_extra));
    if (origuid)
      e->origuid = g_strdup(origuid);
    e->recur = recur;
    *extra = g_list_append(*extra, e);
  }
  return(g_list_append(list, c));
}

void sync_free_modify_results(GList *results) {
  while (results) {
    syncobj_modify_result *result = results->data;
    if (result->returnuid)
      g_free(result->returnuid);
    g_free(result);
    results = g_list_remove(results, results->data);
  }
}

// Send a modification list to the plugin (either one at a time or the 
// full list) and return a list of syncobj_modify_result's.
GList* sync_do_syncobj_modifies(sync_pair *thissync, client_connection *conn,
				 sync_plugin* plugin,
				 GList *change_list) {
  int ret = 0;
  GList *results = NULL;
  dd(printf("Got %d changes.\n", g_list_length(change_list)));
  sync_print_changes(change_list);
  if (dlsym(plugin->plugin,"syncobj_modify_list")) {
    // Send list in one batch (new API function)
    async_set_pairlist_status(thissync, "Synchronizing...", 1);
    CALL_PLUGIN_ASYNC(plugin, "syncobj_modify_list", 
		      (conn, change_list));
    ret=sync_wait_msg_data(&thissync->msg_callret, NULL, (gpointer*) &results);
  } else {
    // Send list command by command (normal API)
    char msg[256];
    GList *change;
    change = change_list;
    async_set_pairlist_status(thissync, "Synchronizing...", 1);
    while(change) {
      changed_object *obj = change->data;
      syncobj_modify_result *result = g_malloc0(sizeof(syncobj_modify_result));
      
      ret = SYNC_MSG_NOMSG;
      if (obj->change_type == SYNC_OBJ_HARDDELETED ||
	  obj->change_type == SYNC_OBJ_SOFTDELETED) {
	CALL_PLUGIN_ASYNC(plugin, "syncobj_delete", 
			  (conn, obj->uid, obj->object_type, 
			   obj->change_type == SYNC_OBJ_SOFTDELETED));
	ret=sync_wait_msg(&thissync->msg_callret, NULL);
      }
      if (obj->change_type == SYNC_OBJ_ADDED || 
	  obj->change_type == SYNC_OBJ_MODIFIED) {
	char outluid[256];
	int outluidlen = 256;
	CALL_PLUGIN_ASYNC(plugin, "syncobj_modify",
			  (conn, obj->comp, obj->uid, 
			   obj->object_type,
			   outluid, &outluidlen));
	ret=sync_wait_msg(&thissync->msg_callret, NULL);
	if (ret < 0 && obj->uid) {
	  outluidlen = 256;
	  g_free(obj->uid);
	  obj->uid = NULL;
	  CALL_PLUGIN_ASYNC(plugin, "syncobj_modify",
			    (conn, obj->comp, NULL, 
			     obj->object_type,
			     outluid, &outluidlen));
	  ret=sync_wait_msg(&thissync->msg_callret, NULL);
	}
	if (ret >= 0 && outluidlen > 0 && !obj->uid)
	  result->returnuid = g_strdup(outluid);
      }
      result->result = ret;
      results = g_list_append(results, result);
      change = change->next;
    }
  }
  return(results);
}


void sync_process_changes(client_connection *localconn,
			  client_connection *remoteconn, GList *localchanges,
			  GList *remotechanges, sync_object_type newdbs, 
			  sync_pair *thissync) {
  int t,n, ret;
  int count = 0, totcount = 0, actualcount = 0;
  int fromlocal;
  gboolean success = TRUE;
  sync_compare_result compare;

  if (newdbs)
    sync_delete_all_idpairs(thissync, newdbs); // Remove outdated ID pairs if new database

  dd(printf("Process: %d %d\n", g_list_length(remotechanges), 
	 g_list_length(localchanges)));

  thissync->default_duplicate_action = thissync->duplicate_mode;
  // Check for double remote-local modifies of the same object
  for (t = 0; t < g_list_length(localchanges); t++) {
    for (n = 0; n < g_list_length(remotechanges); n++) {
      changed_object *robj, *lobj;
      char *luid, *uid;
      
      robj = g_list_nth_data(remotechanges, n);
      lobj = g_list_nth_data(localchanges, t);
      if (robj->uid && lobj->uid && robj->object_type == lobj->object_type &&
	  lobj->change_type != SYNC_OBJ_FILTERED &&
	  robj->change_type != SYNC_OBJ_FILTERED) {
	luid = sync_get_luid(lobj->uid, lobj->object_type, thissync);
	if (luid && !strcmp(robj->uid,luid)) {
	  compare = sync_compare_objects(robj->comp, lobj->comp, lobj->object_type);
	  // Double modify (or slow sync). Check of objects are equal.
	  if (compare == SYNC_COMPARE_EQUAL){
	    // They are equal, so just don't synchronize.
	    robj->change_type = SYNC_OBJ_FILTERED;
	    lobj->change_type = SYNC_OBJ_FILTERED;
	    dd(printf("Old changed object %s is equal to %s. No sync.\n",
		   lobj->uid, robj->uid));
	  } else {
	    sync_duplicate_action action = SYNC_DUPLICATE_ACTION_KEEPBOTH;
	    if (thissync->default_duplicate_action ==
		SYNC_DUPLICATE_ACTION_NONE) {
	      sync_ask_duplicate(lobj, robj, TRUE, thissync);
	      sync_wait_msg_data(&thissync->msg_callret, NULL, 
				 (gpointer*) &action);
	    } else
	      action = thissync->default_duplicate_action;
	    if (action == SYNC_DUPLICATE_ACTION_KEEPBOTH)
	      sync_delete_idpair(lobj->uid, NULL, lobj->object_type, thissync);
	    else if (action == SYNC_DUPLICATE_ACTION_KEEPFIRST)
	      robj->change_type = SYNC_OBJ_FILTERED;
	    else
	      lobj->change_type = SYNC_OBJ_FILTERED;
	  }
	}
	/*if (!luid) {
	  uid = sync_get_uid(robj->uid, robj->object_type, thissync);
	  if (!uid) {*/
	    // None of the two objects has a UID connection
	    // If they are equal, just connect them
	    sync_compare_result compare = 
	      sync_compare_objects(robj->comp, lobj->comp, lobj->object_type);
	    if (compare == SYNC_COMPARE_EQUAL) {
	      sync_insert_idpair(lobj->uid, robj->uid, 
				 lobj->object_type, 
				 lobj->removepriority, SYNC_RECUR_NONE, 
				 thissync);
	      robj->change_type = SYNC_OBJ_FILTERED;
	      lobj->change_type = SYNC_OBJ_FILTERED;
	      dd(printf("New changed object %s is equal to %s. Merging.\n",
		     lobj->uid, robj->uid));
	    } else {
	      sync_duplicate_action action = SYNC_DUPLICATE_ACTION_KEEPBOTH;
	      if (compare == SYNC_COMPARE_SIMILAR) {
		if (thissync->default_duplicate_action ==
		    SYNC_DUPLICATE_ACTION_NONE) {
		  sync_ask_duplicate(lobj, robj, FALSE, thissync);
		  sync_wait_msg_data(&thissync->msg_callret, NULL, 
				     (gpointer*) &action);
		} else 
		  action = thissync->default_duplicate_action;
	      }
	      if (action == SYNC_DUPLICATE_ACTION_KEEPFIRST) {
		robj->change_type = SYNC_OBJ_FILTERED;
		sync_insert_idpair(lobj->uid, robj->uid, 
				   lobj->object_type, 
				   lobj->removepriority, SYNC_RECUR_NONE, 
				   thissync);
	      }
	      else if (action == SYNC_DUPLICATE_ACTION_KEEPSECOND) {
		lobj->change_type = SYNC_OBJ_FILTERED;
		sync_insert_idpair(lobj->uid, robj->uid, 
				   robj->object_type, 
				   robj->removepriority, SYNC_RECUR_NONE, 
				   thissync);
	      }
	      /* }
		 }*/
	}
      }
    }
  }
  totcount = g_list_length(remotechanges)+g_list_length(localchanges);
  for (fromlocal = 1; fromlocal >=0 ; fromlocal--) {
    sync_plugin *myplugin, *otherplugin;
    GList *currentchanges;
    client_connection *myconn, *otherconn;
    sync_direction dir;
    GList *change_list = NULL;
    GList *change_extra = NULL;
    GList *results = NULL;

    if (fromlocal) {
      currentchanges = localchanges;
      myconn = localconn;
      otherconn = remoteconn;
      myplugin = thissync->localclient;
      otherplugin = thissync->remoteclient;
      dir = SYNC_DIR_LOCALTOREMOTE;
    } else {
      currentchanges = remotechanges;
      myconn = remoteconn;
      otherconn = localconn;
      myplugin = thissync->remoteclient;
      otherplugin = thissync->localclient;
      dir = SYNC_DIR_REMOTETOLOCAL;
    }
    
    // Loop twice, first process local changes, then remote changes.
    for (t = 0; success && t < g_list_length(currentchanges); t++) {
      changed_object *obj;
      char *luid = NULL;
      char newluid[256];
      int newluidlen = 256;
      int filt;

      obj = g_list_nth_data(currentchanges, t);
      
      if (obj->uid) {
	luid = fromlocal?sync_get_luid(obj->uid,obj->object_type, thissync): 
	  sync_get_uid(obj->uid,obj->object_type, thissync);
	if (!luid) {
	  // Since both VEVENT and VTODO can be in a VCALENDAR,
	  // some apps may not be able to tell them apart. 
	  // => Try the other type.
	  if (obj->object_type == SYNC_OBJECT_TYPE_CALENDAR) {
	    luid = fromlocal?sync_get_luid(obj->uid,
					   SYNC_OBJECT_TYPE_TODO, 
					   thissync): 
	      sync_get_uid(obj->uid,obj->object_type, thissync);
	    if (luid)
	      obj->object_type = SYNC_OBJECT_TYPE_TODO;
	  } else if (obj->object_type == SYNC_OBJECT_TYPE_TODO) {
	    luid = fromlocal?sync_get_luid(obj->uid,
					   SYNC_OBJECT_TYPE_CALENDAR, 
					   thissync): 
	      sync_get_uid(obj->uid,obj->object_type, thissync);
	    if (luid)
	      obj->object_type = SYNC_OBJECT_TYPE_CALENDAR;
	  }
	}   
      } else {
	luid = NULL;
      }
      //printf("Change, UID: %s\n", obj->uid);
      //printf("LUID: %s\n", luid);
      //printf("Type: %d\n", obj->change_type);
      //if (obj->object_type == SYNC_OBJECT_TYPE_PHONEBOOK)
      //printf("Phonebook entry:\n%s\n", obj->comp);
      //if (obj->object_type == SYNC_OBJECT_TYPE_CALENDAR)
      //printf("Calendar entry:\n%s\n", obj->comp);

      // See if the event should be filtered out
      for (filt = 0; filt < g_list_length(thissync->filters); filt++) {
	sync_filter *filter = g_list_nth_data(thissync->filters, filt);
	if ((filter->type & obj->object_type) &&
	    (filter->dir & dir) && 
	    (obj->object_type <= SYNC_OBJECT_TYPE_TODO) && obj->comp) {
	  // Type and direction are right
	  char *fielddata = sync_get_key_data(obj->comp, filter->field);
	  gboolean match = FALSE;
	  if (fielddata && filter->data) {
	    char *pos = fielddata;
	    while (pos) {
	      char *nextword = strstr(pos, ",");
	      if (nextword) {
		nextword[0] = 0;
		nextword++;
	      }
	      if (!g_strcasecmp(pos, filter->data))
		match = TRUE;
	      pos = nextword;
	    }
	    g_free(fielddata);
	  }
	  if (filter->rule == SYNC_RULE_NONE)
	    obj->change_type = SYNC_OBJ_FILTERED;
	  if (filter->rule == SYNC_RULE_ALLBUT && match)
	    obj->change_type = SYNC_OBJ_FILTERED;
	  if (filter->rule == SYNC_RULE_ONLYIF && !match)
	    obj->change_type = SYNC_OBJ_FILTERED;
	}
      }
	
      if (luid && (obj->change_type == SYNC_OBJ_SOFTDELETED || 
		   obj->change_type == SYNC_OBJ_HARDDELETED)) {
	// This entry exists in remote db. Delete it.
	actualcount++;
	if (obj->change_type == SYNC_OBJ_HARDDELETED) {
	  GList *recurs;
	  // Deleted not only for space reasons.
	  change_list = sync_add_to_change_list(change_list, NULL,
						luid, NULL,
						obj->change_type,
						obj->object_type,
						&change_extra, obj->uid, 
						SYNC_RECUR_NONE);
	  // If there are faked recurs, delete them as well
	  recurs = fromlocal?sync_get_recur_luids(obj->uid,obj->object_type, 
						  thissync):
	    sync_get_recur_uids(obj->uid,obj->object_type, thissync);
	  // Remove old faked recurrance entries in remote db
	  while(recurs) {
	    GList *recur;
	    recur = g_list_first(recurs);
	    change_list = 
	      sync_add_to_change_list(change_list, NULL, recur->data, NULL, 
				      SYNC_OBJ_HARDDELETED, obj->object_type,
				      &change_extra, obj->uid, 
				      fromlocal?SYNC_RECUR_UID:
				      SYNC_RECUR_LUID);
	    recurs = g_list_remove(recurs, recur->data);
	  }
	}
	//sync_delete_idpair(fromlocal?obj->uid:NULL, 
	//		   !fromlocal?obj->uid:NULL,obj->object_type, thissync);
      }
      if (obj->change_type == SYNC_OBJ_ADDED || 
	  obj->change_type == SYNC_OBJ_MODIFIED) {
	int t;
	GList *recurs;
	actualcount++;
	// This entry is new or modified.
	if (!luid) {
	  // A new entry is to be added, check db capacity
	  change_list = 
	    sync_adjust_capacity(change_list,&change_extra,
				 fromlocal,otherconn,otherplugin,
				 obj->object_type,thissync);
	}
	change_list =
	  sync_add_to_change_list(change_list, obj->comp, luid, 
				  obj->removepriority,
				  obj->change_type, obj->object_type,
				  &change_extra, obj->uid, 
				  SYNC_RECUR_NONE);
	if (otherconn->fake_recurring) {
	  recurs = fromlocal?sync_get_recur_luids(obj->uid,obj->object_type, 
						  thissync):
	    sync_get_recur_uids(obj->uid,obj->object_type, thissync);
	  // Remove old faked recurrance entries in remote db
	  while(recurs) {
	    GList *recur;
	    recur = g_list_first(recurs);
	    change_list = 
	      sync_add_to_change_list(change_list, NULL, recur->data, NULL,  
				      SYNC_OBJ_HARDDELETED, obj->object_type,
				      &change_extra, obj->uid, 
				      SYNC_RECUR_NONE);
	    recurs = g_list_remove(recurs, recur->data);
	  }
	  CALL_PLUGIN_ASYNC(myplugin, "syncobj_get_recurring", 
			    (myconn, obj));
	  ret=sync_wait_msg_data(&thissync->msg_callret, NULL, 
				 (gpointer*) &recurs);
	  if (ret>=0) {
	    // This object is recurring, fake entries in remote db
	    for (t=0; t < g_list_length(recurs); t++) {
	      changed_object *recurobj;
	      recurobj = g_list_nth_data(recurs, t);
	      change_list = 
		sync_adjust_capacity(change_list,&change_extra,
				     fromlocal,otherconn,
				     otherplugin, obj->object_type, thissync);
	      change_list =
		sync_add_to_change_list(change_list, recurobj->comp, NULL,
					recurobj->removepriority,
					SYNC_OBJ_ADDED, obj->object_type,
					&change_extra, obj->uid, 
					fromlocal?SYNC_RECUR_UID:
					SYNC_RECUR_LUID);
	    }
	    sync_free_changes(recurs);
	  }
	}
      }
    }
    // Send change_list to plugin, either in batch or as separate commands.
    results = sync_do_syncobj_modifies(thissync,
				       otherconn, otherplugin, change_list);
    if (results) {
      // Update the UID-LUID database and check success
      int n;
      gboolean databasefullasked = FALSE;
      for (n = 0; n < g_list_length(change_list) && success; n++) {
	changed_object *obj = NULL;
	syncobj_modify_result *result = NULL;
	change_list_extra *extra = NULL;
	obj = g_list_nth_data(change_list, n);
	result = g_list_nth_data(results, n);
	extra = g_list_nth_data(change_extra, n);
	if (result)
	  ret = result->result;
	if (obj && result && extra && ret >= 0) {
	  if (obj->change_type == SYNC_OBJ_HARDDELETED ||
	      obj->change_type == SYNC_OBJ_SOFTDELETED) {
	    sync_delete_idpair(!fromlocal?obj->uid:NULL, 
			       fromlocal?obj->uid:NULL,
			       obj->object_type, thissync);
	  }
	  if ((obj->change_type == SYNC_OBJ_ADDED || 
	       obj->change_type == SYNC_OBJ_MODIFIED) && 
	      (result->returnuid)) {
	    sync_insert_idpair(fromlocal?extra->origuid:result->returnuid, 
			       !fromlocal?extra->origuid:result->returnuid,
			       obj->object_type, 
			       obj->removepriority, extra->recur, 
			       thissync);
	  }
	}
	if (ret == SYNC_MSG_CONNECTIONERROR)
	  success = FALSE;
	if (ret == SYNC_MSG_DATABASEFULLERROR && 
	    !databasefullasked)  {
	  int questret = 0;
	  sync_ask_dbfull(obj, thissync);
	  questret = sync_wait_msg(&thissync->msg_callret, NULL);
	  if (questret == SYNC_MSG_FALSE)
	    success = FALSE; // Try again
	  else 
	    databasefullasked = TRUE;
	}
      }
      sync_free_modify_results(results);
      results = NULL;
    }
    // Free change list
    sync_free_changes(change_list);
    change_list = NULL;
    // Free extra info list
    while (change_extra) {
      change_list_extra *extra = change_extra->data;
      if (extra->origuid)
	g_free(extra->origuid);
      g_free(extra);
      change_extra = g_list_remove(change_extra, change_extra->data);
    }
  }
  if (success) {
    sync_log_sync_success(actualcount, thissync);
  }
  else {
    async_add_pairlist_log(thissync, "Synchronization failed.", SYNC_LOG_ERROR);
    dd(printf("Synchronization failed!\n"));
  }
  CALL_PLUGIN_ASYNC(thissync->localclient, "sync_done", (localconn,success));
  ret = sync_wait_msg(&thissync->msg_callret, NULL);
  CALL_PLUGIN_ASYNC(thissync->remoteclient, "sync_done", (remoteconn,success));
  ret = sync_wait_msg(&thissync->msg_callret, NULL);
}

void sync_log_sync_success(int actualcount, sync_pair *thissync) {
  char *log;
  dd(printf("Synchronization success!\n"));
  if (actualcount > 0) {
    if (thissync->playsyncsound && thissync->syncsound)
      gnome_sound_play(thissync->syncsound);
    if (actualcount > 1)
      log = g_strdup_printf("Synchronization of %d entries succeeded.", actualcount);
    else
      log = g_strdup_printf("Synchronization of one entry succeeded.");
    async_add_pairlist_log(thissync, log, SYNC_LOG_SUCCESS);
    g_free(log);
  } else {
    async_add_pairlist_log(thissync, "Connection OK; no new changes reported.", SYNC_LOG_SUCCESS);
  }
}

// Send a message with both type and data. The data is NOT copied.
void sync_send_msg_data(sync_msg_port *port, int type, gpointer data) {
  sync_msg *msg;
  g_mutex_lock(port->msg_mutex);
  msg = g_malloc0(sizeof(sync_msg));
  g_assert(msg);
  msg->type = type;
  msg->data = data;
  port->msg_queue = g_list_append(port->msg_queue, msg);
  port->msg_status=1;
  g_cond_signal(port->msg_signal);
  g_mutex_unlock(port->msg_mutex);
}

void sync_send_msg(sync_msg_port *port, int type) {
  sync_send_msg_data(port, type, NULL);
}

// If the message is on the queue already, dont add this too
void sync_send_msg_once(sync_msg_port *port, int type) {
  sync_msg *msg;
  GList *q;
  g_mutex_lock(port->msg_mutex);
  q = port->msg_queue;
  while (q) {
    msg = q->data;
    if (msg && msg->type == type) {
      port->msg_status=1;
      g_cond_signal(port->msg_signal);
      g_mutex_unlock(port->msg_mutex);
      return;
    }
    q = q->next;
  }
  msg = g_malloc0(sizeof(sync_msg));
  g_assert(msg);
  msg->type = type;
  msg->data = NULL;
  port->msg_queue = g_list_append(port->msg_queue, msg);
  port->msg_status=1;
  g_cond_signal(port->msg_signal);
  g_mutex_unlock(port->msg_mutex);
}


int sync_wait_msg_data(sync_msg_port *port, GTimeVal *untiltime, 
		       gpointer *data) {
  GList *entry;
  sync_msg *msg;
  int ret = 0;
  g_mutex_lock(port->msg_mutex);
  while(!(entry = g_list_first(port->msg_queue))) {
    if (untiltime) {
      while (port->msg_status==0) {
        ret=g_cond_timed_wait(port->msg_signal, port->msg_mutex, untiltime);
        if (!ret) {
          port->msg_status=0;
          g_mutex_unlock(port->msg_mutex);
          return(0);
	}
      }
      port->msg_status=0;
    } else {
      while (port->msg_status==0) {
        g_cond_wait(port->msg_signal, port->msg_mutex);
      }
      port->msg_status=0;
    }
  }
  msg = entry->data;
  if (msg) {
    ret = msg->type;
    if (data)
      *data = msg->data;
    g_free(msg);
  }
  port->msg_queue = g_list_remove_link(port->msg_queue, entry);
  g_mutex_unlock(port->msg_mutex);
  return(ret);
}

int sync_wait_msg(sync_msg_port *port, GTimeVal *untiltime) {
  return(sync_wait_msg_data(port, untiltime, NULL));
}


void sync_feedthrough_get_changes(sync_pair *thissync, connection_type type,
				  sync_object_type newdbs) {
  if (type == CONNECTION_TYPE_LOCAL)
    sync_send_msg_data(&thissync->msg_objchange, SYNC_MSG_GET_REMOTE_CHANGES,
		       (gpointer) newdbs);
  else
    sync_send_msg_data(&thissync->msg_objchange, SYNC_MSG_GET_LOCAL_CHANGES,
		       (gpointer) newdbs);
}

void sync_feedthrough_syncdone(sync_pair *thissync, connection_type type,
			       gboolean success) {
  if (type == CONNECTION_TYPE_LOCAL)
    sync_send_msg_data(&thissync->msg_objchange, SYNC_MSG_REMOTE_SYNCDONE, 
		       (gpointer) success);
  else
    sync_send_msg_data(&thissync->msg_objchange, SYNC_MSG_LOCAL_SYNCDONE,
		       (gpointer) success);
}

void sync_feedthrough_modify(sync_pair *thissync, connection_type type,
			     GList *modify) {
  if (type == CONNECTION_TYPE_LOCAL)
    sync_send_msg_data(&thissync->msg_objchange, SYNC_MSG_REMOTE_MODIFY,
		       modify);
  else
    sync_send_msg_data(&thissync->msg_objchange, SYNC_MSG_LOCAL_MODIFY,
		       modify);
}

void sync_object_changed(sync_pair *thissync) {
  sync_send_msg_once(&thissync->msg_objchange, SYNC_MSG_OBJECTCHANGE);
}

void sync_force_sync(sync_pair *thissync) {
  sync_send_msg_once(&thissync->msg_objchange, SYNC_MSG_FORCESYNC);
}

void sync_force_resync(sync_pair *thissync) {
  sync_send_msg(&thissync->msg_objchange, SYNC_MSG_FORCERESYNC);
}

void sync_settings_changed(sync_pair *thissync) {
  sync_send_msg(&thissync->msg_objchange, SYNC_MSG_SETTINGSCHANGED);
}


void sync_quit(sync_pair *thissync, gboolean (*callback)(gpointer), gpointer data) {
  thissync->callback = callback;
  thissync->callbackdata = data;
  sync_send_msg(&thissync->msg_objchange, SYNC_MSG_QUIT);
}

void sync_set_requestdata(gpointer data,sync_pair *thissync) {
  if (thissync->errorstr)
    g_free(thissync->errorstr);
  thissync->errorstr = NULL;
  sync_send_msg_data(&thissync->msg_callret, SYNC_MSG_REQDONE, data);
}

void sync_set_requestdatamsg(gpointer data, sync_msg_type type,
			     sync_pair *thissync) {
  if (thissync->errorstr)
    g_free(thissync->errorstr);
  thissync->errorstr = NULL;
  sync_send_msg_data(&thissync->msg_callret, type, data);
}

void sync_set_requestdone(sync_pair *thissync) {
  if (thissync->errorstr)
    g_free(thissync->errorstr);
  thissync->errorstr = NULL;
  sync_send_msg(&thissync->msg_callret, SYNC_MSG_REQDONE);
}

gpointer sync_set_requestfailed(sync_pair *thissync) {
  if (thissync->errorstr)
    g_free(thissync->errorstr);
  thissync->errorstr = NULL;
  sync_send_msg(&thissync->msg_callret, SYNC_MSG_REQFAILED);
  return(NULL);
}

gpointer sync_set_requestfailederror(char* errorstr, sync_pair *thissync) {
  if (thissync->errorstr)
    g_free(thissync->errorstr);
  thissync->errorstr = g_strdup(errorstr); // Non-queued error string, 
  sync_send_msg(&thissync->msg_callret, SYNC_MSG_REQFAILED);
  return(NULL);
}

void sync_set_requestmsg(sync_msg_type type, sync_pair *thissync) {
  if (thissync->errorstr)
    g_free(thissync->errorstr);
  thissync->errorstr = NULL;
  sync_send_msg(&thissync->msg_callret, type);
}

void sync_set_requestmsgerror(sync_msg_type type, char *errorstr, 
			      sync_pair *thissync) {
  if (thissync->errorstr)
    g_free(thissync->errorstr);
  thissync->errorstr = g_strdup(errorstr); // Non-queued error string, 
  // doesn't really matter
  sync_send_msg(&thissync->msg_callret, type);
}

// Wrapper for the gui function async_add...
void sync_log(sync_pair *pair, char* logstring, sync_log_type type) {
  async_add_pairlist_log(pair, logstring, type);
}

char *name_extension(char *name) {
  int n = strlen(name)-1;
  
  while (n>=0 && name[n]!='.')
    n--;
  if (n < 0)
    return(NULL);
  return(name+n+1);
}

void read_pluginlist() {
  DIR *dir;
  char *ptr;

  if ((dir = opendir(PLUGINDIR))) {
    struct dirent *de = NULL;
    
    while ((de = readdir(dir))) {
      char name[256];
      gpointer mod;
      char *ext;
      sync_plugin *plugin;
      strncpy(name, de->d_name,255);
      ext = name_extension(name);
      if (ext && !strcmp(ext, "so")) {
	char *path;
	path = g_strdup_printf("%s/%s", PLUGINDIR, name);
	dd(printf("Trying %s...\n", path));
	mod = dlopen(path, RTLD_LAZY);
	g_free(path);
	if (mod && dlsym(mod,"short_name")) {
	  sync_plugin tmp_plugin;
	  int APIver = 0;
	  tmp_plugin.plugin = mod;
	  APIver = (int) CALL_PLUGIN((&tmp_plugin), "plugin_API_version", ());
	  if (APIver != MULTISYNC_API_VER) {
	    char *name = CALL_PLUGIN((&tmp_plugin), "long_name", ());
	    char *msg;
	    if (!name)
	      name = CALL_PLUGIN((&tmp_plugin), "short_name", ());
	    msg = g_strdup_printf("The plugin \"%s\" cannot be loaded\n"
				  "since it is of another version than "
				  "MultiSync itself.\n"
				  "Please install the correct "
				  "version or remove the plugin.", name);
	    sync_async_msg(msg);
	    g_free(msg);
	    if (mod) 
              dlclose(mod);
	  } else {
	    plugin = g_malloc0(sizeof(sync_plugin));
	    g_assert(plugin);
	    plugin->plugin = mod;
	    plugin->shortname = CALL_PLUGIN(plugin, "short_name", ());
            ptr=dlerror();
            if (ptr != 0) {
              printf("ERROR: Failed to load plugin: %s! (Error=%s, missing short_name-symbol)\n", name, ptr);
              dlclose(mod);
              continue;
            }
	    
	    plugin->longname = CALL_PLUGIN(plugin, "long_name", ());
	    CALL_PLUGIN(plugin, "plugin_init", ());
	    dd(printf("Plugin found: %s\n", plugin->longname));
	    pluginlist = g_list_append(pluginlist, plugin);
	  }
	} else {
	  dd(printf("Failed to load plugin: %s (Error=%s)\n", name, dlerror()));
	}
      }
    }
    closedir(dir);
  }
}

sync_plugin* get_plugin_by_name(char *name) {
  int n;
  for (n = 0; n < g_list_length(pluginlist); n++) {
    sync_plugin *plugin = g_list_nth_data(pluginlist, n);
    if (plugin && !strcmp(plugin->shortname, name))
      return(plugin);
  }
  async_set_multisync_status("One or more plugins not found.");
  return(NULL);
}

sync_pair *clone_syncpair(sync_pair *orig) {
  sync_pair *pair;
  int n;
  pair = g_malloc0(sizeof(sync_pair));
  memcpy(pair, orig, sizeof(sync_pair));
  pair->localname = g_strdup(orig->localname);
  pair->remotename = g_strdup(orig->remotename);
  pair->datapath = g_strdup(orig->datapath);
  if (orig->errorstr)
    pair->errorstr = g_strdup(orig->errorstr);
  if (orig->syncsound)
    pair->syncsound = g_strdup(orig->syncsound);
  if (orig->welcomesound)
    pair->welcomesound = g_strdup(orig->welcomesound);
  if (orig->displayname)
    pair->displayname = g_strdup(orig->displayname);
  if (orig->status)
    pair->status = g_strdup(orig->status);
  pair->original = orig;
  pair->log = NULL;
  for (n = 0; n < g_list_length(orig->log); n++) {
    sync_pair_log *log = g_list_nth_data(orig->log, n);
    sync_pair_log *newlog = g_malloc0(sizeof(sync_pair_log));
    memcpy(newlog, log, sizeof(sync_pair_log));
    newlog->logstring = g_strdup(log->logstring);
    pair->log = g_list_append(pair->log, newlog);
  }
  pair->filters = NULL;
  for (n = 0; n < g_list_length(orig->filters); n++) {
    sync_filter *filter = g_list_nth_data(orig->filters, n);
    sync_filter *newfilter = g_malloc0(sizeof(sync_filter));
    memcpy(newfilter, filter, sizeof(sync_filter));
    newfilter->field = g_strdup(filter->field);
    newfilter->data = g_strdup(filter->data);
    pair->filters = g_list_append(pair->filters, newfilter);
  }
  return(pair);
}

void apply_syncpair(sync_pair *orig, sync_pair* newpair) {
  if (orig->localname)
    g_free(orig->localname);
  if (orig->remotename)
    g_free(orig->remotename);
  if (orig->datapath)
    g_free(orig->datapath);
  while (orig->log) {
    GList *first = g_list_first(orig->log);
    sync_pair_log *log = first->data;
    if (log->logstring)
      g_free(log->logstring);
    g_free(log);
    orig->log = g_list_remove(orig->log, log);
  }
  if (orig->errorstr)
    g_free(orig->errorstr);
  if (orig->syncsound)
    g_free(orig->syncsound);
  if (orig->status)
    g_free(orig->status);
  while (orig->filters) {
    GList *first = g_list_first(orig->filters);
    sync_filter *filter = first->data;
    if (filter->field)
      g_free(filter->field);
    if (filter->data)
      g_free(filter->data);
    g_free(filter);
    orig->filters = g_list_remove(orig->filters, filter);
  }

  if (orig->welcomesound)
    g_free(orig->welcomesound);
  if (orig->displayname)
    g_free(orig->displayname);
  memcpy(orig, newpair, sizeof(sync_pair));
  g_free(newpair);
  save_syncpair(orig);
}

void free_syncpair(sync_pair *pair) {
  if (pair->localname)
    g_free(pair->localname);
  if (pair->remotename)
    g_free(pair->remotename);
  if (pair->datapath)
    g_free(pair->datapath);
  while (pair->log) {
    GList *first = g_list_first(pair->log);
    sync_pair_log *log = first->data;
    if (log->logstring)
      g_free(log->logstring);
    g_free(log);
    pair->log = g_list_remove(pair->log, log);
  }
  if (pair->errorstr)
    g_free(pair->errorstr);
  if (pair->syncsound)
    g_free(pair->syncsound);
  if (pair->welcomesound)
    g_free(pair->welcomesound);
  if (pair->displayname)
    g_free(pair->displayname);
  if (pair->status)
    g_free(pair->status);
  while (pair->filters) {
    GList *first = g_list_first(pair->filters);
    sync_filter *filter = first->data;
    if (filter->field)
      g_free(filter->field);
    if (filter->data)
      g_free(filter->data);
    g_free(filter);
    pair->filters = g_list_remove(pair->filters, filter);
  }

  g_free(pair);
}

void save_syncpair(sync_pair *pair) {
  if (pair->datapath) {
    char *filename;
    FILE *f;
    
    filename = g_strdup_printf("%s/%s", pair->datapath, "syncpair");
    if ((f = fopen(filename, "w"))) {
      int n;
      if (pair->remotename)
	fprintf(f, "remoteclient = %s\n", pair->remotename);
      if (pair->localname)
	fprintf(f, "localclient = %s\n", pair->localname);
      fprintf(f, "syncinterval = %d\n", pair->syncinterval);
      fprintf(f, "dwelltime = %d\n", pair->dwelltime);
      fprintf(f, "manualonly = %s\n", pair->manualonly?"yes":"no");
      fprintf(f, "reconninterval = %d\n", pair->reconninterval);
      fprintf(f, "object_types = 0x%x\n", pair->object_types);
      if (pair->syncsound)
	fprintf(f, "syncsound = %s\n", pair->syncsound);
      if (pair->welcomesound)
	fprintf(f, "welcomesound = %s\n", pair->welcomesound);
      if (pair->displayname)
	fprintf(f, "displayname = %s\n", pair->displayname);
      fprintf(f, "playsyncsound = %s\n", pair->playsyncsound?"yes":"no");
      fprintf(f, "playwelcomesound = %s\n", pair->playsyncsound?"yes":"no");
      for (n = 0; n < g_list_length(pair->filters); n++) {
	sync_filter *filter = g_list_nth_data(pair->filters, n);
	if (filter->field && filter->data) 
	  fprintf(f, "filter = %d,%d,%d,\"%s\",\"%s\"\n", filter->type,
		  filter->dir, filter->rule, 
		  (filter->field && strlen(filter->field)>0)?filter->field:"*",
		  (filter->data && strlen(filter->data)>0)?filter->data:"*");
      }
      fprintf(f, "duplicatemode = %d\n", pair->duplicate_mode);
      fclose(f);
    }
    g_free(filename);
  }
}

void read_syncpairs() {
  DIR *dir;

  if (!(dir = opendir(datadir))) {
    if (!mkdir(datadir,S_IRWXU))
      dir = opendir(datadir);
  }
  
  if (dir) {
    struct dirent *de;

    while ((de = readdir(dir))) {
      gpointer mod;
      sync_pair *pair;
      struct stat stbuf;
      char *dirname = NULL;
      
      dirname = g_strdup_printf ("%s/%s", datadir, de->d_name);
      if((stat(dirname, &stbuf) == 0) &&
	 ((stbuf.st_mode & S_IFMT) == S_IFDIR)) {
	char *filename;
	FILE *f;
	char line[256];
	filename = g_strdup_printf ("%s/%s/%s", datadir,
				    de->d_name, "syncpair");
	if ((f = fopen(filename, "r"))) {
	  pair = g_malloc0(sizeof(sync_pair));
	  g_assert(pair);
	  pair->object_types = 0xffff;
	  sscanf(de->d_name, "%d", &pair->pairno);
	  pair->datapath = g_strdup_printf ("%s/%s", datadir, de->d_name);
	  while (fgets(line, 256, f)) {
	    char prop[128], data[256];
	    if (sscanf(line, "%128s = %256[^\n]", prop, data) == 2) {
	      if (!strcmp(prop, "localclient")) {
		int n;
		pair->localname = g_strdup(data);
		pair->localclient = get_plugin_by_name(data);
	      }
	      if (!strcmp(prop, "remoteclient")) {
		pair->remotename = g_strdup(data);
		pair->remoteclient = get_plugin_by_name(data);
	      }
	      if (!strcmp(prop, "syncinterval")) {
		sscanf(data, "%d", &pair->syncinterval);
	      }
	      if (!strcmp(prop, "dwelltime")) {
		sscanf(data, "%d", &pair->dwelltime);
	      }
	      if (!strcmp(prop, "manualonly")) {
		if (!strcmp(data, "yes"))
		  pair->manualonly = TRUE;
		else
		  pair->manualonly = FALSE;
	      }
	      if (!strcmp(prop, "duplicatemode")) {
		sscanf(data, "%d", &pair->duplicate_mode);
	      }
	      if (!strcmp(prop, "reconninterval")) {
		sscanf(data, "%d", &pair->reconninterval);
	      }
	      if (!strcmp(prop, "object_types")) {
		sscanf(data, "0x%x", &pair->object_types);
	      }
	      if (!strcmp(prop, "playsyncsound")) {
		if (!strcmp(data, "yes"))
		  pair->playsyncsound = TRUE;
		else
		  pair->playsyncsound = FALSE;
	      }
	      if (!strcmp(prop, "playwelcomesound")) {
		if (!strcmp(data, "yes"))
		  pair->playwelcomesound = TRUE;
		else
		  pair->playwelcomesound = FALSE;
	      }
	      if (!strcmp(prop, "syncsound")) {
		pair->syncsound = g_strdup(data);
	      }
	      if (!strcmp(prop, "welcomesound")) {
		pair->welcomesound = g_strdup(data);
	      }
	      if (!strcmp(prop, "displayname")) {
		pair->displayname = g_strdup(data);
	      }
	      if (!strcmp(prop, "filter")) {
		int type, dir, rule;
		char field[256], content[256];
		if (sscanf(data, "%d,%d,%d,\"%255[^\"]\",\"%255[^\"]\"", 
			   &type, &dir, &rule, field, content) >= 5) {
		  sync_filter *filter = g_malloc0(sizeof(sync_filter));
		  filter->type = type;
		  filter->dir = dir;
		  filter->rule = rule;
		  if (!(strlen(field) == 1 && field[0] == '*'))
		    filter->field = g_strdup(field);
		  if (!(strlen(content) == 1 && content[0] == '*'))
		    filter->data = g_strdup(content);
		  pair->filters = g_list_append(pair->filters, filter);
		}
	      }
	    }
	  }
	  dd(printf("Found pair: %s - %s\n", pair->localname, pair->remotename));
	  syncpairlist = g_list_append(syncpairlist, pair);
	  fclose(f);
	}
	g_free(filename);
      }
      g_free(dirname);
    }
    closedir(dir);
  }
}

char* sync_objtype_as_string(sync_object_type objtype) {
  return(objtype==SYNC_OBJECT_TYPE_CALENDAR?"Event":
	 (objtype==SYNC_OBJECT_TYPE_TODO?"ToDo":
	  (objtype==SYNC_OBJECT_TYPE_PHONEBOOK?"Contact":"Unknown")));
}

void sig_child(void)
{
	waitpid(-1, NULL, WNOHANG);
}

int main (int argc, char **argv)
{
  char *dir;
  int n;
  int nogui = 0;
  char *optdatadir = NULL;
  GConfClient *gconf;
  struct poptOption popts[] = { { "nogui", 'q', POPT_ARG_NONE, &nogui, 1,
				  "Do not show the GUI. Useful for starting "
				  "MultiSync in a script.", NULL },
				{ "datadir", 'd', POPT_ARG_STRING, &optdatadir,
				  1,
				  "Use a non-default data direcory.",
				  NULL }, NULL };
  struct sigaction sg_action;

#ifdef ENABLE_NLS
  bindtextdomain (PACKAGE, PACKAGE_LOCALE_DIR);
  textdomain (PACKAGE);
#endif

  sg_action.sa_handler = (void *)sig_child;
  sg_action.sa_flags = SA_NOCLDSTOP;

  sigaction(SIGCHLD, &sg_action, NULL);

  gnome_program_init (PACKAGE, VERSION, LIBGNOMEUI_MODULE,
                      argc, argv,
		      GNOME_PARAM_POPT_TABLE, popts,
                      GNOME_PARAM_APP_DATADIR, PACKAGE_DATA_DIR,
                      GNOME_PARAM_NONE);
  bonobo_init(&argc, argv);

  if (optdatadir) {
    if (*optdatadir == '/') {
    	datadir = g_strdup(optdatadir);
    } else {
  	datadir = g_strdup_printf ("%s/%s", g_get_home_dir (), optdatadir);
    }
  } else {
  	datadir = g_strdup_printf ("%s/%s", g_get_home_dir (), DATADIR);
  }
  
  gtk_set_locale ();
  gtk_init (&argc, &argv);

  gconf = gconf_client_get_default ();
  gconf_client_add_dir (gconf, SYNC_GCONF_PATH, GCONF_CLIENT_PRELOAD_NONE,
			NULL);
  if (!nogui)
    nogui = gconf_client_get_bool (gconf, SYNC_GCONF_PATH"/no_gui", NULL);
  
  // Initialize sound
  gnome_sound_init("localhost");

  if (getenv ("MULTISYNC_DEBUG"))
      	multisync_debug = TRUE;

  if (optdatadir || sync_open_process_socket()) {
    async_set_multisync_status("Multisync running.");
    read_pluginlist();
    read_syncpairs();
    
    for (n = 0; n < g_list_length(syncpairlist); n++) {
      sync_pair *pair = g_list_nth_data(syncpairlist, n);
      pthread_create(&pair->syncthread, NULL, sync_main, pair);
    }
    init_tray();
    if (!nogui) {
      gtk_idle_add(open_mainwindow, NULL);
    }

    bonobo_main ();
  }
  g_free(datadir);
  return 0;
}

char* sync_get_datapath(sync_pair *pair) {
  return(pair->datapath);
}
