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