/* 
   MultiSync Opie Plugin - Synchronize Opie/Zaurus Devices
   Copyright (C) 2003 Tom Foottit <tom@foottit.com>

   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;

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

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

/*
 *  $Id: opie_sync.c,v 1.12 2004/03/30 04:56:26 irix Exp $
 */

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <glib.h>
#include <multisync.h>

#include "opie_comms.h"
#include "opie_log.h"
#include "opie_debug.h"
#include "opie_qcop.h"
#include "opie_config.h"
#include "opie_changes.h"
#include "opie_vtype.h"


/* this should match MULTISYNC_API_VER in 
 * multisync.h if everything is up to date */
#define OPIE_MULTISYNC_API_VER (3)


/* functions */
void sync_cancelled(void);
char* check_user_cancelled_sync();

/* globals */
GList* calendar = NULL;
GList* contacts = NULL;
GList* todos = NULL;
GList* categories = NULL;
qcop_conn* qcopconn = NULL;
gboolean user_cancelled_sync = FALSE;


/******************************************************************
   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.
*/
opie_conn* sync_connect(sync_pair* handle, 
                        connection_type type,
                        sync_object_type object_types) 
{
  opie_conn *conn = NULL;
  char* errmsg = NULL;
  
  conn = g_malloc0(sizeof(opie_conn));
  g_assert(conn);
  conn->sync_pair = handle;
  conn->commondata.object_types = object_types;
  
  calendar = NULL;
  contacts = NULL;
  todos = NULL;
  categories = NULL;

  OPIE_DEBUG("sync_connect");  
  
  /* load the connection attributes */
  if(!opie_load_config(conn))
  {
    /* failure */
    errmsg = g_strdup("Failed to load configuration");
    sync_set_requestfailederror(errmsg, conn->sync_pair);
    return conn;
  }  
  
  if (conn->enable_qcop)
  {   
    OPIE_DEBUG("qcop_connect");
    
    /* Connect to QCopBridgeServer to lock GUI and get root path */
    qcopconn = qcop_connect(conn->device_addr, 
                            conn->username,
                            conn->password);
    if (qcopconn->result)
    {
      qcop_start_sync(qcopconn, &sync_cancelled);
      if (!qcopconn->result)
      {
        OPIE_DEBUG("qcop_start_sync_failed");
        errmsg = g_strdup(qcopconn->resultmsg);
        sync_set_requestfailederror(errmsg,conn->sync_pair);
        qcop_stop_sync(qcopconn);
        qcop_freeqconn(qcopconn);
        return conn;
      }
    }
    else
    {
      /* Failure does not necessarily mean we can't sync, but if
       the user requested qcop its absence is a reason to fail. */
      OPIE_DEBUG("QCop connection failed");
      errmsg = g_strdup(qcopconn->resultmsg);
      sync_set_requestfailederror(errmsg, conn->sync_pair);
      qcop_freeqconn(qcopconn);
      return conn;
    }
  }
  
  /* connect to the device and pull the required data back */
  if(!opie_connect_and_fetch(conn, 
                             object_types, 
                             &calendar, 
                             &contacts, 
                             &todos,
                             &categories))
  {
    /* failed */
    if(conn->enable_qcop && qcopconn)
    {
      qcop_stop_sync(qcopconn);
      if(!qcopconn->result)
      {
        OPIE_DEBUG(qcopconn->resultmsg);
        g_free(qcopconn->resultmsg); 
      } 
      qcop_disconnect(qcopconn);
    }
    errmsg = g_strdup_printf("Failed to load data from device %s", conn->device_addr);
    sync_set_requestfailederror(errmsg, conn->sync_pair);
    return conn;
  }
  
  errmsg = check_user_cancelled_sync();
  if(errmsg && conn->enable_qcop)
  {
    sync_set_requestfailederror(errmsg, conn->sync_pair);
    return conn;   
  }
    
  sync_set_requestdone(conn->sync_pair);
  return(conn);
}


