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"><b>Connection:</b></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