Here's a patch to add the GPE synchronisation plugin to Multisync.

Thanks

p.

diff -uprN x/configure.in plugins/gpe_plugin/configure.in
--- x/configure.in	1970-01-01 01:00:00.000000000 +0100
+++ plugins/gpe_plugin/configure.in	2005-02-16 21:41:32.000000000 +0000
@@ -0,0 +1,54 @@
+dnl Process this file with autoconf to produce a configure script.
+
+AC_INIT(gpe_plugin, 0.1, [EMAIL PROTECTED])
+AM_INIT_AUTOMAKE([foreign dist-bzip2])
+AM_CONFIG_HEADER(config.h)
+
+AC_ISC_POSIX
+AC_PROG_CC
+AM_PROG_CC_STDC
+AC_HEADER_STDC
+
+pkg_modules="libgnomeui-2.0 gtk+-2.0 glib-2.0 libgpevtype libglade-2.0"
+PKG_CHECK_MODULES(PACKAGE, [$pkg_modules])
+AC_SUBST(PACKAGE_CFLAGS)
+AC_SUBST(PACKAGE_LIBS)
+
+dnl Add extra flags if in debug mode
+AC_ARG_ENABLE(debug,
+  [--enable-debug
+   Enable debugging - useful only for developers and testers],
+  DBG_FLAGS="yes",
+  DBG_FLAGS="no",
+)
+AM_CONDITIONAL(GPE_DBG_FLAGS, test "$DBG_FLAGS" = "yes")
+
+CPPFLAGS="${GTK_CFLAGS} ${GNOME_INCLUDEDIR} ${ORBIT_CFLAGS} ${BONOBO_CFLAGS}"
+
+dnl GETTEXT_PACKAGE=gpe_sync
+dnl AC_SUBST(GETTEXT_PACKAGE)
+dnl AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE,"$GETTEXT_PACKAGE")
+
+dnl Add the languages which your application supports here.
+dnl ALL_LINGUAS=""
+dnl AM_GLIB_GNU_GETTEXT
+
+AC_PROG_LIBTOOL
+
+dnl  gpe_sync required libraries
+AC_CHECK_LIB(nsqlc,nsqlc_exec,,AC_MSG_ERROR([You must have libnsqlc installed.]))
+
+dnl Info for the RPM
+MULTISYNC_TOP="../.."
+AC_SUBST(VERSION)
+AC_SUBST(prefix)
+MULTISYNC_VERSION=`grep "#define VERSION" ${MULTISYNC_TOP}/config.h | sed -e 's/#define VERSION //g' | sed -e 's/\"//g'`
+AC_SUBST(MULTISYNC_VERSION)
+
+AC_OUTPUT([
+Makefile
+src/Makefile
+po/Makefile.in
+../../specs/multisync-gpe.spec
+])
+
diff -uprN x/gpe_sync.glade plugins/gpe_plugin/gpe_sync.glade
--- x/gpe_sync.glade	1970-01-01 01:00:00.000000000 +0100
+++ plugins/gpe_plugin/gpe_sync.glade	2005-02-15 03:10:13.000000000 +0000
@@ -0,0 +1,209 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd";>
+
+<glade-interface>
+<requires lib="gnome"/>
+
+<widget class="GtkDialog" id="gpe_config">
+  <property name="visible">True</property>
+  <property name="title" translatable="yes">GPE Sync Configuration</property>
+  <property name="type">GTK_WINDOW_TOPLEVEL</property>
+  <property name="window_position">GTK_WIN_POS_NONE</property>
+  <property name="modal">False</property>
+  <property name="resizable">True</property>
+  <property name="destroy_with_parent">False</property>
+  <property name="has_separator">True</property>
+
+  <child internal-child="vbox">
+    <widget class="GtkVBox" id="dialog-vbox1">
+      <property name="visible">True</property>
+      <property name="homogeneous">False</property>
+      <property name="spacing">0</property>
+
+      <child internal-child="action_area">
+	<widget class="GtkHButtonBox" id="dialog-action_area1">
+	  <property name="visible">True</property>
+	  <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+	  <child>
+	    <widget class="GtkButton" id="cancelbutton1">
+	      <property name="visible">True</property>
+	      <property name="can_default">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="label">gtk-cancel</property>
+	      <property name="use_stock">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="response_id">-6</property>
+	    </widget>
+	  </child>
+
+	  <child>
+	    <widget class="GtkButton" id="okbutton1">
+	      <property name="visible">True</property>
+	      <property name="can_default">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="label">gtk-ok</property>
+	      <property name="use_stock">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="response_id">-5</property>
+	    </widget>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">True</property>
+	  <property name="pack_type">GTK_PACK_END</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkLabel" id="label8">
+	  <property name="visible">True</property>
+	  <property name="label" translatable="yes">&lt;b&gt;Connection:&lt;/b&gt;</property>
+	  <property name="use_underline">False</property>
+	  <property name="use_markup">True</property>
+	  <property name="justify">GTK_JUSTIFY_LEFT</property>
+	  <property name="wrap">False</property>
+	  <property name="selectable">False</property>
+	  <property name="xalign">0</property>
+	  <property name="yalign">0.5</property>
+	  <property name="xpad">0</property>
+	  <property name="ypad">0</property>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">False</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkTable" id="table5">
+	  <property name="border_width">8</property>
+	  <property name="visible">True</property>
+	  <property name="n_rows">2</property>
+	  <property name="n_columns">2</property>
+	  <property name="homogeneous">False</property>
+	  <property name="row_spacing">4</property>
+	  <property name="column_spacing">4</property>
+
+	  <child>
+	    <widget class="GtkLabel" id="label9">
+	      <property name="visible">True</property>
+	      <property name="label" translatable="yes">Hostname:</property>
+	      <property name="use_underline">False</property>
+	      <property name="use_markup">False</property>
+	      <property name="justify">GTK_JUSTIFY_LEFT</property>
+	      <property name="wrap">False</property>
+	      <property name="selectable">False</property>
+	      <property name="xalign">0</property>
+	      <property name="yalign">0.5</property>
+	      <property name="xpad">0</property>
+	      <property name="ypad">0</property>
+	    </widget>
+	    <packing>
+	      <property name="left_attach">0</property>
+	      <property name="right_attach">1</property>
+	      <property name="top_attach">0</property>
+	      <property name="bottom_attach">1</property>
+	      <property name="x_options">fill</property>
+	      <property name="y_options"></property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <widget class="GtkLabel" id="label10">
+	      <property name="visible">True</property>
+	      <property name="label" translatable="yes">Log in as:</property>
+	      <property name="use_underline">False</property>
+	      <property name="use_markup">False</property>
+	      <property name="justify">GTK_JUSTIFY_LEFT</property>
+	      <property name="wrap">False</property>
+	      <property name="selectable">False</property>
+	      <property name="xalign">0</property>
+	      <property name="yalign">0.5</property>
+	      <property name="xpad">0</property>
+	      <property name="ypad">0</property>
+	    </widget>
+	    <packing>
+	      <property name="left_attach">0</property>
+	      <property name="right_attach">1</property>
+	      <property name="top_attach">1</property>
+	      <property name="bottom_attach">2</property>
+	      <property name="x_options">fill</property>
+	      <property name="y_options"></property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <widget class="GtkCombo" id="combo1">
+	      <property name="visible">True</property>
+	      <property name="value_in_list">False</property>
+	      <property name="allow_empty">True</property>
+	      <property name="case_sensitive">False</property>
+	      <property name="enable_arrow_keys">True</property>
+	      <property name="enable_arrows_always">False</property>
+
+	      <child internal-child="entry">
+		<widget class="GtkEntry" id="combo-entry1">
+		  <property name="visible">True</property>
+		  <property name="can_focus">True</property>
+		  <property name="editable">True</property>
+		  <property name="visibility">True</property>
+		  <property name="max_length">0</property>
+		  <property name="text" translatable="yes"></property>
+		  <property name="has_frame">True</property>
+		  <property name="invisible_char" translatable="yes">*</property>
+		  <property name="activates_default">False</property>
+		</widget>
+	      </child>
+
+	      <child internal-child="list">
+		<widget class="GtkList" id="combo-list1">
+		  <property name="visible">True</property>
+		  <property name="selection_mode">GTK_SELECTION_BROWSE</property>
+		</widget>
+	      </child>
+	    </widget>
+	    <packing>
+	      <property name="left_attach">1</property>
+	      <property name="right_attach">2</property>
+	      <property name="top_attach">0</property>
+	      <property name="bottom_attach">1</property>
+	      <property name="y_options"></property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <widget class="GtkEntry" id="entry1">
+	      <property name="visible">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="editable">True</property>
+	      <property name="visibility">True</property>
+	      <property name="max_length">0</property>
+	      <property name="text" translatable="yes"></property>
+	      <property name="has_frame">True</property>
+	      <property name="invisible_char" translatable="yes">*</property>
+	      <property name="activates_default">False</property>
+	    </widget>
+	    <packing>
+	      <property name="left_attach">1</property>
+	      <property name="right_attach">2</property>
+	      <property name="top_attach">1</property>
+	      <property name="bottom_attach">2</property>
+	      <property name="y_options"></property>
+	    </packing>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">8</property>
+	  <property name="expand">True</property>
+	  <property name="fill">True</property>
+	</packing>
+      </child>
+    </widget>
+  </child>
+</widget>
+
+</glade-interface>
diff -uprN x/gpe_sync.gladep plugins/gpe_plugin/gpe_sync.gladep
--- x/gpe_sync.gladep	1970-01-01 01:00:00.000000000 +0100
+++ plugins/gpe_plugin/gpe_sync.gladep	2005-02-15 03:10:13.000000000 +0000
@@ -0,0 +1,7 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-project SYSTEM "http://glade.gnome.org/glade-project-2.0.dtd";>
+
+<glade-project>
+  <name></name>
+  <program_name></program_name>
+</glade-project>
diff -uprN x/gpe_sync.h plugins/gpe_plugin/gpe_sync.h
--- x/gpe_sync.h	1970-01-01 01:00:00.000000000 +0100
+++ plugins/gpe_plugin/gpe_sync.h	2005-02-16 21:20:36.000000000 +0000
@@ -0,0 +1,61 @@
+/* 
+ * MultiSync GPE Plugin
+ * Copyright (C) 2004 Phil Blundell <[EMAIL PROTECTED]>
+ *
+ * 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;
+ */
+
+#include "multisync.h"
+
+#include <gpe/nsqlc.h>
+#include <glib.h>
+
+typedef struct 
+{
+  client_connection commondata;
+  sync_pair *sync_pair;
+  char* device_addr;
+  char* username;
+
+  pthread_t thread;
+  sync_object_type newdbs;		/* XXX */
+} gpe_conn;
+
+struct db
+{
+  const gchar *name;
+  int type;
+
+  GList *(*get_changes)(struct db *db, int newdb);
+  gboolean (*push_object)(struct db *db, const char *obj, const char *uid,
+			  char *returnuid, int *returnuidlen, GError **err);
+  gboolean (*delete_object)(struct db *db, const char *uid, gboolean soft);
+  
+  nsqlc *db;
+  time_t last_timestamp;
+  time_t current_timestamp;
+  gboolean changed;
+};
+
+#define GPE_DEBUG(conn, x) fprintf (stderr, "%s\n", (x))
+
+extern GSList *db_list;
+
+extern void gpe_disconnect (struct db *db);
+
+extern gboolean gpe_load_config (gpe_conn *conn);
+extern gboolean gpe_save_config (gpe_conn *conn);
+
+extern GSList *fetch_uid_list (nsqlc *db, const gchar *query, ...);
+extern GSList *fetch_tag_data (nsqlc *db, const gchar *query_str, guint id);
+
+extern gboolean store_tag_data (nsqlc *db, const gchar *table, guint id, GSList *tags, gboolean delete);
+
+extern void calendar_init (void);
+extern void todo_init (void);
+extern void contacts_init (void);
+
+extern void *gpe_do_get_changes (void *conn);
+extern void *gpe_do_connect (void *conn);
diff -uprN x/Makefile.am plugins/gpe_plugin/Makefile.am
--- x/Makefile.am	1970-01-01 01:00:00.000000000 +0100
+++ plugins/gpe_plugin/Makefile.am	2005-02-15 05:06:10.000000000 +0000
@@ -0,0 +1,30 @@
+## Process this file with automake to produce Makefile.in
+
+SUBDIRS = src
+
+EXTRA_DIST = \
+	autogen.sh \
+	gpe_sync.glade \
+	gpe_sync.gladep
+
+install-data-local:
+	@$(NORMAL_INSTALL)
+	if test -d $(srcdir)/pixmaps; then \
+	  $(mkinstalldirs) $(DESTDIR)$(datadir)/pixmaps/$(PACKAGE); \
+	  for pixmap in $(srcdir)/pixmaps/*; do \
+	    if test -f $$pixmap; then \
+	      $(INSTALL_DATA) $$pixmap $(DESTDIR)$(datadir)/pixmaps/$(PACKAGE); \
+	    fi \
+	  done \
+	fi
+
+dist-hook:
+	if test -d pixmaps; then \
+	  mkdir $(distdir)/pixmaps; \
+	  for pixmap in pixmaps/*; do \
+	    if test -f $$pixmap; then \
+	      cp -p $$pixmap $(distdir)/pixmaps; \
+	    fi \
+	  done \
+	fi
+
diff -uprN x/po/Makefile.in.in plugins/gpe_plugin/po/Makefile.in.in
--- x/po/Makefile.in.in	1970-01-01 01:00:00.000000000 +0100
+++ plugins/gpe_plugin/po/Makefile.in.in	2005-02-16 21:16:25.000000000 +0000
@@ -0,0 +1,196 @@
+# Makefile for program source directory in GNU NLS utilities package.
+# Copyright (C) 1995-1997, 2000, 2001 by Ulrich Drepper <[EMAIL PROTECTED]>
+#
+# This file file be copied and used freely without restrictions.  It can
+# be used in projects which are not available under the GNU Public License
+# but which still want to provide support for the GNU gettext functionality.
+# Please note that the actual code is *not* freely available.
+
+PACKAGE = @PACKAGE@
+VERSION = @VERSION@
+
+# These two variables depend on the location of this directory.
+subdir = po
+top_builddir = ..
+
+SHELL = /bin/sh
[EMAIL PROTECTED]@
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = @srcdir@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+datadir = @datadir@
+localedir = $(datadir)/locale
+gettextsrcdir = $(datadir)/gettext/po
+
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+MKINSTALLDIRS = @MKINSTALLDIRS@
+mkinstalldirs = $(SHELL) `case "$(MKINSTALLDIRS)" in /*) echo "$(MKINSTALLDIRS)" ;; *) echo "$(top_builddir)/$(MKINSTALLDIRS)" ;; esac`
+
+CC = @CC@
+GMSGFMT = @GMSGFMT@
+MSGFMT = @MSGFMT@
+XGETTEXT = @XGETTEXT@
+MSGMERGE = msgmerge
+
+DEFS = @DEFS@
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+
+INCLUDES = -I.. -I$(top_srcdir)/intl
+
+COMPILE = $(CC) -c $(DEFS) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) $(XCFLAGS)
+
+POFILES = @POFILES@
+GMOFILES = @GMOFILES@
+DISTFILES = ChangeLog Makefile.in.in POTFILES.in $(PACKAGE).pot \
+$(POFILES) $(GMOFILES)
+
+POTFILES = \
+
+CATALOGS = @CATALOGS@
+
+.SUFFIXES:
+.SUFFIXES: .c .o .po .pox .gmo .mo
+
+.c.o:
+	$(COMPILE) $<
+
+.po.pox:
+	$(MAKE) $(PACKAGE).pot
+	$(MSGMERGE) $< $(srcdir)/$(PACKAGE).pot -o $*.pox
+
+.po.mo:
+	$(MSGFMT) -o $@ $<
+
+.po.gmo:
+	file=$(srcdir)/`echo $* | sed 's,.*/,,'`.gmo \
+	  && rm -f $$file && $(GMSGFMT) --statistics -o $$file $<
+
+
+all: [EMAIL PROTECTED]@
+
+all-yes: $(CATALOGS)
+all-no:
+
+# Note: Target 'all' must not depend on target '$(srcdir)/$(PACKAGE).pot',
+# otherwise packages like GCC can not be built if only parts of the source
+# have been downloaded.
+
+$(srcdir)/$(PACKAGE).pot: $(POTFILES) $(srcdir)/POTFILES.in
+	$(XGETTEXT) --default-domain=$(PACKAGE) --directory=$(top_srcdir) \
+	  --add-comments --keyword=_ --keyword=N_ \
+	  --files-from=$(srcdir)/POTFILES.in \
+	&& test ! -f $(PACKAGE).po \
+	   || ( rm -f $(srcdir)/$(PACKAGE).pot \
+		&& mv $(PACKAGE).po $(srcdir)/$(PACKAGE).pot )
+
+
+install: install-exec install-data
+install-exec:
+install-data: [EMAIL PROTECTED]@
+	if test "$(PACKAGE)" = "gettext"; then \
+	  $(mkinstalldirs) $(DESTDIR)$(gettextsrcdir); \
+	  $(INSTALL_DATA) $(srcdir)/Makefile.in.in \
+			  $(DESTDIR)$(gettextsrcdir)/Makefile.in.in; \
+	else \
+	  : ; \
+	fi
+install-data-no: all
+install-data-yes: all
+	$(mkinstalldirs) $(DESTDIR)$(datadir)
+	@catalogs='$(CATALOGS)'; \
+	for cat in $$catalogs; do \
+	  cat=`basename $$cat`; \
+	  lang=`echo $$cat | sed 's/\.gmo$$//'`; \
+	  dir=$(localedir)/$$lang/LC_MESSAGES; \
+	  $(mkinstalldirs) $(DESTDIR)$$dir; \
+	  if test -r $$cat; then \
+	    $(INSTALL_DATA) $$cat $(DESTDIR)$$dir/$(PACKAGE).mo; \
+	    echo "installing $$cat as $(DESTDIR)$$dir/$(PACKAGE).mo"; \
+	  else \
+	    $(INSTALL_DATA) $(srcdir)/$$cat $(DESTDIR)$$dir/$(PACKAGE).mo; \
+	    echo "installing $(srcdir)/$$cat as" \
+		 "$(DESTDIR)$$dir/$(PACKAGE).mo"; \
+	  fi; \
+	done
+
+# Define this as empty until I found a useful application.
+installcheck:
+
+uninstall:
+	catalogs='$(CATALOGS)'; \
+	for cat in $$catalogs; do \
+	  cat=`basename $$cat`; \
+	  lang=`echo $$cat | sed 's/\.gmo$$//'`; \
+	  rm -f $(DESTDIR)$(localedir)/$$lang/LC_MESSAGES/$(PACKAGE).mo; \
+	done
+	if test "$(PACKAGE)" = "gettext"; then \
+	  rm -f $(DESTDIR)$(gettextsrcdir)/Makefile.in.in; \
+	else \
+	  : ; \
+	fi
+
+check: all
+
+dvi info tags TAGS ID:
+
+mostlyclean:
+	rm -f core core.* *.pox $(PACKAGE).po *.new.po
+	rm -fr *.o
+
+clean: mostlyclean
+
+distclean: clean
+	rm -f Makefile Makefile.in POTFILES *.mo
+
+maintainer-clean: distclean
+	@echo "This command is intended for maintainers to use;"
+	@echo "it deletes files that may require special tools to rebuild."
+	rm -f $(GMOFILES)
+
+distdir = $(top_builddir)/$(PACKAGE)-$(VERSION)/$(subdir)
+dist distdir:
+	$(MAKE) update-po
+	@$(MAKE) dist2
+# This is a separate target because 'update-po' must be executed before.
+dist2: $(DISTFILES)
+	dists="$(DISTFILES)"; \
+	for file in $$dists; do \
+	  if test -f $$file; then dir=.; else dir=$(srcdir); fi; \
+	  cp -p $$dir/$$file $(distdir); \
+	done
+
+update-po: Makefile
+	$(MAKE) $(PACKAGE).pot
+	if test "$(PACKAGE)" = "gettext"; then PATH=`pwd`/../src:$$PATH; fi; \
+	cd $(srcdir); \
+	catalogs='$(GMOFILES)'; \
+	for cat in $$catalogs; do \
+	  cat=`basename $$cat`; \
+	  lang=`echo $$cat | sed 's/\.gmo$$//'`; \
+	  echo "$$lang:"; \
+	  if $(MSGMERGE) $$lang.po $(PACKAGE).pot -o $$lang.new.po; then \
+	    mv -f $$lang.new.po $$lang.po; \
+	  else \
+	    echo "msgmerge for $$cat failed!"; \
+	    rm -f $$lang.new.po; \
+	  fi; \
+	done
+	$(MAKE) update-gmo
+
+update-gmo: Makefile $(GMOFILES)
+	@:
+
+Makefile: Makefile.in.in $(top_builddir)/config.status POTFILES.in
+	cd $(top_builddir) \
+	  && CONFIG_FILES=$(subdir)/[EMAIL PROTECTED] CONFIG_HEADERS= \
+	       $(SHELL) ./config.status
+
+# Tell versions [3.59,3.63) of GNU make not to export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff -uprN x/src/calendar.c plugins/gpe_plugin/src/calendar.c
--- x/src/calendar.c	1970-01-01 01:00:00.000000000 +0100
+++ plugins/gpe_plugin/src/calendar.c	2005-02-15 03:10:13.000000000 +0000
@@ -0,0 +1,139 @@
+/* 
+ * MultiSync GPE Plugin
+ * Copyright (C) 2004 Phil Blundell <[EMAIL PROTECTED]>
+ *
+ * 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;
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+#include <glib.h>
+#include <libintl.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "gpe_sync.h"
+
+#include <gpe/vevent.h>
+#include <gpe/tag-db.h>
+
+#include <mimedir/mimedir-vcal.h>
+
+GList *
+calendar_get_changes (struct db *db, int newdb)
+{
+  GList *data = NULL;
+  GSList *list, *i;
+  
+  if (newdb)
+    list = fetch_uid_list (db->db, "select distinct uid from calendar_urn");
+  else
+    list = fetch_uid_list (db->db, "select uid from calendar where (tag='modified' or tag='MODIFIED') and value>%d",
+			   db->last_timestamp);
+
+  for (i = list; i; i = i->next)
+    {
+      GSList *tags;
+      MIMEDirVCal *vcal;
+      MIMEDirVEvent *vevent;
+      gchar *string;
+      changed_object *obj;
+      int urn = (int)i->data;
+      
+      tags = fetch_tag_data (db->db, "select tag,value from calendar where uid=%d", urn);
+      vevent = vevent_from_tags (tags);
+      gpe_tag_list_free (tags);
+      vcal = mimedir_vcal_new ();
+      mimedir_vcal_add_component (vcal, MIMEDIR_VCOMPONENT (vevent));
+      string = mimedir_vcal_write_to_string (vcal);
+      g_object_unref (vcal);
+
+      obj = g_malloc0 (sizeof (*obj));
+      obj->comp = string;
+      obj->uid = g_strdup_printf ("%d", urn);
+      obj->object_type = SYNC_OBJECT_TYPE_CALENDAR;
+      obj->change_type = SYNC_OBJ_MODIFIED; 
+
+      data = g_list_append (data, obj);
+    }
+
+  g_slist_free (list);
+
+  return data;
+}
+
+gboolean
+calendar_push_object (struct db *db, const char *obj, const char *uid, 
+		      char *returnuid, int *returnuidlen, GError **err)
+{
+  GSList *list, *tags;
+  MIMEDirVEvent *vevent;
+  MIMEDirVCal *vcal;
+  int id;
+
+  vcal = mimedir_vcal_new_from_string (obj, err);
+  if (vcal == NULL)
+    return FALSE;
+
+  list = mimedir_vcal_get_event_list (vcal);
+  if (list == NULL)
+    {
+      g_object_unref (vcal);
+      return FALSE;
+    }
+  
+  vevent = MIMEDIR_VEVENT (list->data);
+
+  tags = vevent_to_tags (vevent);
+
+  if (uid)
+    id = atoi (uid);
+  else
+    {
+      char *errmsg;
+
+      if (nsqlc_exec (db->db, "insert into calendar_urn values (NULL)",
+		      NULL, NULL, &errmsg))
+	return FALSE;
+      
+      id = nsqlc_last_insert_rowid (db->db);
+    }
+   
+  mimedir_vcal_free_component_list (list);
+   
+  g_object_unref (vcal);
+
+  store_tag_data (db->db, "calendar", id, tags, TRUE);
+
+  sprintf (returnuid, "%d", id);
+  *returnuidlen = strlen (returnuid);
+
+  return TRUE;
+}
+
+gboolean
+calendar_delete_object (struct db *db, const char *uid, gboolean soft)
+{
+  nsqlc_exec_printf (db->db, "delete from calendar where uid='%q'", NULL, NULL, NULL, uid);
+  nsqlc_exec_printf (db->db, "delete from calendar_urn where uid='%q'", NULL, NULL, NULL, uid);
+
+  return TRUE;
+}
+
+struct db calendar_db = 
+{
+  .type = SYNC_OBJECT_TYPE_CALENDAR,
+  .name = "calendar",
+
+  .get_changes = calendar_get_changes,
+  .push_object = calendar_push_object,
+  .delete_object = calendar_delete_object,
+};
+
+void
+calendar_init (void)
+{
+  db_list = g_slist_append (db_list, &calendar_db);
+}
diff -uprN x/src/config.c plugins/gpe_plugin/src/config.c
--- x/src/config.c	1970-01-01 01:00:00.000000000 +0100
+++ plugins/gpe_plugin/src/config.c	2005-02-15 03:10:13.000000000 +0000
@@ -0,0 +1,92 @@
+/* 
+ * MultiSync GPE Plugin
+ * Copyright (C) 2004 Phil Blundell <[EMAIL PROTECTED]>
+ *
+ * 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;
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+#include <glib.h>
+#include <libintl.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "multisync.h"
+
+#include "gpe_sync.h"
+
+gchar *
+gpe_config_path (gpe_conn *conn)
+{
+  gchar *filename;
+
+  filename = g_strdup_printf ("%s/%s", 
+			      sync_get_datapath (conn->sync_pair),
+			      "gpe_config.dat");
+
+  return filename;
+}
+
+gboolean
+gpe_load_config (gpe_conn *conn)
+{
+  gchar *path;
+  FILE *fp;
+
+  path = gpe_config_path (conn);
+
+  fp = fopen (path, "r");
+  if (fp)
+    {
+      char buf[256];
+      
+      if (fgets (buf, sizeof (buf), fp))
+	{
+	  buf [strlen (buf) - 1] = 0;
+	  conn->device_addr = g_strdup (buf);
+	}
+
+      if (fgets (buf, sizeof (buf), fp))
+	{
+	  buf [strlen (buf) - 1] = 0;
+	  conn->username = g_strdup (buf);
+	}
+
+      fclose (fp);
+    }
+  else
+    {
+      conn->username = g_strdup (g_get_user_name ());
+      conn->device_addr = g_strdup ("localhost");
+    }
+
+  g_free (path);
+
+  return TRUE;
+}
+
+gboolean
+gpe_save_config (gpe_conn *conn)
+{
+  gchar *path;
+  FILE *fp;
+
+  path = gpe_config_path (conn);
+
+  fprintf (stderr, "Saving config to %s\n", path);
+
+  fp = fopen (path, "w");
+  if (fp)
+    {
+      fprintf (fp, "%s\n", conn->device_addr);
+      fprintf (fp, "%s\n", conn->username);
+      fclose (fp);
+    }
+
+  g_free (path);
+
+  return TRUE;
+}
diff -uprN x/src/connection.c plugins/gpe_plugin/src/connection.c
--- x/src/connection.c	1970-01-01 01:00:00.000000000 +0100
+++ plugins/gpe_plugin/src/connection.c	2005-02-15 03:10:13.000000000 +0000
@@ -0,0 +1,237 @@
+/* 
+ * MultiSync GPE Plugin
+ * Copyright (C) 2004 Phil Blundell <[EMAIL PROTECTED]>
+ *
+ * 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;
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+#include <glib.h>
+#include <libintl.h>
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include "gpe_sync.h"
+
+#include <gpe/tag-db.h>
+
+#define _(x)  gettext(x)
+
+nsqlc *
+gpe_connect_one (gpe_conn *conn, const gchar *db, char **err)
+{
+  gchar *path;
+  nsqlc *r;
+
+  path = g_strdup_printf ("[EMAIL PROTECTED]:.gpe/%s", conn->username, conn->device_addr, db);
+
+  fprintf (stderr, "connecting to %s\n", path);
+
+  r = nsqlc_open_ssh (path, O_RDWR, err);
+
+  g_free (path);
+
+  return r;
+}
+
+void *
+gpe_do_connect (void *_conn)
+{
+  GSList *i;
+  char* errmsg = NULL;
+  gboolean failed = FALSE;
+  gpe_conn *conn;
+
+  conn = (gpe_conn *)_conn;
+
+  GPE_DEBUG(conn, "sync_connect");  
+  
+  /* load the connection attributes */
+  if (! gpe_load_config (conn))
+  {
+    /* failure */
+    errmsg = g_strdup (_("Failed to load configuration"));
+    sync_set_requestfailederror (errmsg, conn->sync_pair);
+    pthread_exit (0);
+  }  
+
+  for (i = db_list; i; i = i->next)
+    {
+      struct db *db = i->data;
+  
+      db->db = gpe_connect_one (conn, db->name, &errmsg);
+
+      if (!db->db)
+	{
+	  failed = TRUE;
+	  break;
+	}
+    }
+
+  if (failed)
+    {
+      for (i = db_list; i; i = i->next)
+	{
+	  struct db *db = i->data;
+
+	  gpe_disconnect (db);
+	}
+
+      sync_set_requestfailederror (g_strdup (errmsg), conn->sync_pair);
+      pthread_exit (0);
+    }
+
+  sync_set_requestdone (conn->sync_pair);
+  pthread_exit (0);
+}
+
+void
+gpe_disconnect (struct db *db)
+{
+  if (db->db)
+    nsqlc_close (db->db);
+  db->db = NULL;
+}
+
+void *
+gpe_do_get_changes (void *_conn) 
+{
+  GSList *i;
+  GList *changes = NULL;
+  sync_object_type retnewdbs = 0;
+  change_info *chinfo;
+  gpe_conn *conn;
+  sync_object_type newdbs;
+
+  conn = (gpe_conn *)_conn;
+  newdbs = conn->newdbs;
+
+  GPE_DEBUG(conn, "get_changes"); 
+
+  for (i = db_list; i; i = i->next)
+    {
+      struct db *db = i->data;
+      gchar *filename;
+      FILE *fp;
+
+      db->last_timestamp = 0;
+
+      filename = g_strdup_printf ("%s/%s", 
+				  sync_get_datapath (conn->sync_pair),
+				  db->name);
+
+      fp = fopen (filename, "r");
+      if (fp)
+	{
+	  int i;
+
+	  if (fscanf (fp, "%d", &i))
+	    db->last_timestamp = i;
+
+	  fclose (fp);
+	}
+
+      g_free (filename);
+
+      nsqlc_get_time (db->db, &db->current_timestamp, NULL);
+
+      if (conn->commondata.object_types & db->type)
+	{
+	  GList *local_changes = db->get_changes (db, newdbs & db->type);
+
+	  if (local_changes)
+	    {
+	      db->changed = TRUE;
+	      changes = g_list_concat (changes, local_changes);
+	    }
+	}
+    }
+  
+  /* Allocate the change_info struct */
+  chinfo = g_malloc0 (sizeof (change_info));
+  chinfo->changes = changes;
+  
+  /* Did we detect any reset databases */
+  chinfo->newdbs = retnewdbs;
+  sync_set_requestdata (chinfo, conn->sync_pair);
+
+  pthread_exit (0);
+}
+
+static int
+fetch_callback (void *arg, int argc, char **argv, char **names)
+{
+  if (argc == 2)
+    {
+      GSList **data = (GSList **)arg;
+      gpe_tag_pair *p = g_malloc (sizeof (*p));
+
+      p->tag = g_strdup (argv[0]);
+      p->value = g_strdup (argv[1]);
+
+      *data = g_slist_prepend (*data, p);
+    }
+
+  return 0;
+}
+
+GSList *
+fetch_tag_data (nsqlc *db, const gchar *query_str, guint id)
+{
+  GSList *data = NULL;
+
+  nsqlc_exec_printf (db, query_str, fetch_callback, &data, NULL, id);
+
+  return data;
+}
+
+gboolean
+store_tag_data (nsqlc *db, const gchar *table, guint id, GSList *tags, gboolean delete)
+{
+  if (delete)
+    nsqlc_exec_printf (db, "delete from '%q' where urn=%d", NULL, NULL, NULL, table, id);
+
+  while (tags)
+    {
+      gpe_tag_pair *p = tags->data;
+
+      nsqlc_exec_printf (db, "insert into '%q' values (%d, '%q', '%q')", NULL, NULL, NULL,
+			 table, id, p->tag, p->value);
+
+      tags = tags->next;
+    }
+
+  return TRUE;
+}
+
+static int
+fetch_uid_callback (void *arg, int argc, char **argv, char **names)
+{
+  if (argc == 1)
+    {
+      GSList **data = (GSList **)arg;
+
+      *data = g_slist_prepend (*data, (void *)atoi (argv[0]));
+    }
+
+  return 0;
+}
+
+GSList *
+fetch_uid_list (nsqlc *db, const gchar *query, ...)
+{
+  GSList *data = NULL;
+  va_list ap;
+
+  va_start (ap, query);
+
+  nsqlc_exec_vprintf (db, query, fetch_uid_callback, &data, NULL, ap);
+
+  va_end (ap);
+
+  return data;
+}
diff -uprN x/src/contacts.c plugins/gpe_plugin/src/contacts.c
--- x/src/contacts.c	1970-01-01 01:00:00.000000000 +0100
+++ plugins/gpe_plugin/src/contacts.c	2005-02-15 03:10:13.000000000 +0000
@@ -0,0 +1,120 @@
+/* 
+ * MultiSync GPE Plugin
+ * Copyright (C) 2004 Phil Blundell <[EMAIL PROTECTED]>
+ *
+ * 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;
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+#include <glib.h>
+#include <libintl.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "gpe_sync.h"
+
+#include <gpe/vcard.h>
+#include <gpe/tag-db.h>
+
+GList *
+contacts_get_changes (struct db *db, int newdb)
+{
+  GList *data = NULL;
+  GSList *list, *i;
+  
+  if (newdb)
+    list = fetch_uid_list (db->db, "select distinct urn from contacts_urn");
+  else
+    list = fetch_uid_list (db->db, "select urn from contacts where (tag='modified' or tag='MODIFIED') and value>%d",
+			   db->last_timestamp);
+  
+  for (i = list; i; i = i->next)
+    {
+      GSList *tags;
+      MIMEDirVCard *vcard;
+      gchar *string;
+      changed_object *obj;
+      int urn = (int)i->data;
+      
+      tags = fetch_tag_data (db->db, "select tag,value from contacts where urn=%d", urn);
+      vcard = vcard_from_tags (tags);
+      gpe_tag_list_free (tags);
+      string = mimedir_vcard_write_to_string (vcard);
+      g_object_unref (vcard);
+
+      obj = g_malloc0 (sizeof (*obj));
+      obj->comp = string;
+      obj->uid = g_strdup_printf ("%d", urn);
+      obj->object_type = SYNC_OBJECT_TYPE_PHONEBOOK;
+      obj->change_type = SYNC_OBJ_MODIFIED; 
+
+      data = g_list_append (data, obj);
+    }
+
+  g_slist_free (list);
+
+  return data;
+}
+
+gboolean
+contacts_push_object (struct db *db, const char *obj, const char *uid, 
+		      char *returnuid, int *returnuidlen, GError **err)
+{
+  GSList *tags;
+  MIMEDirVCard *vcard;
+  int id;
+
+  vcard = mimedir_vcard_new_from_string (obj, err);
+  if (vcard == NULL)
+    return FALSE;
+
+  tags = vcard_to_tags (vcard);
+
+  if (uid)
+    id = atoi (uid);
+  else
+    {
+      char *errmsg;
+
+      if (nsqlc_exec (db->db, "insert into contacts_urn values (NULL)",
+		      NULL, NULL, &errmsg))
+	return FALSE;
+      
+      id = nsqlc_last_insert_rowid (db->db);
+    }
+    
+  store_tag_data (db->db, "contacts", id, tags, TRUE);
+
+  sprintf (returnuid, "%d", id);
+  *returnuidlen = strlen (returnuid);
+
+  return TRUE;
+}
+
+gboolean
+contacts_delete_object (struct db *db, const char *uid, gboolean soft)
+{
+  nsqlc_exec_printf (db->db, "delete from contacts where urn='%q'", NULL, NULL, NULL, uid);
+  nsqlc_exec_printf (db->db, "delete from contacts_urn where urn='%q'", NULL, NULL, NULL, uid);
+
+  return TRUE;
+}
+
+struct db contacts_db = 
+{
+  .type = SYNC_OBJECT_TYPE_PHONEBOOK,
+  .name = "contacts",
+
+  .get_changes = contacts_get_changes,
+  .push_object = contacts_push_object,
+  .delete_object = contacts_delete_object,
+};
+
+void
+contacts_init (void)
+{
+  db_list = g_slist_append (db_list, &contacts_db);
+}
diff -uprN x/src/gpe_sync.c plugins/gpe_plugin/src/gpe_sync.c
--- x/src/gpe_sync.c	1970-01-01 01:00:00.000000000 +0100
+++ plugins/gpe_plugin/src/gpe_sync.c	2005-02-15 03:10:13.000000000 +0000
@@ -0,0 +1,365 @@
+/* 
+ * MultiSync GPE Plugin
+ * Copyright (C) 2004 Phil Blundell <[EMAIL PROTECTED]>
+ *
+ * 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;
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+#include <glib.h>
+#include <libintl.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "multisync.h"
+
+#include "gpe_sync.h"
+
+/* this should match MULTISYNC_API_VER in 
+ * multisync.h if everything is up to date */
+#define GPE_MULTISYNC_API_VER (3)
+
+#define _(x)  gettext(x)
+
+GSList *db_list;
+
+/******************************************************************
+   The following functions are called by the syncengine thread, and
+   syncengine expects an asynchronous answer using on of
+
+   // Success
+   sync_set_requestdone(sync_pair*);
+   // General failure (equal to sync_set_requestmsg(SYNC_MSG_REQFAILED,...))
+   sync_set_requestfailed(sync_pair*);
+   // General failure with specific log string
+   sync_set_requestfailederror(char*, sync_pair*);
+   // Success with data
+   sync_set_requestdata(gpointer data, sync_pair*);
+   // General return (with either failure or other request)
+   sync_set_requestmsg(sync_msg_type, sync_pair*);
+   // General return with log string
+   sync_set_requestmsgerror(sync_msg_type, char*, sync_pair*);
+   // General return with data pointere
+   sync_set_requestdatamsg(gpointer data, sync_msg_type, sync_pair*);
+
+   (Yes, there are lots of them for convenience.)
+   These functions do not have to be called from this thread. If
+   your client uses the gtk main loop, use gtk_idle_add() to call your
+   real function and let that function call sync_set_requestsomething()
+   when done.
+******************************************************************/
+
+/* sync_connect()
+
+   This is called once every time the sync engine tries to get changes
+   from the two plugins, or only once if always_connected() returns true.
+   Typically, this is where you should try to connect to the device
+   to be synchronized, as well as load any options and so on.
+   The returned struct must contain a
+
+   client_connection commondata; 
+
+   first for common MultiSync data.
+
+   NOTE: Oddly enough (for historical reasons) this callback MUST
+   return the connection handle from this function call (and NOT by
+   using sync_set_requestdata()). The sync engine still waits for a 
+   sync_set_requestdone() call (or _requestfailed) before continuing.
+*/
+gpe_conn* 
+sync_connect (sync_pair* handle, connection_type type,
+	      sync_object_type object_types) 
+{
+  gpe_conn *conn = NULL;
+  
+  conn = g_malloc0 (sizeof (gpe_conn));
+  g_assert (conn);
+  conn->sync_pair = handle;
+  conn->commondata.object_types = object_types;
+
+  pthread_create (&conn->thread, NULL, gpe_do_connect, conn);
+
+  return conn;
+}
+
+
+/* sync_disconnect()
+
+   Called by the sync engine to free the connection handle and disconnect
+   from the database client.
+*/
+void 
+sync_disconnect (gpe_conn *conn) 
+{
+  GSList *i;
+  sync_pair *sync_pair = conn->sync_pair;
+      
+  GPE_DEBUG(conn, "sync_disconnect");    
+  
+  for (i = db_list; i; i = i->next)
+    {
+      struct db *db = i->data;
+
+      gpe_disconnect (db);
+    }
+
+  /* cleanup memory from the connection */
+  if (conn->device_addr)
+    g_free (conn->device_addr);
+
+  if (conn->username)
+    g_free (conn->username);
+
+  g_free (conn);  
+  
+  sync_set_requestdone (sync_pair);
+}
+
+
+/* get_changes()
+
+   The most important function in the plugin. This function is called
+   periodically by the sync engine to poll for changes in the database to
+   be synchronized. The function should return a pointer to a gmalloc'ed
+   change_info struct (which will be freed by the sync engine after usage).
+   using sync_set_requestdata(change_info*, sync_pair*).
+   
+   For all data types set in the argument "newdbs", ALL entries should
+   be returned. This is used when the other end reports that a database has
+   been reset (by e.g. selecting "Reset all data" in a mobile phone.)
+   Testing for a data type is simply done by 
+   
+   if (newdbs & SYNC_OBJECT_TYPE_SOMETHING) ...
+
+   The "commondata" field of the connection handle contains the field
+   commondata.object_types which specifies which data types should
+   be synchronized. Only return changes from these data types.
+
+   The changes reported by this function should be the remembered
+   and rereported every time until sync_done() (see below) has been 
+   called with a success value. This ensures that no changes get lost
+   if some connection fails.  
+*/
+
+void 
+get_changes (gpe_conn *conn, sync_object_type newdbs) 
+{
+  conn->newdbs = newdbs;
+
+  pthread_create (&conn->thread, NULL, gpe_do_get_changes, conn);
+}
+
+/* syncobj_modify() 
+
+   Modify or add an object in the database. This is called by the sync
+   engine when a change has been reported in the other end.
+
+   Arguments:
+   object     A string containing the actual data of the object. E.g. for
+              an objtype of SYNC_OBJECT_TYPE_CALENDAR, this is a 
+	      vCALENDAR 2.0 string (see RFC 2445).
+   uid        The unique ID of this entry. If it is new (i.e. the sync engine
+              has not seen it before), this is NULL.
+   objtype    The data type of this object.
+   returnuid  If uid is NULL, then the ID of the newly created object should
+              be returned in this buffer (if non-NULL). The length of the
+	      ID should be returned in returnuidlen.
+*/
+
+void 
+syncobj_modify (gpe_conn *conn, char* object, char *uid,
+		sync_object_type objtype, char *returnuid, int *returnuidlen) 
+{
+  GError *err = NULL;
+  GSList *i;
+
+  GPE_DEBUG (conn, "syncobj_modify");  
+  
+  for (i = db_list; i; i = i->next)
+    {
+      struct db *db = i->data;
+
+      if (objtype & db->type)
+	db->push_object (db, object, uid, returnuid, returnuidlen, &err);
+    }
+  
+  sync_set_requestdone (conn->sync_pair);
+}
+
+
+/* syncobj_delete() 
+
+   Delete an object from the database. If the argument softdelete is 
+   true, then this object is deleted by the sync engine for storage reasons.
+*/
+void 
+syncobj_delete (gpe_conn *conn, char *uid, 
+		sync_object_type objtype, int softdelete) 
+{
+  gboolean soft = softdelete ? TRUE : FALSE;
+  GSList *i;
+
+  GPE_DEBUG (conn, "syncobj_delete");
+   
+  if (!uid)
+  {
+    GPE_DEBUG (conn, "item to delete not specified by syncengine"); 
+    sync_set_requestfailed (conn->sync_pair); 
+    return;
+  }
+  
+  for (i = db_list; i; i = i->next)
+    {
+      struct db *db = i->data;
+
+      if (objtype & db->type)
+	db->delete_object (db, uid, soft);
+    }
+  
+  sync_set_requestdone (conn->sync_pair);
+}
+
+
+/* syncobj_get_recurring()
+
+   This is a very optional function which may very well be removed in 
+   the future. It should return a list of all recurrence instance of
+   an object (such as all instances of a recurring calendar event).
+   
+   The recurring events should be returned as a GList of changed_objects
+   with change type SYNC_OBJ_RECUR.
+*/
+
+void 
+syncobj_get_recurring (gpe_conn *conn, changed_object *obj) 
+{
+  GPE_DEBUG(conn, "syncobj_get_recurring");      
+  
+  /* 
+   * not implemented
+   */
+  
+  sync_set_requestdata (NULL, conn->sync_pair);
+}
+
+
+/* sync_done()
+
+   This function is called by the sync engine after a synchronization has
+   been completed. If success is true, the sync was successful, and 
+   all changes reported by get_changes can be forgot. If your database
+   is based on a change counter, this can be done by simply saving the new
+   change counter.
+*/
+void 
+sync_done (gpe_conn *conn, gboolean success) 
+{
+  GSList *i;
+
+  for (i = db_list; i; i = i->next)
+    {
+      struct db *db = i->data;
+
+      if (db->changed)
+	{
+	  gchar *filename;
+	  FILE *fp;
+
+	  filename = g_strdup_printf ("%s/%s", 
+				      sync_get_datapath (conn->sync_pair),
+				      db->name);
+
+	  fp = fopen (filename, "w");
+	  if (fp)
+	    {
+	      fprintf (fp, "%d\n", (int)db->current_timestamp);
+	      fclose (fp);
+	    }
+	  
+	  g_free (filename);
+	}
+    }
+
+  sync_set_requestdone (conn->sync_pair);
+}
+
+
+/***********************************************************************
+ The following functions are synchronous, i.e. the syncengine
+ expects an immedieate answer without using sync_set_requestsomething()
+************************************************************************/
+
+/* always_connected()
+   Return TRUE if this client does not have to be polled (i.e. can be 
+   constantly connected).  */
+gboolean 
+always_connected (void) 
+{
+  return FALSE;
+}
+
+
+/* short_name()
+   Return a short plugin name for internal use.  */
+char *
+short_name (void)
+{
+  return "gpe";
+}
+
+
+/* long_name()
+   Return a long name which can be shown to the user.  */
+char *
+long_name (void)
+{
+  return _("GPE");
+}
+
+
+/* plugin_info()
+   Return an even longer description of what this plugin does. This will
+   be shown next to the drop-down menu in the sync pair options.  */
+char * 
+plugin_info (void) 
+{
+  return _("This plugin allows you to synchronize with the GPE Palmtop Environment");
+}
+
+/* plugin_init()
+   Initialize the plugin. Called once upon loading of the plugin (NOT
+   once per sync pair).  */
+void 
+plugin_init (void) 
+{
+  calendar_init ();
+  todo_init ();
+  contacts_init ();
+}
+
+
+/* object_types()
+
+   Return the data types this plugin can handle.  */
+sync_object_type 
+object_types (void)
+{
+  return SYNC_OBJECT_TYPE_CALENDAR | SYNC_OBJECT_TYPE_TODO | 
+	 SYNC_OBJECT_TYPE_PHONEBOOK;
+}
+
+
+/* plugin_API_version()
+   
+   Return the MultiSync API version for which the plugin was compiled.
+   It is defined in multisync.h as MULTISYNC_API_VER.
+   Do not use return(MULTISYNC_API_VER), though, as the plugin will then
+   get valid after a simple recompilation. This may not be all that is needed.  */
+int 
+plugin_API_version (void)
+{
+  return GPE_MULTISYNC_API_VER;
+}
diff -uprN x/src/gui.c plugins/gpe_plugin/src/gui.c
--- x/src/gui.c	1970-01-01 01:00:00.000000000 +0100
+++ plugins/gpe_plugin/src/gui.c	2005-02-16 21:44:39.000000000 +0000
@@ -0,0 +1,104 @@
+/* 
+ * MultiSync GPE Plugin
+ * Copyright (C) 2004 Phil Blundell <[EMAIL PROTECTED]>
+ *
+ * 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;
+ */
+
+#include "config.h"
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+#include "gpe_sync.h"
+
+void
+cancel_clicked (GtkWidget *w, GtkWidget *data)
+{
+  gtk_widget_destroy (data);
+}
+
+void
+ok_clicked (GtkWidget *w, GtkWidget *data)
+{
+  gpe_conn *conn;
+
+  conn = g_object_get_data (G_OBJECT (data), "conn");
+
+  g_free (conn->username);
+  g_free (conn->device_addr);
+
+  w = g_object_get_data (G_OBJECT (data), "username");
+  conn->username = gtk_editable_get_chars (GTK_EDITABLE (w), 0, -1);
+
+  w = g_object_get_data (G_OBJECT (data), "addr");
+  conn->device_addr = gtk_editable_get_chars (GTK_EDITABLE (GTK_COMBO (w)->entry), 0, -1);
+
+  gpe_save_config (conn);
+
+  gtk_widget_destroy (data);
+}
+
+void
+delete_window (GtkWidget *w)
+{
+  gpe_conn *conn;
+
+  conn = g_object_get_data (G_OBJECT (w), "conn");
+  g_free (conn->username);
+  g_free (conn->device_addr);
+  g_free (conn);
+
+  sync_plugin_window_closed ();
+}
+
+GtkWidget* 
+open_option_window (sync_pair *pair, connection_type type)
+{
+  GladeXML *xml;
+  gchar *filename;
+  GtkWidget *config_window;
+  GtkWidget *w;
+  gpe_conn *conn;
+
+  filename = g_build_filename (PREFIX, "share", PACKAGE_NAME,
+			       "gpe_sync.glade", NULL);
+
+  xml = glade_xml_new (filename, NULL, NULL);
+
+  g_free (filename);
+
+  if (xml == NULL)
+    return FALSE;
+
+  conn = g_malloc0 (sizeof (*conn));
+
+  conn->sync_pair = pair;
+
+  config_window = glade_xml_get_widget (xml, "gpe_config");
+
+  gpe_load_config (conn);
+
+  g_object_set_data (G_OBJECT (config_window), "conn", conn);
+
+  g_signal_connect (G_OBJECT (config_window), "destroy", G_CALLBACK (delete_window), NULL);
+
+  w = glade_xml_get_widget (xml, "entry1");
+  gtk_entry_set_text (GTK_ENTRY (w), conn->username);
+  g_object_set_data (G_OBJECT (config_window), "username", w);
+
+  w = glade_xml_get_widget (xml, "combo1");
+  gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (w)->entry), conn->device_addr);
+  g_object_set_data (G_OBJECT (config_window), "addr", w);
+
+  w = glade_xml_get_widget (xml, "cancelbutton1");
+  g_signal_connect (G_OBJECT (w), "clicked", G_CALLBACK (cancel_clicked), config_window);
+
+  w = glade_xml_get_widget (xml, "okbutton1");
+  g_signal_connect (G_OBJECT (w), "clicked", G_CALLBACK (ok_clicked), config_window);
+
+  g_object_unref (G_OBJECT (xml));
+
+  return config_window;
+}
diff -uprN x/src/Makefile.am plugins/gpe_plugin/src/Makefile.am
--- x/src/Makefile.am	1970-01-01 01:00:00.000000000 +0100
+++ plugins/gpe_plugin/src/Makefile.am	2005-02-16 21:45:10.000000000 +0000
@@ -0,0 +1,31 @@
+## Process this file with automake to produce Makefile.in
+
+libdir=$(prefix)/lib/multisync
+
+PLUGINDIR = $(libdir)
+
+if GPE_DBG_FLAGS
+GPE_CFLAGS = -g -D_GPE_PRINT_DEBUG -D_GPE_LOG_DEBUG
+else
+GPE_CFLAGS = -O2
+endif
+
+MULTISYNC_HOME = "$(prefix)/include/multisync"
+
+AM_CFLAGS = $(GPE_CFLAGS) -DPLUGINDIR=\"$(PLUGINDIR)\" -DPREFIX=\"$(prefix)\"
+
+INCLUDES = \
+	-DPACKAGE_DATA_DIR=\""$(datadir)"\" \
+	-DPACKAGE_LOCALE_DIR=\""$(prefix)/$(DATADIRNAME)/locale"\" \
+	-I../../../src/libversit -I$(top_srcdir) -I$(top_srcdir)/intl -I$(top_srcdir)/../../include \
+	-I$(MULTISYNC_HOME) \
+	@PACKAGE_CFLAGS@
+
+lib_LTLIBRARIES = libgpe_sync.la
+
+libgpe_sync_la_SOURCES = \
+	gpe_sync.c calendar.c connection.c contacts.c todo.c gui.c
+
+libgpe_sync_la_LIBADD = -lnsqlc @PACKAGE_LIBS@
+
+
diff -uprN x/src/todo.c plugins/gpe_plugin/src/todo.c
--- x/src/todo.c	1970-01-01 01:00:00.000000000 +0100
+++ plugins/gpe_plugin/src/todo.c	2005-02-15 03:10:13.000000000 +0000
@@ -0,0 +1,140 @@
+/* 
+ * MultiSync GPE Plugin
+ * Copyright (C) 2004 Phil Blundell <[EMAIL PROTECTED]>
+ *
+ * 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;
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+#include <glib.h>
+#include <libintl.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "gpe_sync.h"
+
+#include <gpe/vtodo.h>
+#include <gpe/tag-db.h>
+
+#include <mimedir/mimedir-vcal.h>
+
+GList *
+todo_get_changes (struct db *db, int newdb)
+{
+  GList *data = NULL;
+  GSList *list, *i;
+  
+  if (newdb)
+    list = fetch_uid_list (db->db, "select distinct uid from todo_urn");
+  else
+    list = fetch_uid_list (db->db, "select uid from todo where (tag='modified' or tag='MODIFIED') and value>%d",
+			   db->last_timestamp);
+  
+  for (i = list; i; i = i->next)
+    {
+      GSList *tags;
+      MIMEDirVCal *vcal;
+      MIMEDirVTodo *vtodo;
+      gchar *string;
+      changed_object *obj;
+      int urn = (int)i->data;
+      
+      tags = fetch_tag_data (db->db, "select tag,value from todo where uid=%d", urn);
+      vtodo = vtodo_from_tags (tags);
+      gpe_tag_list_free (tags);
+      vcal = mimedir_vcal_new ();
+      mimedir_vcal_add_component (vcal, MIMEDIR_VCOMPONENT (vtodo));
+      string = mimedir_vcal_write_to_string (vcal);
+      g_object_unref (vcal);
+
+      obj = g_malloc0 (sizeof (*obj));
+      obj->comp = string;
+      obj->uid = g_strdup_printf ("todo-%d", urn);
+      obj->object_type = SYNC_OBJECT_TYPE_TODO;
+      obj->change_type = SYNC_OBJ_MODIFIED; 
+
+      data = g_list_append (data, obj);
+    }
+
+  g_slist_free (list);
+
+  return data;
+}
+
+gboolean
+todo_push_object (struct db *db, const char *obj, const char *uid, 
+		  char *returnuid, int *returnuidlen, GError **err)
+{
+  GSList *list, *tags;
+  MIMEDirVTodo *vtodo;
+  MIMEDirVCal *vcal;
+  int id;
+
+  vcal = mimedir_vcal_new_from_string (obj, err);
+  if (vcal == NULL)
+    return FALSE;
+
+  list = mimedir_vcal_get_todo_list (vcal);
+  if (list == NULL)
+    {
+      g_object_unref (vcal);
+      return FALSE;
+    }
+
+  vtodo = MIMEDIR_VTODO (list->data);
+
+  tags = vtodo_to_tags (vtodo);
+
+  if (uid)
+    sscanf (uid, "todo-%d", &id);
+  else
+    {
+      char *errmsg;
+
+      if (nsqlc_exec (db->db, "insert into todo_urn values (NULL)",
+		      NULL, NULL, &errmsg))
+	return FALSE;
+      
+      id = nsqlc_last_insert_rowid (db->db);
+    }
+   
+  mimedir_vcal_free_component_list (list);
+   
+  g_object_unref (vcal);
+
+  nsqlc_exec_printf (db->db, "delete from todo where uid='%q'", NULL, NULL, NULL, uid);
+  store_tag_data (db->db, "todo", id, tags, FALSE);
+
+  sprintf (returnuid, "%d", id);
+  *returnuidlen = strlen (returnuid);
+
+  return TRUE;
+}
+
+gboolean
+todo_delete_object (struct db *db, const char *uid, gboolean soft)
+{
+  nsqlc_exec_printf (db->db, "delete from todo where uid='%q'", NULL, NULL, NULL, uid);
+  nsqlc_exec_printf (db->db, "delete from todo_urn where uid='%q'", NULL, NULL, NULL, uid);
+
+  return TRUE;
+}
+
+struct db todo_db = 
+{
+  .type = SYNC_OBJECT_TYPE_TODO,
+  .name = "todo",
+
+  .get_changes = todo_get_changes,
+  .push_object = todo_push_object,
+  .delete_object = todo_delete_object,
+};
+
+void
+todo_init (void)
+{
+  db_list = g_slist_append (db_list, &todo_db);
+}
--- /dev/null	2005-02-14 22:51:21.000000000 +0000
+++ specs/multisync-gpe.spec.in	2005-02-15 05:41:59.000000000 +0000
@@ -0,0 +1,29 @@
+Name    :   multisync-gpe
+Version :   @VERSION@
+Release :   1
+Group   :   Applications/Productivity
+Summary :   GPE plugin for MultiSync
+Copyright : GPL
+Requires:   multisync = @MULTISYNC_VERSION@
+BuildRoot:  %{_tmppath}/%{name}-%{PACKAGE_VERSION}-root
+
+%description
+This is a plugin for Multisync that allows you to sync GPE devices.
+
+The MultiSync homepage can be found at http://multisync.sourceforge.net
+
+%define _unpackaged_files_terminate_build 0 
+%define _missing_doc_files_terminate_build 0
+
+%files
[EMAIL PROTECTED]@/lib/multisync/libgpe_sync.so.0.0.0
[EMAIL PROTECTED]@/lib/multisync/libgpe_sync.so.0
[EMAIL PROTECTED]@/lib/multisync/libgpe_sync.so
+
+%install
+rm -rf %{buildroot}
+mkdir -p $RPM_BUILD_ROOT/@prefix@/lib/multisync/
+cp -a @prefix@/lib/multisync/libgpe_sync.so.0.0.0 $RPM_BUILD_ROOT/@prefix@/lib/multisync/
+cp -a @prefix@/lib/multisync/libgpe_sync.so.0 $RPM_BUILD_ROOT/@prefix@/lib/multisync/
+cp -a @prefix@/lib/multisync/libgpe_sync.so $RPM_BUILD_ROOT/@prefix@/lib/multisync/
+strip $RPM_BUILD_ROOT/@prefix@/lib/multisync/libgpe_sync.so.0.0.0

Reply via email to