/* sync_disconnect()

   Called by the sync engine to free the connection handle and disconnect
   from the database client.
*/
void sync_disconnect(opie_conn *conn) 
{
  GList* li;
  contact_data* contact;
  todo_data* todo;
  cal_data* cal;
  category_data* cat;
  sync_pair *sync_pair = conn->sync_pair;
      
  OPIE_DEBUG("sync_disconnect");    
  
  /* clean up memory from the global lists */
  for(li = contacts; li != NULL; li = g_list_next(li))
  {
    contact = (contact_data*)li->data;
    free_contact_data(contact);
  }  
  g_list_free(contacts);
  
  for(li = todos; li != NULL; li = g_list_next(li))
  {
    todo = (todo_data*)li->data;
    free_todo_data(todo);
  }  
  g_list_free(todos);
  
  for(li = calendar; li != NULL; li = g_list_next(li))
  {
    cal = (cal_data*)li->data;
    free_cal_data(cal);
  }  
  g_list_free(calendar);
  
  for(li = categories; li != NULL; li = g_list_next(li))
  {
    cat = (category_data*)li->data;
    free_category_data(cat);
  }  
  g_list_free(categories);

  calendar = NULL;
  contacts = NULL;
  todos = NULL;
  categories = NULL;  
  
  if(conn->enable_qcop && qcopconn) 
  {
    qcop_stop_sync(qcopconn);
    if (!qcopconn->result)
    {
      OPIE_DEBUG(qcopconn->resultmsg);
      g_free(qcopconn->resultmsg);
    }
    qcop_disconnect(qcopconn);
  }
    
  /* cleanup memory from the connection */
  if(conn->device_addr)
    g_free(conn->device_addr);
  if(conn->username)
    g_free(conn->username);
  if(conn->password)
    g_free(conn->password);
  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(opie_conn *conn, sync_object_type newdbs) 
{
  GList *changes = NULL;
  sync_object_type retnewdbs = 0;
  change_info *chinfo;
  char* errmsg;
  gboolean report_all_data, cal_reset, phonebook_reset, todo_reset;

  OPIE_DEBUG("get_changes"); 
  
  errmsg = check_user_cancelled_sync();
  if(errmsg && conn->enable_qcop)
  {
    sync_set_requestfailederror(errmsg, conn->sync_pair);
    return;   
  }
  
  if (conn->commondata.object_types & SYNC_OBJECT_TYPE_CALENDAR) 
  {
    report_all_data = FALSE;
    if(newdbs & SYNC_OBJECT_TYPE_CALENDAR)
    {
      OPIE_DEBUG("other plugin requesting all calendar changes");
      report_all_data = TRUE;
    }
    
    cal_reset = FALSE;
    
    /* ask for calendar changes */
    if(!opie_get_calendar_changes(conn, 
                                  calendar, 
                                  &changes, 
                                  categories,
                                  report_all_data,
                                  &cal_reset))
    {
      /* failure */
      errmsg = g_strdup("Failed to extract calendar changes.");  
      sync_set_requestfailederror(errmsg, conn->sync_pair);
      g_free(errmsg);
      return;
    }
    
    if(cal_reset)
    {
      /* opie device calendar reset - get all changes */
      OPIE_DEBUG("reporting calendar reset");
      retnewdbs = retnewdbs | SYNC_OBJECT_TYPE_CALENDAR;
    }
  }
  
  if (conn->commondata.object_types & SYNC_OBJECT_TYPE_PHONEBOOK) 
  {
    report_all_data = FALSE;
    if(newdbs & SYNC_OBJECT_TYPE_PHONEBOOK)
    {
      OPIE_DEBUG("other plugin requesting all phonebook changes");
      report_all_data = TRUE;
    }
    
    phonebook_reset = FALSE;
    
    /* ask for phonebook changes */
    if(!opie_get_phonebook_changes(conn, 
                                   contacts, 
                                   &changes,
                                   categories,
                                   report_all_data,
                                   &phonebook_reset))
    {
      /* failure */
      errmsg = g_strdup("Failed to extract phonebook changes.");  
      sync_set_requestfailederror(errmsg, conn->sync_pair);
      g_free(errmsg);
      return;
    }
    
    if(phonebook_reset)
    {
      /* opie device phonebook reset - get all changes */
      OPIE_DEBUG("reporting phonebook reset");
      retnewdbs = retnewdbs | SYNC_OBJECT_TYPE_PHONEBOOK;
    }
  }
  
  if (conn->commondata.object_types & SYNC_OBJECT_TYPE_TODO) 
  {
    report_all_data = FALSE;
    if(newdbs & SYNC_OBJECT_TYPE_TODO)
    {
      OPIE_DEBUG("other plugin requesting all todo list changes");
      report_all_data = TRUE;
    }
    
    todo_reset = FALSE;
    
    /* ask for todo list changes */
    if(!opie_get_todo_changes(conn, 
                              todos, 
                              &changes,
                              categories,
                              report_all_data,
                              &todo_reset))
    {
      /* failure */
      errmsg = g_strdup("Failed to extract todo changes.");  
      sync_set_requestfailederror(errmsg, conn->sync_pair);
      g_free(errmsg);
      return;
    }
    
    if(todo_reset)
    {
      /* opie device todo list reset - get all changes */
      OPIE_DEBUG("reporting todo list reset");
      retnewdbs = retnewdbs | SYNC_OBJECT_TYPE_TODO;
    }
  }
  
  /* 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);
  
  return;
}

/* 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(opie_conn *conn, 
		    char* object, char *uid,
		    sync_object_type objtype,
		    char *returnuid, int *returnuidlen) 
{
  contact_data* new_contact;
  contact_data* existing_contact;
  todo_data* existing_todo;
  todo_data* new_todo;
  cal_data* existing_cal;
  cal_data* new_cal;
  gboolean found = FALSE;
  GList* li;
  char* errmsg;

  OPIE_DEBUG("syncobj_modify");  
  
  errmsg = check_user_cancelled_sync();
  if(errmsg && conn->enable_qcop)
  {
    sync_set_requestfailederror(errmsg, conn->sync_pair);
    return;   
  }
  
  /* 
   * calendar
   */
  if(objtype & (SYNC_OBJECT_TYPE_CALENDAR))
  {
    if(NULL == uid)
    {
      /* new calendar entry */
      new_cal = (cal_data*)vcal_to_cal_data(object, &categories);
      
      if(NULL != new_cal)
      {
        new_cal->uid = g_strdup_printf("%u", random());
        
        *returnuidlen = strlen(new_cal->uid);
        memcpy(returnuid, new_cal->uid, *returnuidlen);
        returnuid[*returnuidlen] = 0;
        
        OPIE_DEBUG("adding new calendar entry"); 
      
        calendar = g_list_append(calendar, new_cal);
      }
      else
      {
        /* parse error */
        OPIE_DEBUG("calendar parse error");
        errmsg = g_strdup("Failed to parse calendar vobject.");  
        sync_set_requestfailederror(errmsg, conn->sync_pair);
        g_free(errmsg);
        return;
      }
    }
    else
    {
      /* changed calendar entry */
      new_cal = (cal_data*)vcal_to_cal_data(object, &categories);
      if(NULL == new_cal)
      {
        /* parse error */
        OPIE_DEBUG("calendar parse error");
        errmsg = g_strdup("Failed to parse calendar vobject.");  
        sync_set_requestfailederror(errmsg, conn->sync_pair);
        g_free(errmsg);
        return;
      }
      
      new_cal->uid = g_strdup(uid);
                
      /* find the existing entry to replace */
      found = FALSE;
      for(li = calendar; li != NULL; li = g_list_next(li))
      {
        existing_cal = (cal_data*)li->data;
        
        if(!existing_cal->uid) continue;
        
        if(!strcmp(uid, existing_cal->uid))
        {
          found = TRUE;
          
          OPIE_DEBUG("modifying existing calendar entry");
          
          /* deep copy the existing anon attributes over to the new cal
           * so they do not get lost 
           */
          if(existing_cal->anons)
          {
            anon_data* anon = NULL;
            GList* current_anon = existing_cal->anons;
            
            while(current_anon != NULL)
            {
              if(current_anon->data && 
                 ((anon_data*)(current_anon->data))->attr && 
                 ((anon_data*)(current_anon->data))->val)
              {
                anon = g_malloc0(sizeof(anon_data)); 
                anon->attr = g_strdup(((anon_data*)(current_anon->data))->attr);
                anon->val = g_strdup(((anon_data*)(current_anon->data))->val);
                new_cal->anons = g_list_append(new_cal->anons, anon);
                current_anon = current_anon->next;
              }
            }
          }
          
          /* remove the existing entry and add this one */
          calendar = g_list_remove(calendar, existing_cal);
          free_cal_data(existing_cal);
          
          calendar = g_list_append(calendar, new_cal);
              
          break;       
        }  
      }
      
      if(!found)
      {
        /* this was supposed to be a changed entry
         * but we can't find it
         */
        OPIE_DEBUG("adding new calendar entry (existing not found)"); 
        calendar = g_list_append(calendar, new_cal); 
      }
    }     
  }
  
  /* 
   * phonebook
   */
  else if(objtype & (SYNC_OBJECT_TYPE_PHONEBOOK))
  {
    if(NULL == uid)
    {
      /* new phonebook entry */
      new_contact = (contact_data*)vcard_to_contact_data(object, &categories);
      
      if(NULL != new_contact)
      {
        new_contact->uid = g_strdup_printf("%u", random());
        
        *returnuidlen = strlen(new_contact->uid);
        memcpy(returnuid, new_contact->uid, *returnuidlen);
        returnuid[*returnuidlen] = 0;
        
        OPIE_DEBUG("adding new contact"); 
      
        contacts = g_list_append(contacts, new_contact);
      }
      else
      {
        /* parse error */
        OPIE_DEBUG("phonebook parse error");
        errmsg = g_strdup("Failed to parse phonebook vobject.");  
        sync_set_requestfailederror(errmsg, conn->sync_pair);
        g_free(errmsg);
        return;
      }
    }
    else
    {
      /* changed phonebook entry */
      new_contact = (contact_data*)vcard_to_contact_data(object, &categories);
      if(NULL == new_contact)
      {
        /* parse error */
        OPIE_DEBUG("phonebook parse error");
        errmsg = g_strdup("Failed to parse phonebook vobject.");  
        sync_set_requestfailederror(errmsg, conn->sync_pair);
        g_free(errmsg);
        return;      
      }
      
      new_contact->uid = g_strdup(uid);
                
      /* find the existing entry to replace */
      found = FALSE;
      for(li = contacts; li != NULL; li = g_list_next(li))
      {
        existing_contact = (contact_data*)li->data;
        if(!existing_contact->uid) continue;
        
        if(!strcmp(uid, existing_contact->uid))
        {
          found = TRUE;
          
          OPIE_DEBUG("modifying existing contact");
          
          /* deep copy the existing anon attributes over to the new contact
           * so they do not get lost 
           */
          if(existing_contact->anons)
          {
            anon_data* anon = NULL;
            GList* current_anon = existing_contact->anons;
            
            while(current_anon != NULL)
            {
              if(current_anon->data && 
                 ((anon_data*)(current_anon->data))->attr && 
                 ((anon_data*)(current_anon->data))->val)
              {
                anon = g_malloc0(sizeof(anon_data)); 
                anon->attr = g_strdup(((anon_data*)(current_anon->data))->attr);
                anon->val = g_strdup(((anon_data*)(current_anon->data))->val);
                new_contact->anons = g_list_append(new_contact->anons, anon);
                current_anon = current_anon->next;
              }
            }
          }
          
          /* remove the existing entry and add this one */
          contacts = g_list_remove(contacts, existing_contact);
          free_contact_data(existing_contact);
          
          contacts = g_list_append(contacts, new_contact);
              
          break;       
        }  
      }
      
      if(!found)
      {
        /* this was supposed to be a changed entry
         * but we can't find it
         */
        OPIE_DEBUG("adding new contact (existing not found)"); 
        contacts = g_list_append(contacts, new_contact); 
      }
    }     
  }
  
  /* 
   * todo list
   */
  else if(objtype & (SYNC_OBJECT_TYPE_TODO))
  {
    if(NULL == uid)
    {
      /* new todo entry */
      new_todo = (todo_data*)vtodo_to_todo_data(object, &categories);
      
      if(NULL != new_todo)
      {
        new_todo->uid = g_strdup_printf("%u", random());
        
        *returnuidlen = strlen(new_todo->uid);
        memcpy(returnuid, new_todo->uid, *returnuidlen);
        returnuid[*returnuidlen] = 0;
        
        OPIE_DEBUG("adding new todo"); 
      
        todos = g_list_append(todos, new_todo);
      }
      else
      {
        /* parse error */
        OPIE_DEBUG("todo parse error");
        errmsg = g_strdup("Failed to parse todo vobject.");  
        sync_set_requestfailederror(errmsg, conn->sync_pair);
        g_free(errmsg);
        return;      
      }
    }
    else
    {
      /* changed todo entry */
      new_todo = (todo_data*)vtodo_to_todo_data(object, &categories);
      if(NULL == new_todo)
      {
        /* parse error */
        OPIE_DEBUG("todo parse error");
        errmsg = g_strdup("Failed to parse todo vobject.");  
        sync_set_requestfailederror(errmsg, conn->sync_pair);
        g_free(errmsg);
        return;      
      }
      
      new_todo->uid = g_strdup(uid);
      
      /* find the existing entry to replace */
      found = FALSE;
      for(li = todos; li != NULL; li = g_list_next(li))
      {
        existing_todo = (todo_data*)li->data;
        if(!existing_todo->uid) continue;
        
        if(!strcmp(uid, existing_todo->uid))
        {
          found = TRUE;
          
          OPIE_DEBUG("modifying existing todo");
          
          /* deep copy the existing anon attributes over to the new cal
           * so they do not get lost 
           */
          if(existing_todo->anons)
          {
            anon_data* anon = NULL;
            GList* current_anon = existing_todo->anons;
            
            while(current_anon != NULL)
            {
              if(current_anon->data && 
                 ((anon_data*)(current_anon->data))->attr && 
                 ((anon_data*)(current_anon->data))->val)
              {
                anon = g_malloc0(sizeof(anon_data)); 
                anon->attr = g_strdup(((anon_data*)(current_anon->data))->attr);
                anon->val = g_strdup(((anon_data*)(current_anon->data))->val);
                new_todo->anons = g_list_append(new_todo->anons, anon);
                current_anon = current_anon->next;
              }
            }
          }
          
          /* remove the existing entry and add this one */
          todos = g_list_remove(todos, existing_todo);
          free_todo_data(existing_todo);
          
          todos = g_list_append(todos, new_todo);
              
          break;       
        }  
      }
      
      if(!found)
      {
        /* this was supposed to be a changed entry
         * but we can't find it
         */
        OPIE_DEBUG("adding new todo (existing not found)"); 
        todos = g_list_append(todos, new_todo); 
      }
    }     
  }
  
  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(opie_conn *conn, char *uid,
		    sync_object_type objtype, int softdelete) 
{
  contact_data *contact_todelete;
  todo_data *todo_todelete;
  cal_data *cal_todelete;
  gboolean found = FALSE;
  GList* li;
  char* errmsg;
  
  OPIE_DEBUG("syncobj_delete");     
   
  errmsg = check_user_cancelled_sync();
  if(errmsg && conn->enable_qcop)
  {
    sync_set_requestfailederror(errmsg, conn->sync_pair);
    return;   
  }
  
  if(!uid)
  {
    OPIE_DEBUG("item to delete not specified by syncengine"); 
    sync_set_requestfailed(conn->sync_pair); 
  }
  
  if(objtype & (SYNC_OBJECT_TYPE_CALENDAR))
  {
    /* find the existing entry to delete */
    found = FALSE;
    for(li = calendar; li != NULL; li = g_list_next(li))
    {
      cal_todelete = (cal_data*)li->data;
      
      if(!cal_todelete->uid)
      {
        OPIE_DEBUG("null uid in exiting calendar list");
        continue;
      }
      
      if(!strcmp(uid, cal_todelete->uid))
      {
        found = TRUE;

        OPIE_DEBUG("deleting existing calendar entry"); 
        
        /* remove the existing entry and add this one */
        calendar = g_list_remove(calendar, cal_todelete);
        free_cal_data(cal_todelete);

        break;       
      }  
    }
    
    if(!found)
    {
      /* can't find object to delete - log */ 
      OPIE_DEBUG("could not find existing calendar entry to delete"); 
    }   
  }
  else if(objtype & (SYNC_OBJECT_TYPE_PHONEBOOK))
  {    
    /* find the existing entry to delete */
    found = FALSE;
    for(li = contacts; li != NULL; li = g_list_next(li))
    {
      contact_todelete = (contact_data*)li->data;
      
      if(!contact_todelete->uid)
      {
        OPIE_DEBUG("null uid in exiting contacts list");
        continue;
      }
      
      errmsg = g_strdup_printf("delete: does %s = %s\n", uid, contact_todelete->uid);
      OPIE_DEBUG(errmsg);
      g_free(errmsg);
      
      if(!strcmp(uid, contact_todelete->uid))
      {
        found = TRUE;

        OPIE_DEBUG("deleting existing contact"); 
        
        /* remove the existing entry and add this one */
        contacts = g_list_remove(contacts, contact_todelete);
        free_contact_data(contact_todelete);

        break;       
      }  
    }
    
    if(!found)
    {
      /* can't find object to delete - log */ 
      OPIE_DEBUG("could not find existing contact to delete"); 
    }   
  }
  else if(objtype & (SYNC_OBJECT_TYPE_TODO))
  {
    /* find the existing entry to delete */
    found = FALSE;
    for(li = todos; li != NULL; li = g_list_next(li))
    {
      todo_todelete = (todo_data*)li->data;
      
      if(!todo_todelete->uid)
      {
        OPIE_DEBUG("null uid in existing todo list");
        continue;
      }
      
      errmsg = g_strdup_printf("delete: does %s = %s\n", uid, todo_todelete->uid);
      OPIE_DEBUG(errmsg);
      g_free(errmsg);
      
      if(!strcmp(uid, todo_todelete->uid))
      {
        found = TRUE;

        OPIE_DEBUG("deleting existing todo"); 
        
        /* remove the existing entry and add this one */
        todos = g_list_remove(todos, todo_todelete);
        free_todo_data(todo_todelete);

        break;       
      }  
    }
    
    if(!found)
    {
      /* can't find object to delete - log */ 
      OPIE_DEBUG("could not find existing todo to delete"); 
    }   
  }
  
  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(opie_conn *conn, 
			   changed_object *obj) 
{
  OPIE_DEBUG("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(opie_conn *conn, gboolean success) 
{
  char* contacts_file = NULL;
  char* contacts_xml = NULL;
  char* todo_file = NULL;
  char* todo_xml = NULL;
  char* cal_file = NULL;
  char* cal_xml = NULL;
  char* categories_file = NULL;
  char* categories_xml = NULL;
  char* errmsg = NULL;
  FILE* fd;
  
  OPIE_DEBUG("sync_done"); 
  
  if(success)
  {  
    /* 
     * save categories
     */
    if(g_list_length(categories) > 0)
    {
      categories_file = g_strdup_printf("%s/Categories.xml",
                                        sync_get_datapath(conn->sync_pair));
      if ((fd = fopen(categories_file, "w")))
      {
        /* write to disk */
        categories_xml = serialize_category_data(conn, categories); 

        if(categories_xml && (strlen(categories_xml) > 0))
        {
          fprintf(fd, "%s", categories_xml);
          fclose(fd);

          /* transfer file to opie device */
          if(!opie_connect_and_put(conn,
                                   categories_file,
                                   SYNC_OBJECT_TYPE_UNKNOWN))      
          {
            /* failed to transfer back to the device */ 
            errmsg = g_strdup("Failed to transfer file back to device.");  
            sync_set_requestfailederror(errmsg, conn->sync_pair);
            g_free(errmsg);
            g_free(categories_file);
            return;       
          }
        }
        else
        {
          /* failed to translate into XML */
          OPIE_DEBUG("Failed to translate categories list to XML.\n");
          fclose(fd);
        }

        if(categories_xml)
          g_free(categories_xml);

      }
      else
      {
        /* failed to open file */
        errmsg = g_strdup_printf("Failed to save state to disk - %s.", categories_file);  
        sync_set_requestfailederror(errmsg, conn->sync_pair);
        g_free(errmsg);
        g_free(categories_file);
        return;           
      }

      g_free(categories_file);

      /* move the original file fetched to /tmp out of the way */
      if(rename("/tmp/Categories.xml", "/tmp/Categories.xml.bak") == -1)
      {
        OPIE_DEBUG("Unable to rename file Categories.xml");
      }
    }

    /* 
     * save contacts
     */
    if(conn->commondata.object_types & SYNC_OBJECT_TYPE_PHONEBOOK)
    {
      contacts_file = g_strdup_printf("%s/addressbook.xml",
                                      sync_get_datapath(conn->sync_pair));
      if ((fd = fopen(contacts_file, "w")))
      {
        /* write to disk */
        contacts_xml = serialize_contact_data(conn, contacts); 

        if(contacts_xml && (strlen(contacts_xml) > 0))
        {
          fprintf(fd, "%s", contacts_xml);
          fclose(fd);

          /* transfer file to opie device */
          if(!opie_connect_and_put(conn,
                                   contacts_file,
                                   SYNC_OBJECT_TYPE_PHONEBOOK))      
          {
            /* failed to transfer back to the device */ 
            errmsg = g_strdup("Failed to transfer file back to device.");  
            sync_set_requestfailederror(errmsg, conn->sync_pair);
            g_free(errmsg);
            g_free(contacts_file);
            return;       
          }
        }
        else
        {
          /* failed to translate into XML */
          OPIE_DEBUG("Failed to translate contacts list to XML.\n");
          fclose(fd);
        }

        if(contacts_xml)
          g_free(contacts_xml);

      }
      else
      {
        /* failed to open file */
        errmsg = g_strdup_printf("Failed to save state to disk - %s.", contacts_file);  
        sync_set_requestfailederror(errmsg, conn->sync_pair);
        g_free(errmsg);
        g_free(contacts_file);
        return;           
      }

      g_free(contacts_file);

      /* move the original file fetched to /tmp out of the way */
      if(rename("/tmp/addressbook.xml", "/tmp/addressbook.xml.bak") == -1)
      {
        OPIE_DEBUG("Unable to rename file addressbook.xml");
      }
    }


    /* 
     * save todos
     */
    if(conn->commondata.object_types & SYNC_OBJECT_TYPE_TODO)
    {
      todo_file = g_strdup_printf("%s/todolist.xml",
                                      sync_get_datapath(conn->sync_pair));

      if ((fd = fopen(todo_file, "w")))
      {
        /* write to disk */
        todo_xml = serialize_todo_data(conn, todos); 

        if(todo_xml && (strlen(todo_xml) > 0))
        {
          fprintf(fd, "%s", todo_xml);
          fclose(fd);

          /* transfer file to opie device */
          if(!opie_connect_and_put(conn,
                                   todo_file,
                                   SYNC_OBJECT_TYPE_TODO))      
          {
            /* failed to transfer back to the device */ 
            errmsg = g_strdup("Failed to transfer file back to device.");  
            sync_set_requestfailederror(errmsg, conn->sync_pair);
            g_free(errmsg);
            g_free(todo_file);
            return;       
          }
        }
        else
        {
          /* failed to translate into XML */
          OPIE_DEBUG("Failed to translate todo list to XML.\n");
          fclose(fd);
        }

        if(todo_xml)
          g_free(todo_xml);


      }
      else
      {
        /* failed to open file */
        errmsg = g_strdup_printf("Failed to save state to disk - %s.", todo_file);  
        sync_set_requestfailederror(errmsg, conn->sync_pair);
        g_free(errmsg);
        g_free(todo_file);
        return;           
      }

      g_free(todo_file);

      /* move the original file fetched to /tmp out of the way */
      if(rename("/tmp/todolist.xml", "/tmp/todolist.xml.bak") == -1)
      {
        OPIE_DEBUG("Unable to rename file todolist.xml");
      }
    }


    /* 
     * save calendar entries
     */
    if(conn->commondata.object_types & SYNC_OBJECT_TYPE_CALENDAR)
    {
      cal_file = g_strdup_printf("%s/datebook.xml",
                                 sync_get_datapath(conn->sync_pair));

      if(fd = fopen(cal_file, "w"))
      {
        /* write to disk */
        cal_xml = serialize_cal_data(conn, calendar); 

        if(cal_xml && (strlen(cal_xml) > 0))
        {
          fprintf(fd, "%s", cal_xml);
          fclose(fd);

          /* transfer file to opie device */
          if(!opie_connect_and_put(conn,
                                   cal_file,
                                   SYNC_OBJECT_TYPE_CALENDAR))      
          {
            /* failed to transfer back to the device */ 
            errmsg = g_strdup("Failed to transfer file back to device.");  
            sync_set_requestfailederror(errmsg, conn->sync_pair);
            g_free(errmsg);
            g_free(todo_file);
            return;       
          }
        }
        else
        {
          /* failed to translate into XML */
          OPIE_DEBUG("Failed to translate calendar entry list to XML.\n");
          fclose(fd);
        }

        if(cal_xml)
          g_free(cal_xml);


      }
      else
      {
        /* failed to open file */
        errmsg = g_strdup_printf("Failed to save state to disk - %s.", cal_file);  
        sync_set_requestfailederror(errmsg, conn->sync_pair);
        g_free(errmsg);
        g_free(todo_file);
        return;           
      }

      g_free(cal_file);

      /* move the original file fetched to /tmp out of the way */
      if(rename("/tmp/datebook.xml", "/tmp/datebook.xml.bak") == -1)
      {
        OPIE_DEBUG("Unable to rename file datebook.xml");
      }
    }
  
  } /* if success */
  else
  {
    OPIE_DEBUG("sync_done: sync engine did not indicate success"); 
  }
  
  
  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() 
{
  return(FALSE);
}


/* short_name()

 Return a short plugin name for internal use.
*/
char* short_name() 
{
  return("opie-sync");
}


/* long_name()

   Return a long name which can be shown to the user.
*/
char* long_name() 
{
  return("Opie and Zaurus");
}


/* 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 Opie and Zaurus devices.");
}


/* plugin_init()

   Initialize the plugin. Called once upon loading of the plugin (NOT
   once per sync pair).
*/
void plugin_init(void) 
{
  /* seed the random number generator */
  srandom(time(0)); 
  
  comms_init();
  
  if (getenv ("MULTISYNC_DEBUG"))
    opie_debug = TRUE;
  
  /* TODO
   * Need to call comms_shutdown() from somewhere...
   */
}


/* object_types()

   Return the data types this plugin can handle.
*/
sync_object_type object_types() 
{
  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(OPIE_MULTISYNC_API_VER);
}


/* sync_cancelled()
 * 
 * Callback from the opie monitor thread.
 */
void sync_cancelled(void)
{
  user_cancelled_sync = TRUE;
}


/* check_user_cancelled_sync()
 * 
 * Check to see if the user cancelled the sync and return
 * an appropriate error message.
 */
char* check_user_cancelled_sync()
{
  if(user_cancelled_sync)
  {
    return g_strdup("User has cancelled the sync.");   
  }
  
  return NULL;
}


